import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/domain/entity/delivery.dart'; import 'package:hl_lieferservice/domain/entity/delivery_service_value.dart'; import 'package:hl_lieferservice/domain/entity/service.dart'; import 'package:hl_lieferservice/domain/entity/tour_details.dart'; import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart'; import 'package:hl_lieferservice/feature/car_selection/bloc/state.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'; /// Step 4 — Services (früher „Lieferoptionen"). /// /// Rendert die aktiven Service-Definitionen (`TourDetails.services`, /// admin-konfigurierbar) und lässt den Fahrer sie pro Lieferung auswählen: /// `boolean` → Checkbox, `numeric` → Zahlenfeld mit min/max. Werte landen /// über den `TourBloc` (`SetDeliveryServiceValue`/`RemoveDeliveryServiceValue`) /// im Backend. Setzen nur bei aktiver Lieferung. class StepServices extends StatelessWidget { const StepServices({super.key, required this.delivery, required this.details}); final Delivery delivery; final TourDetails details; String _actorCarId(BuildContext context) { final state = context.read().state; if (state is CarSelectComplete) return state.selectedCar.id; return '00000000-0000-0000-0000-000000000000'; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final active = delivery.state == DeliveryState.active; return BlocBuilder( buildWhen: (a, b) { if (a is! TourLoaded || b is! TourLoaded) return true; return a.details.services != b.details.services || a.details.serviceValuesByDeliveryId[delivery.id] != b.details.serviceValuesByDeliveryId[delivery.id]; }, builder: (context, state) { final d = state is TourLoaded ? state.details : details; final services = d.services; if (services.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.construction_outlined, size: 56, color: theme.colorScheme.onSurfaceVariant), const SizedBox(height: 12), Text('Keine Services konfiguriert', style: theme.textTheme.titleMedium), const SizedBox(height: 4), Text( 'Ein Administrator kann Services anlegen.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), ); } // Wie im alten `delivery_options.dart`: zwei Kategorien — // „Auswählbare Optionen" (Checkboxen) und „Zahlenwerte". final bools = services.where((s) => s.kind == ServiceKind.boolean).toList(); final numerics = services.where((s) => s.kind == ServiceKind.numeric).toList(); _ServiceTile tileFor(Service service) => _ServiceTile( service: service, value: d.serviceValueOf(delivery.id, service.id), enabled: active, onSetBool: (v) => context.read().add( SetDeliveryServiceValue( deliveryId: delivery.id, serviceId: service.id, boolValue: v, actorCarId: _actorCarId(context), ), ), onSetNumeric: (n) => context.read().add( SetDeliveryServiceValue( deliveryId: delivery.id, serviceId: service.id, numericValue: n, actorCarId: _actorCarId(context), ), ), onClear: () => context.read().add( RemoveDeliveryServiceValue( deliveryId: delivery.id, serviceId: service.id, ), ), ); Widget sectionCard(List items) => Card( margin: EdgeInsets.zero, child: Column( children: [ for (int i = 0; i < items.length; i++) ...[ tileFor(items[i]), if (i < items.length - 1) const Divider(height: 1, indent: 16, endIndent: 16), ], ], ), ); return ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), children: [ if (!active) Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ Icon(Icons.lock_outline, size: 16, color: theme.colorScheme.onSurfaceVariant), const SizedBox(width: 6), Text( 'Nur bei aktiver Lieferung änderbar.', style: TextStyle( fontSize: 12, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), if (bools.isNotEmpty) ...[ const _CategoryHeader( icon: Icons.check_box_outlined, text: 'Auswählbare Optionen', ), const SizedBox(height: 8), sectionCard(bools), ], if (bools.isNotEmpty && numerics.isNotEmpty) const SizedBox(height: 24), if (numerics.isNotEmpty) ...[ const _CategoryHeader( icon: Icons.pin_outlined, text: 'Zahlenwerte', ), const SizedBox(height: 8), sectionCard(numerics), ], ], ); }, ); } } /// Kategorie-Überschrift (Icon + Titel) — trennt Checkboxen von Zahlenwerten. class _CategoryHeader extends StatelessWidget { const _CategoryHeader({required this.icon, required this.text}); final IconData icon; final String text; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Row( children: [ Icon(icon, size: 18, color: theme.colorScheme.primary), const SizedBox(width: 8), Text( text, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), ], ); } } /// Eine Service-Zeile — Checkbox (boolean) oder Zahlenfeld (numeric). class _ServiceTile extends StatelessWidget { const _ServiceTile({ required this.service, required this.value, required this.enabled, required this.onSetBool, required this.onSetNumeric, required this.onClear, }); final Service service; final DeliveryServiceValue? value; final bool enabled; final ValueChanged onSetBool; final ValueChanged onSetNumeric; final VoidCallback onClear; @override Widget build(BuildContext context) { switch (service.kind) { case ServiceKind.boolean: return CheckboxListTile( value: value?.boolValue ?? false, onChanged: enabled ? (v) => onSetBool(v ?? false) : null, title: Text(service.name), controlAffinity: ListTileControlAffinity.leading, dense: true, ); case ServiceKind.numeric: return _NumericServiceField( key: ValueKey(service.id), service: service, initial: value?.numericValue, enabled: enabled, onSetNumeric: onSetNumeric, onClear: onClear, ); } } } /// Zahlenfeld eines numerischen Service — eigener Controller, persistiert beim /// Verlassen/Submit, klemmt auf [min,max]. Leeres Feld → Wert entfernen. class _NumericServiceField extends StatefulWidget { const _NumericServiceField({ super.key, required this.service, required this.initial, required this.enabled, required this.onSetNumeric, required this.onClear, }); final Service service; final int? initial; final bool enabled; final ValueChanged onSetNumeric; final VoidCallback onClear; @override State<_NumericServiceField> createState() => _NumericServiceFieldState(); } class _NumericServiceFieldState extends State<_NumericServiceField> { late final TextEditingController _controller = TextEditingController(text: widget.initial?.toString() ?? ''); @override void didUpdateWidget(_NumericServiceField old) { super.didUpdateWidget(old); // Server-Stand übernehmen, wenn er sich geändert hat (z. B. nach // Reconcile) und sich vom angezeigten Text unterscheidet. final incoming = widget.initial?.toString() ?? ''; if (old.initial != widget.initial && _controller.text != incoming) { _controller.text = incoming; } } @override void dispose() { _controller.dispose(); super.dispose(); } void _commit() { final raw = _controller.text.trim(); if (raw.isEmpty) { widget.onClear(); return; } final parsed = int.tryParse(raw); if (parsed == null) { _controller.text = widget.initial?.toString() ?? ''; return; } var n = parsed; final min = widget.service.minValue; final max = widget.service.maxValue; if (min != null && n < min) n = min; if (max != null && n > max) n = max; if (n.toString() != _controller.text) { _controller.text = n.toString(); } widget.onSetNumeric(n); } @override Widget build(BuildContext context) { final s = widget.service; return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: TextField( controller: _controller, enabled: widget.enabled, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], textInputAction: TextInputAction.done, decoration: InputDecoration( labelText: s.name, border: const OutlineInputBorder(), isDense: true, ), onTapOutside: (_) { FocusScope.of(context).unfocus(); _commit(); }, onSubmitted: (_) => _commit(), ), ); } }