- 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)
208 lines
6.8 KiB
Dart
208 lines
6.8 KiB
Dart
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/order_progress_bar.dart';
|
|
import '../../core/widgets/step_indicator.dart';
|
|
import '../../data/models/models.dart';
|
|
import '../../data/services/order_service.dart';
|
|
|
|
class OrderDetailScreen extends StatefulWidget {
|
|
final int orderId;
|
|
const OrderDetailScreen({super.key, required this.orderId});
|
|
|
|
@override
|
|
State<OrderDetailScreen> createState() => _OrderDetailScreenState();
|
|
}
|
|
|
|
class _OrderDetailScreenState extends State<OrderDetailScreen> {
|
|
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; });
|
|
}
|
|
|
|
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())
|
|
: _order == null
|
|
? const Center(child: Text('Замовлення не знайдено'))
|
|
: _buildBody(),
|
|
);
|
|
}
|
|
|
|
Widget _buildBody() {
|
|
final order = _order!;
|
|
return Column(
|
|
children: [
|
|
const StepIndicator(currentStep: BatchStep.picking),
|
|
// Customer info bar
|
|
Container(
|
|
color: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(order.customer.name,
|
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
if (order.customer.comment != null)
|
|
Text(order.customer.comment!,
|
|
style: const TextStyle(fontSize: 12, color: AppColors.warning),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.call, color: AppColors.primary),
|
|
onPressed: _callCustomer,
|
|
tooltip: 'Зателефонувати',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
// Overall progress
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
child: OrderProgressBar(picked: order.pickedItems, total: order.totalItems),
|
|
),
|
|
const Divider(height: 1),
|
|
// Segments list
|
|
Expanded(
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
itemCount: order.segments.length,
|
|
itemBuilder: (context, i) {
|
|
final segment = order.segments[i];
|
|
return _SegmentCard(
|
|
segment: segment,
|
|
onTap: () => context.push('/orders/${order.id}/picking/${segment.id}'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
// Bottom action
|
|
SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: ElevatedButton(
|
|
onPressed: order.isFullyPicked
|
|
? () => context.push('/orders/${order.id}/sorting')
|
|
: null,
|
|
child: const Text('Далі: Сортування'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SegmentCard extends StatelessWidget {
|
|
final OrderSegment segment;
|
|
final VoidCallback onTap;
|
|
|
|
const _SegmentCard({required this.segment, required this.onTap});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(12),
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(segment.name,
|
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
Text('Зона ${segment.zone}',
|
|
style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
|
|
],
|
|
),
|
|
),
|
|
if (segment.isCompleted)
|
|
const Icon(Icons.check_circle, color: AppColors.success)
|
|
else
|
|
const Icon(Icons.chevron_right, color: AppColors.textSecondary),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
OrderProgressBar(
|
|
picked: segment.pickedCount,
|
|
total: segment.totalCount,
|
|
showLabel: true,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 6,
|
|
children: _buildTypeTags(segment),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildTypeTags(OrderSegment segment) {
|
|
final types = segment.items.map((i) => i.type).toSet();
|
|
return types.map((type) {
|
|
switch (type) {
|
|
case ItemType.cold:
|
|
return _tag('Холодне', AppColors.info);
|
|
case ItemType.frozen:
|
|
return _tag('Морозилка', Colors.indigo);
|
|
case ItemType.alcohol:
|
|
return _tag('Алкоголь', AppColors.warning);
|
|
case ItemType.clarify:
|
|
return _tag('Уточнення', AppColors.accent);
|
|
case ItemType.normal:
|
|
return const SizedBox.shrink();
|
|
}
|
|
}).toList();
|
|
}
|
|
|
|
Widget _tag(String label, Color color) => Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: color.withOpacity(0.4)),
|
|
),
|
|
child: Text(label, style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.w600)),
|
|
);
|
|
}
|