diff --git a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
index 8c7bf2f..2f8c4a6 100644
--- a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
+++ b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
@@ -32,16 +32,16 @@ assets/assets/images/tshirt-incrust.webp,1737393735487,af7cb34adfca19c0b41c8eb63
assets/assets/icons/truss.svg,1761734811263,8ddfbbb4f96de5614348eb23fa55f61b2eb1edb064719a8bbd791c35883ec4cc
assets/assets/icons/tape.svg,1761734809221,631183f0ff972aa4dc3f9f51dc7abd41a607df749d1f9a44fa7e77202d95ccde
assets/assets/icons/flight-case.svg,1761734822495,0cef47fdf5d7efdd110763c32f792ef9735df35c4f42ae7d02d5fbda40e6148d
-version.json,1768738172901,f258e76dbf34b4a64999cb6d1d983255ad592c590e53f7c4fe380b2bfef82762
-index.html,1768738180374,4e8c00552c71ef134bead8bc03706952e7a415d70fca602a3839dc02a3f7ae10
-flutter_service_worker.js,1768738281912,ad5fcbc95e3f4e31b6c3ae92df0a872c24434ba7ac7448fdd9359f2e3bf7d76c
-assets/FontManifest.json,1768738277185,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5
-flutter_bootstrap.js,1768738180360,f1963883a54097e939404b503b6a9963408fe0187a18d73adb648f6ef0f81578
-assets/AssetManifest.bin.json,1768738277185,f446eb3de964f3a6f9e76fcc98d79a81b0429e076c9c7bf30cf8edd0263a0b0a
-assets/AssetManifest.bin,1768738277184,72bbccb69d9a02d3885df0c5e58ebfed29e25a4919e10bf195b59542f4709ca3
-assets/AssetManifest.json,1768738277185,1e1501af5844823ef215cf650f4cef4002c0389d88770225ac07576d57dc1067
-assets/shaders/ink_sparkle.frag,1768738277454,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406
-assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1768738280959,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb
-assets/fonts/MaterialIcons-Regular.otf,1768738280969,9e7c35e587de73a0aee5675d5aef4c6830478af0aa31ad0da76b84a503906b03
-assets/NOTICES,1768738277188,fc20c3c3c998057eb7e58ad2e009c7268bf748bfde685e95130431f4c54bd51c
-main.dart.js,1768738275891,4ef7f90056f38602de6430a68a479a005268f9d83395ad9b444337c214a3710c
+version.json,1770478530807,2cbfdf7f34574c2f9d4f1af02acb86d8d230af93790c97a3c7e1674c4db42ef4
+index.html,1770478536326,4e8c00552c71ef134bead8bc03706952e7a415d70fca602a3839dc02a3f7ae10
+flutter_service_worker.js,1770478628965,cb72807cfcb05b0a2e7b3f4f0cf618a0284a3d2476c93672bd86ea99670b0f5d
+assets/FontManifest.json,1770478624084,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5
+assets/AssetManifest.json,1770478624084,1e1501af5844823ef215cf650f4cef4002c0389d88770225ac07576d57dc1067
+flutter_bootstrap.js,1770478536318,bf4a3b4bf79eaed1ce24892f20cfb270bcc22fb392bc9f6a1d17aeed42ed4ed8
+assets/AssetManifest.bin.json,1770478624084,f446eb3de964f3a6f9e76fcc98d79a81b0429e076c9c7bf30cf8edd0263a0b0a
+assets/AssetManifest.bin,1770478624084,72bbccb69d9a02d3885df0c5e58ebfed29e25a4919e10bf195b59542f4709ca3
+assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1770478628013,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb
+assets/shaders/ink_sparkle.frag,1770478624492,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406
+assets/fonts/MaterialIcons-Regular.otf,1770478628013,50e06fd231edee237d875cddbae1e22b682d32bb1284e3c32ca409fa489f9c21
+assets/NOTICES,1770478624086,d02d64a466e62fdaeee2534a3f65541362ccf29beb495e2af0fdce41f4ae28d9
+main.dart.js,1770478620736,03d43aeaa96cfdbe5b7491f9610223ec95c29d47095570dd61cd6cddac863496
diff --git a/em2rp/SYSTEME_MISE_A_JOUR.md b/em2rp/SYSTEME_MISE_A_JOUR.md
deleted file mode 100644
index db72b84..0000000
--- a/em2rp/SYSTEME_MISE_A_JOUR.md
+++ /dev/null
@@ -1,337 +0,0 @@
-# Système de Gestion des Mises à Jour - EM2RP
-
-## 📋 Vue d'ensemble
-
-Ce système permet de gérer automatiquement les mises à jour de l'application web Flutter, en notifiant les utilisateurs et en forçant le rechargement du cache si nécessaire.
-
----
-
-## 🔧 Architecture
-
-### Fichiers impliqués
-
-#### Configuration
-- **`lib/config/app_version.dart`** : Fichier source de vérité pour la version
-- **`web/version.json`** : Fichier déployé avec l'app pour vérification côté serveur
-
-#### Services
-- **`lib/services/update_service.dart`** : Service de vérification des mises à jour
-- **`lib/views/widgets/common/update_dialog.dart`** : Widget d'affichage du dialog de mise à jour
-
-#### Scripts
-- **`scripts/increment_version.js`** : Incrémente automatiquement la version
-- **`scripts/update_version_json.js`** : Génère version.json depuis app_version.dart
-- **`deploy.bat`** : Script de déploiement complet
-
-#### Documentation
-- **`CHANGELOG.md`** : Notes de version (utilisées dans le dialog)
-
----
-
-## 🚀 Workflow de déploiement
-
-### 1. Développement normal
-Travaillez normalement sur votre code en mode développement.
-
-### 2. Déploiement d'une nouvelle version
-```bash
-deploy.bat
-```
-
-Ce script exécute automatiquement :
-1. ✅ Bascule en mode PRODUCTION
-2. ✅ **Incrémente la version** (0.3.8 → 0.3.9)
-3. ✅ **Incrémente le buildNumber** (1 → 2)
-4. ✅ **Génère version.json** depuis app_version.dart
-5. ✅ Build Flutter Web
-6. ✅ Déploie sur Firebase Hosting
-7. ✅ Retour en mode DÉVELOPPEMENT
-
-### 3. Mise à jour côté utilisateur
-Au prochain chargement de l'app (ou après 2 secondes) :
-- L'app vérifie `https://em2rp.web.app/version.json`
-- Compare avec la version locale dans `app_version.dart`
-- Si `buildNumber serveur > buildNumber local` → Affiche le dialog
-
----
-
-## 📝 Format de version
-
-### app_version.dart
-```dart
-class AppVersion {
- static const String version = '0.3.8'; // Version sémantique
- static const int buildNumber = 1; // Numéro de build (incrémenté automatiquement)
-
- static String get fullVersion => 'v$version';
- static String get fullVersionWithBuild => 'v$version+$buildNumber';
-}
-```
-
-### version.json (déployé)
-```json
-{
- "version": "0.3.8",
- "buildNumber": 1,
- "updateUrl": "https://em2rp.web.app",
- "forceUpdate": false,
- "releaseNotes": "• Scanner QR Code\n• Génération QR conteneurs\n• Performance améliorée"
-}
-```
-
----
-
-## 🔄 Comparaison des versions
-
-Le système compare uniquement le **buildNumber** :
-- `buildNumber serveur > buildNumber local` → Mise à jour disponible
-- Ignore les versions identiques même si la version sémantique change
-
-**Exemple** :
-- Local : `0.3.8+1`
-- Serveur : `0.3.9+2`
-- Résultat : Mise à jour proposée (2 > 1) ✅
-
----
-
-## 🎨 Expérience utilisateur
-
-### Mise à jour normale (forceUpdate: false)
-```
-┌────────────────────────────────────┐
-│ 🔄 Mise à jour disponible │
-├────────────────────────────────────┤
-│ Version actuelle : 0.3.8 (1) │
-│ Nouvelle version : 0.3.9 (2) │
-│ │
-│ Nouveautés : │
-│ • Scanner QR Code │
-│ • Performance améliorée │
-│ │
-│ [Plus tard] [Mettre à jour] 🔄 │
-└────────────────────────────────────┘
-```
-
-### Mise à jour forcée (forceUpdate: true)
-```
-┌────────────────────────────────────┐
-│ ⚠️ Mise à jour requise │
-├────────────────────────────────────┤
-│ Version actuelle : 0.3.8 (1) │
-│ Nouvelle version : 0.3.9 (2) │
-│ │
-│ ⚠️ Cette mise à jour est │
-│ obligatoire pour continuer │
-│ │
-│ [Mettre à jour] 🔄 │
-└────────────────────────────────────┘
-```
-
----
-
-## 🛠️ Utilisation avancée
-
-### Forcer une mise à jour critique
-Si vous déployez un correctif critique :
-
-1. Modifiez `web/version.json` **après le déploiement** :
-```json
-{
- "version": "0.3.9",
- "buildNumber": 2,
- "forceUpdate": true, // ← Changer à true
- "releaseNotes": "🔴 Correctif de sécurité important"
-}
-```
-
-2. Les utilisateurs ne pourront plus fermer le dialog jusqu'à la mise à jour
-
-### Personnaliser les notes de version
-Éditez `CHANGELOG.md` avant le déploiement :
-
-```markdown
-## [0.3.9] - 2026-01-16
-
-### Ajouté
-- Scanner QR Code pour équipements
-- Génération QR pour conteneurs
-
-### Amélioré
-- Performance du dialog de sélection
-- Gestion du cache
-
-### Corrigé
-- Bug de cache des équipements
-```
-
-Les 5 premières lignes de la section seront utilisées dans le dialog.
-
----
-
-## 🧪 Tests
-
-### Test 1 : Vérification de version locale
-```dart
-// Dans n'importe quel fichier
-import 'package:em2rp/config/app_version.dart';
-
-print('Version: ${AppVersion.version}');
-print('Build: ${AppVersion.buildNumber}');
-print('Full: ${AppVersion.fullVersionWithBuild}');
-```
-
-### Test 2 : Forcer l'affichage du dialog
-Modifiez temporairement `web/version.json` :
-```json
-{
- "buildNumber": 999 // Très grand nombre
-}
-```
-
-Rechargez l'app → Le dialog s'affiche immédiatement
-
-### Test 3 : Tester le rechargement
-1. Cliquez sur "Mettre à jour"
-2. Vérifiez que la page se recharge
-3. Vérifiez que le cache est vidé (nouvelles ressources chargées)
-
----
-
-## 📊 Logs de debug
-
-En mode debug, des logs sont affichés dans la console :
-
-```
-[UpdateService] Current version: 0.3.8+1
-[UpdateService] Server version: 0.3.9+2
-```
-
-Si pas de mise à jour disponible, rien ne s'affiche.
-
----
-
-## 🔐 Sécurité
-
-### Headers HTTP pour forcer le non-cache
-Le fichier `web/index.html` contient :
-```html
-
-
-
-```
-
-### Cache-busting sur version.json
-Chaque requête ajoute un timestamp :
-```dart
-final timestamp = DateTime.now().millisecondsSinceEpoch;
-Uri.parse('$versionUrl?t=$timestamp')
-```
-
-Garantit que la version la plus récente est toujours récupérée.
-
----
-
-## 🚨 Résolution de problèmes
-
-### Problème : Le dialog ne s'affiche pas
-**Causes possibles :**
-1. Le `buildNumber` serveur n'est pas supérieur au local
-2. Erreur réseau (timeout 10s)
-3. Le fichier `version.json` n'existe pas sur le serveur
-
-**Solution :**
-```bash
-# Vérifier la version déployée
-curl https://em2rp.web.app/version.json
-
-# Forcer un nouveau déploiement
-deploy.bat
-```
-
-### Problème : Le cache ne se vide pas
-**Causes possibles :**
-1. Service Worker actif (ancienne version)
-2. Cache navigateur très persistant
-
-**Solution :**
-```javascript
-// Dans les DevTools du navigateur
-navigator.serviceWorker.getRegistrations().then(registrations => {
- registrations.forEach(r => r.unregister());
-});
-
-// Puis CTRL+SHIFT+R (rechargement forcé)
-```
-
-### Problème : Le script increment_version.js échoue
-**Solution :**
-```bash
-# Vérifier la syntaxe du fichier app_version.dart
-# Doit contenir exactement :
-static const String version = '0.3.8';
-static const int buildNumber = 1;
-```
-
----
-
-## 📈 Évolution future
-
-### Fonctionnalités possibles
-- [ ] Afficher un changelog complet dans le dialog
-- [ ] Permettre de sauter une version (skip this version)
-- [ ] Notifications push pour les mises à jour critiques
-- [ ] Analytics sur le taux d'adoption des mises à jour
-- [ ] Support des mises à jour en arrière-plan
-
-### Améliorations techniques
-- [ ] Utiliser un CDN pour version.json
-- [ ] Implémenter un rollback automatique si erreur
-- [ ] Ajouter une vérification de santé post-déploiement
-
----
-
-## 🎯 Commandes rapides
-
-```bash
-# Déployer une nouvelle version
-deploy.bat
-
-# Incrémenter manuellement la version
-node scripts\increment_version.js
-
-# Générer version.json manuellement
-node scripts\update_version_json.js
-
-# Vérifier la version actuelle
-type lib\config\app_version.dart
-
-# Vérifier la version déployée
-curl https://em2rp.web.app/version.json
-```
-
----
-
-## ✅ Checklist de déploiement
-
-Avant chaque déploiement :
-
-- [ ] Tester l'application en local
-- [ ] Mettre à jour `CHANGELOG.md` avec les nouveautés
-- [ ] Vérifier que tous les tests passent
-- [ ] Exécuter `deploy.bat`
-- [ ] Vérifier le déploiement sur https://em2rp.web.app
-- [ ] Tester la mise à jour sur un navigateur propre
-- [ ] Informer l'équipe de la nouvelle version
-
----
-
-## 📞 Support
-
-En cas de problème avec le système de mise à jour, vérifier :
-1. Les logs dans la console du navigateur
-2. Le fichier `version.json` déployé
-3. Le fichier `app_version.dart` local
-4. La connexion réseau de l'utilisateur
-
-**Le système est conçu pour échouer silencieusement** : Si une erreur se produit, l'utilisateur peut continuer à utiliser l'app normalement sans être bloqué.
-
diff --git a/em2rp/firestore.indexes.json b/em2rp/firestore.indexes.json
index 6dc6673..181f03d 100644
--- a/em2rp/firestore.indexes.json
+++ b/em2rp/firestore.indexes.json
@@ -1,23 +1,97 @@
{
"indexes": [
{
- "collectionGroup": "events",
+ "collectionGroup": "alerts",
"queryScope": "COLLECTION",
"fields": [
{
- "fieldPath": "EndDateTime",
+ "fieldPath": "assignedTo",
+ "arrayConfig": "CONTAINS"
+ },
+ {
+ "fieldPath": "isRead",
"order": "ASCENDING"
},
{
- "fieldPath": "StartDateTime",
- "order": "ASCENDING"
+ "fieldPath": "createdAt",
+ "order": "DESCENDING"
+ }
+ ]
+ },
+ {
+ "collectionGroup": "alerts",
+ "queryScope": "COLLECTION",
+ "fields": [
+ {
+ "fieldPath": "assignedTo",
+ "arrayConfig": "CONTAINS"
},
{
"fieldPath": "status",
"order": "ASCENDING"
},
{
- "fieldPath": "__name__",
+ "fieldPath": "createdAt",
+ "order": "DESCENDING"
+ }
+ ]
+ },
+ {
+ "collectionGroup": "containers",
+ "queryScope": "COLLECTION",
+ "fields": [
+ {
+ "fieldPath": "status",
+ "order": "ASCENDING"
+ },
+ {
+ "fieldPath": "id",
+ "order": "ASCENDING"
+ }
+ ]
+ },
+ {
+ "collectionGroup": "containers",
+ "queryScope": "COLLECTION",
+ "fields": [
+ {
+ "fieldPath": "status",
+ "order": "ASCENDING"
+ },
+ {
+ "fieldPath": "type",
+ "order": "ASCENDING"
+ },
+ {
+ "fieldPath": "id",
+ "order": "ASCENDING"
+ }
+ ]
+ },
+ {
+ "collectionGroup": "containers",
+ "queryScope": "COLLECTION",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "order": "ASCENDING"
+ },
+ {
+ "fieldPath": "id",
+ "order": "ASCENDING"
+ }
+ ]
+ },
+ {
+ "collectionGroup": "equipments",
+ "queryScope": "COLLECTION",
+ "fields": [
+ {
+ "fieldPath": "category",
+ "order": "ASCENDING"
+ },
+ {
+ "fieldPath": "id",
"order": "ASCENDING"
}
]
@@ -27,7 +101,7 @@
"queryScope": "COLLECTION",
"fields": [
{
- "fieldPath": "status",
+ "fieldPath": "EndDateTime",
"order": "ASCENDING"
},
{
@@ -35,12 +109,11 @@
"order": "ASCENDING"
},
{
- "fieldPath": "EndDateTime",
+ "fieldPath": "status",
"order": "ASCENDING"
}
]
}
],
"fieldOverrides": []
-}
-
+}
\ No newline at end of file
diff --git a/em2rp/functions/.env b/em2rp/functions/.env
new file mode 100644
index 0000000..5af40c5
--- /dev/null
+++ b/em2rp/functions/.env
@@ -0,0 +1,9 @@
+# Configuration SMTP pour l'envoi d'emails
+SMTP_HOST="mail.em2events.fr"
+SMTP_PORT=465
+SMTP_USER="notify@em2events.fr"
+SMTP_PASS="aL8@Rx8xqFrNij$a"
+
+# URL de l'application
+APP_URL="https://app.em2events.fr"
+
diff --git a/em2rp/functions/createAlert.js b/em2rp/functions/createAlert.js
index ce00614..1f8ab87 100644
--- a/em2rp/functions/createAlert.js
+++ b/em2rp/functions/createAlert.js
@@ -46,7 +46,11 @@ const withCors = (handler) => {
* Crée une alerte et envoie les notifications
* Gère tout le processus côté backend de A à Z
*/
-exports.createAlert = onRequest({cors: false, invoker: 'public'}, withCors(async (req, res) => {
+exports.createAlert = onRequest({
+ cors: false,
+ invoker: 'public',
+ region: 'europe-west9'
+}, withCors(async (req, res) => {
try {
// Vérifier l'authentification
const decodedToken = await auth.authenticateUser(req);
diff --git a/em2rp/functions/index.js b/em2rp/functions/index.js
index 0abdb5d..d6512b8 100644
--- a/em2rp/functions/index.js
+++ b/em2rp/functions/index.js
@@ -28,6 +28,7 @@ const db = admin.firestore();
const httpOptions = {
cors: false,
invoker: 'public', // Permet les invocations non authentifiées (l'auth est gérée par notre token Firebase)
+ region: 'europe-west9', // Région européenne (Paris)
// Version: 2.0 - Ajout de l'invoker public pour résoudre les problèmes CORS
};
@@ -2049,19 +2050,20 @@ exports.getUsers = onRequest(httpOptions, withCors(async (req, res) => {
* Récupère un utilisateur spécifique par son ID
* Tout utilisateur authentifié peut accéder aux données publiques
*/
-exports.getUser = onCall(async (request) => {
+exports.getUser = onRequest(httpOptions, withCors(async (req, res) => {
try {
- await authenticateUser(request);
- const db = getFirestore();
+ const decodedToken = await auth.authenticateUser(req);
- const { userId } = request.data;
+ const { userId } = req.body.data || req.body || {};
if (!userId) {
- throw new Error("userId is required");
+ res.status(400).json({ error: 'userId is required' });
+ return;
}
- const userDoc = await db.collection("users").doc(userId).get();
+ const userDoc = await db.collection('users').doc(userId).get();
if (!userDoc.exists) {
- throw new Error("User not found");
+ res.status(404).json({ error: 'User not found' });
+ return;
}
const user = userDoc.data();
@@ -2070,11 +2072,11 @@ exports.getUser = onCall(async (request) => {
const userData = {
id: userDoc.id,
uid: user.uid || userDoc.id,
- email: user.email || "",
- firstName: user.firstName || "",
- lastName: user.lastName || "",
- phoneNumber: user.phoneNumber || "",
- profilePhotoUrl: user.profilePhotoUrl || "",
+ email: user.email || '',
+ firstName: user.firstName || '',
+ lastName: user.lastName || '',
+ phoneNumber: user.phoneNumber || '',
+ profilePhotoUrl: user.profilePhotoUrl || '',
};
// Inclure le rôle si disponible
@@ -2088,12 +2090,12 @@ exports.getUser = onCall(async (request) => {
}
}
- return { user: userData };
+ res.status(200).json({ user: userData });
} catch (error) {
- logger.error("Error fetching user:", error);
- throw new Error(error.message || "Failed to fetch user");
+ logger.error('Error fetching user:', error);
+ res.status(500).json({ error: error.message });
}
-});
+}));
// ============================================================================
@@ -3488,6 +3490,7 @@ const {sendDailyDigest} = require('./sendDailyDigest');
exports.sendDailyDigest = onSchedule({
schedule: '0 8 * * *',
timeZone: 'Europe/Paris',
+ region: 'europe-west9',
retryCount: 2,
memory: '512MiB'
}, async (context) => {
@@ -3507,7 +3510,10 @@ exports.sendDailyDigest = onSchedule({
* Trigger : Nouvel événement créé
* Envoie une notification à tous les membres de la workforce
*/
-exports.onEventCreated = onDocumentCreated('events/{eventId}', async (event) => {
+exports.onEventCreated = onDocumentCreated({
+ document: 'events/{eventId}',
+ region: 'europe-west9'
+}, async (event) => {
logger.info(`[onEventCreated] Événement créé: ${event.params.eventId}`);
try {
@@ -3547,7 +3553,10 @@ exports.onEventCreated = onDocumentCreated('events/{eventId}', async (event) =>
* Trigger : Événement modifié (workforce changée)
* Envoie une notification aux nouveaux membres ajoutés à la workforce
*/
-exports.onEventUpdated = onDocumentUpdated('events/{eventId}', async (event) => {
+exports.onEventUpdated = onDocumentUpdated({
+ document: 'events/{eventId}',
+ region: 'europe-west9'
+}, async (event) => {
const before = event.data.before.data();
const after = event.data.after.data();
const eventId = event.params.eventId;
@@ -3598,7 +3607,10 @@ exports.onEventUpdated = onDocumentUpdated('events/{eventId}', async (event) =>
* Trigger : Nouvelle alerte créée
* Envoie un email immédiat si l'alerte est critique
*/
-exports.onAlertCreated = onDocumentCreated('alerts/{alertId}', async (event) => {
+exports.onAlertCreated = onDocumentCreated({
+ document: 'alerts/{alertId}',
+ region: 'europe-west9'
+}, async (event) => {
const alertId = event.params.alertId;
const alertData = event.data.data();
diff --git a/em2rp/functions/processEquipmentValidation.js b/em2rp/functions/processEquipmentValidation.js
index 8ab6c71..8aa424c 100644
--- a/em2rp/functions/processEquipmentValidation.js
+++ b/em2rp/functions/processEquipmentValidation.js
@@ -8,7 +8,10 @@ const {getSmtpConfig, EMAIL_CONFIG} = require('./utils/emailConfig');
* Appelée par le client lors du chargement/déchargement
* Crée automatiquement les alertes nécessaires
*/
-exports.processEquipmentValidation = onCall({cors: true}, async (request) => {
+exports.processEquipmentValidation = onCall({
+ cors: true,
+ region: 'europe-west9'
+}, async (request) => {
try {
// L'authentification est automatique avec onCall
const {auth, data} = request;
diff --git a/em2rp/functions/sendAlertEmail.js b/em2rp/functions/sendAlertEmail.js
index 9ed0e3c..259b4f7 100644
--- a/em2rp/functions/sendAlertEmail.js
+++ b/em2rp/functions/sendAlertEmail.js
@@ -1,4 +1,4 @@
-const functions = require('firebase-functions');
+const {onCall} = require('firebase-functions/v2/https');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
@@ -10,22 +10,19 @@ const {getSmtpConfig, EMAIL_CONFIG} = require('./utils/emailConfig');
* Envoie un email d'alerte à un utilisateur
* Appelé par le client Dart via callable function
*/
-exports.sendAlertEmail = functions.https.onCall(async (data, context) => {
+exports.sendAlertEmail = onCall({
+ region: 'europe-west9',
+ cors: true
+}, async (request) => {
// Vérifier l'authentification
- if (!context.auth) {
- throw new functions.https.HttpsError(
- 'unauthenticated',
- 'L\'utilisateur doit être authentifié',
- );
+ if (!request.auth) {
+ throw new Error('L\'utilisateur doit être authentifié');
}
- const {alertId, userId, templateType} = data;
+ const {alertId, userId, templateType} = request.data;
if (!alertId || !userId) {
- throw new functions.https.HttpsError(
- 'invalid-argument',
- 'alertId et userId sont requis',
- );
+ throw new Error('alertId et userId sont requis');
}
try {
@@ -36,10 +33,7 @@ exports.sendAlertEmail = functions.https.onCall(async (data, context) => {
.get();
if (!alertDoc.exists) {
- throw new functions.https.HttpsError(
- 'not-found',
- 'Alerte introuvable',
- );
+ throw new Error('Alerte introuvable');
}
const alert = alertDoc.data();
@@ -51,10 +45,7 @@ exports.sendAlertEmail = functions.https.onCall(async (data, context) => {
.get();
if (!userDoc.exists) {
- throw new functions.https.HttpsError(
- 'not-found',
- 'Utilisateur introuvable',
- );
+ throw new Error('Utilisateur introuvable');
}
const user = userDoc.data();
@@ -112,10 +103,7 @@ exports.sendAlertEmail = functions.https.onCall(async (data, context) => {
};
} catch (error) {
console.error('Erreur envoi email:', error);
- throw new functions.https.HttpsError(
- 'internal',
- `Erreur lors de l'envoi de l'email: ${error.message}`,
- );
+ throw new Error(`Erreur lors de l'envoi de l'email: ${error.message}`);
}
});
diff --git a/em2rp/lib/config/api_config.dart b/em2rp/lib/config/api_config.dart
index 0b65c06..9f78e33 100644
--- a/em2rp/lib/config/api_config.dart
+++ b/em2rp/lib/config/api_config.dart
@@ -4,8 +4,8 @@ class ApiConfig {
static const bool isDevelopment = false; // false = utilise Cloud Functions prod
// URL de base pour les Cloud Functions
- static const String productionUrl = 'https://us-central1-em2rp-951dc.cloudfunctions.net';
- static const String developmentUrl = 'http://localhost:5001/em2rp-951dc/us-central1';
+ static const String productionUrl = 'https://europe-west9-em2rp-951dc.cloudfunctions.net';
+ static const String developmentUrl = 'http://localhost:5001/em2rp-951dc/europe-west9';
/// Retourne l'URL de base selon l'environnement
static String get baseUrl => isDevelopment ? developmentUrl : productionUrl;
diff --git a/em2rp/lib/config/app_version.dart b/em2rp/lib/config/app_version.dart
index c8bd4dd..71565f1 100644
--- a/em2rp/lib/config/app_version.dart
+++ b/em2rp/lib/config/app_version.dart
@@ -1,6 +1,6 @@
/// Configuration de la version de l'application
class AppVersion {
- static const String version = '1.0.6';
+ static const String version = '1.0.9';
/// Retourne la version complète de l'application
static String get fullVersion => 'v$version';
diff --git a/em2rp/lib/main.dart b/em2rp/lib/main.dart
index c296020..3b3744f 100644
--- a/em2rp/lib/main.dart
+++ b/em2rp/lib/main.dart
@@ -5,6 +5,7 @@ import 'package:em2rp/providers/container_provider.dart';
import 'package:em2rp/providers/maintenance_provider.dart';
import 'package:em2rp/providers/alert_provider.dart';
import 'package:em2rp/utils/auth_guard_widget.dart';
+import 'package:em2rp/utils/performance_monitor.dart';
import 'package:em2rp/views/alerts_page.dart';
import 'package:em2rp/views/calendar_page.dart';
import 'package:em2rp/views/login_page.dart';
@@ -203,22 +204,22 @@ class _AutoLoginWrapperState extends State {
}
Future _autoLogin() async {
+ PerformanceMonitor.start('App.autoLogin');
try {
final localAuthProvider =
Provider.of(context, listen: false);
// Vérifier si l'utilisateur est déjà connecté
if (FirebaseAuth.instance.currentUser == null && Env.isDevelopment) {
+ PerformanceMonitor.start('App.signIn');
// Connexion automatique en mode développement
await localAuthProvider.signInWithEmailAndPassword(
Env.devAdminEmail,
Env.devAdminPassword,
);
+ PerformanceMonitor.end('App.signIn');
}
- // Charger les données utilisateur
- await localAuthProvider.loadUserData();
-
if (mounted) {
// MODIFIÉ : Vérifier si une route spécifique est demandée dans l'URL
// En Flutter Web, on peut vérifier window.location.hash
@@ -227,7 +228,7 @@ class _AutoLoginWrapperState extends State {
print('[AutoLoginWrapper] Fragment URL: $fragment');
- // Si une route spécifique est demandée (autre que / ou vide)
+ // Navigation immédiate sans attendre le chargement des données
if (fragment.isNotEmpty && fragment != '/' && fragment != '/calendar') {
print('[AutoLoginWrapper] Redirection vers: $fragment');
Navigator.of(context).pushReplacementNamed(fragment);
@@ -236,9 +237,18 @@ class _AutoLoginWrapperState extends State {
print('[AutoLoginWrapper] Redirection vers: /calendar (défaut)');
Navigator.of(context).pushReplacementNamed('/calendar');
}
+
+ PerformanceMonitor.end('App.autoLogin');
+ PerformanceMonitor.printSummary();
+
+ // Charger les données utilisateur en arrière-plan
+ localAuthProvider.loadUserData().catchError((e) {
+ print('Error loading user data: $e');
+ });
}
} catch (e) {
print('Auto login failed: $e');
+ PerformanceMonitor.end('App.autoLogin');
if (mounted) {
Navigator.of(context).pushReplacementNamed('/login');
}
@@ -247,9 +257,41 @@ class _AutoLoginWrapperState extends State {
@override
Widget build(BuildContext context) {
- return const Scaffold(
+ return Scaffold(
+ backgroundColor: Colors.white,
body: Center(
- child: CircularProgressIndicator(),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // Logo de l'application
+ Image.asset(
+ 'assets/logos/RectangleLogoBlack.png',
+ width: 200,
+ height: 200,
+ fit: BoxFit.contain,
+ errorBuilder: (context, error, stackTrace) {
+ return const Icon(
+ Icons.event_available,
+ size: 80,
+ color: AppColors.rouge,
+ );
+ },
+ ),
+ const SizedBox(height: 40),
+ const CircularProgressIndicator(
+ valueColor: AlwaysStoppedAnimation(AppColors.rouge),
+ ),
+ const SizedBox(height: 20),
+ const Text(
+ 'Chargement...',
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.grey,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
),
);
}
diff --git a/em2rp/lib/providers/event_provider.dart b/em2rp/lib/providers/event_provider.dart
index 24b9c8b..078e480 100644
--- a/em2rp/lib/providers/event_provider.dart
+++ b/em2rp/lib/providers/event_provider.dart
@@ -3,6 +3,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:em2rp/models/event_model.dart';
import 'package:em2rp/services/data_service.dart';
import 'package:em2rp/services/api_service.dart';
+import 'package:em2rp/utils/performance_monitor.dart';
class EventProvider with ChangeNotifier {
final DataService _dataService = DataService(FirebaseFunctionsApiService());
@@ -15,19 +16,43 @@ class EventProvider with ChangeNotifier {
// Cache des utilisateurs chargés depuis getEvents
Map> _usersCache = {};
+ // Cache pour éviter les rechargements inutiles
+ DateTime? _lastLoadTime;
+ String? _lastUserId;
+ bool _lastCanViewAll = false;
+
+ /// Vérifie si les données doivent être rechargées (cache de 30 secondes)
+ bool _shouldReload(String userId, bool canViewAllEvents) {
+ if (_lastLoadTime == null) return true;
+ if (_lastUserId != userId || _lastCanViewAll != canViewAllEvents) return true;
+
+ final now = DateTime.now();
+ final difference = now.difference(_lastLoadTime!);
+ return difference.inSeconds > 30;
+ }
+
/// Charger les événements d'un utilisateur via l'API
- Future loadUserEvents(String userId, {bool canViewAllEvents = false}) async {
+ Future loadUserEvents(String userId, {bool canViewAllEvents = false, bool forceReload = false}) async {
+ PerformanceMonitor.start('EventProvider.loadUserEvents');
+
+ // Éviter les rechargements inutiles
+ if (!forceReload && !_shouldReload(userId, canViewAllEvents)) {
+ print('Using cached events (loaded ${DateTime.now().difference(_lastLoadTime!).inSeconds}s ago)');
+ PerformanceMonitor.end('EventProvider.loadUserEvents');
+ return;
+ }
+
_isLoading = true;
notifyListeners();
- // Sauvegarder les paramètres
- _saveLastLoadParams(userId, canViewAllEvents);
-
try {
print('Loading events for user: $userId (canViewAllEvents: $canViewAllEvents)');
+ PerformanceMonitor.start('EventProvider.getEvents_API');
// Charger via l'API - les permissions sont vérifiées côté serveur
final result = await _dataService.getEvents(userId: userId);
+ PerformanceMonitor.end('EventProvider.getEvents_API');
+
final eventsData = result['events'] as List