Début page calendrier
This commit is contained in:
@ -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';
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
62
em2rp/lib/models/event_model.dart
Normal file
62
em2rp/lib/models/event_model.dart
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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,
|
}
|
||||||
|
|
||||||
|
Widget _buildDesktopLayout() {
|
||||||
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const Text('Page Calendrier', style: TextStyle(fontSize: 24)),
|
// Calendrier (65% de la largeur)
|
||||||
const SizedBox(height: 20),
|
Expanded(
|
||||||
if (localAuthProvider.role == 'ADMIN') // Get role from UserProvider
|
flex: 65,
|
||||||
const Text('Vue Admin du Calendrier',
|
child: _buildCalendar(),
|
||||||
style: TextStyle(fontSize: 18, color: AppColors.rouge))
|
),
|
||||||
else
|
// Détails de l'événement (35% de la largeur)
|
||||||
const Text('Vue Utilisateur du Calendrier',
|
Expanded(
|
||||||
style: TextStyle(fontSize: 18, color: Colors.blueGrey)),
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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>(
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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'),
|
||||||
|
123
em2rp/lib/widgets/event_details.dart
Normal file
123
em2rp/lib/widgets/event_details.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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:
|
||||||
|
Reference in New Issue
Block a user