diff --git a/em2rp/lib/config/env.dart b/em2rp/lib/config/env.dart index 7af019d..df62d3d 100644 --- a/em2rp/lib/config/env.dart +++ b/em2rp/lib/config/env.dart @@ -4,7 +4,7 @@ class Env { // Configuration de l'auto-login en développement static const String devAdminEmail = 'paul.fournel@em2events.fr'; static const String devAdminPassword = - 'votre_mot_de_passe'; // À remplacer par le vrai mot de passe + "Azerty\$1!"; // À remplacer par le vrai mot de passe // URLs et endpoints static const String baseUrl = 'https://em2rp-951dc.firebaseapp.com'; diff --git a/em2rp/lib/main.dart b/em2rp/lib/main.dart index 3bf1647..400a43d 100644 --- a/em2rp/lib/main.dart +++ b/em2rp/lib/main.dart @@ -58,7 +58,7 @@ class MyApp extends StatelessWidget { textTheme: const TextTheme( bodyMedium: TextStyle(color: AppColors.noir), ), - inputDecorationTheme: InputDecorationTheme( + inputDecorationTheme: const InputDecorationTheme( focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: AppColors.noir), ), diff --git a/em2rp/lib/models/event_model.dart b/em2rp/lib/models/event_model.dart new file mode 100644 index 0000000..9795ef5 --- /dev/null +++ b/em2rp/lib/models/event_model.dart @@ -0,0 +1,62 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:latlong2/latlong.dart'; + +class EventModel { + final String id; + final String name; + final String description; + final DateTime startDateTime; + final DateTime endDateTime; + final double price; + final int installationTime; + final int disassemblyTime; + final String eventTypeId; + final String customerId; + final LatLng address; + + EventModel({ + required this.id, + required this.name, + required this.description, + required this.startDateTime, + required this.endDateTime, + required this.price, + required this.installationTime, + required this.disassemblyTime, + required this.eventTypeId, + required this.customerId, + required this.address, + }); + + factory EventModel.fromMap(Map map, String id) { + final GeoPoint geoPoint = map['address'] as GeoPoint; + return EventModel( + id: id, + name: map['name'] ?? '', + description: map['description'] ?? '', + startDateTime: (map['startDateTime'] as Timestamp).toDate(), + endDateTime: (map['endDateTime'] as Timestamp).toDate(), + price: (map['price'] ?? 0.0).toDouble(), + installationTime: map['installationTime'] ?? 0, + disassemblyTime: map['disassemblyTime'] ?? 0, + eventTypeId: map['eventType'] ?? '', + customerId: map['customer'] ?? '', + address: LatLng(geoPoint.latitude, geoPoint.longitude), + ); + } + + Map toMap() { + return { + 'name': name, + 'description': description, + 'startDateTime': Timestamp.fromDate(startDateTime), + 'endDateTime': Timestamp.fromDate(endDateTime), + 'price': price, + 'installationTime': installationTime, + 'disassemblyTime': disassemblyTime, + 'eventType': eventTypeId, + 'customer': customerId, + 'address': GeoPoint(address.latitude, address.longitude), + }; + } +} diff --git a/em2rp/lib/pages/auth/reset_password_page.dart b/em2rp/lib/pages/auth/reset_password_page.dart index 06da411..5ed60b1 100644 --- a/em2rp/lib/pages/auth/reset_password_page.dart +++ b/em2rp/lib/pages/auth/reset_password_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:provider/provider.dart'; -import '../../providers/users_provider.dart'; class ResetPasswordPage extends StatefulWidget { final String email; diff --git a/em2rp/lib/views/calendar_page.dart b/em2rp/lib/views/calendar_page.dart index 7e7c77b..892f42e 100644 --- a/em2rp/lib/views/calendar_page.dart +++ b/em2rp/lib/views/calendar_page.dart @@ -4,34 +4,755 @@ import 'package:em2rp/widgets/custom_app_bar.dart'; import 'package:em2rp/views/widgets/nav/main_drawer.dart'; import 'package:provider/provider.dart'; // Import Provider import 'package:em2rp/utils/colors.dart'; +import 'package:table_calendar/table_calendar.dart'; +import 'package:em2rp/models/event_model.dart'; +import 'package:em2rp/widgets/event_details.dart'; +import 'package:latlong2/latlong.dart'; -class CalendarPage extends StatelessWidget { +class CalendarPage extends StatefulWidget { const CalendarPage({Key? key}) : super(key: key); + @override + State createState() => _CalendarPageState(); +} + +class _CalendarPageState extends State { + CalendarFormat _calendarFormat = CalendarFormat.month; + DateTime _focusedDay = DateTime.now(); + DateTime? _selectedDay; + EventModel? _selectedEvent; + + // Événements de test + final List _testEvents = [ + EventModel( + id: '1', + name: 'Bal a Grammond', + description: 'Lorem Ipsum', + startDateTime: DateTime(2025, 3, 28, 12, 30), + endDateTime: DateTime(2025, 3, 28, 23, 30), + price: 950.34, + installationTime: 3, + disassemblyTime: 2, + eventTypeId: '/eventTypes/Bal', + customerId: '/customers/DnjJ1HOPBLqEeExs0nDl', + address: const LatLng(45.566521035268224, 4.439601075086365), + ), + EventModel( + id: '2', + name: 'Mariage à Lyon', + description: 'Cérémonie et réception', + startDateTime: DateTime(2025, 3, 28, 15, 0), + endDateTime: DateTime(2025, 3, 29, 4, 0), + price: 2500.00, + installationTime: 4, + disassemblyTime: 3, + eventTypeId: '/eventTypes/Mariage', + customerId: '/customers/Test123', + address: const LatLng(45.7578137, 4.8320114), + ), + ]; + + void _changeWeek(int delta) { + setState(() { + _focusedDay = _focusedDay.add(Duration(days: 7 * delta)); + }); + } + @override Widget build(BuildContext context) { final localAuthProvider = Provider.of(context); + final isMobile = MediaQuery.of(context).size.width < 600; return Scaffold( appBar: const CustomAppBar( title: 'Calendrier', ), drawer: const MainDrawer(currentPage: '/calendar'), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Page Calendrier', style: TextStyle(fontSize: 24)), - const SizedBox(height: 20), - if (localAuthProvider.role == 'ADMIN') // Get role from UserProvider - const Text('Vue Admin du Calendrier', - style: TextStyle(fontSize: 18, color: AppColors.rouge)) - else - const Text('Vue Utilisateur du Calendrier', - style: TextStyle(fontSize: 18, color: Colors.blueGrey)), - ], + body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(), + ); + } + + Widget _buildDesktopLayout() { + return Row( + children: [ + // Calendrier (65% de la largeur) + Expanded( + flex: 65, + child: _buildCalendar(), + ), + // Détails de l'événement (35% de la largeur) + Expanded( + flex: 35, + child: _selectedEvent != null + ? EventDetails(event: _selectedEvent!) + : const Center( + child: + Text('Sélectionnez un événement pour voir les détails'), + ), + ), + ], + ); + } + + Widget _buildMobileLayout() { + return Column( + children: [ + // Calendrier + Expanded( + child: _buildCalendar(), + ), + // Détails de l'événement + if (_selectedEvent != null) + Expanded( + child: EventDetails(event: _selectedEvent!), + ), + ], + ); + } + + Widget _buildCalendar() { + return LayoutBuilder( + builder: (context, constraints) { + if (_calendarFormat == CalendarFormat.week) { + return _buildWeekView(constraints); + } else { + return _buildMonthView(constraints); + } + }, + ); + } + + Widget _buildMonthView(BoxConstraints constraints) { + // Calculer la hauteur des lignes en fonction de la hauteur disponible + // Ajustement pour les mois à 6 semaines + final rowHeight = (constraints.maxHeight - 100) / + 6; // Augmenté de 80 à 100 pour donner plus d'espace + + return Container( + height: constraints.maxHeight, + padding: + const EdgeInsets.all(8), // Réduit de 16 à 8 pour gagner de l'espace + child: TableCalendar( + firstDay: DateTime.utc(2020, 1, 1), + lastDay: DateTime.utc(2030, 12, 31), + focusedDay: _focusedDay, + calendarFormat: _calendarFormat, + startingDayOfWeek: StartingDayOfWeek.monday, + availableCalendarFormats: const { + CalendarFormat.month: 'Mois', + CalendarFormat.week: 'Semaine', + }, + selectedDayPredicate: (day) { + return isSameDay(_selectedDay, day); + }, + onDaySelected: (selectedDay, focusedDay) { + setState(() { + _selectedDay = selectedDay; + _focusedDay = focusedDay; + }); + }, + onFormatChanged: (format) { + setState(() { + _calendarFormat = format; + }); + }, + onPageChanged: (focusedDay) { + _focusedDay = focusedDay; + }, + calendarStyle: CalendarStyle( + defaultDecoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(4), + ), + selectedDecoration: BoxDecoration( + color: AppColors.rouge, + border: Border.all(color: AppColors.rouge), + borderRadius: BorderRadius.circular(4), + ), + todayDecoration: BoxDecoration( + color: AppColors.rouge.withOpacity(0.1), + border: Border.all(color: AppColors.rouge), + borderRadius: BorderRadius.circular(4), + ), + outsideDecoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(4), + ), + cellMargin: EdgeInsets.zero, + cellPadding: EdgeInsets.zero, + ), + rowHeight: rowHeight, + headerStyle: HeaderStyle( + formatButtonVisible: true, + titleCentered: true, + formatButtonShowsNext: false, + formatButtonDecoration: BoxDecoration( + color: AppColors.rouge, + borderRadius: BorderRadius.circular(16), + ), + formatButtonTextStyle: const TextStyle(color: Colors.white), + leftChevronIcon: + const Icon(Icons.chevron_left, color: AppColors.rouge), + rightChevronIcon: + const Icon(Icons.chevron_right, color: AppColors.rouge), + headerPadding: const EdgeInsets.symmetric(vertical: 8), + ), + calendarBuilders: CalendarBuilders( + defaultBuilder: (context, day, focusedDay) { + final events = _getEventsForDay(day); + + return Container( + margin: const EdgeInsets.all(4), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(4), + ), + child: Stack( + children: [ + Center( + child: Text( + day.day.toString(), + style: TextStyle( + color: + isSameDay(day, _selectedDay) ? Colors.white : null, + ), + ), + ), + if (events.isNotEmpty) + Positioned( + bottom: 2, + left: 2, + right: 2, + top: 24, // Espace pour le numéro du jour + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: events + .map((event) => GestureDetector( + onTap: () { + setState(() { + _selectedEvent = event; + _selectedDay = day; + }); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 2), + decoration: BoxDecoration( + color: AppColors.rouge.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + event.name, + style: const TextStyle( + fontSize: 12, + color: AppColors.rouge, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (_isMultiDayEvent(event)) + Text( + 'Jour ${_calculateDayNumber(event.startDateTime, day)}/${_calculateTotalDays(event)}', + style: const TextStyle( + fontSize: 10, + color: AppColors.rouge, + ), + maxLines: 1, + ), + ], + ), + ), + )) + .toList(), + ), + ), + ), + ], + ), + ); + }, + selectedBuilder: (context, day, focusedDay) { + final events = _getEventsForDay(day); + + return Container( + margin: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.rouge, + border: Border.all(color: AppColors.rouge), + borderRadius: BorderRadius.circular(4), + ), + child: Stack( + children: [ + Center( + child: Text( + day.day.toString(), + style: const TextStyle(color: Colors.white), + ), + ), + if (events.isNotEmpty) + Positioned( + bottom: 2, + left: 2, + right: 2, + top: 24, // Espace pour le numéro du jour + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: events + .map((event) => GestureDetector( + onTap: () { + setState(() { + _selectedEvent = event; + }); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 2), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + event.name, + style: const TextStyle( + fontSize: 12, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (_isMultiDayEvent(event)) + Text( + 'Jour ${_calculateDayNumber(event.startDateTime, day)}/${_calculateTotalDays(event)}', + style: const TextStyle( + fontSize: 10, + color: Colors.white, + ), + maxLines: 1, + ), + ], + ), + ), + )) + .toList(), + ), + ), + ), + ], + ), + ); + }, ), ), ); } + + Widget _buildWeekView(BoxConstraints constraints) { + final weekStart = + _focusedDay.subtract(Duration(days: _focusedDay.weekday - 1)); + final weekEnd = weekStart.add(const Duration(days: 6)); + + // Ajustement de la hauteur pour éviter l'overflow + double availableHeight = + constraints.maxHeight - 80; // Réserver de l'espace pour les en-têtes + final hourHeight = availableHeight / 24; + final dayWidth = (constraints.maxWidth - 50) / 7; + + // Préparer les événements par jour (en tenant compte des multi-jours) + List> eventsByDay = List.generate(7, (i) => []); + for (final event in _testEvents) { + // Pour chaque jour de la semaine + for (int i = 0; i < 7; i++) { + final day = weekStart.add(Duration(days: i)); + final dayStart = DateTime(day.year, day.month, day.day, 0, 0); + final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59); + // Si l'événement recouvre ce jour + if (!(event.endDateTime.isBefore(dayStart) || + event.startDateTime.isAfter(dayEnd))) { + // Tronquer les heures de début/fin si besoin + final start = event.startDateTime.isBefore(dayStart) + ? dayStart + : event.startDateTime; + final end = + event.endDateTime.isAfter(dayEnd) ? dayEnd : event.endDateTime; + eventsByDay[i].add(_PositionedEvent(event, start, end)); + } + } + } + + // Pour chaque jour, calculer les "colonnes" d'événements qui se chevauchent + List> eventsWithColumnsByDay = []; + for (final dayEvents in eventsByDay) { + final columns = _assignColumns(dayEvents); + eventsWithColumnsByDay.add(columns); + } + + return Column( + children: [ + // Barre d'en-tête semaine + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.chevron_left), + onPressed: () => _changeWeek(-1), + ), + Text( + _getMonthYearString(weekStart, weekEnd), + style: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + ), + Row( + children: [ + TextButton( + onPressed: () { + setState(() { + _calendarFormat = CalendarFormat.month; + }); + }, + style: TextButton.styleFrom( + backgroundColor: AppColors.rouge, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + child: const Text('Mois'), + ), + IconButton( + icon: const Icon(Icons.chevron_right), + onPressed: () => _changeWeek(1), + ), + ], + ), + ], + ), + ), + // En-tête avec les jours + SizedBox( + height: 40, + child: Row( + children: [ + Container( + width: 50, + color: Colors.transparent, + ), + ...List.generate(7, (index) { + final day = weekStart.add(Duration(days: index)); + return Container( + width: dayWidth, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: index < 6 + ? Colors.grey.shade300 + : Colors.transparent, + width: 1, + ), + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(_getDayName(day.weekday), + style: + const TextStyle(fontWeight: FontWeight.bold)), + Text('${day.day}', + style: const TextStyle(fontSize: 13)), + ], + ), + ), + ); + }), + ], + ), + ), + // Grille des heures + jours + Expanded( + child: SingleChildScrollView( + child: SizedBox( + height: 24 * hourHeight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Colonne des heures + Column( + children: List.generate(24, (index) { + return Container( + width: 50, + height: hourHeight, + alignment: Alignment.topRight, + padding: const EdgeInsets.only(right: 4), + child: Text( + '${index.toString().padLeft(2, '0')}:00', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 12, + ), + ), + ); + }), + ), + // Grille des jours + Expanded( + child: Stack( + children: [ + // Lignes horizontales + Column( + children: List.generate(24, (index) { + return Container( + height: hourHeight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.grey.shade300, + width: 0.5, + ), + ), + ), + ); + }), + ), + // Bordures verticales entre jours + Positioned.fill( + child: Row( + children: List.generate(7, (i) { + return Container( + width: dayWidth, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: i < 6 + ? Colors.grey.shade300 + : Colors.transparent, + width: 1, + ), + ), + ), + ); + }), + ), + ), + // Événements (chevauchements et multi-jours) + ...List.generate(7, (dayIdx) { + final dayEvents = eventsWithColumnsByDay[dayIdx]; + return Stack( + children: dayEvents.map((e) { + final startHour = + e.start.hour + e.start.minute / 60; + final endHour = e.end.hour + e.end.minute / 60; + final duration = endHour - startHour; + final width = dayWidth / e.totalColumns; + return Positioned( + left: dayIdx * dayWidth + e.column * width, + top: startHour * hourHeight, + width: width, + height: duration * hourHeight, + child: GestureDetector( + onTap: () { + setState(() { + _selectedEvent = e.event; + _selectedDay = + weekStart.add(Duration(days: dayIdx)); + }); + }, + child: Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.rouge.withOpacity(0.2), + border: + Border.all(color: AppColors.rouge), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + e.event.name, + style: const TextStyle( + color: AppColors.rouge, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (_isMultiDayEvent(e.event)) + Text( + 'Jour ${_calculateDayNumber(e.event.startDateTime, weekStart.add(Duration(days: dayIdx)))}/${_calculateTotalDays(e.event)}', + style: const TextStyle( + color: AppColors.rouge, + fontSize: 10, + ), + maxLines: 1, + ), + ], + ), + ), + ), + ); + }).toList(), + ); + }), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + // Méthode pour récupérer les événements pour un jour donné (inclut les multi-jours) + List _getEventsForDay(DateTime day) { + final dayStart = DateTime(day.year, day.month, day.day, 0, 0); + final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59); + + return _testEvents.where((event) { + return !(event.endDateTime.isBefore(dayStart) || + event.startDateTime.isAfter(dayEnd)); + }).toList(); + } + + // Méthodes pour gérer les événements multi-jours + bool _isMultiDayEvent(EventModel event) { + return event.startDateTime.day != event.endDateTime.day || + event.startDateTime.month != event.endDateTime.month || + event.startDateTime.year != event.endDateTime.year; + } + + int _calculateTotalDays(EventModel event) { + final startDate = DateTime(event.startDateTime.year, + event.startDateTime.month, event.startDateTime.day); + final endDate = DateTime( + event.endDateTime.year, event.endDateTime.month, event.endDateTime.day); + return endDate.difference(startDate).inDays + 1; + } + + int _calculateDayNumber(DateTime startDate, DateTime currentDay) { + final start = DateTime(startDate.year, startDate.month, startDate.day); + final current = DateTime(currentDay.year, currentDay.month, currentDay.day); + return current.difference(start).inDays + 1; + } + + String _getDayName(int weekday) { + switch (weekday) { + case DateTime.monday: + return 'Lun'; + case DateTime.tuesday: + return 'Mar'; + case DateTime.wednesday: + return 'Mer'; + case DateTime.thursday: + return 'Jeu'; + case DateTime.friday: + return 'Ven'; + case DateTime.saturday: + return 'Sam'; + case DateTime.sunday: + return 'Dim'; + default: + return ''; + } + } + + String _getMonthYearString(DateTime weekStart, DateTime weekEnd) { + final months = [ + '', + 'Janvier', + 'Février', + 'Mars', + 'Avril', + 'Mai', + 'Juin', + 'Juillet', + 'Août', + 'Septembre', + 'Octobre', + 'Novembre', + 'Décembre' + ]; + if (weekStart.month == weekEnd.month) { + return '${months[weekStart.month]} ${weekStart.year}'; + } else { + return '${months[weekStart.month]} - ${months[weekEnd.month]} ${weekEnd.year}'; + } + } +} + +class _PositionedEvent { + final EventModel event; + final DateTime start; + final DateTime end; + _PositionedEvent(this.event, this.start, this.end); +} + +class _PositionedEventWithColumn extends _PositionedEvent { + final int column; + final int totalColumns; + _PositionedEventWithColumn(EventModel event, DateTime start, DateTime end, + this.column, this.totalColumns) + : super(event, start, end); +} + +List<_PositionedEventWithColumn> _assignColumns(List<_PositionedEvent> events) { + // Algorithme simple : + // - Trier par heure de début + // - Pour chaque événement, trouver la première colonne libre + // - Attribuer le nombre total de colonnes pour le groupe de chevauchement + events.sort((a, b) => a.start.compareTo(b.start)); + List<_PositionedEventWithColumn> result = []; + List> columns = []; + for (final e in events) { + bool placed = false; + for (int col = 0; col < columns.length; col++) { + if (columns[col].isEmpty || !(_overlap(columns[col].last, e))) { + columns[col] + .add(_PositionedEventWithColumn(e.event, e.start, e.end, col, 0)); + placed = true; + break; + } + } + if (!placed) { + columns.add([ + _PositionedEventWithColumn(e.event, e.start, e.end, columns.length, 0) + ]); + } + } + // Mettre à jour le nombre total de colonnes pour chaque événement + int totalCols = columns.length; + for (final col in columns) { + for (final e in col) { + result.add(_PositionedEventWithColumn( + e.event, e.start, e.end, e.column, totalCols)); + } + } + return result; +} + +bool _overlap(_PositionedEvent a, _PositionedEvent b) { + return a.end.isAfter(b.start) && a.start.isBefore(b.end); } diff --git a/em2rp/lib/views/my_account_page.dart b/em2rp/lib/views/my_account_page.dart index 024e63e..225d715 100644 --- a/em2rp/lib/views/my_account_page.dart +++ b/em2rp/lib/views/my_account_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/views/widgets/inputs/styled_text_field.dart'; import 'package:em2rp/views/widgets/image/profile_picture_selector.dart'; +import 'package:em2rp/widgets/custom_app_bar.dart'; class MyAccountPage extends StatelessWidget { const MyAccountPage({super.key}); @@ -11,8 +12,10 @@ class MyAccountPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Mon Compte')), - drawer: MainDrawer(currentPage: '/my_account'), + appBar: const CustomAppBar( + title: 'Mon compte', + ), + drawer: const MainDrawer(currentPage: '/my_account'), body: Consumer( builder: (context, userProvider, child) { final user = userProvider.currentUser; diff --git a/em2rp/lib/views/user_management_page.dart b/em2rp/lib/views/user_management_page.dart index f7d678e..933142c 100644 --- a/em2rp/lib/views/user_management_page.dart +++ b/em2rp/lib/views/user_management_page.dart @@ -28,12 +28,12 @@ class _UserManagementPageState extends State { @override Widget build(BuildContext context) { return PermissionGate( - requiredPermissions: [Permission.viewUsers], - fallback: Scaffold( - appBar: const CustomAppBar( + requiredPermissions: const [Permission.viewUsers], + fallback: const Scaffold( + appBar: CustomAppBar( title: 'Accès refusé', ), - body: const Center( + body: Center( child: Text( 'Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', textAlign: TextAlign.center, @@ -109,7 +109,7 @@ class _UserManagementPageState extends State { final phoneController = TextEditingController(); String selectedRole = Roles.values.first.name; - InputDecoration _buildInputDecoration(String label, IconData icon) { + InputDecoration buildInputDecoration(String label, IconData icon) { return InputDecoration( labelText: label, prefixIcon: Icon(icon, color: AppColors.rouge), @@ -159,31 +159,31 @@ class _UserManagementPageState extends State { TextField( controller: firstNameController, decoration: - _buildInputDecoration('Prénom', Icons.person_outline), + buildInputDecoration('Prénom', Icons.person_outline), ), const SizedBox(height: 16), TextField( controller: lastNameController, - decoration: _buildInputDecoration('Nom', Icons.person), + decoration: buildInputDecoration('Nom', Icons.person), ), const SizedBox(height: 16), TextField( controller: emailController, decoration: - _buildInputDecoration('Email', Icons.email_outlined), + buildInputDecoration('Email', Icons.email_outlined), keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 16), TextField( controller: phoneController, - decoration: _buildInputDecoration( + decoration: buildInputDecoration( 'Téléphone', Icons.phone_outlined), keyboardType: TextInputType.phone, ), const SizedBox(height: 16), DropdownButtonFormField( value: selectedRole, - decoration: _buildInputDecoration( + decoration: buildInputDecoration( 'Rôle', Icons.admin_panel_settings_outlined), items: Roles.values.map((Role role) { return DropdownMenuItem( diff --git a/em2rp/lib/views/widgets/auth/welcome_text.dart b/em2rp/lib/views/widgets/auth/welcome_text.dart index c2fc3f5..2ddc273 100644 --- a/em2rp/lib/views/widgets/auth/welcome_text.dart +++ b/em2rp/lib/views/widgets/auth/welcome_text.dart @@ -6,7 +6,7 @@ class WelcomeTextWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( + return const Text( 'Bienvenue !', textAlign: TextAlign.center, style: TextStyle( diff --git a/em2rp/lib/views/widgets/image/profile_picture.dart b/em2rp/lib/views/widgets/image/profile_picture.dart index 1abf26a..32d3b02 100644 --- a/em2rp/lib/views/widgets/image/profile_picture.dart +++ b/em2rp/lib/views/widgets/image/profile_picture.dart @@ -56,7 +56,7 @@ class ProfilePictureWidget extends StatelessWidget { child: SizedBox( width: radius * 0.8, // Ajuster la taille du loader height: radius * 0.8, - child: CircularProgressIndicator( + child: const CircularProgressIndicator( strokeWidth: 2), // Indicateur de chargement ), ); diff --git a/em2rp/lib/views/widgets/image/profile_picture_selector.dart b/em2rp/lib/views/widgets/image/profile_picture_selector.dart index f90c7a8..060608e 100644 --- a/em2rp/lib/views/widgets/image/profile_picture_selector.dart +++ b/em2rp/lib/views/widgets/image/profile_picture_selector.dart @@ -43,7 +43,7 @@ class _ProfilePictureSelectorState extends State { Container( width: 160, height: 160, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.black54, shape: BoxShape.circle, ), diff --git a/em2rp/lib/views/widgets/nav/main_drawer.dart b/em2rp/lib/views/widgets/nav/main_drawer.dart index 64319f0..4a3dad7 100644 --- a/em2rp/lib/views/widgets/nav/main_drawer.dart +++ b/em2rp/lib/views/widgets/nav/main_drawer.dart @@ -30,7 +30,7 @@ class MainDrawer extends StatelessWidget { DrawerHeader( decoration: BoxDecoration( image: DecorationImage( - image: AssetImage('assets/EM2_NsurB.jpg'), + image: const AssetImage('assets/EM2_NsurB.jpg'), fit: BoxFit.cover, colorFilter: ColorFilter.mode( AppColors.noir.withOpacity(0.4), @@ -52,7 +52,7 @@ class MainDrawer extends StatelessWidget { radius: 30, ) else - CircleAvatar( + const CircleAvatar( radius: 30, child: Icon(Icons.account_circle, size: 45), ), @@ -61,7 +61,7 @@ class MainDrawer extends StatelessWidget { hasUser ? 'Bonjour, ${userProvider.currentUser!.firstName}' : 'Bonjour, Utilisateur', - style: TextStyle( + style: const TextStyle( color: AppColors.blanc, fontSize: 18, fontWeight: FontWeight.bold, @@ -88,7 +88,7 @@ class MainDrawer extends StatelessWidget { Navigator.pop(context); Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => CalendarPage()), + MaterialPageRoute(builder: (context) => const CalendarPage()), ); }, ), @@ -116,7 +116,7 @@ class MainDrawer extends StatelessWidget { }, ), PermissionGate( - requiredPermissions: [Permission.viewUsers], + requiredPermissions: const [Permission.viewUsers], child: ListTile( leading: const Icon(Icons.group), title: const Text('Gestion des Utilisateurs'), diff --git a/em2rp/lib/widgets/event_details.dart b/em2rp/lib/widgets/event_details.dart new file mode 100644 index 0000000..60c49f4 --- /dev/null +++ b/em2rp/lib/widgets/event_details.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:em2rp/models/event_model.dart'; +import 'package:em2rp/utils/colors.dart'; +import 'package:intl/intl.dart'; + +class EventDetails extends StatelessWidget { + final EventModel event; + + const EventDetails({ + Key? key, + required this.event, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final dateFormat = DateFormat('dd/MM/yyyy HH:mm'); + final currencyFormat = NumberFormat.currency(locale: 'fr_FR', symbol: '€'); + + return Card( + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + event.name, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: AppColors.noir, + fontWeight: FontWeight.bold, + ), + ), + 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), + Text( + 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), + Text( + '${event.address.latitude}° N, ${event.address.longitude}° E', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ); + } + + Widget _buildInfoRow( + BuildContext context, + IconData icon, + String label, + String value, + ) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + Icon(icon, color: AppColors.rouge), + const SizedBox(width: 8), + Text( + '$label : ', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: AppColors.noir, + fontWeight: FontWeight.bold, + ), + ), + Text( + value, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + ); + } +} diff --git a/em2rp/pubspec.yaml b/em2rp/pubspec.yaml index 8c3ed53..79220c7 100644 --- a/em2rp/pubspec.yaml +++ b/em2rp/pubspec.yaml @@ -1,7 +1,7 @@ name: em2rp description: "A new Flutter project." publish_to: 'none' -version: 0.1.0 +version: 1.0.0+1 environment: sdk: ^3.5.4 @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + firebase_core: ^3.12.1 firebase_auth: ^5.5.1 cloud_firestore: ^5.6.5 @@ -17,6 +18,40 @@ dependencies: firebase_storage: ^12.4.4 image_picker: ^1.1.2 universal_io: ^2.2.2 + cupertino_icons: ^1.0.2 + table_calendar: ^3.0.9 + intl: ^0.19.0 + google_maps_flutter: ^2.5.0 + permission_handler: ^11.1.0 + geolocator: ^10.1.0 + flutter_map: ^6.1.0 + latlong2: ^0.9.0 + flutter_launcher_icons: ^0.13.1 + flutter_native_splash: ^2.3.9 + url_launcher: ^6.2.2 + share_plus: ^7.2.1 + path_provider: ^2.1.2 + pdf: ^3.10.7 + printing: ^5.11.1 + flutter_local_notifications: ^16.3.0 + timezone: ^0.9.2 + flutter_secure_storage: ^9.0.0 + http: ^1.1.2 + flutter_dotenv: ^5.1.0 + google_fonts: ^6.1.0 + flutter_svg: ^2.0.9 + cached_network_image: ^3.3.1 + flutter_staggered_grid_view: ^0.7.0 + shimmer: ^3.0.0 + flutter_slidable: ^3.0.1 + flutter_datetime_picker: ^1.5.1 + flutter_colorpicker: ^1.0.3 + flutter_rating_bar: ^4.0.1 + flutter_chat_ui: ^1.6.10 + flutter_chat_types: ^3.6.2 + uuid: ^4.2.2 + flutter_localizations: + sdk: flutter dev_dependencies: flutter_test: