import 'package:flutter/material.dart'; import 'l10n/app_localizations.dart'; import 'l10n/localization_helpers.dart'; import 'models/delivery_station.dart'; import 'models/job.dart'; import 'services/database_service.dart'; import 'task_view.dart'; import 'widgets/offline_banner.dart'; @visibleForTesting Color? deliveryStationCardBackgroundColor( DeliveryStation station, Map taskStatuses, ) { if (station.tasks.isEmpty) { return null; } final isCompleted = station.tasks.every( (task) => taskStatuses[task.id] ?? task.completed, ); return isCompleted ? Colors.green[50] : null; } class CargoItemsView extends StatefulWidget { final Job job; const CargoItemsView({super.key, required this.job}); @override State createState() => _CargoItemsViewState(); } class _CargoItemsViewState extends State { final DatabaseService _databaseService = DatabaseService(); Map _taskStatuses = const {}; @override void initState() { super.initState(); _loadLocalTaskStatuses(); } Future _loadLocalTaskStatuses() async { final map = await _databaseService.loadAllTaskStatuses(); if (!mounted) return; setState(() { _taskStatuses = map; }); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( title: Text(widget.job.jobNumber), backgroundColor: Colors.deepPurple[100], leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { Navigator.of(context).pop(); }, ), actions: [ IconButton( icon: const Icon(Icons.chat), onPressed: () { Navigator.of(context).pushNamed('/chats'); }, tooltip: AppLocalizations.of(context).openChat, ), ], ), body: Column( children: [ OfflineBanner(), // Main content area Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Job summary card Card( margin: const EdgeInsets.only(bottom: 16), elevation: 2, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.job.jobNumber.isNotEmpty ? widget.job.jobNumber : localizeKnownText(context, widget.job.title), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), if (widget.job.customerSelection.isNotEmpty) ...[ const SizedBox(height: 4), Text( widget.job.customerSelection, style: TextStyle( fontSize: 14, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], const SizedBox(height: 8), Row( children: [ Icon( Icons.arrow_upward, size: 16, color: Colors.green[600], ), const SizedBox(width: 4), Text( widget.job.pickupCity, style: TextStyle( fontSize: 12, color: Colors.grey[700], ), ), const SizedBox(width: 8), Icon( Icons.arrow_forward, size: 16, color: Colors.grey[600], ), const SizedBox(width: 8), Icon( Icons.arrow_downward, size: 16, color: Colors.blue[600], ), const SizedBox(width: 4), Text( widget.job.deliveryCitiesDisplay.isNotEmpty ? widget.job.deliveryCitiesDisplay : widget.job.deliveryCity, style: TextStyle( fontSize: 12, color: Colors.grey[700], ), ), ], ), ], ), ), ), // Delivery stations section header Row( children: [ Icon( Icons.local_shipping_outlined, size: 24, color: Colors.deepPurple[600], ), const SizedBox(width: 8), Text( l10n.deliveryStationsCount(_deliveryStations.length), style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 16), Expanded(child: _buildDeliveryStationsList()), ], ), ), ), ], ), ); } List get _deliveryStations { if (widget.job.deliveryStations.isNotEmpty) { return widget.job.deliveryStations; } return [ DeliveryStation( stationOrder: 0, company: widget.job.deliveryCompany, salutation: widget.job.deliverySalutation, firstName: widget.job.deliveryFirstName, lastName: widget.job.deliveryLastName, phone: widget.job.deliveryPhone, street: widget.job.deliveryStreet, houseNumber: widget.job.deliveryHouseNumber, addressAddition: widget.job.deliveryAddressAddition, zip: widget.job.deliveryZip, city: widget.job.deliveryCity, deliveryDate: widget.job.deliveryDate, deliveryTime: widget.job.deliveryTime, tasks: widget.job.tasks, ), ]; } Widget _buildDeliveryStationsList() { if (_deliveryStations.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.local_shipping_outlined, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( AppLocalizations.of(context).noDeliveryStations, style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), const SizedBox(height: 8), Text( AppLocalizations.of(context).noDeliveryStationsMessage, style: TextStyle(fontSize: 14, color: Colors.grey[500]), textAlign: TextAlign.center, ), ], ), ); } return ListView.builder( itemCount: _deliveryStations.length, itemBuilder: (context, index) { final station = _deliveryStations[index]; return _buildDeliveryStationCard(station); }, ); } Widget _buildDeliveryStationCard(DeliveryStation station) { final backgroundColor = deliveryStationCardBackgroundColor( station, _taskStatuses, ); final title = station.displayName.isNotEmpty ? station.displayName : station.company; final subtitle = station.company.isNotEmpty && station.company != title ? station.company : null; final l10n = AppLocalizations.of(context); final addressLines = [ [ station.street, station.houseNumber, ].where((part) => part.trim().isNotEmpty).join(' '), if (station.addressAddition.trim().isNotEmpty) station.addressAddition, [ station.zip, station.city, ].where((part) => part.trim().isNotEmpty).join(' '), ].where((line) => line.trim().isNotEmpty).toList(); return Card( color: backgroundColor, margin: const EdgeInsets.symmetric(horizontal: 0, vertical: 8), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.grey[300]!, width: 1), ), clipBehavior: Clip.antiAlias, child: InkWell( onTap: () async { await Navigator.of(context).push( MaterialPageRoute( builder: (context) => TaskView( job: widget.job, stationOrder: station.stationOrder, stationTitle: station.displayName.isNotEmpty ? station.displayName : l10n.stationNumber(station.stationOrder + 1), ), ), ); await _loadLocalTaskStatuses(); }, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.deepPurple[100], borderRadius: BorderRadius.circular(12), ), child: Text( l10n.stationNumber(station.stationOrder + 1), style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.deepPurple[700], ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title.isNotEmpty ? localizeKnownText(context, title) : l10n.unnamedStation, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), if (subtitle != null) ...[ const SizedBox(height: 2), Text( subtitle, style: TextStyle( fontSize: 13, color: Colors.grey[700], ), ), ], ], ), ), ], ), const SizedBox(height: 12), _buildDetailItem( Icons.location_on_outlined, AppLocalizations.of(context).location, addressLines.join('\n'), Colors.blue, ), if (station.phone.trim().isNotEmpty) ...[ const SizedBox(height: 12), _buildDetailItem( Icons.phone_outlined, l10n.phone, station.phone, Colors.green, ), ], if (station.deliveryDate.trim().isNotEmpty || station.deliveryTime.trim().isNotEmpty) ...[ const SizedBox(height: 12), _buildDetailItem( Icons.schedule, AppLocalizations.of(context).delivery, [ station.deliveryDate, station.deliveryTime, ].where((part) => part.trim().isNotEmpty).join(' '), Colors.orange, ), ], const SizedBox(height: 12), _buildDetailItem( Icons.task_alt, AppLocalizations.of(context).tasks, '${station.tasks.length}', Colors.deepPurple, ), ], ), ), ), ); } Widget _buildDetailItem( IconData icon, String label, String value, Color color, ) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 16, color: color.withValues(alpha: 0.8)), const SizedBox(width: 4), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), ], ), ); } }