import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/format.dart'; import '../../core/models.dart'; import '../../core/providers.dart'; import '../../core/theme.dart'; import '../../widgets/ui.dart'; class OrderDetailScreen extends ConsumerStatefulWidget { const OrderDetailScreen({super.key, required this.orderId}); final int orderId; @override ConsumerState createState() => _OrderDetailScreenState(); } class _OrderDetailScreenState extends ConsumerState { bool _full = false; bool _busy = false; Future _run(Future Function() fn, {String? done}) async { if (_busy) return; setState(() => _busy = true); try { await mutate(ref, fn); if (mounted && done != null) toast(context, done); } catch (e) { if (mounted) toast(context, errText(e)); } finally { if (mounted) setState(() => _busy = false); } } Future _setQty(Order o, EndingQty l, int qty) { if (qty < 1 || qty > 9999) return Future.value(); return _run(() => ref.read(apiProvider).setLineQty(o.id, l.ending, qty)); } Future _remove(Order o, EndingQty l) async { final last = o.lines.length == 1; final ok = await confirm( context, title: last ? 'Remove last ending?' : 'Remove ${l.ending}?', body: last ? 'This is the only ending in this order. Removing it will delete the order.' : '${plural(l.qty, 'ticket')} will be removed from this order and the shop totals.', confirmLabel: 'Remove', ); if (!ok || !mounted) return; await _run( () => ref.read(apiProvider).removeLine(o.id, l.ending), done: last ? 'Order deleted' : 'Ending removed', ); if (last && mounted) context.pop(); } Future _delete(Order o) async { final ok = await confirm( context, title: 'Delete this order?', body: '${plural(o.tickets, 'ticket')} will be removed from ${o.shop.name}’s totals and results. This action cannot be undone.', confirmLabel: 'Delete order', ); if (!ok || !mounted) return; await _run( () => ref.read(apiProvider).cancelOrder(o.id), done: 'Order deleted', ); if (mounted) context.pop(); } @override Widget build(BuildContext context) { final order = ref.watch(orderProvider(widget.orderId)); return Scaffold( body: SafeArea( child: asyncView( order, (o) => Column( children: [ const ScreenHeader(title: 'Order details', back: true), Expanded(child: _body(o)), ], ), retry: () => ref.invalidate(orderProvider(widget.orderId)), ), ), ); } Widget _body(Order o) { final out = o.lines.any((l) => l.wins) || o.drawDate.isBefore(ref.read(apiProvider).now); final lines = (o.rawText ?? '').split('\n'); final shown = _full ? lines : lines.take(4).toList(); return PaneList( top: 0, breakpoint: 820, startFlex: 1, endFlex: 1, gap: S.s24, start: [ SrsCard( padding: const EdgeInsets.all(S.s16), child: Row( children: [ CodeTile(o.shop.code, size: 52, dark: true), const SizedBox(width: S.s14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( '${inr(o.tickets)} tickets', maxLines: 1, overflow: TextOverflow.ellipsis, style: ts(18, w: w7), ), ), if (o.edited) ...[ const SizedBox(width: S.s8), const Pill('Edited', bg: C.goldTint, fg: C.goldText), ], ], ), const SizedBox(height: S.s4), Text( '${o.shop.code} · ${time12(o.time)} · ${o.isPhoto ? 'Photo' : 'Text'}', style: ts(12, c: C.muted), ), const SizedBox(height: S.s2), Text(dateLong(o.drawDate), style: ts(12, c: C.muted)), ], ), ), if (o.prize > 0) Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( rupees(o.prize), style: ts(16, w: w7, c: C.green), ), Text('won', style: ts(11, c: C.muted)), ], ), ], ), ), const SectionTitle('Original message'), if (o.isPhoto) Container( height: 150, alignment: Alignment.center, decoration: BoxDecoration( color: C.chip, borderRadius: BorderRadius.circular(R.card), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.photo_outlined, size: 36, color: C.navy), const SizedBox(height: S.s8), Text( 'Handwritten slip', style: ts(13, w: w6, c: C.navy), ), const SizedBox(height: S.s2), Text('Received via WhatsApp', style: ts(12, c: C.muted)), ], ), ) else SrsCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( shown.join('\n'), style: ts(13, w: w5, c: C.ink, h: 1.6), ), if (lines.length > 4) ...[ const SizedBox(height: S.s10), Pressable( onTap: () => setState(() => _full = !_full), radius: R.sm, child: Text( _full ? 'Show less' : 'Show full message', style: ts(13, w: w6, c: C.navy), ), ), ], ], ), ), ], end: [ SectionTitle( 'Endings', trailing: '${plural(o.lines.length, 'ending')} · ${plural(o.tickets, 'ticket')}', ), for (final l in o.lines) ...[ _LineRow( line: l, busy: _busy, onMinus: () => _setQty(o, l, l.qty - 1), onPlus: () => _setQty(o, l, l.qty + 1), onRemove: () => _remove(o, l), ), const SizedBox(height: S.s8), ], const SizedBox(height: S.s6), Padding( padding: const EdgeInsets.symmetric(horizontal: S.s4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.info_outline_rounded, size: 15, color: C.muted), const SizedBox(width: S.s8), Expanded( child: Text( out ? 'Changes are reflected in shop totals and results.' : 'Changes are reflected in shop totals and, once published, in results.', style: ts(12, c: C.muted, h: 1.45), ), ), ], ), ), const SizedBox(height: S.s22), BigButton( 'Delete order', outline: true, danger: true, icon: Icons.delete_outline_rounded, onPressed: _busy ? null : () => _delete(o), ), ], ); } } class _LineRow extends StatelessWidget { const _LineRow({ required this.line, required this.busy, required this.onMinus, required this.onPlus, required this.onRemove, }); final EndingQty line; final bool busy; final VoidCallback onMinus, onPlus, onRemove; @override Widget build(BuildContext context) { final win = line.wins; return Container( padding: const EdgeInsets.fromLTRB(S.s14, S.s10, S.s8, S.s10), decoration: BoxDecoration( color: win ? C.greenTint : Colors.white, borderRadius: BorderRadius.circular(R.button), border: Border.all(color: win ? C.green.withAlpha(110) : C.border), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( line.ending, style: ts(18, w: w7, c: win ? C.green : C.ink, ls: 1), ), if (win) ...[ const SizedBox(height: S.s2), Text( '${line.tierText} · ${rupees(line.prize)}', style: ts(12, w: w6, c: C.green), ), ], ], ), ), _step( Icons.remove_rounded, 'Fewer tickets', line.qty > 1 && !busy ? onMinus : null, ), SizedBox( width: 44, child: Center( child: Text('${line.qty}', style: ts(16, w: w7)), ), ), _step( Icons.add_rounded, 'More tickets', line.qty < 9999 && !busy ? onPlus : null, ), const SizedBox(width: S.s6), IconButton( onPressed: busy ? null : onRemove, icon: const Icon( Icons.delete_outline_rounded, size: 21, color: C.red, ), tooltip: 'Remove ending', ), ], ), ); } Widget _step(IconData icon, String label, VoidCallback? tap) => Pressable( onTap: tap, label: label, radius: 18, builder: (_, hover) => AnimatedContainer( duration: const Duration(milliseconds: 140), width: 36, height: 36, decoration: BoxDecoration( color: tap == null ? C.disabled : (hover ? C.border2 : C.chip), shape: BoxShape.circle, ), child: Icon(icon, size: 18, color: tap == null ? C.placeholder : C.navy), ), ); }