15 KiB
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)
// 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/dashboardsi déjà connecté - Rate limiting : 5 tentatives / 10 min
Données Inertia passées :
// 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}, eventMachineStatusUpdated) - 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 :
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
// 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
// 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 :
- Login échoué (mauvais mot de passe) → message d'erreur
- Login réussi → redirection dashboard
- Créer une règle tarifaire → vérification dans la liste
- Créer une promotion avec chevauchement → message d'avertissement
- Mise en maintenance d'une machine → badge mis à jour en temps réel
9. Variables d'environnement spécifiques
# 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.mdavec instructions de déploiement front (Vite build + assets)