Phasenbasierte Lieferübersicht + Beladen-Flow, plus Migrationsplan für Rust-Backend
UI-Restructuring: - TabBar in scan_page durch dedizierte Phasen ersetzt: Sortieren / Beladen / Ausliefern - PhaseBloc + PhaseService leiten Phase aus Tour-/Item-States ab - DeliverySelectionPage (ab 2 Autos) und DeliverySortPage als eigene Flows - LoadingOverviewPage / LoadingCustomerPage für die Beladephase - PhaseStepper-Widget im Home für Phasen-Anzeige - Lager-Differenzierung (Standardlager 0 vs. Außenlager) via WarehouseBadge Process-Stubs: - ProcessRepository für Hold/Cancel/Sort/Assign-Flows (stub, bereit für Backend-Anbindung) Doku: - docs/BACKEND_MIGRATION.md: Phasenplan für Umstellung auf das neue Rust-Backend (OpenAPI-Generator, Keycloak OIDC, Clean-Arch-Layering)
This commit is contained in:
178
lib/feature/delivery/overview/widget/sortable_delivery_list.dart
Normal file
178
lib/feature/delivery/overview/widget/sortable_delivery_list.dart
Normal file
@ -0,0 +1,178 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/util.dart';
|
||||
import 'package:hl_lieferservice/model/delivery.dart';
|
||||
|
||||
/// Drag&Drop-Liste der heutigen Lieferungen eines Fahrzeugs.
|
||||
///
|
||||
/// Hält die aktuell sichtbare Reihenfolge in lokalem State, damit das
|
||||
/// Verschieben spürbar unmittelbar wirkt — und gibt jeden Drop zusätzlich
|
||||
/// als [ReorderDeliveryEvent] an den [TourBloc] weiter. Dort übernimmt
|
||||
/// der [ReorderService] die lokale Persistenz. Es findet hier bewusst
|
||||
/// **kein** API-Call statt; der Backend-Sync läuft erst, wenn der Fahrer
|
||||
/// die Reihenfolge in der übergeordneten Page bestätigt.
|
||||
class SortableDeliveryList extends StatefulWidget {
|
||||
const SortableDeliveryList({
|
||||
super.key,
|
||||
required this.selectedCarId,
|
||||
this.controller,
|
||||
});
|
||||
|
||||
final int? selectedCarId;
|
||||
|
||||
/// Optionaler Controller zum Zurücksetzen der Liste durch Eltern-Widgets
|
||||
/// (z. B. Button "Zurücksetzen" in der Page).
|
||||
final SortableDeliveryListController? controller;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SortableDeliveryListState();
|
||||
}
|
||||
|
||||
class _SortableDeliveryListState extends State<SortableDeliveryList> {
|
||||
late List<String> _localSortedList;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_localSortedList = _readSortedListFromBloc();
|
||||
widget.controller?._attach(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SortableDeliveryList oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller?._detach(this);
|
||||
widget.controller?._attach(this);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller?._detach(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<String> _readSortedListFromBloc() {
|
||||
final state = context.read<TourBloc>().state;
|
||||
if (state is TourLoaded) {
|
||||
return [
|
||||
...state.sortingInformation[widget.selectedCarId.toString()] ?? [],
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/// Setzt die Liste auf die natürliche Reihenfolge zurück, in der die
|
||||
/// Lieferungen in der Tour stehen. Wird vom Controller (Button
|
||||
/// "Zurücksetzen") aufgerufen und meldet jeden notwendigen Swap als
|
||||
/// Reorder-Event, damit der lokale Persistenz-State synchron bleibt.
|
||||
///
|
||||
/// Filterlogik (muss konsistent zu `_ensureSortingForCar` im TourBloc
|
||||
/// sein):
|
||||
/// * Ein-Auto-Teams: alle Tour-Lieferungen.
|
||||
/// * Mehr-Auto-Teams: nur Lieferungen, die dem ausgewählten Fahrzeug
|
||||
/// nach der Auswahl bereits zugeordnet sind.
|
||||
void _resetToDefault() {
|
||||
final state = context.read<TourBloc>().state;
|
||||
if (state is! TourLoaded) return;
|
||||
|
||||
final cars = state.tour.driver.cars;
|
||||
final carIdStr = widget.selectedCarId.toString();
|
||||
final List<String> defaultOrder = cars.length >= 2
|
||||
? state.tour.deliveries
|
||||
.where((d) => d.carId?.toString() == carIdStr)
|
||||
.map((d) => d.id)
|
||||
.toList()
|
||||
: state.tour.deliveries.map((d) => d.id).toList();
|
||||
|
||||
setState(() {
|
||||
_localSortedList = [...defaultOrder];
|
||||
});
|
||||
|
||||
final container = {
|
||||
...state.sortingInformation,
|
||||
carIdStr: [...defaultOrder],
|
||||
};
|
||||
context.read<TourBloc>().add(
|
||||
ReplaceSortingEvent(
|
||||
carId: carIdStr,
|
||||
newSortingInformation: container,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TourBloc, TourState>(
|
||||
builder: (context, state) {
|
||||
if (state is! TourLoaded) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return ReorderableListView(
|
||||
buildDefaultDragHandles: true,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
_localSortedList = reorderList(
|
||||
_localSortedList,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
);
|
||||
});
|
||||
|
||||
context.read<TourBloc>().add(
|
||||
ReorderDeliveryEvent(
|
||||
newPosition: newIndex,
|
||||
oldPosition: oldIndex,
|
||||
carId: widget.selectedCarId.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
children: _localSortedList.map((id) {
|
||||
final Delivery delivery = state.tour.deliveries.firstWhere(
|
||||
(delivery) => delivery.id == id,
|
||||
);
|
||||
final int pos = _localSortedList.indexOf(id) + 1;
|
||||
|
||||
return ListTile(
|
||||
key: Key("reorder-item-${delivery.id}"),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
child: Text(
|
||||
"$pos",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(delivery.customer.name),
|
||||
subtitle: Text(
|
||||
delivery.customer.address.toString(),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
trailing: const Icon(Icons.drag_handle),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Schmaler Controller, mit dem Eltern-Widgets die Liste zurücksetzen
|
||||
/// können, ohne den internen State direkt anzufassen.
|
||||
class SortableDeliveryListController {
|
||||
_SortableDeliveryListState? _state;
|
||||
|
||||
void _attach(_SortableDeliveryListState state) => _state = state;
|
||||
void _detach(_SortableDeliveryListState state) {
|
||||
if (_state == state) _state = null;
|
||||
}
|
||||
|
||||
/// Setzt die Liste auf die Default-Reihenfolge (Tour-Reihenfolge) zurück.
|
||||
void resetToDefault() => _state?._resetToDefault();
|
||||
}
|
||||
Reference in New Issue
Block a user