import 'package:flutter/material.dart'; import '../theme/app_theme.dart'; import '../theme/etm_theme.dart'; import 'power_bar.dart'; /// Données d'un créneau horaire dans la timeline EMS. class TimelineSlot { final DateTime start; final DateTime end; final double solarW; final double homeW; final double evW; // > 0 = actif final double batteryW; // > 0 = charge, < 0 = décharge final double gridW; // > 0 = import, < 0 = export final String reasoning; // Explication de la décision final double savings; // € final double selfSufficiency; // % final bool isNow; final bool hasOverride; const TimelineSlot({ required this.start, required this.end, required this.solarW, required this.homeW, this.evW = 0, this.batteryW = 0, this.gridW = 0, this.reasoning = '', this.savings = 0, this.selfSufficiency = 0, this.isNow = false, this.hasOverride = false, }); } /// Carte d'un créneau horaire dans la timeline EMS. class TimelineSlotCard extends StatelessWidget { final TimelineSlot slot; final VoidCallback? onOverride; const TimelineSlotCard({ super.key, required this.slot, this.onOverride, }); @override Widget build(BuildContext context) { final maxW = [ slot.solarW, slot.homeW, slot.evW.abs(), slot.batteryW.abs(), slot.gridW.abs(), ].fold(0, (a, b) => a > b ? a : b).clamp(100.0, double.infinity); return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: slot.isNow ? ETMTheme.accentColor.withValues(alpha: 0.08) : Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: slot.isNow ? ETMTheme.accentColor.withValues(alpha: 0.4) : Colors.grey.withValues(alpha: 0.12), ), ), child: Padding( padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── En-tête ────────────────────────────────────────────────────── Row( children: [ Text( '${_fmt(slot.start)} – ${_fmt(slot.end)}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 13, color: slot.isNow ? ETMTheme.accentColor : AppTheme.textDark, ), ), if (slot.isNow) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( color: ETMTheme.accentColor, borderRadius: BorderRadius.circular(6), ), child: const Text( 'MAINTENANT', style: TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ), ], if (slot.hasOverride) ...[ const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 7, vertical: 2), decoration: BoxDecoration( color: ETMTheme.warningColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text( 'Override', style: TextStyle( color: ETMTheme.warningColor, fontSize: 9, fontWeight: FontWeight.bold, ), ), ), ], ], ), const SizedBox(height: 10), // ── Barres de puissance ────────────────────────────────────────── if (slot.solarW > 0) _PowerRow( emoji: '☀️', label: 'Solaire', value: slot.solarW, maxValue: maxW, color: AppTheme.solarYellow, sign: '+', ), _PowerRow( emoji: '🏠', label: 'Maison', value: slot.homeW, maxValue: maxW, color: AppTheme.homeBlue, sign: '-', ), if (slot.evW != 0) _PowerRow( emoji: '🚗', label: 'VE', value: slot.evW.abs(), maxValue: maxW, color: AppTheme.accentTeal, sign: slot.evW > 0 ? '-' : '+', badge: slot.evW > 0 ? 'ON' : null, ), if (slot.batteryW != 0) _PowerRow( emoji: '🔋', label: 'Batterie', value: slot.batteryW.abs(), maxValue: maxW, color: AppTheme.batteryGreen, sign: slot.batteryW > 0 ? '-' : '+', badge: slot.batteryW > 0 ? '↑' : '↓', ), if (slot.gridW != 0) _PowerRow( emoji: '⚡', label: 'Réseau', value: slot.gridW.abs(), maxValue: maxW, color: slot.gridW > 0 ? AppTheme.powerAcquisitionColor : AppTheme.powerReturnColor, sign: slot.gridW > 0 ? '-' : '+', badge: slot.gridW > 0 ? '↓' : '↑', ), if (slot.reasoning.isNotEmpty) ...[ const SizedBox(height: 10), Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('💬 ', style: TextStyle(fontSize: 13)), Expanded( child: Text( '"${slot.reasoning}"', style: const TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: AppTheme.textLight, ), ), ), ], ), ), ], const SizedBox(height: 8), // ── Métriques + Override ───────────────────────────────────────── Row( children: [ if (slot.savings != 0) Text( '💰 ${slot.savings >= 0 ? '-' : '+'}${slot.savings.abs().toStringAsFixed(3)}€', style: TextStyle( fontSize: 11, color: slot.savings > 0 ? ETMTheme.successColor : ETMTheme.errorColor, ), ), if (slot.savings != 0 && slot.selfSufficiency > 0) const SizedBox(width: 12), if (slot.selfSufficiency > 0) Text( 'Autosuff.: ${slot.selfSufficiency.toStringAsFixed(0)}%', style: const TextStyle( fontSize: 11, color: AppTheme.textLight), ), const Spacer(), if (onOverride != null) TextButton( style: TextButton.styleFrom( foregroundColor: ETMTheme.accentColor, padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), ), onPressed: onOverride, child: const Text('Override manuel', style: TextStyle(fontSize: 11)), ), ], ), ], ), ), ); } String _fmt(DateTime dt) => '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}'; } class _PowerRow extends StatelessWidget { final String emoji; final String label; final double value; final double maxValue; final Color color; final String sign; final String? badge; const _PowerRow({ required this.emoji, required this.label, required this.value, required this.maxValue, required this.color, required this.sign, this.badge, }); @override Widget build(BuildContext context) { final displayW = value >= 1000 ? '${(value / 1000).toStringAsFixed(1)} kW' : '${value.toStringAsFixed(0)} W'; return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ Text(emoji, style: const TextStyle(fontSize: 14)), const SizedBox(width: 6), SizedBox( width: 70, child: Text( label, style: const TextStyle(fontSize: 12, color: AppTheme.textLight), ), ), Text( '$sign$displayW', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppTheme.textDark, ), ), const SizedBox(width: 8), Expanded( child: PowerBar( value: value, maxValue: maxValue, color: color, height: 6, ), ), if (badge != null) ...[ const SizedBox(width: 6), Text(badge!, style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: color)), ], ], ), ); } }