feat: Refactor event equipment management with advanced selection and conflict detection

This commit introduces a complete overhaul of how equipment is assigned to events, focusing on an enhanced user experience, advanced selection capabilities, and robust conflict detection.

**Key Features & Enhancements:**

-   **Advanced Equipment Selection UI (`EquipmentSelectionDialog`):**
    -   New full-screen dialog to select equipment and containers ("boîtes") for an event.
    -   Hierarchical view showing containers and a flat list of all individual equipment.
    -   Real-time search and filtering by equipment category.
    -   Side panel summarizing the current selection and providing recommendations for containers based on selected equipment.
    -   Supports quantity selection for consumables and cables.

-   **Conflict Detection & Management (`EventAvailabilityService`):**
    -   A new service (`EventAvailabilityService`) checks for equipment availability against other events based on the selected date range.
    -   The selection dialog visually highlights equipment and containers with scheduling conflicts (e.g., already used, partially unavailable).
    -   A dedicated conflict resolution dialog (`EquipmentConflictDialog`) appears if conflicting items are selected, allowing the user to either remove them or force the assignment.

-   **Integrated Event Form (`EventAssignedEquipmentSection`):**
    -   The event creation/editing form now includes a new section for managing assigned equipment.
    -   It clearly displays assigned containers and standalone equipment, showing the composition of each container.
    -   Integrates the new selection dialog, ensuring all assignments are checked for conflicts before being saved.

-   **Event Preparation & Return Workflow (`EventPreparationPage`):**
    -   New page (`EventPreparationPage`) for managing the check-out (preparation) and check-in (return) of equipment for an event.
    -   Provides a checklist of all assigned equipment.
    -   Users can validate each item, with options to "validate all" or finalize with missing items.
    -   Includes a dialog (`MissingEquipmentDialog`) to handle discrepancies.
    -   Supports tracking returned quantities for consumables.

**Data Model and Other Changes:**

-   The `EventModel` now includes `assignedContainers` to explicitly link containers to an event.
-   `EquipmentAssociatedEventsSection` on the equipment detail page is now functional, displaying current, upcoming, and past events for that item.
-   Added deployment and versioning scripts (`scripts/deploy.js`, `scripts/increment_version.js`, `scripts/toggle_env.js`) to automate the release process.
-   Introduced an application version display in the main drawer (`AppVersion`).
This commit is contained in:
ElPoyo
2025-11-30 20:33:03 +01:00
parent e59e3e6316
commit 08f046c89c
31 changed files with 4955 additions and 46 deletions

71
em2rp/scripts/deploy.js Normal file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
/**
* Script de déploiement automatique pour Firebase Hosting
* - Bascule en mode PRODUCTION
* - Incrémente la version
* - Build l'application Flutter pour le web
* - Déploie sur Firebase Hosting
* - Rebascule en mode DÉVELOPPEMENT
*/
const { execSync } = require('child_process');
const { incrementVersion } = require('./increment_version');
const { setProductionMode, setDevelopmentMode } = require('./toggle_env');
console.log('🚀 Démarrage du déploiement Firebase Hosting...\n');
// Étape 0: Basculer en mode production
console.log('🔒 Étape 0/4: Basculement en mode PRODUCTION');
if (!setProductionMode()) {
console.error('❌ Impossible de basculer en mode production');
process.exit(1);
}
console.log('');
// Étape 1: Incrémenter la version
console.log('📝 Étape 1/4: Incrémentation de la version');
const newVersion = incrementVersion();
console.log('');
// Étape 2: Build Flutter pour le web
console.log('🔨 Étape 2/4: Build Flutter Web');
try {
execSync('flutter build web --release', {
stdio: 'inherit',
cwd: process.cwd()
});
console.log('✅ Build terminé avec succès\n');
} catch (error) {
console.error('❌ Erreur lors du build Flutter');
// Rebascule en mode dev avant de quitter
setDevelopmentMode();
process.exit(1);
}
// Étape 3: Déploiement Firebase
console.log('🌐 Étape 3/4: Déploiement sur Firebase Hosting');
try {
execSync('firebase deploy --only hosting', {
stdio: 'inherit',
cwd: process.cwd()
});
console.log('\n✅ Déploiement terminé avec succès!');
console.log(`🎉 Version ${newVersion} déployée sur Firebase Hosting`);
} catch (error) {
console.error('❌ Erreur lors du déploiement Firebase');
// Rebascule en mode dev avant de quitter
setDevelopmentMode();
process.exit(1);
}
// Étape 4: Rebascule en mode développement
console.log('\n🔓 Étape 4/4: Retour en mode DÉVELOPPEMENT');
if (!setDevelopmentMode()) {
console.warn('⚠️ Impossible de rebascule en mode développement');
console.warn('⚠️ Exécutez manuellement: npm run env:dev');
} else {
console.log('✅ Mode développement restauré');
}
console.log('\n✨ Processus de déploiement terminé!');

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env node
/**
* Script pour incrémenter automatiquement le numéro de version patch
* lors du déploiement Firebase Hosting
*/
const fs = require('fs');
const path = require('path');
const VERSION_FILE = path.join(__dirname, '../lib/config/app_version.dart');
function incrementVersion() {
try {
// Lire le fichier
let content = fs.readFileSync(VERSION_FILE, 'utf8');
// Extraire la version actuelle
const versionRegex = /static const String version = '(\d+)\.(\d+)\.(\d+)';/;
const match = content.match(versionRegex);
if (!match) {
console.error('❌ Impossible de trouver la version dans le fichier');
process.exit(1);
}
const major = parseInt(match[1]);
const minor = parseInt(match[2]);
const patch = parseInt(match[3]);
// Incrémenter le patch
const newPatch = patch + 1;
const newVersion = `${major}.${minor}.${newPatch}`;
// Remplacer dans le fichier
const newContent = content.replace(
versionRegex,
`static const String version = '${newVersion}';`
);
// Écrire le fichier
fs.writeFileSync(VERSION_FILE, newContent, 'utf8');
console.log(`✅ Version incrémentée: ${match[0].match(/\d+\.\d+\.\d+/)[0]}${newVersion}`);
return newVersion;
} catch (error) {
console.error('❌ Erreur lors de l\'incrémentation:', error.message);
process.exit(1);
}
}
// Exécuter si appelé directement
if (require.main === module) {
incrementVersion();
}
module.exports = { incrementVersion };

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env node
/**
* Script pour basculer entre mode développement et production
*/
const fs = require('fs');
const path = require('path');
const ENV_FILE = path.join(__dirname, '../lib/config/env.dart');
const ENV_DEV_FILE = path.join(__dirname, '../lib/config/env.dev.dart');
const PROD_CONFIG = `class Env {
static const bool isDevelopment = false;
// Configuration de l'auto-login en développement
static const String devAdminEmail = '';
static const String devAdminPassword = '';
// URLs et endpoints
static const String baseUrl = 'https://em2rp-951dc.firebaseapp.com';
// Configuration Firebase
static const String firebaseProjectId = 'em2rp-951dc';
// Autres configurations
static const int apiTimeout = 30000; // 30 secondes
}
`;
function setProductionMode() {
try {
// Sauvegarder la config actuelle dans env.dev.dart si elle n'existe pas
if (!fs.existsSync(ENV_DEV_FILE) && fs.existsSync(ENV_FILE)) {
const currentContent = fs.readFileSync(ENV_FILE, 'utf8');
if (currentContent.includes('isDevelopment = true')) {
fs.writeFileSync(ENV_DEV_FILE, currentContent, 'utf8');
console.log('✅ Configuration de développement sauvegardée dans env.dev.dart');
}
}
// Écrire la configuration de production
fs.writeFileSync(ENV_FILE, PROD_CONFIG, 'utf8');
console.log('✅ Mode PRODUCTION activé (isDevelopment = false, credentials masqués)');
return true;
} catch (error) {
console.error('❌ Erreur lors du basculement en mode production:', error.message);
return false;
}
}
function setDevelopmentMode() {
try {
if (!fs.existsSync(ENV_DEV_FILE)) {
console.error('❌ Fichier env.dev.dart introuvable. Créez-le d\'abord avec vos credentials.');
return false;
}
const devContent = fs.readFileSync(ENV_DEV_FILE, 'utf8');
fs.writeFileSync(ENV_FILE, devContent, 'utf8');
console.log('✅ Mode DÉVELOPPEMENT activé (isDevelopment = true)');
return true;
} catch (error) {
console.error('❌ Erreur lors du basculement en mode développement:', error.message);
return false;
}
}
// Exécuter si appelé directement
if (require.main === module) {
const args = process.argv.slice(2);
const mode = args[0];
if (mode === 'prod' || mode === 'production') {
process.exit(setProductionMode() ? 0 : 1);
} else if (mode === 'dev' || mode === 'development') {
process.exit(setDevelopmentMode() ? 0 : 1);
} else {
console.log('Usage: node toggle_env.js [prod|dev]');
console.log(' prod : Bascule en mode production (isDevelopment = false, sans credentials)');
console.log(' dev : Bascule en mode développement (isDevelopment = true, avec credentials)');
process.exit(1);
}
}
module.exports = { setProductionMode, setDevelopmentMode };