fix: Tour-Datum — Leer-Texte, veraltete Antworten, Kalenderfenster
- Leer-Ansichten (Auswählen, Sortieren, Beladen, Ausliefern) nennen den gewählten Tag statt immer „heute": „Für den 02.09.2026 ist keine Tour zugewiesen." (TourDayText). - TourBloc: Loads/Refreshes laufen nebenläufig; bei schnellem Datumswechsel konnte die langsamere Antwort des vorigen Tages die Tour des gewählten Tages überschreiben. Jetzt schreibt nur die jüngste Anfrage ihr Ergebnis. - Kalender reicht 90 statt 14 Tage zurück (z. B. nachimportierte Touren vom Monatsanfang). - Test: neuer Fahrer startet auf „heute", auch wenn der vorige einen anderen Tag gewählt hatte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -72,6 +72,12 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
/// damit ein Pull-to-refresh denselben Tag neu lädt (nicht heute).
|
||||
DateTime? _selectedDate;
|
||||
|
||||
/// Laufnummer der Tour-Anfragen. Loads/Refreshes laufen nebenläufig; wählt
|
||||
/// der Fahrer schnell nacheinander zwei Tage, darf die langsamere Antwort
|
||||
/// des ersten Tages die des zuletzt gewählten nicht überschreiben. Nur die
|
||||
/// jüngste Anfrage schreibt ihr Ergebnis.
|
||||
int _requestSeq = 0;
|
||||
|
||||
/// Räumt verwaiste Cache-Bilder weg, sobald frische Tour-Daten vorliegen.
|
||||
/// Fire-and-forget — Cache-Pflege darf den Load nie verzögern oder
|
||||
/// scheitern lassen.
|
||||
@ -85,9 +91,11 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
// Nur bei explizit gesetztem Datum die Wahl aktualisieren; ein reiner
|
||||
// Reload (LoadTour()) behält den aktuell angezeigten Tag.
|
||||
if (event.hasDate) _selectedDate = event.date;
|
||||
final seq = ++_requestSeq;
|
||||
emit(const TourLoading());
|
||||
try {
|
||||
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
||||
if (seq != _requestSeq) return; // überholt von neuerer Anfrage
|
||||
if (details == null) {
|
||||
emit(const TourEmpty());
|
||||
return;
|
||||
@ -95,6 +103,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
emit(TourLoaded(details: details));
|
||||
_pruneAttachmentCache(details);
|
||||
} catch (e, st) {
|
||||
if (seq != _requestSeq) return;
|
||||
debugPrint('TourBloc.LoadTour fehlgeschlagen: $e\n$st');
|
||||
final message = _messageOf(e, 'Tour konnte nicht geladen werden');
|
||||
emit(TourLoadFailed(message: message));
|
||||
@ -113,9 +122,11 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final seq = ++_requestSeq;
|
||||
emit(current.copyWith(isRefreshing: true, refreshError: null));
|
||||
try {
|
||||
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
||||
if (seq != _requestSeq) return; // überholt von neuerer Anfrage
|
||||
if (details == null) {
|
||||
emit(const TourEmpty());
|
||||
return;
|
||||
@ -123,6 +134,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
emit(TourLoaded(details: details));
|
||||
_pruneAttachmentCache(details);
|
||||
} catch (e, st) {
|
||||
if (seq != _requestSeq) return;
|
||||
debugPrint('TourBloc.RefreshTour fehlgeschlagen: $e\n$st');
|
||||
final message = _messageOf(e, 'Tour konnte nicht neu geladen werden');
|
||||
// alten Stand sichtbar lassen, Fehler oben mitführen
|
||||
|
||||
40
lib/feature/delivery/model/tour_day_text.dart
Normal file
40
lib/feature/delivery/model/tour_day_text.dart
Normal file
@ -0,0 +1,40 @@
|
||||
/// Formulierung des gewählten Tour-Tags für Fließtexte, z. B. in den
|
||||
/// Leer-Ansichten („Für heute ist keine Tour zugewiesen." bzw. „Für den
|
||||
/// 22.09.2026 …").
|
||||
///
|
||||
/// `null` als gewähltes Datum heißt „heute" (Server-Default, siehe
|
||||
/// `TourDateCubit`). Heute/morgen/gestern werden relativ zur Geräte-Uhr
|
||||
/// benannt, alle anderen Tage mit Datum.
|
||||
class TourDayText {
|
||||
const TourDayText._(this._relative, this.date);
|
||||
|
||||
factory TourDayText.of(DateTime? selected, {DateTime? now}) {
|
||||
final clock = now ?? DateTime.now();
|
||||
final today = DateTime(clock.year, clock.month, clock.day);
|
||||
final day = selected == null
|
||||
? today
|
||||
: DateTime(selected.year, selected.month, selected.day);
|
||||
final relative = switch (day.difference(today).inDays) {
|
||||
0 => 'heute',
|
||||
1 => 'morgen',
|
||||
-1 => 'gestern',
|
||||
_ => null,
|
||||
};
|
||||
return TourDayText._(relative, _format(day));
|
||||
}
|
||||
|
||||
final String? _relative;
|
||||
|
||||
/// Datum als „TT.MM.JJJJ".
|
||||
final String date;
|
||||
|
||||
/// Umstandsangabe: „heute" / „am 22.09.2026".
|
||||
String get when => _relative ?? 'am $date';
|
||||
|
||||
/// Nach „für": „heute" / „den 22.09.2026".
|
||||
String get afterFor => _relative ?? 'den $date';
|
||||
|
||||
static String _format(DateTime d) =>
|
||||
'${d.day.toString().padLeft(2, '0')}.'
|
||||
'${d.month.toString().padLeft(2, '0')}.${d.year}';
|
||||
}
|
||||
@ -11,6 +11,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
||||
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview.dart';
|
||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||
|
||||
/// Inhalt der Phase "Ausliefern". Sortieren und Beladen werden über eigene
|
||||
/// Pages und das Phasen-Routing in `Home` gerendert — diese Page übernimmt
|
||||
@ -77,25 +79,26 @@ class _EmptyTourBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Wenn der ERP-Sync für heute keine Tour gemeldet hat, ist das ein
|
||||
// Wenn der ERP-Sync für den Tag keine Tour gemeldet hat, ist das ein
|
||||
// normaler Zustand — kein Fehler. UX-Hinweis und Pull-to-refresh.
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<TourBloc>().add(const RefreshTour());
|
||||
},
|
||||
child: ListView(
|
||||
children: const [
|
||||
SizedBox(height: 120),
|
||||
Icon(Icons.event_busy, size: 64, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
children: [
|
||||
const SizedBox(height: 120),
|
||||
const Icon(Icons.event_busy, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Text(
|
||||
'Für heute ist keine Tour zugewiesen.\n'
|
||||
'Für ${TourDayText.of(context.watch<TourDateCubit>().state).afterFor} '
|
||||
'ist keine Tour zugewiesen.\n'
|
||||
'Zum Aktualisieren nach unten ziehen.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -14,6 +14,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||
|
||||
/// Page für die erste Phase bei Mehr-Auto-Teams: Auswählen der eigenen
|
||||
/// Lieferungen aus dem gemeinsamen Tour-Pool.
|
||||
@ -388,12 +390,13 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
|
||||
return Scaffold(
|
||||
drawer: const HomeAppDrawer(),
|
||||
appBar: AppBar(title: const Text('Lieferungen auswählen')),
|
||||
body: const Center(
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
'Für heute ist keine Tour zugewiesen.',
|
||||
style: TextStyle(fontSize: 16),
|
||||
'Für ${TourDayText.of(context.watch<TourDateCubit>().state).afterFor} '
|
||||
'ist keine Tour zugewiesen.',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
@ -15,6 +15,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||
|
||||
/// Page für die zweite Phase des Lieferprozesses (Sortieren). Der Fahrer
|
||||
/// legt per Drag&Drop die Reihenfolge fest, ändert lokal so oft er möchte
|
||||
@ -142,25 +144,27 @@ class _DeliverySortPageState extends State<DeliverySortPage> {
|
||||
}
|
||||
|
||||
Widget _emptyState() {
|
||||
final day = TourDayText.of(context.watch<TourDateCubit>().state);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.inbox_outlined, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Keine Lieferungen heute',
|
||||
'Keine Lieferungen ${day.when}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Text(
|
||||
'Für das ausgewählte Fahrzeug sind heute keine Lieferungen geplant.',
|
||||
'Für das ausgewählte Fahrzeug sind ${day.when} keine '
|
||||
'Lieferungen geplant.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Erneut die heutige Tour vom Backend laden. `LoadTour` zeigt
|
||||
// Erneut die Tour des gewählten Tages vom Backend laden. `LoadTour` zeigt
|
||||
// währenddessen den Lade-Branch (Spinner) und landet danach wieder
|
||||
// hier (TourEmpty) oder in der sortierbaren Lieferungsliste.
|
||||
FilledButton.tonalIcon(
|
||||
|
||||
@ -18,6 +18,8 @@ import 'package:hl_lieferservice/feature/loading/presentation/loading_customer_p
|
||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||
|
||||
/// Übersichts-Ansicht für die Beladen-Phase: alle Kunden mit ihren
|
||||
/// Artikeln und Soll/Ist-Mengen.
|
||||
@ -884,6 +886,7 @@ class _EmptyOverview extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final day = TourDayText.of(context.watch<TourDateCubit>().state);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -896,7 +899,7 @@ class _EmptyOverview extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Für heute ist aktuell keine Tour verfügbar.',
|
||||
'Für ${day.afterFor} ist aktuell keine Tour verfügbar.',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
|
||||
@ -432,7 +432,9 @@ class _SelectedDatePill extends StatelessWidget {
|
||||
// Standard-Fenster um heute; falls das aktuell angezeigte Datum (z. B.
|
||||
// ein Dev-Override in der Vergangenheit) außerhalb liegt, Fenster
|
||||
// erweitern, damit initialDate immer im gültigen Bereich bleibt.
|
||||
final firstDefault = today.subtract(const Duration(days: 14));
|
||||
// 90 Tage zurück: vergangene Touren (z. B. zum Nachsehen oder nach
|
||||
// einem Nach-Import) müssen erreichbar sein.
|
||||
final firstDefault = today.subtract(const Duration(days: 90));
|
||||
final lastDefault = today.add(const Duration(days: 60));
|
||||
final firstDate = current.isBefore(firstDefault) ? current : firstDefault;
|
||||
final lastDate = current.isAfter(lastDefault) ? current : lastDefault;
|
||||
|
||||
Reference in New Issue
Block a user