feat(énergie): la vue Mois est le mois civil

Patrick, 2026-10-16 : le titre disait « oct. 2026 » au-dessus d'une vue
des 30 derniers jours (17/9 → 16/10). L'onglet Mois prend désormais le
mois civil du jour choisi, du 1er au dernier jour — barres, tuiles du
bilan et titre disent la même chose. Les flèches avancent d'un MOIS (et
non de 30 jours : le 31 janvier menait au 2 mars) ; revenir sur le mois
en cours reprend le suivi d'aujourd'hui. Vu sur le téléphone (oct. 2026,
sept. 2026).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-10-17 00:33:30 +02:00
parent e562fb5bf5
commit 4da904fc7a

View File

@ -137,10 +137,16 @@ class _Tab {
/// les N dernières heures. C'est ce que « Jour » promet, et ce qu'il ne faisait pas.
final bool journeeCivile;
/// Vrai si la plage est le **mois civil** du jour choisi (du 1er au dernier jour), et non
/// les 30 derniers jours (Patrick, 2026-10-16 : le titre disait « oct. 2026 » au-dessus
/// d'une vue du 17/9 au 16/10).
final bool moisCivil;
const _Tab(this.label, this.range, this.sampleRate,
{required this.showTime,
this.rendu = _Rendu.courbes,
this.journeeCivile = false});
this.journeeCivile = false,
this.moisCivil = false});
}
class EnergyScreen extends StatefulWidget {
@ -163,7 +169,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
showTime: true, rendu: _Rendu.barres, journeeCivile: true),
_Tab('Semaine', Duration(days: 7), 'SampleRate1Day',
showTime: false, rendu: _Rendu.barres),
_Tab('Mois', Duration(days: 30), 'SampleRate1Day',
_Tab('Mois', Duration(days: 30), 'SampleRate1Day', moisCivil: true,
showTime: false, rendu: _Rendu.barres),
_Tab('Année', Duration(days: 365), 'SampleRate1Month',
showTime: false, rendu: _Rendu.barres),
@ -424,6 +430,10 @@ class _EnergyScreenState extends State<EnergyScreen> {
/// que l'onglet promet. Les autres restent glissantes à partir de maintenant.
(DateTime, DateTime) _fenetreRequete(_Tab tab) {
final ancre = _anchorDate();
if (tab.moisCivil) {
final debut = DateTime(ancre.year, ancre.month, 1);
return (debut.subtract(_pasEchantillon(tab)), DateTime(ancre.year, ancre.month + 1, 1));
}
// 🔴 **Toutes les vues en barres se calent sur minuit**, pas seulement « Jour ».
//
// Sans ça, une plage de 7 jours partant de 08:12 coupe deux journées en deux : le
@ -461,6 +471,13 @@ class _EnergyScreenState extends State<EnergyScreen> {
final a = _anchorDate();
final maintenant = DateTime.now();
final jourSeul = tab.label == 'Heures' || tab.journeeCivile;
if (tab.moisCivil) {
final finMois = DateTime(a.year, a.month + 1, 1);
return (
DateTime(a.year, a.month, 1),
finMois.isAfter(maintenant) ? maintenant : finMois,
);
}
final finJour = DateTime(a.year, a.month, a.day + 1);
final debut = jourSeul
? DateTime(a.year, a.month, a.day)
@ -526,19 +543,30 @@ class _EnergyScreenState extends State<EnergyScreen> {
///
/// Sans cette borne, on part dans un futur où la box n'a rien : un graphe vide qui ne
/// dit pas qu'il est vide **parce qu'on est allé trop loin**.
bool _peutAvancer() =>
_selectedDate != null &&
_anchorDate().add(_tabs[_tabIdx].range).isBefore(DateTime.now());
bool _peutAvancer() {
if (_selectedDate == null) return false;
final tab = _tabs[_tabIdx];
final a = _anchorDate();
if (tab.moisCivil) return !DateTime(a.year, a.month + 1, 1).isAfter(DateTime.now());
return a.add(tab.range).isBefore(DateTime.now());
}
void _decalerPeriode(int sens) {
final tab = _tabs[_tabIdx];
final base = _selectedDate ?? DateTime.now();
final cible = base.add(tab.range * sens);
// Mois civil : on avance d'UN mois, pas de 30 jours — sinon le 31 janvier mènerait au
// 2 mars. Le 1er du mois visé suffit d'ancre : la vue prend le mois entier.
final cible = tab.moisCivil
? DateTime(base.year, base.month + sens, 1)
: base.add(tab.range * sens);
setState(() {
// Revenir sur aujourd'hui remet le mode « suivi », où la fenêtre se recale à chaque
// lecture. Rester sur une date figée l'aurait gelée sans le dire.
final maintenant = DateTime.now();
_selectedDate = cible.isAfter(maintenant) ? null : cible;
final moisCourant = tab.moisCivil &&
cible.year == maintenant.year &&
cible.month == maintenant.month;
_selectedDate = cible.isAfter(maintenant) || moisCourant ? null : cible;
_initialSocFetched = false;
});
_fetch();