Début page calendrier

This commit is contained in:
2025-05-15 20:42:49 +02:00
parent b8e4f39e4c
commit 72bb8f03de
13 changed files with 981 additions and 39 deletions

View File

@ -4,7 +4,7 @@ class Env {
// Configuration de l'auto-login en développement // Configuration de l'auto-login en développement
static const String devAdminEmail = 'paul.fournel@em2events.fr'; static const String devAdminEmail = 'paul.fournel@em2events.fr';
static const String devAdminPassword = 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 // URLs et endpoints
static const String baseUrl = 'https://em2rp-951dc.firebaseapp.com'; static const String baseUrl = 'https://em2rp-951dc.firebaseapp.com';

View File

@ -58,7 +58,7 @@ class MyApp extends StatelessWidget {
textTheme: const TextTheme( textTheme: const TextTheme(
bodyMedium: TextStyle(color: AppColors.noir), bodyMedium: TextStyle(color: AppColors.noir),
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.noir), borderSide: BorderSide(color: AppColors.noir),
), ),

View File

@ -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<String, dynamic> 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<String, dynamic> 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),
};
}
}

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import '../../providers/users_provider.dart';
class ResetPasswordPage extends StatefulWidget { class ResetPasswordPage extends StatefulWidget {
final String email; final String email;

View File

@ -4,34 +4,755 @@ import 'package:em2rp/widgets/custom_app_bar.dart';
import 'package:em2rp/views/widgets/nav/main_drawer.dart'; import 'package:em2rp/views/widgets/nav/main_drawer.dart';
import 'package:provider/provider.dart'; // Import Provider import 'package:provider/provider.dart'; // Import Provider
import 'package:em2rp/utils/colors.dart'; 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); const CalendarPage({Key? key}) : super(key: key);
@override
State<CalendarPage> createState() => _CalendarPageState();
}
class _CalendarPageState extends State<CalendarPage> {
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
EventModel? _selectedEvent;
// Événements de test
final List<EventModel> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localAuthProvider = Provider.of<LocalUserProvider>(context); final localAuthProvider = Provider.of<LocalUserProvider>(context);
final isMobile = MediaQuery.of(context).size.width < 600;
return Scaffold( return Scaffold(
appBar: const CustomAppBar( appBar: const CustomAppBar(
title: 'Calendrier', title: 'Calendrier',
), ),
drawer: const MainDrawer(currentPage: '/calendar'), drawer: const MainDrawer(currentPage: '/calendar'),
body: Center( body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(),
child: Column( );
mainAxisAlignment: MainAxisAlignment.center, }
children: [
const Text('Page Calendrier', style: TextStyle(fontSize: 24)), Widget _buildDesktopLayout() {
const SizedBox(height: 20), return Row(
if (localAuthProvider.role == 'ADMIN') // Get role from UserProvider children: [
const Text('Vue Admin du Calendrier', // Calendrier (65% de la largeur)
style: TextStyle(fontSize: 18, color: AppColors.rouge)) Expanded(
else flex: 65,
const Text('Vue Utilisateur du Calendrier', child: _buildCalendar(),
style: TextStyle(fontSize: 18, color: Colors.blueGrey)), ),
], // 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<List<_PositionedEvent>> 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<List<_PositionedEventWithColumn>> 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<EventModel> _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<List<_PositionedEventWithColumn>> 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);
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:em2rp/views/widgets/inputs/styled_text_field.dart'; import 'package:em2rp/views/widgets/inputs/styled_text_field.dart';
import 'package:em2rp/views/widgets/image/profile_picture_selector.dart'; import 'package:em2rp/views/widgets/image/profile_picture_selector.dart';
import 'package:em2rp/widgets/custom_app_bar.dart';
class MyAccountPage extends StatelessWidget { class MyAccountPage extends StatelessWidget {
const MyAccountPage({super.key}); const MyAccountPage({super.key});
@ -11,8 +12,10 @@ class MyAccountPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Mon Compte')), appBar: const CustomAppBar(
drawer: MainDrawer(currentPage: '/my_account'), title: 'Mon compte',
),
drawer: const MainDrawer(currentPage: '/my_account'),
body: Consumer<LocalUserProvider>( body: Consumer<LocalUserProvider>(
builder: (context, userProvider, child) { builder: (context, userProvider, child) {
final user = userProvider.currentUser; final user = userProvider.currentUser;

View File

@ -28,12 +28,12 @@ class _UserManagementPageState extends State<UserManagementPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PermissionGate( return PermissionGate(
requiredPermissions: [Permission.viewUsers], requiredPermissions: const [Permission.viewUsers],
fallback: Scaffold( fallback: const Scaffold(
appBar: const CustomAppBar( appBar: CustomAppBar(
title: 'Accès refusé', title: 'Accès refusé',
), ),
body: const Center( body: Center(
child: Text( child: Text(
'Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', 'Vous n\'avez pas les permissions nécessaires pour accéder à cette page.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -109,7 +109,7 @@ class _UserManagementPageState extends State<UserManagementPage> {
final phoneController = TextEditingController(); final phoneController = TextEditingController();
String selectedRole = Roles.values.first.name; String selectedRole = Roles.values.first.name;
InputDecoration _buildInputDecoration(String label, IconData icon) { InputDecoration buildInputDecoration(String label, IconData icon) {
return InputDecoration( return InputDecoration(
labelText: label, labelText: label,
prefixIcon: Icon(icon, color: AppColors.rouge), prefixIcon: Icon(icon, color: AppColors.rouge),
@ -159,31 +159,31 @@ class _UserManagementPageState extends State<UserManagementPage> {
TextField( TextField(
controller: firstNameController, controller: firstNameController,
decoration: decoration:
_buildInputDecoration('Prénom', Icons.person_outline), buildInputDecoration('Prénom', Icons.person_outline),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: lastNameController, controller: lastNameController,
decoration: _buildInputDecoration('Nom', Icons.person), decoration: buildInputDecoration('Nom', Icons.person),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: emailController, controller: emailController,
decoration: decoration:
_buildInputDecoration('Email', Icons.email_outlined), buildInputDecoration('Email', Icons.email_outlined),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: phoneController, controller: phoneController,
decoration: _buildInputDecoration( decoration: buildInputDecoration(
'Téléphone', Icons.phone_outlined), 'Téléphone', Icons.phone_outlined),
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: selectedRole, value: selectedRole,
decoration: _buildInputDecoration( decoration: buildInputDecoration(
'Rôle', Icons.admin_panel_settings_outlined), 'Rôle', Icons.admin_panel_settings_outlined),
items: Roles.values.map((Role role) { items: Roles.values.map((Role role) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(

View File

@ -6,7 +6,7 @@ class WelcomeTextWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( return const Text(
'Bienvenue !', 'Bienvenue !',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(

View File

@ -56,7 +56,7 @@ class ProfilePictureWidget extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: radius * 0.8, // Ajuster la taille du loader width: radius * 0.8, // Ajuster la taille du loader
height: radius * 0.8, height: radius * 0.8,
child: CircularProgressIndicator( child: const CircularProgressIndicator(
strokeWidth: 2), // Indicateur de chargement strokeWidth: 2), // Indicateur de chargement
), ),
); );

View File

@ -43,7 +43,7 @@ class _ProfilePictureSelectorState extends State<ProfilePictureSelector> {
Container( Container(
width: 160, width: 160,
height: 160, height: 160,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.black54, color: Colors.black54,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),

View File

@ -30,7 +30,7 @@ class MainDrawer extends StatelessWidget {
DrawerHeader( DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage('assets/EM2_NsurB.jpg'), image: const AssetImage('assets/EM2_NsurB.jpg'),
fit: BoxFit.cover, fit: BoxFit.cover,
colorFilter: ColorFilter.mode( colorFilter: ColorFilter.mode(
AppColors.noir.withOpacity(0.4), AppColors.noir.withOpacity(0.4),
@ -52,7 +52,7 @@ class MainDrawer extends StatelessWidget {
radius: 30, radius: 30,
) )
else else
CircleAvatar( const CircleAvatar(
radius: 30, radius: 30,
child: Icon(Icons.account_circle, size: 45), child: Icon(Icons.account_circle, size: 45),
), ),
@ -61,7 +61,7 @@ class MainDrawer extends StatelessWidget {
hasUser hasUser
? 'Bonjour, ${userProvider.currentUser!.firstName}' ? 'Bonjour, ${userProvider.currentUser!.firstName}'
: 'Bonjour, Utilisateur', : 'Bonjour, Utilisateur',
style: TextStyle( style: const TextStyle(
color: AppColors.blanc, color: AppColors.blanc,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -88,7 +88,7 @@ class MainDrawer extends StatelessWidget {
Navigator.pop(context); Navigator.pop(context);
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => CalendarPage()), MaterialPageRoute(builder: (context) => const CalendarPage()),
); );
}, },
), ),
@ -116,7 +116,7 @@ class MainDrawer extends StatelessWidget {
}, },
), ),
PermissionGate( PermissionGate(
requiredPermissions: [Permission.viewUsers], requiredPermissions: const [Permission.viewUsers],
child: ListTile( child: ListTile(
leading: const Icon(Icons.group), leading: const Icon(Icons.group),
title: const Text('Gestion des Utilisateurs'), title: const Text('Gestion des Utilisateurs'),

View File

@ -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,
),
],
),
);
}
}

View File

@ -1,7 +1,7 @@
name: em2rp name: em2rp
description: "A new Flutter project." description: "A new Flutter project."
publish_to: 'none' publish_to: 'none'
version: 0.1.0 version: 1.0.0+1
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@ -9,6 +9,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
firebase_core: ^3.12.1 firebase_core: ^3.12.1
firebase_auth: ^5.5.1 firebase_auth: ^5.5.1
cloud_firestore: ^5.6.5 cloud_firestore: ^5.6.5
@ -17,6 +18,40 @@ dependencies:
firebase_storage: ^12.4.4 firebase_storage: ^12.4.4
image_picker: ^1.1.2 image_picker: ^1.1.2
universal_io: ^2.2.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: dev_dependencies:
flutter_test: flutter_test: