velmart-picker/lib/features/clarification/clarification_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

193 lines
6.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.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 ClarificationScreen extends StatefulWidget {
final int orderId;
const ClarificationScreen({super.key, required this.orderId});
@override
State<ClarificationScreen> createState() => _ClarificationScreenState();
}
class _ClarificationScreenState extends State<ClarificationScreen> {
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; });
}
List<OrderItem> get _clarifyItems {
final result = <OrderItem>[];
for (final seg in (_order?.segments ?? [])) {
result.addAll(seg.items.where((i) => i.type == ItemType.clarify && !i.isCompleted));
}
return result;
}
void _callCustomer() async {
if (_order == null) return;
final uri = Uri(scheme: 'tel', path: _order!.customer.phone);
if (await canLaunchUrl(uri)) launchUrl(uri);
}
@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.clarification),
Expanded(
child: _clarifyItems.isEmpty
? _buildNoClarify()
: _buildClarifyList(),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () => context.push('/orders/${widget.orderId}/slots'),
child: const Text('Далі: Ячейки'),
),
),
),
],
),
);
}
Widget _buildNoClarify() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, size: 64, color: AppColors.success),
SizedBox(height: 16),
Text('Немає товарів для уточнення', style: TextStyle(fontSize: 16, color: AppColors.textSecondary)),
],
),
);
}
Widget _buildClarifyList() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.warning.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.warning.withOpacity(0.3)),
),
child: Row(
children: [
const Icon(Icons.warning_amber, color: AppColors.warning),
const SizedBox(width: 8),
Expanded(
child: Text('Зателефонуйте клієнту для уточнення ${_clarifyItems.length} товарів',
style: const TextStyle(color: AppColors.warning)),
),
TextButton.icon(
onPressed: _callCustomer,
icon: const Icon(Icons.call),
label: const Text('Дзвонити'),
),
],
),
),
const SizedBox(height: 16),
..._clarifyItems.map((item) => _ClarifyItemCard(
item: item,
service: _service,
onUpdated: () => setState(() {}),
)),
],
);
}
}
class _ClarifyItemCard extends StatelessWidget {
final OrderItem item;
final OrderService service;
final VoidCallback onUpdated;
const _ClarifyItemCard({required this.item, required this.service, required this.onUpdated});
@override
Widget build(BuildContext context) {
return Card(
color: AppColors.itemClarify,
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.name, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
Text('${item.planQty} шт • ${item.price} грн',
style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
const SizedBox(height: 8),
if (item.substitutes.isNotEmpty) ...[
const Text('Можливі заміни:', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)),
const SizedBox(height: 4),
...item.substitutes.map((sub) => InkWell(
onTap: () async {
await service.replaceItem(item.id, sub.sku, item.planQty);
item.status = ItemStatus.replaced;
onUpdated();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Icon(Icons.swap_horiz, size: 16, color: AppColors.primary),
const SizedBox(width: 4),
Text(sub.name, style: const TextStyle(fontSize: 13, color: AppColors.primary)),
],
),
),
)),
],
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () async {
await service.markItemNotFound(item.id);
item.status = ItemStatus.notFound;
onUpdated();
},
style: OutlinedButton.styleFrom(foregroundColor: AppColors.error),
child: const Text('Не замінювати'),
),
),
],
),
],
),
),
);
}
}