import 'package:em2rp/models/event_model.dart'; import 'package:intl/intl.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class IcsExportService { /// Génère un fichier ICS à partir d'un événement static Future generateIcsContent(EventModel event) async { final now = DateTime.now().toUtc(); final timestamp = DateFormat('yyyyMMddTHHmmss').format(now) + 'Z'; // Récupérer les informations supplémentaires final eventTypeName = await _getEventTypeName(event.eventTypeId); final workforce = await _getWorkforceDetails(event.workforce); final optionsWithNames = await _getOptionsDetails(event.options); // Formater les dates au format ICS (UTC) final startDate = _formatDateForIcs(event.startDateTime); final endDate = _formatDateForIcs(event.endDateTime); // Construire la description détaillée final description = _buildDescription(event, eventTypeName, workforce, optionsWithNames); // Générer un UID unique basé sur l'ID de l'événement final uid = 'em2rp-${event.id}@em2rp.app'; // Construire le contenu ICS final icsContent = '''BEGIN:VCALENDAR VERSION:2.0 PRODID:-//EM2RP//Event Manager//FR CALSCALE:GREGORIAN METHOD:PUBLISH BEGIN:VEVENT UID:$uid DTSTAMP:$timestamp DTSTART:$startDate DTEND:$endDate SUMMARY:${_escapeIcsText(event.name)} DESCRIPTION:${_escapeIcsText(description)} LOCATION:${_escapeIcsText(event.address)} STATUS:${_getEventStatus(event.status)} CATEGORIES:${_escapeIcsText(eventTypeName)} END:VEVENT END:VCALENDAR'''; return icsContent; } /// Récupère le nom du type d'événement static Future _getEventTypeName(String eventTypeId) async { if (eventTypeId.isEmpty) return 'Non spécifié'; try { final doc = await FirebaseFirestore.instance .collection('eventTypes') .doc(eventTypeId) .get(); if (doc.exists) { return doc.data()?['name'] as String? ?? eventTypeId; } } catch (e) { print('Erreur lors de la récupération du type d\'événement: $e'); } return eventTypeId; } /// Récupère les détails de la main d'œuvre static Future> _getWorkforceDetails(List workforce) async { final List workforceNames = []; for (final ref in workforce) { try { DocumentReference? docRef; // Gérer String (UID) ou DocumentReference if (ref is String) { docRef = FirebaseFirestore.instance.collection('users').doc(ref); } else if (ref is DocumentReference) { docRef = ref; } if (docRef != null) { final doc = await docRef.get(); if (doc.exists) { final data = doc.data() as Map?; if (data != null) { final firstName = data['firstName'] ?? ''; final lastName = data['lastName'] ?? ''; if (firstName.isNotEmpty || lastName.isNotEmpty) { workforceNames.add('$firstName $lastName'.trim()); } } } } } catch (e) { print('Erreur lors de la récupération des détails utilisateur: $e'); } } return workforceNames; } /// Récupère les détails des options static Future>> _getOptionsDetails(List> options) async { final List> optionsWithNames = []; for (final option in options) { try { final optionId = option['id'] ?? option['optionId']; if (optionId == null || optionId.toString().isEmpty) { // Si pas d'ID, garder le nom tel quel optionsWithNames.add({ 'name': option['name'] ?? 'Option inconnue', 'quantity': option['quantity'], }); continue; } // Récupérer le nom depuis Firestore final doc = await FirebaseFirestore.instance .collection('options') .doc(optionId.toString()) .get(); if (doc.exists) { final data = doc.data(); optionsWithNames.add({ 'name': data?['name'] ?? option['name'] ?? 'Option inconnue', 'quantity': option['quantity'], }); } else { // Document n'existe pas, garder le nom de l'option optionsWithNames.add({ 'name': option['name'] ?? 'Option inconnue', 'quantity': option['quantity'], }); } } catch (e) { print('Erreur lors de la récupération des détails option: $e'); optionsWithNames.add({ 'name': option['name'] ?? 'Option inconnue', 'quantity': option['quantity'], }); } } return optionsWithNames; } /// Construit la description détaillée de l'événement static String _buildDescription( EventModel event, String eventTypeName, List workforce, List> optionsWithNames, ) { final buffer = StringBuffer(); // Type d'événement buffer.writeln('TYPE: $eventTypeName'); buffer.writeln(''); // Description if (event.description.isNotEmpty) { buffer.writeln('DESCRIPTION:'); buffer.writeln(event.description); buffer.writeln(''); } // Jauge if (event.jauge != null) { buffer.writeln('JAUGE: ${event.jauge} personnes'); } // Contact email if (event.contactEmail != null && event.contactEmail!.isNotEmpty) { buffer.writeln('EMAIL DE CONTACT: ${event.contactEmail}'); } // Contact téléphone if (event.contactPhone != null && event.contactPhone!.isNotEmpty) { buffer.writeln('TÉLÉPHONE DE CONTACT: ${event.contactPhone}'); } // Adresse if (event.address.isNotEmpty) { buffer.writeln(''); buffer.writeln('ADRESSE: ${event.address}'); } // Temps d'installation et démontage if (event.installationTime > 0 || event.disassemblyTime > 0) { buffer.writeln(''); if (event.installationTime > 0) { buffer.writeln('INSTALLATION: ${event.installationTime}h'); } if (event.disassemblyTime > 0) { buffer.writeln('DÉMONTAGE: ${event.disassemblyTime}h'); } } // Main d'œuvre if (workforce.isNotEmpty) { buffer.writeln(''); buffer.writeln('MAIN D\'ŒUVRE:'); for (final name in workforce) { buffer.writeln(' - $name'); } } // Options if (optionsWithNames.isNotEmpty) { buffer.writeln(''); buffer.writeln('OPTIONS:'); for (final option in optionsWithNames) { final optionName = option['name'] ?? 'Option inconnue'; final quantity = option['quantity']; if (quantity != null && quantity > 1) { buffer.writeln(' - $optionName (x$quantity)'); } else { buffer.writeln(' - $optionName'); } } } // Prix if (event.basePrice > 0) { buffer.writeln(''); buffer.writeln('PRIX DE BASE: ${event.basePrice.toStringAsFixed(2)}€'); } // Lien vers l'application buffer.writeln(''); buffer.writeln('---'); buffer.writeln('Géré par EM2RP Event Manager'); return buffer.toString(); } /// Formate une date au format ICS (yyyyMMddTHHmmssZ) static String _formatDateForIcs(DateTime dateTime) { final utcDate = dateTime.toUtc(); return DateFormat('yyyyMMddTHHmmss').format(utcDate) + 'Z'; } /// Échappe les caractères spéciaux pour le format ICS static String _escapeIcsText(String text) { return text .replaceAll('\\', '\\\\') .replaceAll(',', '\\,') .replaceAll(';', '\\;') .replaceAll('\n', '\\n') .replaceAll('\r', ''); } /// Convertit le statut de l'événement en statut ICS static String _getEventStatus(EventStatus status) { switch (status) { case EventStatus.confirmed: return 'CONFIRMED'; case EventStatus.canceled: return 'CANCELLED'; case EventStatus.waitingForApproval: return 'TENTATIVE'; } } /// Génère le nom du fichier ICS static String generateFileName(EventModel event) { final safeName = event.name .replaceAll(RegExp(r'[^\w\s-]'), '') .replaceAll(RegExp(r'\s+'), '_'); final dateStr = DateFormat('yyyyMMdd').format(event.startDateTime); return 'event_${safeName}_$dateStr.ics'; } }