import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/scheduler_provider.dart'; import '../../services/nymea_service.dart'; import '../../theme/app_theme.dart'; import '../../theme/etm_theme.dart'; import '../../widgets/pro_lock_badge.dart'; /// Écran "Scheduler & stratégie". class SchedulerScreen extends StatefulWidget { const SchedulerScreen({super.key}); @override State createState() => _SchedulerScreenState(); } class _SchedulerScreenState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().load(context.read()); }); } @override Widget build(BuildContext context) { final scheduler = context.watch(); return Scaffold( backgroundColor: const Color(0xFFF0F2F5), appBar: AppBar( title: const Text('Scheduler & stratégie'), backgroundColor: Colors.white, foregroundColor: const Color(0xFF1A1A2E), elevation: 0, ), body: scheduler.loading ? const Center(child: CircularProgressIndicator()) : ListView( padding: const EdgeInsets.all(16), children: [ _StrategyCard(scheduler), const SizedBox(height: 12), _ConfigCard(scheduler), const SizedBox(height: 12), _StatusCard(scheduler), ], ), ); } } // ── Carte stratégie ─────────────────────────────────────────────────────────── class _StrategyCard extends StatelessWidget { final SchedulerProvider scheduler; const _StrategyCard(this.scheduler); @override Widget build(BuildContext context) { return _Card( title: 'Stratégie active', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DropdownButtonFormField( value: scheduler.strategy.isPro ? SchedulerStrategy.rulesBased : scheduler.strategy, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 12), ), items: SchedulerStrategy.values.map((s) { return DropdownMenuItem( value: s.isPro ? null : s, enabled: !s.isPro, child: Row( children: [ Text(s.label), if (s.isPro) ...[ const SizedBox(width: 8), const ProLockBadge(featureName: 'Stratégie AI'), ], ], ), ); }).toList(), onChanged: (v) { if (v != null) { context .read() .setStrategy(v); } }, ), ], ), ); } } // ── Carte paramètres ────────────────────────────────────────────────────────── class _ConfigCard extends StatelessWidget { final SchedulerProvider scheduler; const _ConfigCard(this.scheduler); @override Widget build(BuildContext context) { final cfg = scheduler.config; return _Card( title: 'Paramètres', child: Column( children: [ _ConfigRow( label: 'Seuil prix recharge', value: cfg.priceThreshold, unit: '€/kWh', min: 0.01, max: 0.5, decimals: 3, onChanged: (v) => context .read() .updateConfig(cfg.copyWith(priceThreshold: v)), ), _ConfigRow( label: 'Surplus minimum', value: cfg.minSurplus, unit: 'W', min: 50, max: 2000, onChanged: (v) => context .read() .updateConfig(cfg.copyWith(minSurplus: v)), ), _ConfigRow( label: 'Horizon planification', value: cfg.planningHorizon.toDouble(), unit: 'h', min: 1, max: 72, onChanged: (v) => context .read() .updateConfig(cfg.copyWith(planningHorizon: v.toInt())), ), _ConfigRow( label: 'Recalcul toutes les', value: cfg.recalcInterval.toDouble(), unit: 'min', min: 5, max: 60, onChanged: (v) => context .read() .updateConfig(cfg.copyWith(recalcInterval: v.toInt())), ), _ConfigRow( label: 'Objectif autosuffisance', value: cfg.selfSufficiencyGoal, unit: '%', min: 0, max: 100, onChanged: (v) => context .read() .updateConfig(cfg.copyWith(selfSufficiencyGoal: v)), ), ], ), ); } } class _ConfigRow extends StatefulWidget { final String label; final double value; final String unit; final double min; final double max; final int decimals; final ValueChanged onChanged; const _ConfigRow({ required this.label, required this.value, required this.unit, required this.min, required this.max, required this.onChanged, this.decimals = 0, }); @override State<_ConfigRow> createState() => _ConfigRowState(); } class _ConfigRowState extends State<_ConfigRow> { late TextEditingController _ctrl; @override void initState() { super.initState(); _ctrl = TextEditingController( text: widget.value.toStringAsFixed(widget.decimals)); } @override void didUpdateWidget(_ConfigRow old) { super.didUpdateWidget(old); final formatted = widget.value.toStringAsFixed(widget.decimals); if (_ctrl.text != formatted) _ctrl.text = formatted; } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ Expanded( child: Text(widget.label, style: const TextStyle(fontSize: 13)), ), SizedBox( width: 80, child: TextFormField( controller: _ctrl, textAlign: TextAlign.center, keyboardType: const TextInputType.numberWithOptions( decimal: true), decoration: InputDecoration( isDense: true, filled: true, fillColor: Colors.grey.shade50, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8)), contentPadding: const EdgeInsets.symmetric( horizontal: 8, vertical: 8), ), onFieldSubmitted: (v) { final n = double.tryParse(v); if (n != null) { widget.onChanged(n.clamp(widget.min, widget.max)); } }, ), ), const SizedBox(width: 6), Text(widget.unit, style: const TextStyle( fontSize: 12, color: AppTheme.textLight)), ], ), ); } } // ── Carte état du scheduler ─────────────────────────────────────────────────── class _StatusCard extends StatelessWidget { final SchedulerProvider scheduler; const _StatusCard(this.scheduler); @override Widget build(BuildContext context) { final state = scheduler.state; final statusColor = switch (state.status) { SchedulerStatus.ok => AppTheme.primaryGreen, SchedulerStatus.degraded => Colors.orange, SchedulerStatus.error => AppTheme.boostRed, }; final statusLabel = switch (state.status) { SchedulerStatus.ok => '✅ OK', SchedulerStatus.degraded => '⚠️ Dégradé', SchedulerStatus.error => '❌ Erreur', }; String _ago(DateTime? dt) { if (dt == null) return '—'; final diff = DateTime.now().difference(dt); if (diff.inMinutes < 1) return 'à l\'instant'; return 'il y a ${diff.inMinutes} min'; } String _in(DateTime? dt) { if (dt == null) return '—'; final diff = dt.difference(DateTime.now()); if (diff.isNegative) return 'dépassé'; return 'dans ${diff.inMinutes} min'; } return _Card( title: 'État du scheduler', trailing: ElevatedButton.icon( icon: scheduler.loading ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Icon(Icons.refresh_rounded, size: 16), label: const Text('Forcer recalcul'), style: ElevatedButton.styleFrom( backgroundColor: ETMTheme.accentColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), textStyle: const TextStyle(fontSize: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), onPressed: () => context .read() .forceRecalc(context.read()), ), child: Column( children: [ _StatusRow('Dernière planification', _ago(state.lastPlanning)), _StatusRow('Prochaine planification', _in(state.nextPlanning)), Row( children: [ const Expanded( child: Text('État', style: TextStyle(fontSize: 13))), Text(statusLabel, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: statusColor)), ], ), if (state.reason.isNotEmpty) ...[ const SizedBox(height: 6), Text( 'Raison : ${state.reason}', style: const TextStyle( fontSize: 12, color: AppTheme.textLight), ), ], ], ), ); } } class _StatusRow extends StatelessWidget { final String label; final String value; const _StatusRow(this.label, this.value); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( children: [ Expanded( child: Text(label, style: const TextStyle(fontSize: 13))), Text(value, style: const TextStyle( fontSize: 13, color: AppTheme.textLight)), ], ), ); } } // ── Card générique ──────────────────────────────────────────────────────────── class _Card extends StatelessWidget { final String title; final Widget child; final Widget? trailing; const _Card({required this.title, required this.child, this.trailing}); @override Widget build(BuildContext context) { return Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14)), const Spacer(), if (trailing != null) trailing!, ], ), const SizedBox(height: 14), child, ], ), ), ); } }