# Cahier des Charges — Back-Office Superviseur ## Projet : Laverie Connectée — Interface d'Administration **Version :** 1.0 **Date :** 2026-06-27 **Stack principale :** Laravel 11 (API déjà existante) + Vue.js 3 + Inertia.js --- ## 1. Contexte & Objectifs ### 1.1 Contexte Interface web d'administration destinée aux **superviseurs** (exploitants de laveries). Elle s'appuie sur les endpoints `/api/v1/supervisor/*` de l'API Laravel et s'authentifie avec le guard `supervisor`. Le back-office est une **SPA (Single Page Application)** rendue côté serveur via Inertia.js pour simplifier le déploiement (pas d'API séparée pour le back-office, utilisation directe de Laravel comme backend Inertia). ### 1.2 Utilisateurs cibles | Rôle | Droits | |------|--------| | `admin` | Accès complet multi-établissements, gestion des superviseurs | | `manager` | Accès complet à son établissement | | `viewer` | Lecture seule (statistiques, tableau de bord) | ### 1.3 Objectifs fonctionnels - Visualiser l'occupation en temps réel du parc de machines - Consulter les statistiques d'utilisation et le chiffre d'affaires - Gérer la tarification et les promotions - Recevoir et gérer les alertes machines (pannes, hors ligne) - (v2) Envoyer des campagnes de notifications aux utilisateurs --- ## 2. Stack Technique | Composant | Technologie | Version | Justification | |-----------|-------------|---------|---------------| | Backend | Laravel 11 | (partagé avec l'API) | Inertia server-side rendering | | Intégration SPA | Inertia.js | 2.x | SSR Laravel → Vue.js sans API REST dédiée | | Framework UI | Vue.js | 3.x (Composition API) | Réactivité, écosystème riche | | Build tool | Vite | 5.x | HMR rapide, bundling optimisé | | UI Components | PrimeVue | 4.x | DataTable, Chart, Calendar, etc. | | CSS | Tailwind CSS | 3.x | Utility-first, cohérence design | | Graphiques | Chart.js via PrimeVue | 4.x | Courbes CA, camembert machines | | State management | Pinia | 2.x | Store réactif Vue 3 | | Temps réel | Laravel Echo + Pusher (ou Soketi self-hosted) | — | Mise à jour statut machines | | Validation forms | VeeValidate + Zod | — | Validation côté client | | Tables | PrimeVue DataTable | — | Tri, filtre, pagination, export | | Auth | Laravel Sanctum (session cookie) | — | SPA same-domain | | Tests back | Pest | — | Tests controllers superviseur | | Tests front | Vitest + Vue Test Utils | — | Composants Vue | | E2E | Playwright | — | Parcours critiques superviseur | --- ## 3. Architecture ### 3.1 Structure des fichiers ``` resources/ ├── js/ │ ├── app.js # Point d'entrée Inertia │ ├── bootstrap.js # Echo, Axios │ ├── Components/ │ │ ├── Layout/ │ │ │ ├── AppLayout.vue # Layout principal (sidebar + topbar) │ │ │ ├── Sidebar.vue │ │ │ └── Topbar.vue │ │ ├── Charts/ │ │ │ ├── RevenueChart.vue │ │ │ ├── OccupancyChart.vue │ │ │ └── MachineStatusChart.vue │ │ ├── Machines/ │ │ │ ├── MachineCard.vue # Carte statut en temps réel │ │ │ └── MachineGrid.vue │ │ └── UI/ │ │ ├── StatCard.vue │ │ ├── AlertBadge.vue │ │ └── ConfirmDialog.vue │ │ │ └── Pages/ │ ├── Auth/ │ │ └── Login.vue │ ├── Dashboard/ │ │ └── Index.vue │ ├── Machines/ │ │ ├── Index.vue │ │ └── Show.vue │ ├── Pricing/ │ │ ├── Index.vue │ │ └── Edit.vue │ ├── Promotions/ │ │ ├── Index.vue │ │ └── Create.vue │ ├── Stats/ │ │ └── Index.vue │ └── Settings/ │ └── Index.vue │ app/ ├── Http/Controllers/Supervisor/ # (déjà définis dans CDC API) │ ├── DashboardController.php │ ├── MachineController.php │ ├── PricingController.php │ ├── PromotionController.php │ └── StatsController.php └── Events/ └── MachineStatusUpdated.php # Broadcast temps réel ``` ### 3.2 Routage Laravel (Inertia) ```php // routes/supervisor.php (middleware: auth:supervisor, inertia) Route::prefix('supervisor')->name('supervisor.')->group(function () { Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); Route::resource('/machines', MachineController::class)->only(['index','show','update']); Route::resource('/pricing', PricingController::class); Route::resource('/promotions', PromotionController::class); Route::get('/stats', StatsController::class)->name('stats'); Route::get('/settings', SettingsController::class)->name('settings'); }); ``` --- ## 4. Écrans & Fonctionnalités ### 4.1 Connexion (`/supervisor/login`) **Formulaire :** - Email + Mot de passe - Bouton "Se connecter" - Lien "Mot de passe oublié" **Comportement :** - Session cookie Sanctum (SPA same-domain) - Redirection automatique vers `/supervisor/dashboard` si déjà connecté - Rate limiting : 5 tentatives / 10 min **Données Inertia passées :** ```php // Aucune — page statique ``` --- ### 4.2 Tableau de Bord (`/supervisor/dashboard`) C'est l'écran central. Il se rafraîchit partiellement en temps réel via Laravel Echo. #### 4.2.1 Bandeau de KPIs (haut de page) | KPI | Description | Période | |-----|-------------|---------| | CA du jour | Somme des `washes.cost` du jour | Aujourd'hui | | Lavages du jour | Nombre de `washes` terminés | Aujourd'hui | | Taux d'occupation | % machines actives / total | Temps réel | | Solde moyen rechargé | Moyenne des top-ups | Cette semaine | Chaque KPI affiche la variation vs. la veille (flèche + couleur). #### 4.2.2 Grille des Machines (temps réel) - Grille de cartes, une carte par machine - Mise à jour via **WebSocket** (Laravel Echo, channel `establishment.{id}`, event `MachineStatusUpdated`) - Chaque carte affiche : - Nom et type de machine (icône) - Statut (badge coloré) - Temps restant si en cours (countdown) - Utilisateur en cours (avatar anonymisé : initiales) - Dernière activité **Couleurs statut :** | Statut | Couleur | |--------|---------| | `available` | 🟢 Vert | | `running` | 🔵 Bleu + animation pulse | | `reserved` | 🟡 Jaune | | `maintenance` | 🟠 Orange | | `offline` | 🔴 Rouge | **Actions rapides (rôle manager/admin) :** - Mettre en maintenance - Forcer disponible - Voir le détail #### 4.2.3 Alertes Actives Panel latéral ou section dédié listant : - Machines hors ligne (dernier heartbeat > 5 min) - Machines en erreur signalée - Réservations no-show récentes Chaque alerte : icône, machine concernée, heure, bouton "Traité". #### 4.2.4 Graphique d'occupation journalière Courbe Chart.js (via PrimeVue) : - Axe X : heures (00h → 23h) - Axe Y : % d'occupation - Deux courbes : Aujourd'hui vs Moyenne 30 jours **Données Inertia passées :** ```php Inertia::render('Dashboard/Index', [ 'machines' => MachineResource::collection($machines), 'kpis' => $dashboardService->getTodayKpis($establishment), 'alerts' => AlertResource::collection($activeAlerts), 'occupancy_chart' => $statsService->getHourlyOccupancy($establishment, today()), ]); ``` --- ### 4.3 Statistiques (`/supervisor/stats`) Écran dédié à l'analyse de données avec filtres temporels. #### 4.3.1 Filtres - Période : Aujourd'hui / 7 jours / 30 jours / 3 mois / Personnalisée (date picker) - Machine : Toutes / Par type / Par machine spécifique #### 4.3.2 Blocs de statistiques **Chiffre d'affaires** - Graphique en barres : CA par jour sur la période - Ligne de tendance (moyenne mobile 7 jours) - Total période, meilleur jour, pire jour **Utilisation des machines** - Graphique camembert : Répartition par type de machine - Nombre de cycles par machine (tableau trié) - Taux d'utilisation horaire moyen **Utilisateurs** - Nouveaux inscrits sur la période - Utilisateurs actifs (au moins 1 lavage) - Top 10 utilisateurs (anonymisés : "Utilisateur #XXX") **Réservations** - Nombre de réservations vs. lavages directs - Taux de no-show - Taux d'annulation **Rechargements** - CA par source de paiement (Stripe, etc.) - Montant moyen rechargé - Fréquence de rechargement #### 4.3.3 Export - Export CSV des données brutes de la période sélectionnée - Export PDF du rapport (via impression navigateur, layout print-optimized) --- ### 4.4 Gestion de la Tarification (`/supervisor/pricing`) #### 4.4.1 Vue Liste Tableau (PrimeVue DataTable) listant toutes les règles tarifaires : | Machine | Jours | Horaire | Prix | Exclusive App | Actions | |---------|-------|---------|------|---------------|---------| | Lave-linge 7kg | Semaine | 08h-12h | 3.50€ | Non | ✏️ 🗑 | | Lave-linge 7kg | Semaine | 18h-22h | 4.50€ | Oui | ✏️ 🗑 | - Filtrable par machine, type de jour - Tri par colonne - Badge "Actif" / "Inactif" si les horaires sont hors plage #### 4.4.2 Formulaire Création / Édition **Champs :** - Machine (select, avec recherche) - Jours applicables (checkboxes : Lun–Ven / Sam–Dim / Jours fériés / Tous) - Heure de début / fin (time pickers, validation : début < fin) - Prix en euros (input numérique, 2 décimales, min 0.50€) - Label (ex: "Heure creuse", "Heure pleine") - Exclusive app ✓ (si coché, ce tarif n'est visible que via l'application) **Validation :** - Vérification de non-chevauchement avec les règles existantes sur la même machine - Avertissement si la plage couvre toute la journée sans tarif de base restant **Règle de priorité affichée :** > "Une règle spécifique à une machine a priorité sur une règle par type de machine." --- ### 4.5 Gestion des Promotions (`/supervisor/promotions`) #### 4.5.1 Vue Liste Tableau avec onglets : **Actives** / **À venir** / **Terminées** Colonnes : Type machine, Réduction, Début, Fin, Description, Statut, Actions. #### 4.5.2 Formulaire Création **Champs :** - Type de machine concernée (Lave-linge petit / grand / Sèche-linge / Tous) - Type de réduction : - Pourcentage (ex: -20%) - Montant fixe (ex: -0.50€) - Valeur de la réduction - Date/heure de début (datetime picker) - Date/heure de fin (datetime picker) - Description interne (texte libre, visible uniquement dans le back-office) **Validation :** - Date de fin > Date de début - Réduction % : 1–90% - Réduction fixe : ne peut pas dépasser le prix minimum (0.50€ minimum après remise) - Avertissement si chevauchement avec une promotion existante du même type **Impact affiché en temps réel :** Aperçu du nouveau prix après promotion pour chaque règle tarifaire concernée. --- ### 4.6 Détail Machine (`/supervisor/machines/{uuid}`) - Informations générales (nom, type, QR code, ID LLDP) - Statut actuel avec boutons d'action (Maintenance / Disponible) - Historique des 30 derniers cycles (tableau paginé) - Grille tarifaire appliquée - Uptime sur 30 jours (%) - Historique des alertes / pannes (30 derniers jours) --- ## 5. Temps Réel — Laravel Echo ### 5.1 Configuration ```javascript // bootstrap.js import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Echo = new Echo({ broadcaster: 'pusher', key: import.meta.env.VITE_PUSHER_APP_KEY, cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, wsHost: import.meta.env.VITE_PUSHER_HOST, // Soketi en self-hosted wsPort: 6001, forceTLS: false, enabledTransports: ['ws', 'wss'], }); ``` ### 5.2 Channels & Events | Channel | Event | Payload | Déclencheur | |---------|-------|---------|-------------| | `private-establishment.{id}` | `MachineStatusUpdated` | `{ machine_uuid, status, current_user_initials, cycle_ends_at }` | Heartbeat LLDP + fin de cycle | | `private-establishment.{id}` | `NewAlert` | `{ type, machine_uuid, message, created_at }` | Offline détecté, erreur machine | | `private-establishment.{id}` | `KpiUpdated` | `{ ca_today, washes_today, occupancy_rate }` | Fin de chaque lavage | ### 5.3 Implémentation Vue ```javascript // DashboardController.vue (Composition API) onMounted(() => { window.Echo .private(`establishment.${props.establishment.id}`) .listen('MachineStatusUpdated', (event) => { machineStore.updateMachineStatus(event.machine_uuid, event); }) .listen('NewAlert', (event) => { alertStore.addAlert(event); }); }); onUnmounted(() => { window.Echo.leave(`establishment.${props.establishment.id}`); }); ``` --- ## 6. Sécurité ### 6.1 Authentification - Session cookie Sanctum (SameSite=Lax, Secure en prod) - CSRF token sur toutes les mutations - Timeout de session : 8 heures d'inactivité ### 6.2 Autorisation (Policies Laravel) ``` MachinePolicy::update() → role manager ou admin ET même établissement PricingPolicy::create() → role manager ou admin PromotionPolicy::delete() → role admin seulement StatsPolicy::view() → tous les rôles ``` ### 6.3 Données - Les données utilisateurs affichées sont **anonymisées** (initiales, jamais nom complet ou email) - Logs d'audit sur toutes les mutations (qui a modifié quoi et quand) --- ## 7. Responsive & Accessibilité - Layout responsive : sidebar collapsible sur tablette, menu bottom sur mobile - Taille minimale cible : tablette 768px (utilisation en mobilité sur le terrain) - Contraste WCAG AA sur tous les textes - Navigation clavier complète (focus visible) - Attributs ARIA sur les graphiques (descriptions alternatives) --- ## 8. Tests | Type | Outil | Portée | |------|-------|--------| | Tests unitaires | Pest | Services stats, services tarification | | Tests controllers | Pest | Tous les controllers superviseur (mocks auth) | | Tests composants | Vitest + VTU | StatCard, MachineCard, PricingForm | | Tests E2E | Playwright | Login, modifier un tarif, créer une promo, consulter stats | **Scénarios Playwright critiques :** 1. Login échoué (mauvais mot de passe) → message d'erreur 2. Login réussi → redirection dashboard 3. Créer une règle tarifaire → vérification dans la liste 4. Créer une promotion avec chevauchement → message d'avertissement 5. Mise en maintenance d'une machine → badge mis à jour en temps réel --- ## 9. Variables d'environnement spécifiques ```env # Pusher / Soketi (temps réel) PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_HOST=127.0.0.1 PUSHER_PORT=6001 PUSHER_SCHEME=http PUSHER_APP_CLUSTER=mt1 VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" ``` --- ## 10. Livrables attendus - [ ] Code source Vue.js (dans le même dépôt Laravel ou dépôt séparé) - [ ] Migrations et seeders pour les superviseurs de démo - [ ] Documentation des rôles et permissions - [ ] Tests Pest controllers (couverture > 80%) - [ ] Tests Playwright (5 parcours critiques) - [ ] Guide d'utilisation superviseur (PDF ou page dans le back-office) - [ ] `README.md` avec instructions de déploiement front (Vite build + assets)