Ajout des options
This commit is contained in:
		| @@ -88,133 +88,218 @@ class EventDetails extends StatelessWidget { | ||||
|                   ), | ||||
|             ), | ||||
|             const SizedBox(height: 16), | ||||
|             _buildInfoRow( | ||||
|               context, | ||||
|               Icons.calendar_today, | ||||
|               'Date de début', | ||||
|               dateFormat.format(event.startDateTime), | ||||
|             ), | ||||
|             _buildInfoRow( | ||||
|               context, | ||||
|               Icons.calendar_today, | ||||
|               'Date de fin', | ||||
|               dateFormat.format(event.endDateTime), | ||||
|             ), | ||||
|             _buildInfoRow( | ||||
|               context, | ||||
|               Icons.euro, | ||||
|               'Prix', | ||||
|               currencyFormat.format(event.price), | ||||
|             ), | ||||
|             _buildInfoRow( | ||||
|               context, | ||||
|               Icons.build, | ||||
|               'Temps d\'installation', | ||||
|               '${event.installationTime} heures', | ||||
|             ), | ||||
|             _buildInfoRow( | ||||
|               context, | ||||
|               Icons.construction, | ||||
|               'Temps de démontage', | ||||
|               '${event.disassemblyTime} heures', | ||||
|             ), | ||||
|             const SizedBox(height: 16), | ||||
|             Text( | ||||
|               'Description', | ||||
|               style: Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                     color: AppColors.noir, | ||||
|                     fontWeight: FontWeight.bold, | ||||
|                   ), | ||||
|             ), | ||||
|             const SizedBox(height: 8), | ||||
|             SelectableText( | ||||
|               event.description, | ||||
|               style: Theme.of(context).textTheme.bodyLarge, | ||||
|             ), | ||||
|             const SizedBox(height: 16), | ||||
|             Text( | ||||
|               'Adresse', | ||||
|               style: Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                     color: AppColors.noir, | ||||
|                     fontWeight: FontWeight.bold, | ||||
|                   ), | ||||
|             ), | ||||
|             const SizedBox(height: 8), | ||||
|             SelectableText( | ||||
|               event.address, | ||||
|               style: Theme.of(context).textTheme.bodyLarge, | ||||
|             ), | ||||
|             if (event.latitude != 0.0 || event.longitude != 0.0) ...[ | ||||
|               const SizedBox(height: 4), | ||||
|               SelectableText( | ||||
|                 '${event.latitude}° N, ${event.longitude}° E', | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ), | ||||
|             ], | ||||
|             if (event.documents.isNotEmpty) ...[ | ||||
|               const SizedBox(height: 16), | ||||
|               Text('Documents', | ||||
|                   style: Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                       color: AppColors.noir, fontWeight: FontWeight.bold)), | ||||
|               const SizedBox(height: 8), | ||||
|               Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: event.documents.map((doc) { | ||||
|                   final fileName = doc['name'] ?? ''; | ||||
|                   final url = doc['url'] ?? ''; | ||||
|                   final ext = p.extension(fileName).toLowerCase(); | ||||
|                   IconData icon; | ||||
|                   if ([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"] | ||||
|                       .contains(ext)) { | ||||
|                     icon = Icons.image; | ||||
|                   } else if (ext == ".pdf") { | ||||
|                     icon = Icons.picture_as_pdf; | ||||
|                   } else if ([ | ||||
|                     ".txt", | ||||
|                     ".md", | ||||
|                     ".csv", | ||||
|                     ".json", | ||||
|                     ".xml", | ||||
|                     ".docx", | ||||
|                     ".doc", | ||||
|                     ".xls", | ||||
|                     ".xlsx", | ||||
|                     ".ppt", | ||||
|                     ".pptx" | ||||
|                   ].contains(ext)) { | ||||
|                     icon = Icons.description; | ||||
|                   } else { | ||||
|                     icon = Icons.attach_file; | ||||
|                   } | ||||
|                   return ListTile( | ||||
|                     leading: Icon(icon, color: Colors.blueGrey), | ||||
|                     title: SelectableText( | ||||
|                       fileName, | ||||
|                       maxLines: 1, | ||||
|                       textAlign: TextAlign.left, | ||||
|                       style: Theme.of(context).textTheme.bodyMedium, | ||||
|             Expanded( | ||||
|               child: SingleChildScrollView( | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     _buildInfoRow( | ||||
|                       context, | ||||
|                       Icons.calendar_today, | ||||
|                       'Date de début', | ||||
|                       dateFormat.format(event.startDateTime), | ||||
|                     ), | ||||
|                     trailing: IconButton( | ||||
|                       icon: const Icon(Icons.download), | ||||
|                       onPressed: () async { | ||||
|                         if (await canLaunchUrl(Uri.parse(url))) { | ||||
|                           await launchUrl(Uri.parse(url), | ||||
|                               mode: LaunchMode.externalApplication); | ||||
|                         } | ||||
|                       }, | ||||
|                     _buildInfoRow( | ||||
|                       context, | ||||
|                       Icons.calendar_today, | ||||
|                       'Date de fin', | ||||
|                       dateFormat.format(event.endDateTime), | ||||
|                     ), | ||||
|                     onTap: () async { | ||||
|                       if (await canLaunchUrl(Uri.parse(url))) { | ||||
|                         await launchUrl(Uri.parse(url), | ||||
|                             mode: LaunchMode.externalApplication); | ||||
|                       } | ||||
|                     }, | ||||
|                     contentPadding: EdgeInsets.zero, | ||||
|                     dense: true, | ||||
|                   ); | ||||
|                 }).toList(), | ||||
|                     _buildInfoRow( | ||||
|                       context, | ||||
|                       Icons.euro, | ||||
|                       'Prix de base', | ||||
|                       currencyFormat.format(event.basePrice), | ||||
|                     ), | ||||
|                     if (event.options.isNotEmpty) ...[ | ||||
|                       const SizedBox(height: 8), | ||||
|                       Text('Options sélectionnées', | ||||
|                           style: | ||||
|                               Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                                     color: AppColors.noir, | ||||
|                                     fontWeight: FontWeight.bold, | ||||
|                                   )), | ||||
|                       const SizedBox(height: 4), | ||||
|                       Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         children: event.options.map((opt) { | ||||
|                           final price = (opt['price'] ?? 0.0) as num; | ||||
|                           final isNegative = price < 0; | ||||
|                           return ListTile( | ||||
|                             leading: Icon(Icons.tune, | ||||
|                                 color: | ||||
|                                     isNegative ? Colors.red : AppColors.rouge), | ||||
|                             title: Text(opt['name'] ?? '', | ||||
|                                 style: TextStyle(fontWeight: FontWeight.bold)), | ||||
|                             subtitle: Text(opt['details'] ?? ''), | ||||
|                             trailing: Text( | ||||
|                               (isNegative ? '- ' : '+ ') + | ||||
|                                   currencyFormat.format(price.abs()), | ||||
|                               style: TextStyle( | ||||
|                                 color: isNegative ? Colors.red : AppColors.noir, | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                               ), | ||||
|                             ), | ||||
|                             contentPadding: EdgeInsets.zero, | ||||
|                             dense: true, | ||||
|                           ); | ||||
|                         }).toList(), | ||||
|                       ), | ||||
|                       const SizedBox(height: 4), | ||||
|                       Builder( | ||||
|                         builder: (context) { | ||||
|                           final total = event.basePrice + | ||||
|                               event.options.fold<num>( | ||||
|                                   0, (sum, opt) => sum + (opt['price'] ?? 0.0)); | ||||
|                           return Padding( | ||||
|                             padding: | ||||
|                                 const EdgeInsets.only(top: 8.0, bottom: 8.0), | ||||
|                             child: Row( | ||||
|                               children: [ | ||||
|                                 const Icon(Icons.attach_money, | ||||
|                                     color: AppColors.rouge), | ||||
|                                 const SizedBox(width: 8), | ||||
|                                 Text('Prix total : ', | ||||
|                                     style: Theme.of(context) | ||||
|                                         .textTheme | ||||
|                                         .titleMedium | ||||
|                                         ?.copyWith( | ||||
|                                           color: AppColors.noir, | ||||
|                                           fontWeight: FontWeight.bold, | ||||
|                                         )), | ||||
|                                 Text( | ||||
|                                   currencyFormat.format(total), | ||||
|                                   style: Theme.of(context) | ||||
|                                       .textTheme | ||||
|                                       .titleMedium | ||||
|                                       ?.copyWith( | ||||
|                                         color: AppColors.rouge, | ||||
|                                         fontWeight: FontWeight.bold, | ||||
|                                       ), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ); | ||||
|                         }, | ||||
|                       ), | ||||
|                     ], | ||||
|                     _buildInfoRow( | ||||
|                       context, | ||||
|                       Icons.build, | ||||
|                       'Temps d\'installation', | ||||
|                       '${event.installationTime} heures', | ||||
|                     ), | ||||
|                     _buildInfoRow( | ||||
|                       context, | ||||
|                       Icons.construction, | ||||
|                       'Temps de démontage', | ||||
|                       '${event.disassemblyTime} heures', | ||||
|                     ), | ||||
|                     const SizedBox(height: 16), | ||||
|                     Text( | ||||
|                       'Description', | ||||
|                       style: Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                             color: AppColors.noir, | ||||
|                             fontWeight: FontWeight.bold, | ||||
|                           ), | ||||
|                     ), | ||||
|                     const SizedBox(height: 8), | ||||
|                     SelectableText( | ||||
|                       event.description, | ||||
|                       style: Theme.of(context).textTheme.bodyLarge, | ||||
|                     ), | ||||
|                     const SizedBox(height: 16), | ||||
|                     Text( | ||||
|                       'Adresse', | ||||
|                       style: Theme.of(context).textTheme.titleLarge?.copyWith( | ||||
|                             color: AppColors.noir, | ||||
|                             fontWeight: FontWeight.bold, | ||||
|                           ), | ||||
|                     ), | ||||
|                     const SizedBox(height: 8), | ||||
|                     SelectableText( | ||||
|                       event.address, | ||||
|                       style: Theme.of(context).textTheme.bodyLarge, | ||||
|                     ), | ||||
|                     if (event.latitude != 0.0 || event.longitude != 0.0) ...[ | ||||
|                       const SizedBox(height: 4), | ||||
|                       SelectableText( | ||||
|                         '${event.latitude}° N, ${event.longitude}° E', | ||||
|                         style: Theme.of(context).textTheme.bodySmall, | ||||
|                       ), | ||||
|                     ], | ||||
|                     if (event.documents.isNotEmpty) ...[ | ||||
|                       const SizedBox(height: 16), | ||||
|                       Text('Documents', | ||||
|                           style: Theme.of(context) | ||||
|                               .textTheme | ||||
|                               .titleLarge | ||||
|                               ?.copyWith( | ||||
|                                   color: AppColors.noir, | ||||
|                                   fontWeight: FontWeight.bold)), | ||||
|                       const SizedBox(height: 8), | ||||
|                       Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         children: event.documents.map((doc) { | ||||
|                           final fileName = doc['name'] ?? ''; | ||||
|                           final url = doc['url'] ?? ''; | ||||
|                           final ext = p.extension(fileName).toLowerCase(); | ||||
|                           IconData icon; | ||||
|                           if ([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"] | ||||
|                               .contains(ext)) { | ||||
|                             icon = Icons.image; | ||||
|                           } else if (ext == ".pdf") { | ||||
|                             icon = Icons.picture_as_pdf; | ||||
|                           } else if ([ | ||||
|                             ".txt", | ||||
|                             ".md", | ||||
|                             ".csv", | ||||
|                             ".json", | ||||
|                             ".xml", | ||||
|                             ".docx", | ||||
|                             ".doc", | ||||
|                             ".xls", | ||||
|                             ".xlsx", | ||||
|                             ".ppt", | ||||
|                             ".pptx" | ||||
|                           ].contains(ext)) { | ||||
|                             icon = Icons.description; | ||||
|                           } else { | ||||
|                             icon = Icons.attach_file; | ||||
|                           } | ||||
|                           return ListTile( | ||||
|                             leading: Icon(icon, color: Colors.blueGrey), | ||||
|                             title: SelectableText( | ||||
|                               fileName, | ||||
|                               maxLines: 1, | ||||
|                               textAlign: TextAlign.left, | ||||
|                               style: Theme.of(context).textTheme.bodyMedium, | ||||
|                             ), | ||||
|                             trailing: IconButton( | ||||
|                               icon: const Icon(Icons.download), | ||||
|                               onPressed: () async { | ||||
|                                 if (await canLaunchUrl(Uri.parse(url))) { | ||||
|                                   await launchUrl(Uri.parse(url), | ||||
|                                       mode: LaunchMode.externalApplication); | ||||
|                                 } | ||||
|                               }, | ||||
|                             ), | ||||
|                             onTap: () async { | ||||
|                               if (await canLaunchUrl(Uri.parse(url))) { | ||||
|                                 await launchUrl(Uri.parse(url), | ||||
|                                     mode: LaunchMode.externalApplication); | ||||
|                               } | ||||
|                             }, | ||||
|                             contentPadding: EdgeInsets.zero, | ||||
|                             dense: true, | ||||
|                           ); | ||||
|                         }).toList(), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
| @@ -303,7 +388,7 @@ class _EventAddDialogState extends State<EventAddDialog> { | ||||
|         description: _descriptionController.text.trim(), | ||||
|         startDateTime: _startDateTime!, | ||||
|         endDateTime: _endDateTime!, | ||||
|         price: double.tryParse(_priceController.text) ?? 0.0, | ||||
|         basePrice: double.tryParse(_priceController.text) ?? 0.0, | ||||
|         installationTime: int.tryParse(_installationController.text) ?? 0, | ||||
|         disassemblyTime: int.tryParse(_disassemblyController.text) ?? 0, | ||||
|         eventTypeId: '', // à adapter si tu veux gérer les types | ||||
|   | ||||
		Reference in New Issue
	
	Block a user