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:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class LotsOverviewReportScreen extends ConsumerStatefulWidget { const LotsOverviewReportScreen({super.key}); @override ConsumerState createState() => _LotsOverviewReportScreenState(); } class _LotsOverviewReportScreenState extends ConsumerState { String _kind = 'all'; bool _isLoading = true; Map _data = const {}; @override void initState() { super.initState(); _refresh(); } Future _refresh() async { if (mounted) setState(() => _isLoading = true); final response = await ref.read(apiClientProvider).lotsOverviewReport(kind: _kind); if (!mounted) return; setState(() { _data = response.data ?? const {}; _isLoading = false; }); } void _setKind(String kind) { if (kind == _kind) return; setState(() => _kind = kind); _refresh(); } @override Widget build(BuildContext context) { final totals = Map.from(_data['totals'] as Map? ?? const {}); final items = List>.from(_data['items'] as List? ?? const []); final withLots = totals['with_lots'] ?? 0; final withoutLots = totals['without_lots'] ?? 0; final totalGroups = totals['total_lots_groups'] ?? 0; return SafeArea( child: RefreshIndicator( onRefresh: _refresh, child: ListView( padding: AppTokens.pagePadding, children: [ Row( children: [ Expanded( child: _SegmentChip( label: 'Todos', selected: _kind == 'all', onTap: () => _setKind('all'), ), ), const SizedBox(width: 8), Expanded( child: _SegmentChip( label: 'Con lote', selected: _kind == 'with', onTap: () => _setKind('with'), ), ), const SizedBox(width: 8), Expanded( child: _SegmentChip( label: 'Sin lote', selected: _kind == 'without', onTap: () => _setKind('without'), ), ), ], ), const SizedBox(height: 14), Row( children: [ Expanded( child: _StatCard( label: 'Con lote', value: '$withLots', color: AppTokens.primary, icon: Icons.label_important_rounded, ), ), const SizedBox(width: 12), Expanded( child: _StatCard( label: 'Sin lote', value: '$withoutLots', color: AppTokens.secondary, icon: Icons.label_off_rounded, ), ), const SizedBox(width: 12), Expanded( child: _StatCard( label: 'Lotes', value: '$totalGroups', color: AppTokens.cta, icon: Icons.inventory_rounded, ), ), ], ), const SizedBox(height: 14), if (_isLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 32), child: Center(child: CircularProgressIndicator()), ) else if (items.isEmpty) const EmptyStatePanel( icon: Icons.label_outline_rounded, title: 'Sin productos', subtitle: 'No hay productos para este filtro.', ) else AppPanel( title: 'Productos', trailing: Text('${items.length}', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppTokens.primary, fontWeight: FontWeight.w700, )), child: Column( children: [ for (final item in items) ...[ _ItemRow(item: item), if (item != items.last) const Divider(height: 16), ], ], ), ), ], ), ), ); } } class _SegmentChip extends StatelessWidget { const _SegmentChip( {required this.label, required this.selected, required this.onTap}); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppTokens.radiusSmall), onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: selected ? AppTokens.primary.withValues(alpha: 0.10) : AppTokens.surface, borderRadius: BorderRadius.circular(AppTokens.radiusSmall), border: Border.all( color: selected ? AppTokens.primary : AppTokens.border, ), ), child: Center( child: Text( label, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: selected ? AppTokens.primary : AppTokens.secondary, fontWeight: selected ? FontWeight.w700 : FontWeight.w500, ), ), ), ), ), ); } } class _StatCard extends StatelessWidget { const _StatCard({ required this.label, required this.value, required this.color, required this.icon, }); final String label; final String value; final Color color; final IconData icon; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(AppTokens.radiusMedium), border: Border.all(color: color.withValues(alpha: 0.20)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, color: color, size: 18), const SizedBox(height: 4), Text( value, style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: color, fontWeight: FontWeight.w800, ), ), Text( label, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppTokens.secondary, ), ), ], ), ); } } class _ItemRow extends StatefulWidget { const _ItemRow({required this.item}); final Map item; @override State<_ItemRow> createState() => _ItemRowState(); } class _ItemRowState extends State<_ItemRow> { bool _expanded = false; @override Widget build(BuildContext context) { final item = widget.item; final hasLots = item['lots_enabled'] == true; final lotsCount = item['lots_count'] ?? 0; final totalQty = item['total_quantity'] ?? 0; final lots = List>.from( item['lots'] as List? ?? const []); final canExpand = hasLots && lots.isNotEmpty; return Material( color: Colors.transparent, child: InkWell( onTap: canExpand ? () => setState(() => _expanded = !_expanded) : null, borderRadius: BorderRadius.circular(AppTokens.radiusSmall), child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Column( children: [ Row( children: [ Container( width: 8, height: 36, decoration: BoxDecoration( color: hasLots ? AppTokens.primary : AppTokens.secondary, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['description']?.toString() ?? '-', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 2), Text( hasLots ? '$lotsCount lote(s) ยท $totalQty u' : 'Sin control de lotes', style: Theme.of(context) .textTheme .labelSmall ?.copyWith( color: AppTokens.secondary, ), ), ], ), ), if (canExpand) Icon( _expanded ? Icons.expand_less_rounded : Icons.expand_more_rounded, color: AppTokens.secondary, ) else if (hasLots) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppTokens.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(999), ), child: const Text( 'Con lote', style: TextStyle( color: AppTokens.primary, fontSize: 10, fontWeight: FontWeight.w700, ), ), ), ], ), if (canExpand && _expanded) ...[ const SizedBox(height: 8), Container( margin: const EdgeInsets.only(left: 20), padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8), decoration: BoxDecoration( color: AppTokens.primary.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), border: Border.all( color: AppTokens.primary.withValues(alpha: 0.20), ), ), child: Column( children: [ for (var i = 0; i < lots.length; i++) ...[ _LotDetailRow(lot: lots[i]), if (i < lots.length - 1) Divider( height: 14, color: AppTokens.primary.withValues(alpha: 0.15), ), ], ], ), ), ], ], ), ), ), ); } } class _LotDetailRow extends StatelessWidget { const _LotDetailRow({required this.lot}); final Map lot; @override Widget build(BuildContext context) { final code = lot['code']?.toString() ?? '-'; final dueDate = lot['date_of_due']?.toString() ?? '-'; final qty = lot['quantity']?.toString() ?? '0'; Color dueColor = AppTokens.secondary; String dueLabel = dueDate; if (dueDate != '-') { try { final due = DateTime.parse(dueDate); final daysLeft = due.difference(DateTime.now()).inDays; if (daysLeft < 0) { dueColor = AppTokens.danger; dueLabel = '$dueDate (vencido)'; } else if (daysLeft <= 30) { dueColor = AppTokens.danger; dueLabel = '$dueDate (${daysLeft}d)'; } else if (daysLeft <= 90) { dueColor = AppTokens.warning; dueLabel = '$dueDate (${daysLeft}d)'; } } catch (_) {} } return Row( children: [ const Icon(Icons.label_outline_rounded, size: 14, color: AppTokens.primary), const SizedBox(width: 6), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( code, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.labelSmall?.copyWith( fontWeight: FontWeight.w700, ), ), Text( dueLabel, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: dueColor, ), ), ], ), ), const SizedBox(width: 8), Text( '$qty u', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.primary, fontWeight: FontWeight.w800, ), ), ], ); } }