import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/step_indicator.dart'; import '../../data/models/models.dart'; import '../../data/services/order_service.dart'; class PickingScreen extends StatefulWidget { final int orderId; final int segmentId; const PickingScreen({super.key, required this.orderId, required this.segmentId}); @override State createState() => _PickingScreenState(); } class _PickingScreenState extends State { final _service = MockOrderService(); Order? _order; OrderSegment? _segment; int _currentItemIndex = 0; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { final order = await _service.getOrderDetail(widget.orderId); final segment = order.segments.firstWhere((s) => s.id == widget.segmentId); final firstUndone = segment.items.indexWhere((i) => !i.isCompleted); if (mounted) { setState(() { _order = order; _segment = segment; _currentItemIndex = firstUndone >= 0 ? firstUndone : 0; _loading = false; }); } } OrderItem? get _currentItem => _segment?.items.elementAtOrNull(_currentItemIndex); void _onItemDone() { final items = _segment!.items; final next = items.indexWhere((i) => !i.isCompleted, _currentItemIndex + 1); if (next >= 0) { setState(() => _currentItemIndex = next); } else { // Segment done — go back to order detail context.pop(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_order?.orderNumber ?? ''), leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => context.pop()), actions: [ Padding( padding: const EdgeInsets.only(right: 16), child: Center( child: Text( '${_segment?.pickedCount ?? 0}/${_segment?.totalCount ?? 0}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ), ), ], ), body: _loading || _currentItem == null ? const Center(child: CircularProgressIndicator()) : Column( children: [ const StepIndicator(currentStep: BatchStep.picking), Expanded( child: _ItemCard( item: _currentItem!, service: _service, onDone: () { setState(() {}); _onItemDone(); }, ), ), ], ), ); } } class _ItemCard extends StatefulWidget { final OrderItem item; final OrderService service; final VoidCallback onDone; const _ItemCard({required this.item, required this.service, required this.onDone}); @override State<_ItemCard> createState() => _ItemCardState(); } class _ItemCardState extends State<_ItemCard> { final _qtyController = TextEditingController(); bool _loading = false; bool _showSubstitutes = false; @override void initState() { super.initState(); _qtyController.text = widget.item.planQty.toString(); } Color get _cardColor { switch (widget.item.type) { case ItemType.cold: return AppColors.itemCold; case ItemType.frozen: return AppColors.itemFrozen; case ItemType.alcohol: return AppColors.itemAlcohol; case ItemType.clarify: return AppColors.itemClarify; case ItemType.normal: return AppColors.itemNormal; } } String get _typeLabel { switch (widget.item.type) { case ItemType.cold: return '❄️ Холодне'; case ItemType.frozen: return '🧊 Морозилка'; case ItemType.alcohol: return '🍷 Алкоголь'; case ItemType.clarify: return '❓ Уточнення'; case ItemType.normal: return ''; } } Future _markPicked() async { setState(() => _loading = true); final qty = double.tryParse(_qtyController.text) ?? widget.item.planQty; await widget.service.markItemPicked(widget.item.id, qty); widget.item.status = ItemStatus.picked; widget.item.actualQty = qty; if (mounted) setState(() => _loading = false); widget.onDone(); } Future _markNotFound() async { setState(() => _loading = true); await widget.service.markItemNotFound(widget.item.id); widget.item.status = ItemStatus.notFound; if (mounted) setState(() => _loading = false); widget.onDone(); } Future _replaceWith(OrderItemSubstitute sub) async { setState(() => _loading = true); final qty = double.tryParse(_qtyController.text) ?? widget.item.planQty; await widget.service.replaceItem(widget.item.id, sub.sku, qty); widget.item.status = ItemStatus.replaced; if (mounted) setState(() => _loading = false); widget.onDone(); } @override Widget build(BuildContext context) { final item = widget.item; return Container( color: _cardColor, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_typeLabel.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 8), child: Text(_typeLabel, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), ), // Product image + name Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( width: 80, height: 80, color: AppColors.divider, child: item.imageUrl != null ? Image.network(item.imageUrl!, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.image_not_supported, size: 40)) : const Icon(Icons.shopping_bag_outlined, size: 40, color: AppColors.textSecondary), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), maxLines: 3, overflow: TextOverflow.ellipsis), const SizedBox(height: 4), Text(item.category, style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)), const SizedBox(height: 4), Text('${item.price.toStringAsFixed(2)} грн', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.primary)), ], ), ), ], ), const SizedBox(height: 20), // Plan / Actual Row( children: [ Expanded( child: Column( children: [ const Text('Потрібно', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), const SizedBox(height: 4), Text('${item.planQty}', style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: AppColors.primary)), ], ), ), const VerticalDivider(width: 1), Expanded( child: Column( children: [ const Text('Зібрано', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), const SizedBox(height: 4), SizedBox( width: 100, child: TextField( controller: _qtyController, keyboardType: const TextInputType.numberWithOptions(decimal: true), textAlign: TextAlign.center, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold), decoration: const InputDecoration(border: UnderlineInputBorder(), contentPadding: EdgeInsets.zero), ), ), ], ), ), ], ), const SizedBox(height: 24), // Actions ElevatedButton.icon( onPressed: _loading ? null : _markPicked, icon: const Icon(Icons.check), label: const Text('Зібрано'), ), const SizedBox(height: 10), OutlinedButton.icon( onPressed: _loading ? null : () => setState(() => _showSubstitutes = !_showSubstitutes), icon: const Icon(Icons.swap_horiz), label: const Text('Замінити'), style: OutlinedButton.styleFrom( minimumSize: const Size(double.infinity, 52), side: const BorderSide(color: AppColors.warning), foregroundColor: AppColors.warning, ), ), const SizedBox(height: 10), TextButton.icon( onPressed: _loading ? null : _markNotFound, icon: const Icon(Icons.remove_circle_outline, color: AppColors.error), label: const Text('Немає товару', style: TextStyle(color: AppColors.error)), style: TextButton.styleFrom(minimumSize: const Size(double.infinity, 52)), ), // Substitutes if (_showSubstitutes && item.substitutes.isNotEmpty) ...[ const Divider(), const Text('Замінити на:', style: TextStyle(fontWeight: FontWeight.w600)), const SizedBox(height: 8), ...item.substitutes.map((sub) => Card( child: ListTile( title: Text(sub.name), subtitle: Text(sub.sku), trailing: const Icon(Icons.arrow_forward_ios, size: 16), onTap: _loading ? null : () => _replaceWith(sub), ), )), ], if (_showSubstitutes && item.substitutes.isEmpty) const Padding( padding: EdgeInsets.all(8), child: Text('Немає доступних замін', style: TextStyle(color: AppColors.textSecondary)), ), ], ), ), ); } }