import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../app/router.dart'; import '../models/closed_tab.dart'; import '../models/closed_tab_item.dart'; import '../viewmodels/history_view_model.dart'; class HistoryScreenView extends StatefulWidget { const HistoryScreenView({super.key}); @override State createState() => _HistoryScreenViewState(); } class _HistoryScreenViewState extends State with RouteAware { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().ensureLoaded(); }); } @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute); } @override void didPopNext() { // Called when you come back to this screen context.read().load(); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } @override Widget build(BuildContext context) { final viewModel = context.watch(); return Scaffold( appBar: AppBar( title: Row( children: [ IconButton(onPressed: (){ context.go('/bar'); }, icon: const Icon(Icons.arrow_back)), const SizedBox(width: 5), const Text('Tab History'), ], ), actions: [ IconButton( tooltip: 'Refresh', onPressed: viewModel.load, icon: const Icon(Icons.refresh_rounded), ), const SizedBox(width: 8), ], ), body: Builder( builder: (context) { if (viewModel.isLoading && !viewModel.hasLoaded) { return const Center( child: CircularProgressIndicator(strokeWidth: 2.5), ); } if (viewModel.errorMessage != null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline_rounded, size: 40, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 12), Text( viewModel.errorMessage!, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyLarge, ), ], ), ), ); } return Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), child: TextField( onChanged: viewModel.search, decoration: const InputDecoration( hintText: 'Search by name…', prefixIcon: Icon(Icons.search_rounded, size: 20), isDense: true, ), ), ), Expanded( child: viewModel.closedTabs.isEmpty ? _EmptyState() : ListView.separated( padding: const EdgeInsets.fromLTRB(20, 8, 20, 20), itemCount: viewModel.closedTabs.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (context, index) { final closedTab = viewModel.closedTabs[index]; return _ClosedTabCard(closedTab: closedTab); }, ), ), ], ); }, ), ); } } class _EmptyState extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( shape: BoxShape.circle, color: scheme.onSurface.withValues(alpha: 0.05), ), child: Icon( Icons.history_rounded, size: 40, color: scheme.onSurface.withValues(alpha: 0.3), ), ), const SizedBox(height: 16), Text( 'No closed tabs yet', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 4), Text( 'Tabs you close will show up here.', style: Theme.of(context).textTheme.bodyMedium, ), ], ), ); } } class _ClosedTabCard extends StatefulWidget { final ClosedTab closedTab; const _ClosedTabCard({required this.closedTab}); @override State<_ClosedTabCard> createState() => _ClosedTabCardState(); } class _ClosedTabCardState extends State<_ClosedTabCard> { bool _expanded = false; @override Widget build(BuildContext context) { final closedTab = widget.closedTab; final dateFormat = DateFormat('MMM d, y · h:mm a'); final scheme = Theme.of(context).colorScheme; return Container( decoration: BoxDecoration( color: scheme.onSurface.withValues(alpha: 0.04), borderRadius: BorderRadius.circular(14), border: Border.all(color: scheme.onSurface.withValues(alpha: 0.06)), ), clipBehavior: Clip.antiAlias, child: Material( color: Colors.transparent, child: InkWell( onTap: () => setState(() => _expanded = !_expanded), enableFeedback: true, splashFactory: NoSplash.splashFactory, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( closedTab.customerName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: scheme.onSurface, ), ), const SizedBox(height: 2), Text( '${dateFormat.format(closedTab.closedAt)} · ${closedTab.itemCount} items', style: Theme.of(context).textTheme.bodySmall, ), ], ), ), const SizedBox(width: 8), Text( closedTab.formattedTotal, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: scheme.onSurface, ), ), const SizedBox(width: 4), Icon( _expanded ? Icons.expand_less_rounded : Icons.expand_more_rounded, color: scheme.onSurface.withValues(alpha: 0.5), ), ], ), ), AnimatedCrossFade( duration: const Duration(milliseconds: 150), crossFadeState: _expanded ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 14), child: Column( children: [ const Divider(height: 1), const SizedBox(height: 8), ...closedTab.items.map( (item) => _ClosedTabItemRow(item: item), ), ], ), ), secondChild: const SizedBox(width: double.infinity), ), ], ), ), ), ); } } class _ClosedTabItemRow extends StatelessWidget { final ClosedTabItem item; const _ClosedTabItemRow({required this.item}); @override Widget build(BuildContext context) { final unitPrice = NumberFormat.simpleCurrency().format(item.unitPriceInCents / 100); final lineTotal = NumberFormat.simpleCurrency().format(item.lineTotalInCents / 100); final scheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Expanded( child: Text( item.productName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface), ), ), Text( '${item.quantity} × $unitPrice', style: Theme.of(context).textTheme.bodySmall, ), const SizedBox(width: 12), SizedBox( width: 64, child: Text( lineTotal, textAlign: TextAlign.end, style: TextStyle(fontWeight: FontWeight.w700, color: scheme.onSurface), ), ), ], ), ); } }