feat: Sécurisation Firestore, gestion des prix HT/TTC et refactorisation majeure
Cette mise à jour verrouille l'accès direct à Firestore depuis le client pour renforcer la sécurité et introduit une gestion complète des prix HT/TTC dans toute l'application. Elle apporte également des améliorations significatives des permissions, des optimisations de performance et de nouvelles fonctionnalités.
### Sécurité et Backend
- **Firestore Rules :** Ajout de `firestore.rules` qui bloque par défaut tous les accès en lecture/écriture depuis le client. Toutes les opérations de données doivent maintenant passer par les Cloud Functions, renforçant considérablement la sécurité.
- **Index Firestore :** Création d'un fichier `firestore.indexes.json` pour optimiser les requêtes sur la collection `events`.
- **Cloud Functions :** Les fonctions de création/mise à jour d'événements ont été adaptées pour accepter des ID de documents (utilisateurs, type d'événement) et les convertir en `DocumentReference` côté serveur, simplifiant les appels depuis le client.
### Gestion des Prix HT/TTC
- **Calcul Automatisé :** Introduction d'un helper `PriceHelpers` et d'un widget `PriceHtTtcFields` pour calculer et synchroniser automatiquement les prix HT et TTC dans le formulaire d'événement.
- **Affichage Détaillé :**
- Les détails des événements et des options affichent désormais les prix HT, la TVA et le TTC séparément pour plus de clarté.
- Le prix de base (`basePrice`) est maintenant traité comme un prix TTC dans toute l'application.
### Permissions et Rôles
- **Centralisation (`AppPermission`) :** Création d'une énumération `AppPermission` pour centraliser toutes les permissions de l'application, avec descriptions et catégories.
- **Rôles Prédéfinis :** Définition de rôles standards (Admin, Manager, Technicien, User) avec des jeux de permissions prédéfinis.
- **Filtre par Utilisateur :** Ajout d'un filtre par utilisateur sur la page Calendrier, visible uniquement pour les utilisateurs ayant la permission `view_all_user_events`.
### Améliorations et Optimisations (Frontend)
- **`DebugLog` :** Ajout d'un utilitaire `DebugLog` pour gérer les logs, qui sont automatiquement désactivés en mode production.
- **Optimisation du Sélecteur d'Équipement :**
- La boîte de dialogue de sélection d'équipement a été lourdement optimisée pour éviter les reconstructions complètes de la liste lors de la sélection/désélection d'items.
- Utilisation de `ValueNotifier` et de caches locaux (`_cachedContainers`, `_cachedEquipment`) pour des mises à jour d'UI plus ciblées et fluides.
- La position du scroll est désormais préservée.
- **Catégorie d'Équipement :** Ajout de la catégorie `Vehicle` (Véhicule) pour les équipements.
- **Formulaires :** Les formulaires de création/modification d'événements et d'équipements ont été nettoyés de leurs logs de débogage excessifs.
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:em2rp/views/widgets/calendar_widgets/month_view.dart';
|
||||
import 'package:em2rp/views/widgets/calendar_widgets/week_view.dart';
|
||||
import 'package:em2rp/views/event_add_page.dart';
|
||||
import 'package:em2rp/views/widgets/calendar_widgets/mobile_calendar_view.dart';
|
||||
import 'package:em2rp/views/widgets/calendar_widgets/user_filter_dropdown.dart';
|
||||
import 'package:em2rp/utils/colors.dart';
|
||||
|
||||
class CalendarPage extends StatefulWidget {
|
||||
@@ -28,6 +29,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
EventModel? _selectedEvent;
|
||||
bool _calendarCollapsed = false;
|
||||
int _selectedEventIndex = 0;
|
||||
String? _selectedUserId; // Filtre par utilisateur (null = tous les événements)
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -94,6 +96,26 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Filtre les événements selon l'utilisateur sélectionné (si filtre actif)
|
||||
/// TEMPORAIREMENT DÉSACTIVÉ - À réactiver quand permission ajoutée dans Firestore
|
||||
List<EventModel> _getFilteredEvents(List<EventModel> allEvents) {
|
||||
if (_selectedUserId == null) {
|
||||
return allEvents; // Pas de filtre, retourner tous les événements
|
||||
}
|
||||
|
||||
// Filtrer les événements où l'utilisateur sélectionné fait partie de la workforce
|
||||
return allEvents.where((event) {
|
||||
return event.workforce.any((worker) {
|
||||
if (worker is String) {
|
||||
return worker == _selectedUserId;
|
||||
}
|
||||
// Si c'est une DocumentReference, on ne peut pas facilement comparer
|
||||
// On suppose que les données sont chargées correctement en String
|
||||
return false;
|
||||
});
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void _changeWeek(int delta) {
|
||||
setState(() {
|
||||
_focusedDay = _focusedDay.add(Duration(days: 7 * delta));
|
||||
@@ -104,9 +126,13 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final eventProvider = Provider.of<EventProvider>(context);
|
||||
final localUserProvider = Provider.of<LocalUserProvider>(context);
|
||||
final isAdmin = localUserProvider.hasPermission('view_all_users');
|
||||
final canCreateEvents = localUserProvider.hasPermission('create_events');
|
||||
final canViewAllUserEvents = localUserProvider.hasPermission('view_all_user_events');
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
|
||||
// Appliquer le filtre utilisateur si actif
|
||||
final filteredEvents = _getFilteredEvents(eventProvider.events);
|
||||
|
||||
if (eventProvider.isLoading) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
@@ -120,8 +146,42 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
title: "Calendrier",
|
||||
),
|
||||
drawer: const MainDrawer(currentPage: '/calendar'),
|
||||
body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(),
|
||||
floatingActionButton: isAdmin
|
||||
body: Column(
|
||||
children: [
|
||||
// Filtre utilisateur dans le corps de la page
|
||||
if (canViewAllUserEvents && !isMobile)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Colors.grey[100],
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.filter_list, color: AppColors.rouge),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'Filtrer par utilisateur :',
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: UserFilterDropdown(
|
||||
selectedUserId: _selectedUserId,
|
||||
onUserSelected: (userId) {
|
||||
setState(() {
|
||||
_selectedUserId = userId;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Corps du calendrier
|
||||
Expanded(
|
||||
child: isMobile ? _buildMobileLayout(filteredEvents) : _buildDesktopLayout(filteredEvents),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: canCreateEvents
|
||||
? FloatingActionButton(
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: () {
|
||||
@@ -140,14 +200,13 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDesktopLayout() {
|
||||
final eventProvider = Provider.of<EventProvider>(context);
|
||||
Widget _buildDesktopLayout(List<EventModel> filteredEvents) {
|
||||
return Row(
|
||||
children: [
|
||||
// Calendrier (65% de la largeur)
|
||||
Expanded(
|
||||
flex: 65,
|
||||
child: _buildCalendar(),
|
||||
child: _buildCalendar(filteredEvents),
|
||||
),
|
||||
// Détails de l'événement (35% de la largeur)
|
||||
Expanded(
|
||||
@@ -156,7 +215,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
? EventDetails(
|
||||
event: _selectedEvent!,
|
||||
selectedDate: _selectedDay,
|
||||
events: eventProvider.events,
|
||||
events: filteredEvents,
|
||||
onSelectEvent: (event, date) {
|
||||
setState(() {
|
||||
_selectedEvent = event;
|
||||
@@ -175,11 +234,10 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMobileLayout() {
|
||||
final eventProvider = Provider.of<EventProvider>(context);
|
||||
Widget _buildMobileLayout(List<EventModel> filteredEvents) {
|
||||
final eventsForSelectedDay = _selectedDay == null
|
||||
? []
|
||||
: eventProvider.events
|
||||
: filteredEvents
|
||||
.where((e) =>
|
||||
e.startDateTime.year == _selectedDay!.year &&
|
||||
e.startDateTime.month == _selectedDay!.month &&
|
||||
@@ -264,9 +322,9 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
child: MobileCalendarView(
|
||||
focusedDay: _focusedDay,
|
||||
selectedDay: _selectedDay,
|
||||
events: eventProvider.events,
|
||||
events: filteredEvents,
|
||||
onDaySelected: (day) {
|
||||
final eventsForDay = eventProvider.events
|
||||
final eventsForDay = filteredEvents
|
||||
.where((e) =>
|
||||
e.startDateTime.year == day.year &&
|
||||
e.startDateTime.month == day.month &&
|
||||
@@ -502,13 +560,11 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCalendar() {
|
||||
final eventProvider = Provider.of<EventProvider>(context);
|
||||
|
||||
Widget _buildCalendar(List<EventModel> filteredEvents) {
|
||||
if (_calendarFormat == CalendarFormat.week) {
|
||||
return WeekView(
|
||||
focusedDay: _focusedDay,
|
||||
events: eventProvider.events,
|
||||
events: filteredEvents,
|
||||
onWeekChange: _changeWeek,
|
||||
onEventSelected: (event) {
|
||||
setState(() {
|
||||
@@ -522,7 +578,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
});
|
||||
},
|
||||
onDaySelected: (selectedDay) {
|
||||
final eventsForDay = eventProvider.events
|
||||
final eventsForDay = filteredEvents
|
||||
.where((e) =>
|
||||
e.startDateTime.year == selectedDay.year &&
|
||||
e.startDateTime.month == selectedDay.month &&
|
||||
@@ -554,9 +610,9 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
focusedDay: _focusedDay,
|
||||
selectedDay: _selectedDay,
|
||||
calendarFormat: _calendarFormat,
|
||||
events: eventProvider.events,
|
||||
events: filteredEvents,
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
final eventsForDay = eventProvider.events
|
||||
final eventsForDay = filteredEvents
|
||||
.where((event) =>
|
||||
event.startDateTime.year == selectedDay.year &&
|
||||
event.startDateTime.month == selectedDay.month &&
|
||||
|
||||
Reference in New Issue
Block a user