import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../app/providers.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/utils/formatters.dart'; import '../../../domain/entities/customer.dart'; import '../../customer/widgets/customer_capture_sheet.dart'; import '../widgets/module_widgets.dart'; /// Full customer book, independent of the six shown during billing. final allCustomersProvider = FutureProvider>( (ref) => ref.watch(customerRepositoryProvider).recent(limit: 200), ); class CustomersView extends ConsumerStatefulWidget { const CustomersView({super.key}); @override ConsumerState createState() => _CustomersViewState(); } class _CustomersViewState extends ConsumerState { String _query = ''; @override Widget build(BuildContext context) { final all = ref.watch(allCustomersProvider).value ?? const []; final filtered = all.where((c) { final q = _query.trim().toLowerCase(); return q.isEmpty || c.name.toLowerCase().contains(q) || c.mobile.contains(q); }).toList(); final lifetime = all.fold(0, (s, c) => s + c.lifetimeSpend); final points = all.fold(0, (s, c) => s + c.loyaltyPoints); return ModulePage( children: [ Wrap( spacing: AppSpacing.lg, runSpacing: AppSpacing.lg, children: [ StatTile( label: 'Total Customers', value: '${all.length}', icon: Icons.people_alt_rounded, caption: 'registered', ), StatTile( label: 'Lifetime Value', value: Formatters.moneyCompact(lifetime), icon: Icons.payments_rounded, color: AppColors.success, caption: 'all customers', ), StatTile( label: 'Points Outstanding', value: '$points', icon: Icons.stars_rounded, color: AppColors.tierGold, caption: 'worth ${Formatters.money(points * 0.25)}', ), StatTile( label: 'Avg Spend', value: Formatters.money(all.isEmpty ? 0 : lifetime / all.length), icon: Icons.trending_up_rounded, color: AppColors.info, caption: 'per customer', ), ], ), const SizedBox(height: AppSpacing.lg), PanelCard( title: 'Customer book', subtitle: '${filtered.length} shown', action: FilledButton.icon( // Reuses the sheet the till already opens at checkout, so a // customer added here is validated the same way — including the // duplicate-mobile guard. onPressed: () async { await showCustomerCaptureSheet(context); ref.invalidate(allCustomersProvider); }, icon: const Icon(Icons.person_add_alt_1_rounded, size: 17), label: const Text('Add customer'), style: FilledButton.styleFrom( backgroundColor: AppColors.primary, minimumSize: const Size(0, 40), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( onChanged: (v) => setState(() => _query = v), decoration: const InputDecoration( hintText: 'Search by name or mobile number…', prefixIcon: Icon(Icons.search_rounded), isDense: true, ), ), const SizedBox(height: AppSpacing.lg), ResponsiveTable( columns: const [ TableCol('Customer', flex: 4), TableCol('Mobile', flex: 3, priority: 1), TableCol('Points', flex: 2, numeric: true, priority: 1), TableCol('Lifetime', flex: 2, numeric: true), TableCol('Visits', flex: 2, numeric: true, priority: 1), ], rows: filtered .map((c) => [ Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 14, backgroundColor: AppColors.primarySurface, child: Text( Formatters.initials(c.name), style: const TextStyle( fontSize: 10.5, fontWeight: FontWeight.w700, color: AppColors.primary, ), ), ), const SizedBox(width: AppSpacing.sm), Flexible(child: Cell(c.name, bold: true)), ], ), Cell(Formatters.mobile(c.mobile), mono: true), Cell('${c.loyaltyPoints}', mono: true), Cell(Formatters.moneyCompact(c.lifetimeSpend), mono: true, bold: true,), Cell('${c.visitCount}', mono: true), ],) .toList(), ), ], ), ), ], ); } }