import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../transactions/providers/providers.dart'; import '../../transactions/data/models.dart'; import '../../../core/widgets/shimmer_loading.dart'; class BudgetScreen extends ConsumerStatefulWidget { const BudgetScreen({super.key}); @override ConsumerState createState() => _BudgetScreenState(); } class _BudgetScreenState extends ConsumerState { String _searchQuery = ''; String? _filterNature; bool _isSearching = false; final TextEditingController _searchController = TextEditingController(); @override void dispose() { _searchController.dispose(); super.dispose(); } Future _onRefresh() async { ref.invalidate(budgetProvider); ref.invalidate(walletProvider); ref.invalidate(transactionProvider); await Future.delayed(const Duration(milliseconds: 500)); } void _showSetBudgetDialog(BuildContext context, Wallet wallet, Budget? existingBudget) { final controller = TextEditingController(text: existingBudget?.monthlyLimit.toString() ?? ''); bool isShared = existingBudget?.isShared ?? false; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (context, setState) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), elevation: 0, backgroundColor: Colors.transparent, child: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(28), boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10))], ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Set Budget for ${wallet.name}', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 24), TextField( controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: false), textInputAction: TextInputAction.done, style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( hintText: 'Monthly Limit', prefixText: 'Rs. ', prefixStyle: const TextStyle(color: Colors.black87, fontWeight: FontWeight.w500, fontSize: 16), filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2)), ), ), const SizedBox(height: 16), SwitchListTile( title: const Text('Share budget with wallet members', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), value: isShared, activeColor: const Color(0xFF6C63FF), contentPadding: EdgeInsets.zero, onChanged: (val) { setState(() { isShared = val; }); }, ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom(foregroundColor: Colors.grey.shade700, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), child: const Text('Cancel', style: TextStyle(fontWeight: FontWeight.bold)), ), const SizedBox(width: 12), ElevatedButton( onPressed: () async { final limit = double.tryParse(controller.text); if (limit != null && limit > 0) { final newBudget = Budget( id: existingBudget?.id ?? 0, walletId: wallet.id, monthlyLimit: limit, isShared: isShared, ); await ref.read(budgetProvider.notifier).addOrUpdateBudget(newBudget); if (mounted) Navigator.pop(ctx); } }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6C63FF), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 0, ), child: const Text('Save', style: TextStyle(fontWeight: FontWeight.bold)), ) ], ), ], ), ), ), ), ); } ), ); } void _showFilterSheet() { final natures = ['ALL', 'CASH', 'SAVINGS', 'INVESTMENTS', 'LOAN', 'LENDING', 'PAYABLES', 'INCOME', 'EXPENSE', 'RECEIVABLES']; String? tempFilterNature = _filterNature; showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), builder: (ctx) => StatefulBuilder( builder: (context, setSheetState) { return Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SafeArea( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Filter Budgets', style: Theme.of(context).textTheme.titleLarge), IconButton(icon: const Icon(LucideIcons.x), onPressed: () => Navigator.pop(ctx)), ], ), const SizedBox(height: 24), Text('Account Nature', style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 8), DropdownButtonFormField( value: tempFilterNature ?? 'ALL', style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.black87, fontSize: 16), decoration: InputDecoration( filled: true, fillColor: Colors.grey.shade100, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2)), ), items: natures.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(), onChanged: (val) { if (val != null) { setSheetState(() { tempFilterNature = val == 'ALL' ? null : val; }); } }, ), const SizedBox(height: 32), Row( children: [ Expanded( child: OutlinedButton( onPressed: () { setState(() => _filterNature = null); Navigator.pop(ctx); }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), side: BorderSide(color: Colors.grey.shade300), ), child: Text('Reset', style: TextStyle(color: Colors.grey.shade700, fontWeight: FontWeight.bold)), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: () { setState(() => _filterNature = tempFilterNature); Navigator.pop(ctx); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: const Color(0xFF6C63FF), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 0, ), child: const Text('Apply Filter', style: TextStyle(fontWeight: FontWeight.bold)), ), ), ], ), const SizedBox(height: 16), ], ), ), ), ); }, ), ); } @override Widget build(BuildContext context) { final budgetsState = ref.watch(budgetProvider); final walletsState = ref.watch(walletProvider); final transState = ref.watch(transactionProvider); return Scaffold( backgroundColor: Colors.transparent, body: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 12), child: Row( children: [ Expanded( child: TextField( controller: _searchController, onChanged: (val) => setState(() => _searchQuery = val), decoration: InputDecoration( hintText: 'Search budgets...', prefixIcon: const Icon(LucideIcons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(LucideIcons.x), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ) : null, filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ), ), const SizedBox(width: 8), IconButton( icon: Icon( LucideIcons.filter, color: _filterNature != null ? const Color(0xFF6C63FF) : null, ), onPressed: _showFilterSheet, ), ], ), ), Expanded( child: budgetsState.when( loading: () => ListView.builder( padding: const EdgeInsets.all(16), itemCount: 4, itemBuilder: (context, index) => const ShimmerCard(), ), error: (err, stack) => Center(child: Text('Error: $err')), data: (budgets) { if (!walletsState.hasValue || !transState.hasValue) { return ListView.builder( padding: const EdgeInsets.all(16), itemCount: 4, itemBuilder: (context, index) => const ShimmerCard(), ); } final wallets = walletsState.value!.where((w) { final nature = (w.nature ?? 'CASH').trim().toUpperCase(); final hasBudget = budgets.any((b) => b.walletId == w.id); final matchSearch = _searchQuery.isEmpty || w.name.toLowerCase().contains(_searchQuery.toLowerCase()); final matchNature = _filterNature == null || nature == _filterNature; final matchBaseCondition = hasBudget || nature == 'EXPENSE' || nature == 'INVESTMENTS'; return matchBaseCondition && matchSearch && matchNature; }).toList(); final transactions = transState.value!; final now = DateTime.now(); // Calculate spent per wallet for the current month final currentMonthTxs = transactions.where((t) => t.date.month == now.month && t.date.year == now.year); final Map spentByWallet = {}; for (var t in currentMonthTxs) { if (t.toWalletId != null) { spentByWallet[t.toWalletId!] = (spentByWallet[t.toWalletId!] ?? 0) + t.amount; } } if (wallets.isEmpty) { return const Center(child: Padding(padding: EdgeInsets.all(16), child: Text('No budgets or eligible accounts found.'))); } return RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), itemCount: wallets.length, itemBuilder: (context, index) { final wallet = wallets[index]; final existingBudgetIndex = budgets.indexWhere((b) => b.walletId == wallet.id); final budget = existingBudgetIndex >= 0 ? budgets[existingBudgetIndex] : null; final spent = spentByWallet[wallet.id] ?? 0.0; if (budget == null) { return Card( margin: const EdgeInsets.only(bottom: 12), child: ListTile( leading: const CircleAvatar(child: Icon(LucideIcons.wallet)), title: Text(wallet.name), subtitle: Text('${wallet.nature} • No budget set'), trailing: TextButton( onPressed: () => _showSetBudgetDialog(context, wallet, null), child: const Text('Set Budget'), ), ), ); } final limit = budget.monthlyLimit; final double progress; if (limit <= 0) { progress = 1.0; } else { progress = (spent / limit).clamp(0.0, 1.0); } Color progressColor = Colors.green; if (progress > 0.9) progressColor = Colors.red; else if (progress > 0.7) progressColor = Colors.orange; return Card( margin: const EdgeInsets.only(bottom: 16), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( children: [ const Icon(LucideIcons.wallet, size: 20, color: Colors.blue), const SizedBox(width: 8), Expanded(child: Text(wallet.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), overflow: TextOverflow.ellipsis)), ], ), ), IconButton( icon: const Icon(LucideIcons.edit3, size: 18), onPressed: () => _showSetBudgetDialog(context, wallet, budget), ) ], ), const SizedBox(height: 12), LinearProgressIndicator( value: progress, backgroundColor: Colors.grey.shade200, valueColor: AlwaysStoppedAnimation(progressColor), minHeight: 8, borderRadius: BorderRadius.circular(4), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Spent: Rs. ${spent.toStringAsFixed(2)}', style: TextStyle(color: Colors.grey.shade700)), Text('Limit: Rs. ${limit.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold)), ], ), if (progress >= 1.0) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text('Budget Exceeded!', style: TextStyle(color: Colors.red.shade700, fontSize: 12, fontWeight: FontWeight.bold)), ), ], ), ), ); }), ); }, ), ), ], ), ); } }