import 'package:sysfarma_mobile/src/app/widgets/app_panel.dart'; import 'package:sysfarma_mobile/src/app/widgets/empty_state_panel.dart'; import 'package:sysfarma_mobile/src/core/network/api_client.dart'; import 'package:sysfarma_mobile/src/core/theme/app_tokens.dart'; import 'package:sysfarma_mobile/src/core/utils/pdf_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class CashHistoryReportScreen extends ConsumerStatefulWidget { const CashHistoryReportScreen({super.key}); @override ConsumerState createState() => _CashHistoryReportScreenState(); } class _CashHistoryReportScreenState extends ConsumerState { List> _items = const []; bool _isLoading = true; String _filter = 'all'; // all / open / closed @override void initState() { super.initState(); _refresh(); } Future _refresh() async { if (mounted) setState(() => _isLoading = true); final response = await ref.read(apiClientProvider).cashes(perPage: 50, page: 1); if (!mounted) return; setState(() { _items = response.data ?? const []; _isLoading = false; }); } List> get _filtered { if (_filter == 'open') { return _items.where((r) => r['state'] == true).toList(); } if (_filter == 'closed') { return _items.where((r) => r['state'] != true).toList(); } return _items; } String _fmtMoney(dynamic v) { final d = double.tryParse('$v') ?? 0; return 'S/ ${d.toStringAsFixed(2)}'; } Future _openCashReportsSheet(Map cash) async { final cashId = int.tryParse('${cash['id']}') ?? 0; if (cashId <= 0) return; final response = await ref.read(apiClientProvider).cashReport(cashId: cashId); if (!mounted) return; if (!response.success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(response.message ?? 'No se pudo cargar el reporte de caja.'), backgroundColor: AppTokens.danger, ), ); return; } final data = response.data ?? const {}; final reports = Map.from(data['reports'] as Map? ?? const {}); await showModalBottomSheet( context: context, builder: (sheetCtx) { return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 16, 20, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ CircleAvatar( backgroundColor: AppTokens.primary.withValues(alpha: 0.12), foregroundColor: AppTokens.primary, child: const Icon(Icons.point_of_sale_rounded), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Reportes de caja', style: Theme.of(context).textTheme.titleLarge, ), Text( 'Apertura ${cash['opening'] ?? '-'}', style: Theme.of(context) .textTheme .bodySmall ?.copyWith(color: AppTokens.secondary), ), ], ), ), ], ), const SizedBox(height: 16), _PdfRow( icon: Icons.picture_as_pdf_rounded, label: 'General A4', url: reports['general_a4_url']?.toString(), ), const Divider(height: 16), _PdfRow( icon: Icons.receipt_long_rounded, label: 'Descargar ticket PDF', url: reports['general_ticket_url']?.toString(), ), const Divider(height: 16), _PdfRow( icon: Icons.inventory_2_rounded, label: 'Descargar detalle de productos', url: reports['products_url']?.toString(), ), const Divider(height: 16), _PdfRow( icon: Icons.bar_chart_rounded, label: 'Descargar resumen de ingresos', url: reports['income_summary_url']?.toString(), ), ], ), ), ); }, ); } @override Widget build(BuildContext context) { final filtered = _filtered; final openCount = _items.where((r) => r['state'] == true).length; final closedCount = _items.length - openCount; final totalIncome = _items.fold( 0, (sum, r) => sum + (double.tryParse('${r['income'] ?? 0}') ?? 0), ); return SafeArea( child: RefreshIndicator( onRefresh: _refresh, child: ListView( padding: AppTokens.pagePadding, children: [ AppPanel( title: 'Historial de cajas', subtitle: 'Ultimas aperturas y cierres de caja con reportes en PDF.', child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Expanded( child: _StatBox( label: 'Total', value: '${_items.length}', color: AppTokens.primary, ), ), const SizedBox(width: 8), Expanded( child: _StatBox( label: 'Abiertas', value: '$openCount', color: AppTokens.success, ), ), const SizedBox(width: 8), Expanded( child: _StatBox( label: 'Cerradas', value: '$closedCount', color: AppTokens.secondary, ), ), ], ), const SizedBox(height: 10), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTokens.primary.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(AppTokens.radiusMedium), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Ingresos acumulados', style: Theme.of(context).textTheme.bodyMedium, ), Text( _fmtMoney(totalIncome), style: Theme.of(context) .textTheme .titleMedium ?.copyWith( color: AppTokens.primary, fontWeight: FontWeight.w800, ), ), ], ), ), ], ), ), const SizedBox(height: 12), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _FilterChip( label: 'Todas', selected: _filter == 'all', onTap: () => setState(() => _filter = 'all'), ), const SizedBox(width: 8), _FilterChip( label: 'Abiertas', selected: _filter == 'open', onTap: () => setState(() => _filter = 'open'), ), const SizedBox(width: 8), _FilterChip( label: 'Cerradas', selected: _filter == 'closed', onTap: () => setState(() => _filter = 'closed'), ), ], ), ), const SizedBox(height: 12), if (_isLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 32), child: Center(child: CircularProgressIndicator()), ) else if (filtered.isEmpty) const EmptyStatePanel( icon: Icons.point_of_sale_rounded, title: 'Sin cajas', subtitle: 'No hay registros de caja para mostrar.', ) else AppPanel( title: 'Registros', trailing: Text( '${filtered.length}', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppTokens.primary, fontWeight: FontWeight.w700, ), ), child: Column( children: [ for (final item in filtered) ...[ _CashRow( record: item, onTap: () => _openCashReportsSheet(item), ), if (item != filtered.last) const Divider(height: 16), ], ], ), ), ], ), ), ); } } class _CashRow extends StatelessWidget { const _CashRow({required this.record, required this.onTap}); final Map record; final VoidCallback onTap; String _fmtMoney(dynamic v) { final d = double.tryParse('$v') ?? 0; return 'S/ ${d.toStringAsFixed(2)}'; } @override Widget build(BuildContext context) { final isOpen = record['state'] == true; final opening = record['opening']?.toString() ?? '-'; final closed = record['closed']?.toString(); final userName = record['user_name']?.toString() ?? '-'; final income = record['income']; final color = isOpen ? AppTokens.success : AppTokens.secondary; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppTokens.radiusMedium), child: Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), child: Row( children: [ CircleAvatar( radius: 20, backgroundColor: color.withValues(alpha: 0.15), foregroundColor: color, child: Icon( isOpen ? Icons.lock_open_rounded : Icons.lock_rounded, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( opening, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 2), Text( userName, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppTokens.secondary, ), ), if (!isOpen && closed != null && closed.isNotEmpty) ...[ const SizedBox(height: 2), Text( 'Cerrada: $closed', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.secondary, ), ), ], ], ), ), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( _fmtMoney(income), style: Theme.of(context).textTheme.titleSmall?.copyWith( color: AppTokens.textPrimary, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: Text( isOpen ? 'Abierta' : 'Cerrada', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: color, fontWeight: FontWeight.w700, ), ), ), ], ), ], ), ), ); } } class _StatBox extends StatelessWidget { const _StatBox({ required this.label, required this.value, required this.color, }); final String label; final String value; final Color color; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8), decoration: BoxDecoration( color: color.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(AppTokens.radiusMedium), border: Border.all(color: color.withValues(alpha: 0.25)), ), child: Column( children: [ Text( value, style: Theme.of(context).textTheme.titleLarge?.copyWith( color: color, fontWeight: FontWeight.w800, ), ), Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.secondary, ), ), ], ), ); } } class _FilterChip extends StatelessWidget { const _FilterChip({ required this.label, required this.selected, required this.onTap, }); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: selected ? AppTokens.primary : AppTokens.primary.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(20), border: Border.all( color: selected ? AppTokens.primary : AppTokens.primary.withValues(alpha: 0.20), ), ), child: Text( label, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: selected ? Colors.white : AppTokens.primary, fontWeight: FontWeight.w700, ), ), ), ); } } class _PdfRow extends ConsumerWidget { const _PdfRow({ required this.icon, required this.label, required this.url, }); final IconData icon; final String label; final String? url; @override Widget build(BuildContext context, WidgetRef ref) { final isTappable = url != null && url!.startsWith('http'); return InkWell( onTap: isTappable ? () async { final apiClient = ref.read(apiClientProvider); await PdfHelper.openAuthenticatedPdf( context: context, apiClient: apiClient, url: url!, title: label, ); } : null, borderRadius: BorderRadius.circular(AppTokens.radiusMedium), child: Padding( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 4), child: Row( children: [ Icon(icon, color: AppTokens.primary), const SizedBox(width: 12), Expanded( child: Text( label, style: Theme.of(context).textTheme.bodyLarge, ), ), Icon( isTappable ? Icons.arrow_forward_ios_rounded : Icons.block_rounded, size: 16, color: AppTokens.secondary, ), ], ), ), ); } }