import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/providers/event_provider.dart'; import 'package:em2rp/models/event_model.dart'; import 'package:intl/intl.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:em2rp/views/widgets/inputs/int_stepper_field.dart'; import 'package:em2rp/models/user_model.dart'; import 'package:em2rp/views/widgets/image/profile_picture.dart'; import 'package:flutter/services.dart'; import 'package:file_picker/file_picker.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:path/path.dart' as p; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:firebase_auth/firebase_auth.dart'; import 'package:em2rp/providers/local_user_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_dropzone/flutter_dropzone.dart'; import 'package:em2rp/views/widgets/inputs/dropzone_upload_widget.dart'; import 'package:em2rp/views/widgets/user_management/user_multi_select_widget.dart'; class EventAddPage extends StatefulWidget { const EventAddPage({super.key}); @override State createState() => _EventAddPageState(); } class _EventAddPageState extends State { final _formKey = GlobalKey(); final TextEditingController _nameController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _priceController = TextEditingController(); final TextEditingController _installationController = TextEditingController(); final TextEditingController _disassemblyController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); DateTime? _startDateTime; DateTime? _endDateTime; bool _isLoading = false; String? _error; String? _success; String? _selectedEventType; final List _eventTypes = ['Bal', 'Mariage', 'Anniversaire']; int _descriptionMaxLines = 3; List _selectedUserIds = []; List _allUsers = []; bool _isLoadingUsers = true; List> _uploadedFiles = []; DropzoneViewController? _dropzoneController; bool _isDropzoneHighlighted = false; @override void initState() { super.initState(); _descriptionController.addListener(_handleDescriptionChange); _fetchUsers(); } void _handleDescriptionChange() { final lines = '\n'.allMatches(_descriptionController.text).length + 1; setState(() { _descriptionMaxLines = lines.clamp(3, 6); }); } Future _fetchUsers() async { final snapshot = await FirebaseFirestore.instance.collection('users').get(); setState(() { _allUsers = snapshot.docs .map((doc) => UserModel.fromMap(doc.data(), doc.id)) .toList(); _isLoadingUsers = false; }); } @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _priceController.dispose(); _installationController.dispose(); _disassemblyController.dispose(); _addressController.dispose(); super.dispose(); } Future _pickAndUploadFiles() async { final result = await FilePicker.platform .pickFiles(allowMultiple: true, withData: true); if (result != null && result.files.isNotEmpty) { setState(() => _isLoading = true); try { List> files = []; for (final file in result.files) { final fileBytes = file.bytes; final fileName = file.name; if (fileBytes != null) { final ref = FirebaseStorage.instance.ref().child( 'events/temp/${DateTime.now().millisecondsSinceEpoch}_$fileName'); final uploadTask = await ref.putData(fileBytes); final url = await uploadTask.ref.getDownloadURL(); files.add({'name': fileName, 'url': url}); } else { setState(() { _error = "Impossible de lire le fichier ${file.name}"; }); } } setState(() { _uploadedFiles.addAll(files); }); } catch (e) { setState(() { _error = 'Erreur lors de l\'upload : $e'; }); } finally { setState(() => _isLoading = false); } } } Future moveEventFileHttp({ required String sourcePath, required String destinationPath, }) async { final url = Uri.parse( 'https://us-central1-em2rp-951dc.cloudfunctions.net/moveEventFileV2'); final user = FirebaseAuth.instance.currentUser; final idToken = await user?.getIdToken(); final response = await http.post( url, headers: { 'Content-Type': 'application/json', if (idToken != null) 'Authorization': 'Bearer $idToken', }, body: jsonEncode({ 'data': { 'sourcePath': sourcePath, 'destinationPath': destinationPath, } }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['url'] != null) { return data['url'] as String; } else if (data['result'] != null && data['result']['url'] != null) { return data['result']['url'] as String; } return null; } else { print('Erreur Cloud Function: \\n${response.body}'); return null; } } Future _submit() async { if (!_formKey.currentState!.validate() || _startDateTime == null || _endDateTime == null || _selectedEventType == null || _addressController.text.isEmpty) return; if (_endDateTime!.isBefore(_startDateTime!) || _endDateTime!.isAtSameMomentAs(_startDateTime!)) { setState(() { _error = "La date de fin doit être postérieure à la date de début."; }); return; } setState(() { _isLoading = true; _error = null; _success = null; }); try { final eventProvider = Provider.of(context, listen: false); final newEvent = EventModel( id: '', name: _nameController.text.trim(), description: _descriptionController.text.trim(), startDateTime: _startDateTime!, endDateTime: _endDateTime!, price: double.tryParse(_priceController.text) ?? 0.0, installationTime: int.tryParse(_installationController.text) ?? 0, disassemblyTime: int.tryParse(_disassemblyController.text) ?? 0, eventTypeId: _selectedEventType!, customerId: '', address: _addressController.text.trim(), workforce: _selectedUserIds .map((id) => FirebaseFirestore.instance.collection('users').doc(id)) .toList(), latitude: 0.0, longitude: 0.0, documents: _uploadedFiles, ); final docRef = await FirebaseFirestore.instance .collection('events') .add(newEvent.toMap()); final eventId = docRef.id; List> newFiles = []; for (final file in _uploadedFiles) { final fileName = file['name']!; final oldUrl = file['url']!; String sourcePath; final tempPattern = RegExp(r'events/temp/[^?]+'); final match = tempPattern.firstMatch(oldUrl); if (match != null) { sourcePath = match.group(0)!; } else { final tempFileName = Uri.decodeComponent(oldUrl.split('/').last.split('?').first); sourcePath = tempFileName; } final destinationPath = 'events/$eventId/$fileName'; final newUrl = await moveEventFileHttp( sourcePath: sourcePath, destinationPath: destinationPath, ); if (newUrl != null) { newFiles.add({'name': fileName, 'url': newUrl}); } else { newFiles.add({'name': fileName, 'url': oldUrl}); } } await docRef.update({'documents': newFiles}); final localUserProvider = Provider.of(context, listen: false); final userId = localUserProvider.uid; final canViewAllEvents = localUserProvider.hasPermission('view_all_events'); if (userId != null) { await eventProvider.loadUserEvents(userId, canViewAllEvents: canViewAllEvents); } setState(() { _success = "Événement créé avec succès !"; }); if (context.mounted) Navigator.of(context).pop(); } catch (e) { setState(() { _error = "Erreur lors de la création : $e"; }); } finally { setState(() { _isLoading = false; }); } } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(top: 16.0, bottom: 8.0), child: Align( alignment: Alignment.centerLeft, child: Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('Créer un événement'), ), body: Center( child: SingleChildScrollView( child: Card( elevation: 6, margin: const EdgeInsets.all(24), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 32), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(top: 0.0, bottom: 4.0), child: Align( alignment: Alignment.centerLeft, child: Text( 'Informations principales', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), ), ), TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Nom de l\'événement', border: OutlineInputBorder(), prefixIcon: Icon(Icons.event), ), validator: (v) => v == null || v.isEmpty ? 'Champ requis' : null, ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedEventType, items: _eventTypes .map((type) => DropdownMenuItem( value: type, child: Text(type), )) .toList(), onChanged: (val) => setState(() => _selectedEventType = val), decoration: const InputDecoration( labelText: 'Type d\'événement', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), validator: (v) => v == null ? 'Sélectionnez un type' : null, ), const SizedBox(height: 16), Row( children: [ Expanded( child: GestureDetector( onTap: () async { final picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2020), lastDate: DateTime(2099), ); if (picked != null) { final time = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (time != null) { setState(() { _startDateTime = DateTime( picked.year, picked.month, picked.day, time.hour, time.minute, ); if (_endDateTime != null && (_endDateTime! .isBefore(_startDateTime!) || _endDateTime!.isAtSameMomentAs( _startDateTime!))) { _endDateTime = null; } }); } } }, child: AbsorbPointer( child: TextFormField( readOnly: true, decoration: InputDecoration( labelText: 'Début', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.calendar_today), suffixIcon: const Icon(Icons.edit_calendar), ), controller: TextEditingController( text: _startDateTime == null ? '' : DateFormat('dd/MM/yyyy HH:mm') .format(_startDateTime!), ), validator: (v) => _startDateTime == null ? 'Champ requis' : null, ), ), ), ), const SizedBox(width: 16), Expanded( child: GestureDetector( onTap: _startDateTime == null ? null : () async { final picked = await showDatePicker( context: context, initialDate: _startDateTime! .add(const Duration(hours: 1)), firstDate: _startDateTime!, lastDate: DateTime(2099), ); if (picked != null) { final time = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (time != null) { setState(() { _endDateTime = DateTime( picked.year, picked.month, picked.day, time.hour, time.minute, ); }); } } }, child: AbsorbPointer( child: TextFormField( readOnly: true, decoration: InputDecoration( labelText: 'Fin', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.calendar_today), suffixIcon: const Icon(Icons.edit_calendar), ), controller: TextEditingController( text: _endDateTime == null ? '' : DateFormat('dd/MM/yyyy HH:mm') .format(_endDateTime!), ), validator: (v) => _endDateTime == null ? 'Champ requis' : (_startDateTime != null && _endDateTime != null && (_endDateTime!.isBefore( _startDateTime!) || _endDateTime!.isAtSameMomentAs( _startDateTime!))) ? 'La date de fin doit être après la date de début' : null, ), ), ), ), ], ), const SizedBox(height: 16), TextFormField( controller: _priceController, decoration: const InputDecoration( labelText: 'Prix (€)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.euro), hintText: '1050.50', ), keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'^\d*\.?\d{0,2}')), ], validator: (value) { if (value == null || value.isEmpty) { return 'Le prix est requis'; } final price = double.tryParse(value.replaceAll(',', '.')); if (price == null) { return 'Veuillez entrer un nombre valide'; } if (price < 0) { return 'Le prix ne peut pas être négatif'; } return null; }, ), _buildSectionTitle('Détails'), AnimatedContainer( duration: const Duration(milliseconds: 200), constraints: BoxConstraints( minHeight: 48, maxHeight: 48.0 * 10, ), child: TextFormField( controller: _descriptionController, minLines: 1, maxLines: _descriptionMaxLines > 10 ? 10 : _descriptionMaxLines, decoration: const InputDecoration( labelText: 'Description', border: OutlineInputBorder(), prefixIcon: Icon(Icons.description), ), ), ), const SizedBox(height: 20), Row( children: [ Expanded( child: IntStepperField( label: 'Installation (h)', controller: _installationController, min: 0, max: 99, ), ), const SizedBox(width: 16), Expanded( child: IntStepperField( label: 'Démontage (h)', controller: _disassemblyController, min: 0, max: 99, ), ), ], ), _buildSectionTitle('Adresse'), TextFormField( controller: _addressController, decoration: const InputDecoration( labelText: 'Adresse', border: OutlineInputBorder(), prefixIcon: Icon(Icons.location_on), ), validator: (v) => v == null || v.isEmpty ? 'Champ requis' : null, ), _buildSectionTitle('Personnel'), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: UserMultiSelectWidget( allUsers: _allUsers, selectedUserIds: _selectedUserIds, onChanged: (ids) => setState(() => _selectedUserIds = ids), isLoading: _isLoadingUsers, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionTitle('Documents'), DropzoneUploadWidget( uploadedFiles: _uploadedFiles, onFilesChanged: (files) => setState(() => _uploadedFiles = files), isLoading: _isLoading, error: _error, success: _success, ), ], ), ), ], ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _isLoading ? null : () => Navigator.of(context).pop(), child: const Text('Annuler'), ), const SizedBox(width: 8), ElevatedButton.icon( icon: const Icon(Icons.check), onPressed: _isLoading ? null : _submit, label: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Créer'), ), ], ), ], ), ), ), ), ), ), ); } }