# Dashboard ETM-PowerSync — Brief d'implémentation Flutter > Spec pour Claude Code. Objectif : recréer l'écran **Dashboard** de l'app `app.etm-powersync.fr` (Flutter), tel que validé en maquette. Les fichiers HTML de référence (`etm-powersync-dashboard-v4.html`, `etm-powersync-flux-drilldown.html`) sont la **source de vérité visuelle** — ce document en est la transcription technique. --- ## 1. Contexte ETM-PowerSync est un HEMS (Home Energy Management System). Le cœur intelligent s'appelle **Héos** (optimiseur prédictif MILP, horizon 24–48 h). Le Dashboard doit faire deux choses : montrer l'état énergétique **en direct**, et rendre **lisible et explicable** ce que Héos décide — c'est le différenciateur produit. Public : clients résidentiels (langage clair, zéro jargon) + installateurs. --- ## 2. Principes directeurs (ne pas dévier) 1. **Chaque information à un seul endroit.** Pas de doublon entre les blocs. L'autoconsommation est en KPI (haut) → elle n'apparaît nulle part ailleurs sur le Dashboard. 2. **Lisibilité avant densité.** Lecture rapide en haut, détail optionnel en bas. Le client pressé s'arrête à la décision Héos ; le curieux scrolle. 3. **Expliquer = créer la confiance.** Toute action automatique de Héos est accompagnée d'un *pourquoi* en langage courant. 4. **Cohérence des flux = physique réelle.** Héos est au centre. Le solaire et le réseau alimentent (→ vers le centre) ; le centre répartit vers batterie et maison. Jamais « maison → batterie ». 5. **Passé plein, futur voilé.** Sur tout graphe temporel : réalisé en couleur pleine, prévu en hachuré + fond grisé, ligne « maintenant » qui traverse. 6. **Fluide = rien ne saute.** Chaque valeur live s'anime (interpolation), jamais de flash. 7. **Quality floor non négociable.** Responsive, dark mode, focus clavier, `prefers-reduced-motion` respecté, contraste AA. --- ## 3. Design tokens (`etm_tokens.dart`) Compléter le fichier existant avec ces valeurs. Typo : **IBM Plex Sans** (UI) + **IBM Plex Mono** (toutes les valeurs chiffrées / données). ### Couleurs de marque (identiques light/dark sauf note) | Token | Hex | Usage | |---|---|---| | `brand` | `#31A3DD` | bleu ETM, accents, navigation active | | `solar` | `#FEC113` (dark `#FFCB33`) | production solaire | | `eco` | `#28A06A` (dark `#33B87A`) | Héos, éco, autonomie, batterie | | `heat` | `#EF8B2A` | chauffage / PAC | | `water` | `#31A3DD` | eau chaude (ECS) | | `grid` | `#8A98A3` | réseau | | `import` | `#E8806F` | import réseau | ### Thème clair | Token | Valeur | |---|---| | `bg` | `#EEF2F5` | | `bg2` | `#E4EAEE` | | `surface` | `#FFFFFF` | | `ink` | `#0D2B3B` | | `muted` | `#5D6F7B` | | `faint` | `#8B9AA4` | | `line` | `rgba(13,43,59,.10)` | | `lineStrong` | `rgba(13,43,59,.16)` | ### Thème sombre | Token | Valeur | |---|---| | `bg` | `#06141D` | | `bg2` | `#0A1F2B` | | `surface` | `#0D2B3B` (la couleur de marque devient la surface) | | `ink` | `#EAF3F7` | | `muted` | `#9BB0BC` | | `faint` | `#6C8493` | | `line` | `rgba(255,255,255,.09)` | | `lineStrong` | `rgba(255,255,255,.16)` | ### Couleurs des flux (graphe « plan de la journée ») `solToHouse #B8C34D` · `solToEcs #31A3DD` · `solToVe #28A06A` · `solToBatt #7FCF72` · `solToGrid #FEC113` · `battToHouse #2F6FB0` · `gridToHouse #E8806F` ### Formes & profondeur - Rayons : cartes **20**, contrôles internes **13–14**, pills/badges **30** (stadium). - **Parti pris assumé** : bordure hairline 1 px (`line`) + rayon large. Ombres quasi nulles en clair (`0 1px 2px rgba(13,43,59,.04)`), aucune en sombre. **Ne pas** revenir aux cartes blanches à ombre portée Material par défaut. - Espacement entre cartes : 14. Padding interne carte : 14–16. --- ## 4. Architecture de l'écran `DashboardScreen` = `Scaffold` + `SingleChildScrollView` (colonne), bottom nav fixe à 5 onglets. Ordre vertical des blocs : ``` [ TopBar : wordmark + toggle thème clair/sombre ] [ 1. EnergyFlowCard ] ← flux en croix, temps réel, drill-down [ 2. KpiRow ] ← 4 tuiles : autoconso, autonomie, vers/depuis réseau [ 3. HeosDecisionCard ] ← décision en cours + pourquoi [ 4. DayPlanCard ] ← météo + barres horaires (réalisé/prévu) [ BottomNav : Dashboard·Énergie·Things·A/C·Favoris ] ``` Onglets : `Dashboard` actif (couleur `brand`), les autres en `faint`. --- ## 5. Composants ### 5.1 EnergyFlowCard — flux en croix + drill-down **Layout** : carte carrée (aspect 1:1, max 320). `Stack` : - Couche basse : `CustomPaint` (lignes hairline entre hub↔nœuds + **particules animées**). - Couche haute : nœuds en `Positioned` (Stack), `GestureDetector` sur les nœuds tappables. **Vue d'ensemble (`overview`)** — positions cardinales : | Position | Nœud | Valeur (ex.) | Direction flux | Tap | |---|---|---|---|---| | centre (hub) | **Héos** (icône HEMS, anneau eco pulsant) | — | — | — | | haut | Solaire | `3.8 kW` | entrant (nœud→hub) | → `prod` | | droite | Batterie | `87 %` | sortant (hub→nœud) | — | | gauche | Réseau | `0.2 kW` | entrant | — | | bas | Maison | `2.4 kW` | sortant | → `conso` | **Drill-down `conso`** (depuis Maison) — hub = Maison, autour : PAC (haut, `heat`), Eau chaude (droite, `water`), Voiture (bas, `eco`), **Autres (estimé)** (gauche, bordure **dashed**, `grid`). > Le nœud « Autres (estimé) » est **obligatoire** : il absorbe le non-mesuré pour que le bilan boucle (conso totale = somme des postes). Sans lui, l'addition ne tombe pas juste et le client se méfie. **Drill-down `prod`** (depuis Solaire) — hub = Solaire, autour : Onduleur 1 (haut), Onduleur 2 (bas), chacun avec **point d'état** (vert OK / orange défaut). Sens : onduleur → hub. **Particules** : `CustomPainter` + `AnimationController` en boucle. N points par segment circulant de la source vers la destination. **Vitesse et densité ∝ puissance** (flux fort = plus de points, plus rapides ; flux faible = 1 point lent). Couleur = couleur de la source. **Transitions drill-down** : sortie = fade + scale 0.8 (~170 ms) ; entrée = « éclatement radial », chaque nœud apparaît en `scale .25→1` avec overshoot (`Curves.easeOutBack`) et **stagger** (~60 ms entre nœuds). Détail intégral du sens/positions dans `etm-powersync-flux-drilldown.html`. **Affordance tap** : badge `+` discret sur les nœuds tappables (Héos n'est pas tappable ; Solaire et Maison le sont). Profondeur **max 2 niveaux** — au-delà, écran dédié. **Icône HEMS** : utiliser `HemsIcon` (déjà livré, `hems_icon.dart`), couleur `eco`. ### 5.2 KpiRow Grille 2×2. - **Tuile autoconsommation** : anneau de progression (`CustomPaint`, couleur `solar`) + `%` en mono. S'anime au chargement (anneau qui se remplit + count-up). - **Tuile autonomie** : idem, couleur `eco`. - **Tuile « Vers réseau »** : icône flèche ←, valeur `kWh` du jour, badge fond `eco` léger. - **Tuile « Depuis réseau »** : icône flèche →, valeur `kWh`, badge fond `import` léger. > Ces deux métriques (autoconso/autonomie) sont les seules occurrences sur le Dashboard. Ne pas les répéter ailleurs. ### 5.3 HeosDecisionCard Carte bordée `eco` (liseré subtil). Contenu : - **En-tête** : icône étincelle (mark Héos) + titre « Héos pilote en ce moment » + sous-titre mono « optimise sur 24 h · maj HH:MM » + point eco pulsant. - **Résumé** : 1 phrase en langage client, segment clé surligné `eco` (ex. « Le surplus part dans l'eau chaude et la voiture plutôt que d'être revendu à bas prix »). - **Liste d'actions** (1 par asset piloté) : icône + titre court + **le pourquoi** (1 ligne, `muted`) + puissance (`kW`, mono, `eco`), séparées par hairline. - **Pied** : lien « Voir le plan de la journée » → ouvre l'écran plein **Plan de la journée** (séparé, voir `etm-powersync-plan-journee.html` ; c'est là que vivent les pistes appareils détaillées + la ligne gains €). ### 5.4 DayPlanCard — météo + barres horaires `CustomPaint` unique (préférer à fl_chart : contrôle fin du voile futur + bande météo intégrée). **Une seule colonne temporelle, axe horaire partagé**, de haut en bas : 1. **Bande météo** : icône (soleil / soleil-nuage / lune) + température, à intervalles (~toutes les 4 h), posée sur l'axe. 2. **Barres empilées horaires** (kWh) : segments positifs vers le haut (`solToHouse`, `solToEcs`, `solToVe`, `solToBatt`, `solToGrid`), négatifs vers le bas (`battToHouse`, `gridToHouse`). Axe Y `0/2/4`. 3. **Axe X** : `0h 6h 12h 18h 24h`. **Traitement passé/futur** : tout ce qui est à droite de la ligne « maintenant » = **fond grisé + voile hachuré** (lignes diagonales couleur surface, opacité ~.5) ; ligne « maintenant » verticale + tab `HH:MM`. **Légende** sous le graphe : pastilles maison/eau/voiture/batterie/réseau/batt.→maison. > **NE PAS mettre sur le Dashboard** (retirés volontairement, ils vivent dans l'écran plein « Plan de la journée ») : > - les **pistes appareils** (timelines ECS / batterie charge-décharge / VE) ; > - la **ligne gains** (autoconso prévue / réseau ce soir / +€). ### 5.5 Toggle thème Switch clair/sombre dans la TopBar, knob animé (overshoot). Persister le choix (provider de thème). --- ## 6. Animations & widgets | Effet | Approche Flutter | |---|---| | Valeurs live (kW, %) | `TweenAnimationBuilder` (count-up, easeOutCubic ~900 ms) | | Particules de flux | `CustomPainter` + `AnimationController` répété ; position = `(t + offset) % 1` mappée sur le segment | | Anneaux KPI | `CustomPaint`, `Tween` sur l'angle balayé | | Toggles / switch | `AnimatedAlign` ou `AnimatedPositioned`, courbe overshoot (`Curves.easeOutBack`) | | Segmented (pill qui glisse) | `Stack` + `AnimatedPositioned` du « glider » | | Cartes expandables (A/C) | `AnimatedSize` | | Drill-down croix | `AnimationController` + `Interval` staggered (scale + opacity) | | Anneau Héos (hub) | pulsation lente en boucle (opacité 0.25↔0.6) | **Reduced motion** : si `MediaQuery.of(context).disableAnimations`, court-circuiter les animations (valeurs finales directes, pas de particules en boucle). --- ## 7. Modèles de données (esquisse) ```dart class LiveFlow { // alimente EnergyFlowCard (overview) final double solarKw, gridKw, houseKw, batteryKw; // batteryKw signé final int batteryPct; } class FlowNode { // générique, réutilisé à chaque niveau final String label, valueText, iconKey; final FlowDirection direction; // inbound | outbound final bool tappable, estimated; // estimated → bordure dashed final NodeState? state; // ok | warn (onduleurs) final String? drillTo; // 'conso' | 'prod' } class HeosDecision { final String summary; // langage client final List actions; final DateTime updatedAt; } class HeosAction { final String assetIconKey, title, why; final double powerKw; } class Kpi { final int autoconsumptionPct, autonomyPct; final double toGridKwh, fromGridKwh; } class DayPlan { final List hours; // 0..23 final List weather; final double nowHour; // ex. 14.5 } class HourlyFlow { // tous en kWh, signés selon sens final int hour; final double solToHouse, solToEcs, solToVe, solToBatt, solToGrid; final double battToHouse, gridToHouse; } class WeatherPoint { final int hour; final WeatherKind kind; final int tempC; } ``` Toutes les valeurs affichées proviennent de ces modèles (les chiffres des maquettes sont des exemples). Prévoir des états **loading** (skeleton) et **vide** (« en attente de données du compteur… », jamais d'écran blanc). --- ## 8. Assets fournis - `hems_icon.dart` — `HemsIcon(size, color)`, `CustomPainter` natif sans dépendance. À utiliser pour le hub central. - `hems_icon.svg` — équivalent SVG (`currentColor`) si passage par `flutter_svg`. --- ## 9. Arborescence suggérée ``` lib/features/dashboard/ dashboard_screen.dart widgets/energy_flow_card.dart + painters/flow_painter.dart widgets/kpi_row.dart + painters/ring_painter.dart widgets/heos_decision_card.dart widgets/day_plan_card.dart + painters/day_plan_painter.dart models/dashboard_models.dart lib/shared/widgets/hems_icon.dart lib/theme/etm_tokens.dart (compléter) ``` ## 10. Ordre de construction recommandé 1. Compléter `etm_tokens.dart` (couleurs light/dark, typo, radius). 2. `KpiRow` + `RingPainter` (le plus simple, valide les tokens). 3. `HeosDecisionCard` (statique, valide la typo/hairlines). 4. `EnergyFlowCard` overview + `FlowPainter` (lignes + particules), **puis** drill-down. 5. `DayPlanCard` + `DayPlanPainter` (barres + voile futur + bande météo). 6. Assemblage `DashboardScreen` + bottom nav + toggle thème. 7. Passe quality floor : reduced motion, focus, contraste AA en clair **et** sombre. --- *Réfs visuelles : `etm-powersync-dashboard-v4.html` (écran complet) et `etm-powersync-flux-drilldown.html` (interaction croix). En cas de doute sur un espacement ou une couleur, ces fichiers font foi.*