velmart-picker/lib/features/sorting/sorting_screen.dart
Fibe Agent 5ba2691eac feat: initial Velmart Picker Flutter app
- All 5 batch steps: Picking, Sorting, Clarification, Slots, Handoff
- go_router navigation with step indicator
- graphql_flutter client wired up (endpoint via env var)
- Mock data layer swappable with real GraphQL service
- Item types: normal, cold, frozen, alcohol, clarify
- Storage slot assignment (cell/freezer/fridge)
2026-04-23 21:43:07 +00:00

147 lines
5.0 KiB
Dart

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 SortingScreen extends StatefulWidget {
final int orderId;
const SortingScreen({super.key, required this.orderId});
@override
State<SortingScreen> createState() => _SortingScreenState();
}
class _SortingScreenState extends State<SortingScreen> {
final _service = MockOrderService();
Order? _order;
bool _loading = true;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
final order = await _service.getOrderDetail(widget.orderId);
if (mounted) setState(() { _order = order; _loading = false; });
}
/// Groups items by their storage type for sorting.
Map<ItemType, List<OrderItem>> get _grouped {
final result = <ItemType, List<OrderItem>>{};
for (final seg in (_order?.segments ?? [])) {
for (final item in seg.items) {
result.putIfAbsent(item.type, () => []).add(item);
}
}
return result;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_order?.orderNumber ?? ''),
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => context.pop()),
),
body: _loading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
const StepIndicator(currentStep: BatchStep.sorting),
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text(
'Розкладіть товари по типах',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
const Text(
'Перевірте та розсортуйте зібрані товари',
style: TextStyle(color: AppColors.textSecondary),
),
const SizedBox(height: 16),
..._grouped.entries.map((entry) => _SortGroup(type: entry.key, items: entry.value)),
],
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () => context.push('/orders/${widget.orderId}/clarification'),
child: const Text('Далі: Уточнення'),
),
),
),
],
),
);
}
}
class _SortGroup extends StatelessWidget {
final ItemType type;
final List<OrderItem> items;
const _SortGroup({required this.type, required this.items});
String get _title {
switch (type) {
case ItemType.cold: return '❄️ Холодні товари';
case ItemType.frozen: return '🧊 Заморожені';
case ItemType.alcohol: return '🍷 Алкоголь';
case ItemType.clarify: return '❓ Товари для уточнення';
case ItemType.normal: return '📦 Звичайні';
}
}
Color get _color {
switch (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 Colors.white;
}
}
@override
Widget build(BuildContext context) {
final confirmedItems = items.where((i) => i.isCompleted).toList();
if (confirmedItems.isEmpty) return const SizedBox.shrink();
return Card(
color: _color,
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
...confirmedItems.map((item) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Icon(Icons.check_circle_outline, size: 18, color: AppColors.success),
const SizedBox(width: 8),
Expanded(child: Text(item.name, style: const TextStyle(fontSize: 14))),
Text('${item.actualQty}/${item.planQty}',
style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
],
),
)),
],
),
),
);
}
}