Integration fonctionnalités V1 ( resas + gestion machines

This commit is contained in:
bastien
2026-07-03 19:02:48 +02:00
parent 20f383dd62
commit bf191d6396
46 changed files with 4691 additions and 654 deletions
+22
View File
@@ -1,3 +1,5 @@
import 'wash_progress.dart';
/// Modèle lavage (cycle en cours ou terminé).
class Wash {
const Wash({
@@ -9,6 +11,9 @@ class Wash {
this.startedAt,
this.endedAt,
this.durationMinutes,
this.machineType,
this.cycleEndsAt,
this.progress,
});
final String uuid;
@@ -19,14 +24,27 @@ class Wash {
final DateTime? startedAt;
final DateTime? endedAt;
final int? durationMinutes;
final String? machineType;
final DateTime? cycleEndsAt;
final WashProgress? progress;
WashProgress get liveProgress => WashProgress.compute(
startedAt: startedAt,
estimatedEndAt: progress?.estimatedEndAt ?? cycleEndsAt,
durationMinutes: durationMinutes,
machineType: machineType,
status: status,
);
factory Wash.fromJson(Map<String, dynamic> json) {
final machine = json['machine'] as Map<String, dynamic>?;
final progressJson = json['progress'] as Map<String, dynamic>?;
return Wash(
uuid: json['uuid'] as String,
machineUuid: machine?['uuid'] as String? ?? json['machine_uuid'] as String? ?? '',
machineName: machine?['name'] as String? ?? 'Machine',
machineType: machine?['type'] as String?,
status: json['status'] as String? ?? 'pending_start',
cost: (json['cost'] as num?)?.toDouble() ?? 0,
startedAt: json['started_at'] != null
@@ -36,6 +54,10 @@ class Wash {
? DateTime.tryParse(json['ended_at'] as String)
: null,
durationMinutes: json['duration_minutes'] as int?,
cycleEndsAt: machine?['cycle_ends_at'] != null
? DateTime.tryParse(machine!['cycle_ends_at'] as String)
: null,
progress: progressJson != null ? WashProgress.fromJson(progressJson) : null,
);
}
}
+114
View File
@@ -0,0 +1,114 @@
/// Progression d'un lavage en cours.
class WashProgress {
const WashProgress({
required this.percent,
required this.phaseLabel,
this.phase,
this.estimatedEndAt,
this.remainingSeconds,
});
final int percent;
final String phaseLabel;
final String? phase;
final DateTime? estimatedEndAt;
final int? remainingSeconds;
String get remainingLabel {
if (remainingSeconds == null) return '';
final s = remainingSeconds!;
if (s <= 0) return 'Bientôt terminé';
if (s < 60) return '$s s restantes';
final min = (s / 60).ceil();
return '$min min restantes';
}
factory WashProgress.fromJson(Map<String, dynamic>? json) {
if (json == null) {
return const WashProgress(percent: 0, phaseLabel: 'En cours');
}
return WashProgress(
percent: json['percent'] as int? ?? 0,
phase: json['phase'] as String?,
phaseLabel: json['phase_label'] as String? ?? 'En cours',
estimatedEndAt: json['estimated_end_at'] != null
? DateTime.tryParse(json['estimated_end_at'] as String)
: null,
remainingSeconds: json['remaining_seconds'] as int?,
);
}
/// Calcul local si pas de données API (rafraîchissement chaque seconde).
factory WashProgress.compute({
required DateTime? startedAt,
required DateTime? estimatedEndAt,
required int? durationMinutes,
required String? machineType,
required String status,
}) {
if (status == 'pending_start') {
return const WashProgress(percent: 0, phaseLabel: 'En attente de démarrage', phase: 'pending');
}
final now = DateTime.now();
final end = estimatedEndAt ??
(startedAt != null && durationMinutes != null
? startedAt.add(Duration(minutes: durationMinutes))
: null);
if (startedAt == null || end == null) {
return const WashProgress(percent: 5, phaseLabel: 'Démarrage…', phase: 'lock');
}
final total = end.difference(startedAt).inSeconds.clamp(1, 999999);
final elapsed = now.difference(startedAt).inSeconds.clamp(0, total);
final percent = ((elapsed / total) * 100).round().clamp(0, 99);
final remaining = end.difference(now).inSeconds.clamp(0, total);
return WashProgress(
percent: percent,
phaseLabel: _phaseLabel(percent, machineType),
phase: _phase(percent, machineType),
estimatedEndAt: end,
remainingSeconds: remaining,
);
}
static String _phase(int percent, String? type) {
final isDryer = type?.startsWith('dryer') ?? false;
if (percent < 5) return 'lock';
if (isDryer) {
if (percent < 15) return 'heat';
if (percent < 85) return 'dry';
return 'finish';
}
if (percent < 15) return 'fill';
if (percent < 55) return 'wash';
if (percent < 75) return 'rinse';
if (percent < 90) return 'spin';
return 'finish';
}
static String _phaseLabel(int percent, String? type) {
switch (_phase(percent, type)) {
case 'lock':
return 'Verrouillage';
case 'heat':
return 'Préchauffage';
case 'dry':
return 'Séchage';
case 'fill':
return 'Remplissage';
case 'wash':
return 'Lavage';
case 'rinse':
return 'Rinçage';
case 'spin':
return 'Essorage';
case 'finish':
return 'Finition';
default:
return 'En cours';
}
}
}