import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:kooltab2/models/closed_tab.dart'; import 'package:kooltab2/models/closed_tab_item.dart'; import 'package:kooltab2/models/payment_method.dart'; import '../../l10n/app_localizations.dart'; import '../../utils/history_grouping.dart'; class ClosedTabCard extends StatefulWidget { final ClosedTab closedTab; const ClosedTabCard({super.key, required this.closedTab}); @override State createState() => _ClosedTabCardState(); } class _ClosedTabCardState extends State { bool _expanded = false; IconData _paymentIcon(PaymentMethod method) { return switch (method) { PaymentMethod.payconiq => Icons.qr_code_rounded, PaymentMethod.cash => Icons.money_rounded, }; } @override Widget build(BuildContext context) { final closedTab = widget.closedTab; final l10n = AppLocalizations.of(context); final locale = Localizations.localeOf(context).toLanguageTag(); final dateFormat = DateFormat.yMMMd(locale).add_jm(); final dayGroups = groupClosedTabItemsByDay( closedTab.items, fallbackDate: closedTab.closedAt, ); 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Material( color: Colors.transparent, child: InkWell( onTap: () => setState(() => _expanded = !_expanded), enableFeedback: true, splashFactory: NoSplash.splashFactory, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( closedTab.customerName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: scheme.onSurface, ), ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), decoration: BoxDecoration( color: scheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(999), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _paymentIcon(closedTab.paymentMethod), size: 13, color: scheme.primary, ), const SizedBox(width: 4), Text( switch (closedTab.paymentMethod) { PaymentMethod.cash => l10n.cash, PaymentMethod.payconiq => l10n.payconiq, }, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: scheme.primary, ), ), ], ), ), ], ), const SizedBox(height: 2), Text( '${dateFormat.format(closedTab.closedAt)} · ${l10n.tabItemCount(closedTab.itemCount)}', 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), for (var index = 0; index < dayGroups.length; index++) _ClosedTabDayDropdown( key: ValueKey( '${dayGroups[index].items.first.closedTabId}-' '${dayGroups[index].day.toIso8601String()}', ), group: dayGroups[index], fallbackDate: closedTab.closedAt, initiallyExpanded: index == 0, ), ], ), ), secondChild: const SizedBox(width: double.infinity), ), ], ), ); } } class _ClosedTabDayDropdown extends StatefulWidget { final ClosedTabItemDayGroup group; final DateTime fallbackDate; final bool initiallyExpanded; const _ClosedTabDayDropdown({ super.key, required this.group, required this.fallbackDate, required this.initiallyExpanded, }); @override State<_ClosedTabDayDropdown> createState() => _ClosedTabDayDropdownState(); } class _ClosedTabDayDropdownState extends State<_ClosedTabDayDropdown> { late bool _expanded = widget.initiallyExpanded; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final locale = Localizations.localeOf(context).toLanguageTag(); final dayFormat = DateFormat.yMMMMd(locale); final scheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.only(top: 8), child: AnimatedContainer( duration: const Duration(milliseconds: 160), decoration: BoxDecoration( color: _expanded ? scheme.primary.withValues(alpha: 0.08) : scheme.onSurface.withValues(alpha: 0.035), borderRadius: BorderRadius.circular(12), border: Border.all( color: _expanded ? scheme.primary.withValues(alpha: 0.28) : scheme.onSurface.withValues(alpha: 0.08), ), ), clipBehavior: Clip.antiAlias, child: Column( children: [ Material( color: Colors.transparent, child: InkWell( onTap: () => setState(() => _expanded = !_expanded), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), child: Row( children: [ Container( width: 30, height: 30, decoration: BoxDecoration( color: scheme.primary.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(9), ), child: Icon( Icons.calendar_today_rounded, size: 16, color: scheme.primary, ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( dayFormat.format(widget.group.day), style: const TextStyle( fontWeight: FontWeight.w700, ), ), Text( l10n.tabItemCount(widget.group.itemCount), style: Theme.of(context).textTheme.bodySmall, ), ], ), ), Icon( _expanded ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, color: _expanded ? scheme.primary : scheme.onSurface.withValues(alpha: 0.5), ), ], ), ), ), ), AnimatedSize( duration: const Duration(milliseconds: 160), curve: Curves.easeOut, child: _expanded ? Padding( padding: const EdgeInsets.fromLTRB(12, 0, 12, 8), child: Column( children: [ Divider( height: 1, color: scheme.primary.withValues(alpha: 0.18), ), for (final item in widget.group.items) _ClosedTabItemRow( item: item, purchasedAt: item.purchasedAt ?? widget.fallbackDate, ), ], ), ) : const SizedBox.shrink(), ), ], ), ), ); } } class _ClosedTabItemRow extends StatelessWidget { final ClosedTabItem item; final DateTime purchasedAt; const _ClosedTabItemRow({required this.item, required this.purchasedAt}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final locale = Localizations.localeOf(context).toLanguageTag(); final timeFormat = DateFormat.jm(locale); final unitPrice = NumberFormat.simpleCurrency( locale: locale, ).format(item.unitPriceInCents / 100); final lineTotal = NumberFormat.simpleCurrency( locale: locale, ).format(item.lineTotalInCents / 100); final scheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.productName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.w600, color: item.isRemoved ? scheme.onSurface.withValues(alpha: 0.5) : scheme.onSurface, decoration: item.isRemoved ? TextDecoration.lineThrough : null, ), ), Text( timeFormat.format(purchasedAt), style: Theme.of(context).textTheme.bodySmall, ), ], ), ), if (item.isRemoved) Text( l10n.removed, style: TextStyle( color: scheme.error, fontSize: 12, fontWeight: FontWeight.w700, ), ) else ...[ 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, ), ), ), ], ], ), ); } }