import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:intl/intl.dart'; import '../../transactions/providers/providers.dart'; import '../../transactions/data/models.dart'; import '../../transactions/data/repository.dart'; import 'wallet_ledger_screen.dart'; import '../../../core/widgets/responsive_layout.dart'; class AccountsScreen extends ConsumerStatefulWidget { final String? initialFilterNature; const AccountsScreen({super.key, this.initialFilterNature}); @override ConsumerState createState() => _AccountsScreenState(); } class _AccountsScreenState extends ConsumerState { String _searchQuery = ''; String? _filterNature; bool _isSearching = false; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _filterNature = widget.initialFilterNature; } @override void dispose() { _searchController.dispose(); super.dispose(); } Future _onRefresh() async { ref.invalidate(walletProvider); ref.invalidate(invitationProvider); await Future.delayed(const Duration(milliseconds: 500)); } void _showCreateWalletDialog() { final ctrl = TextEditingController(); final amtCtrl = TextEditingController(); final creditLimitCtrl = TextEditingController(); final fixedAmountCtrl = TextEditingController(); final cycleDateCtrl = TextEditingController(); DateTime openingDate = DateTime.now(); String selectedNature = 'CASH'; String? selectedSubNature; String? selectedPaymentCycle; final natures = ['CASH', 'SAVINGS', 'INVESTMENTS', 'LOAN', 'LENDING', 'PAYABLES', 'INCOME', 'EXPENSE', 'RECEIVABLES']; final subNatures = ['CREDIT_CARD', 'OD_LIMIT', 'LOAN_EMI', 'POLICY_PREMIUM', 'OTHER']; final paymentCycles = ['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY']; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (context, setStateDialog) { 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: [ Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: const Color(0xFF6C63FF).withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon(LucideIcons.wallet, color: Color(0xFF6C63FF), size: 24), ), const SizedBox(width: 16), const Text( 'New Account', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 24), TextField( controller: ctrl, style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( hintText: 'Account Name (e.g. Household)', 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), DropdownButtonFormField( value: selectedNature, style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.black87), decoration: InputDecoration( labelText: 'Account Nature', 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)), ), items: natures.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(), onChanged: (val) { if (val != null) setStateDialog(() { selectedNature = val; if (val != 'PAYABLES') { selectedSubNature = null; selectedPaymentCycle = null; creditLimitCtrl.clear(); fixedAmountCtrl.clear(); cycleDateCtrl.clear(); } else { selectedSubNature = 'CREDIT_CARD'; selectedPaymentCycle = 'MONTHLY'; } }); }, ), const SizedBox(height: 16), if (selectedNature == 'PAYABLES') ...[ DropdownButtonFormField( value: selectedSubNature, style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.black87), decoration: InputDecoration( labelText: 'Sub-Nature', 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)), ), items: subNatures.map((n) => DropdownMenuItem(value: n, child: Text(n.replaceAll('_', ' ')))).toList(), onChanged: (val) { if (val != null) setStateDialog(() => selectedSubNature = val); }, ), const SizedBox(height: 16), if (selectedSubNature == 'CREDIT_CARD' || selectedSubNature == 'OD_LIMIT') ...[ TextField( controller: creditLimitCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: false), style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( hintText: 'Credit Limit (Optional)', 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), ], if (selectedSubNature == 'LOAN_EMI' || selectedSubNature == 'POLICY_PREMIUM') ...[ TextField( controller: fixedAmountCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: false), style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( hintText: 'Fixed Amount Due (Optional)', 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), ], Row( children: [ Expanded( flex: 2, child: DropdownButtonFormField( value: selectedPaymentCycle, style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.black87), decoration: InputDecoration( labelText: 'Cycle', 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)), ), items: paymentCycles.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(), onChanged: (val) { if (val != null) setStateDialog(() => selectedPaymentCycle = val); }, ), ), const SizedBox(width: 8), Expanded( flex: 1, child: TextField( controller: cycleDateCtrl, keyboardType: TextInputType.number, style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( labelText: 'Date (1-31)', 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), ], TextField( controller: amtCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true), textInputAction: TextInputAction.done, style: const TextStyle(fontWeight: FontWeight.w500), decoration: InputDecoration( hintText: 'Opening Balance (Optional)', 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), InkWell( onTap: () async { final picked = await showDatePicker( context: context, initialDate: openingDate, firstDate: DateTime.now().subtract(const Duration(days: 365 * 10)), lastDate: DateTime.now(), ); if (picked != null) { setStateDialog(() => openingDate = picked); } }, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ const Icon(LucideIcons.calendar, color: Colors.grey), const SizedBox(width: 12), Text( 'Date: ${DateFormat('MMM dd, yyyy').format(openingDate)}', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), ], ), ), ), 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 { if (ctrl.text.isNotEmpty) { final double amt = double.tryParse(amtCtrl.text) ?? 0.0; final double? cl = double.tryParse(creditLimitCtrl.text); final double? fa = double.tryParse(fixedAmountCtrl.text); final int? cd = int.tryParse(cycleDateCtrl.text); final newWallet = await ref.read(walletProvider.notifier).createWallet( name: ctrl.text, nature: selectedNature, initialBalance: amt, initialBalanceDate: openingDate, subNature: selectedNature == 'PAYABLES' ? selectedSubNature : null, creditLimit: cl, fixedAmount: fa, paymentCycle: selectedNature == 'PAYABLES' ? selectedPaymentCycle : null, cycleDate: cd, ); if (context.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('Create', 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 Accounts', 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 walletsState = ref.watch(walletProvider); return Scaffold( body: MaxContentWidth( maxWidth: 1200, child: SafeArea( bottom: false, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 12), child: Row( children: [ if (Navigator.of(context).canPop()) Padding( padding: const EdgeInsets.only(right: 8.0), child: IconButton( icon: const Icon(LucideIcons.arrowLeft), onPressed: () => Navigator.pop(context), ), ), Expanded( child: TextField( controller: _searchController, onChanged: (val) => setState(() => _searchQuery = val), decoration: InputDecoration( hintText: 'Search accounts...', 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, ), IconButton( icon: const Icon(LucideIcons.plusCircle), onPressed: _showCreateWalletDialog, ), ], ), ), Expanded( child: walletsState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, st) => Center(child: Text('Error: $e')), data: (allWallets) { final wallets = allWallets.where((w) { if (w.nature == 'EQUITY') return false; final matchSearch = _searchQuery.isEmpty || w.name.toLowerCase().contains(_searchQuery.toLowerCase()); final matchNature = _filterNature == null || w.nature == _filterNature; return matchSearch && matchNature; }).toList(); if (wallets.isEmpty) { return const Center(child: Text('No accounts found.')); } return RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), itemCount: wallets.length, itemBuilder: (context, index) { final w = wallets[index]; return Card( margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.grey.shade200)), elevation: 0, child: Column( children: [ InkWell( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => WalletLedgerScreen(wallet: w))); }, child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ CircleAvatar( backgroundColor: Colors.blue.withValues(alpha: 0.1), child: const Icon(LucideIcons.wallet, color: Colors.blue), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(w.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), const SizedBox(height: 4), Text('${w.nature ?? "CASH"} • Balance: Rs. ${w.balance}', style: TextStyle(color: Colors.grey.shade600, fontSize: 13)), ], ), ), ], ), ), ), Divider(height: 1, color: Colors.grey.shade200), FittedBox( fit: BoxFit.scaleDown, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton.icon( icon: const Icon(LucideIcons.userPlus, size: 14), label: const Text('Invite', style: TextStyle(fontSize: 12)), style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)), onPressed: () { showDialog( context: context, builder: (ctx) => InviteDialogWidget(wallet: w), ); }, ), TextButton.icon( icon: const Icon(LucideIcons.edit2, size: 14), label: const Text('Edit', style: TextStyle(fontSize: 12)), style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)), onPressed: () { final editCtrl = TextEditingController(text: w.name); String editNature = w.nature ?? 'CASH'; String? editSubNature = w.subNature; final editCreditLimitCtrl = TextEditingController(text: w.creditLimit?.toString() ?? ''); final editFixedAmountCtrl = TextEditingController(text: w.fixedAmount?.toString() ?? ''); final editCycleDateCtrl = TextEditingController(text: w.cycleDate?.toString() ?? ''); final allTxs = ref.read(transactionProvider).value ?? []; final walletTxs = allTxs.where((t) => t.fromWalletId == w.id || t.toWalletId == w.id).toList(); final obTxs = walletTxs.where((t) => t.description == 'Opening Balance'); final hasRealTx = walletTxs.any((t) => t.description != 'Opening Balance'); final editInitialBalanceCtrl = TextEditingController(text: w.balance != 0 ? w.balance.toString() : ''); DateTime editOpeningDate = obTxs.isNotEmpty ? obTxs.first.date : DateTime.now(); String? editPaymentCycle = w.paymentCycle; final natures = ['CASH', 'SAVINGS', 'EXPENSE', 'INVESTMENTS', 'LOAN', 'LENDING', 'PAYABLES', 'RECEIVABLES', 'INCOME']; final subNatures = ['CREDIT_CARD', 'OD_LIMIT', 'LOAN_EMI', 'POLICY_PREMIUM', 'OTHER']; final paymentCycles = ['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY']; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (context, setStateDialog) => 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)), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Edit Account', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 24), TextField( controller: editCtrl, decoration: InputDecoration( hintText: 'Account Name', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), const SizedBox(height: 16), DropdownButtonFormField( value: editNature, decoration: InputDecoration( labelText: 'Account Nature', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), items: natures.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(), onChanged: (val) { if (val != null) setStateDialog(() { editNature = val; if (val != 'PAYABLES') { editSubNature = null; editPaymentCycle = null; editCreditLimitCtrl.clear(); editFixedAmountCtrl.clear(); editCycleDateCtrl.clear(); } else if (editSubNature == null) { editSubNature = 'CREDIT_CARD'; editPaymentCycle = 'MONTHLY'; } }); }, ), const SizedBox(height: 16), Tooltip( message: hasRealTx ? 'Opening balance cannot be edited after transactions are recorded' : '', child: TextField( controller: editInitialBalanceCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true), enabled: !hasRealTx, decoration: InputDecoration( hintText: 'Opening Balance (Optional)', prefixText: 'Rs. ', filled: true, fillColor: hasRealTx ? Colors.grey.shade200 : Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), ), const SizedBox(height: 16), Tooltip( message: hasRealTx ? 'Opening balance date cannot be edited after transactions are recorded' : '', child: InkWell( onTap: hasRealTx ? null : () async { final picked = await showDatePicker( context: context, initialDate: editOpeningDate, firstDate: DateTime.now().subtract(const Duration(days: 365 * 10)), lastDate: DateTime.now(), ); if (picked != null) { setStateDialog(() => editOpeningDate = picked); } }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: BoxDecoration( color: hasRealTx ? Colors.grey.shade200 : Colors.grey.shade100, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ const Icon(LucideIcons.calendar, color: Colors.black54, size: 20), const SizedBox(width: 12), Text( 'Opening Date: ${editOpeningDate.day.toString().padLeft(2, '0')}/${editOpeningDate.month.toString().padLeft(2, '0')}/${editOpeningDate.year}', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 15, color: hasRealTx ? Colors.black38 : Colors.black87), ), ], ), ), ), ), const SizedBox(height: 16), if (editNature == 'PAYABLES') ...[ DropdownButtonFormField( value: editSubNature, decoration: InputDecoration( labelText: 'Sub-Nature', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), items: subNatures.map((n) => DropdownMenuItem(value: n, child: Text(n.replaceAll('_', ' ')))).toList(), onChanged: (val) { if (val != null) setStateDialog(() => editSubNature = val); }, ), const SizedBox(height: 16), if (editSubNature == 'CREDIT_CARD' || editSubNature == 'OD_LIMIT') ...[ TextField( controller: editCreditLimitCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: false), decoration: InputDecoration( hintText: 'Credit Limit (Optional)', prefixText: 'Rs. ', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), const SizedBox(height: 16), ], if (editSubNature == 'LOAN_EMI' || editSubNature == 'POLICY_PREMIUM') ...[ TextField( controller: editFixedAmountCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: false), decoration: InputDecoration( hintText: 'Fixed Amount Due (Optional)', prefixText: 'Rs. ', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), const SizedBox(height: 16), ], Row( children: [ Expanded( flex: 2, child: DropdownButtonFormField( value: editPaymentCycle, decoration: InputDecoration( labelText: 'Cycle', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), items: paymentCycles.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(), onChanged: (val) { if (val != null) setStateDialog(() => editPaymentCycle = val); }, ), ), const SizedBox(width: 8), Expanded( flex: 1, child: TextField( controller: editCycleDateCtrl, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Date (1-31)', filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), ), ], ), const SizedBox(height: 16), ], const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Cancel', style: TextStyle(fontWeight: FontWeight.bold)), ), const SizedBox(width: 12), ElevatedButton( onPressed: () async { if (editCtrl.text.isNotEmpty) { final double? cl = double.tryParse(editCreditLimitCtrl.text.replaceAll(RegExp(r'[^0-9.]'), '')); final double? fa = double.tryParse(editFixedAmountCtrl.text.replaceAll(RegExp(r'[^0-9.]'), '')); final int? cd = int.tryParse(editCycleDateCtrl.text); // Make parsing super robust by stripping everything except digits and dot final String rawText = editInitialBalanceCtrl.text.replaceAll(RegExp(r'[^0-9.]'), ''); final double? ib = (!hasRealTx && rawText.isNotEmpty) ? double.tryParse(rawText) : null; print("DEBUG KIFI: editWallet called."); print("DEBUG KIFI: hasRealTx = $hasRealTx"); print("DEBUG KIFI: rawText = '$rawText'"); print("DEBUG KIFI: parsed ib = $ib"); try { await ref.read(walletProvider.notifier).editWallet( w.id, name: editCtrl.text.trim(), nature: editNature, initialBalance: ib, initialBalanceDate: ib != null ? editOpeningDate : null, subNature: editNature == 'PAYABLES' ? editSubNature : null, creditLimit: cl, fixedAmount: fa, paymentCycle: editNature == 'PAYABLES' ? editPaymentCycle : null, cycleDate: cd, ); if (context.mounted) { Navigator.pop(ctx); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Wallet updated'))); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to edit: $e'))); } } } }, style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF6C63FF), foregroundColor: Colors.white), child: const Text('Save', style: TextStyle(fontWeight: FontWeight.bold)), ) ], ), ], ), ), ), ), ), ), ); }, ), TextButton.icon( icon: const Icon(LucideIcons.trash2, size: 14, color: Colors.red), label: const Text('Delete', style: TextStyle(color: Colors.red, fontSize: 12)), style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)), onPressed: () async { final confirm = await showDialog( context: context, builder: (ctx) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), elevation: 0, backgroundColor: Colors.transparent, child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(28)), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Delete Account', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.red)), const SizedBox(height: 16), Text('Are you sure you want to delete ${w.name}? This action cannot be undone.', style: const TextStyle(fontSize: 16)), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)), ), const SizedBox(width: 12), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16))), child: const Text('Delete', style: TextStyle(fontWeight: FontWeight.bold)), ) ], ), ], ), ), ), ); if (confirm == true) { try { await ref.read(walletProvider.notifier).deleteWallet(w.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Wallet deleted'))); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString().replaceAll('Exception: ', '')))); } } } }, ), TextButton.icon( icon: const Icon(LucideIcons.list, size: 14), label: const Text('Ledger', style: TextStyle(fontSize: 12)), style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)), onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => WalletLedgerScreen(wallet: w))); }, ), ], ), ), // Close FittedBox ], ), ); }, ), ); }, ), ), ], ), ), ), ); } } class InviteDialogWidget extends ConsumerStatefulWidget { final Wallet wallet; const InviteDialogWidget({super.key, required this.wallet}); @override ConsumerState createState() => _InviteDialogWidgetState(); } class _InviteDialogWidgetState extends ConsumerState { TextEditingController _autoCompleteCtrl = TextEditingController(); bool _isInviting = false; bool _isLoadingMembers = true; List _members = []; List _knownContacts = []; @override void initState() { super.initState(); _fetchMembers(); } @override void dispose() { _autoCompleteCtrl.dispose(); super.dispose(); } Future _fetchMembers() async { try { final members = await ApiRepository().getWalletMembers(widget.wallet.id); List contacts = []; try { contacts = await ApiRepository().getKnownContacts(); } catch (e) { debugPrint("Error fetching contacts: $e"); } if (mounted) { setState(() { _members = members; _knownContacts = contacts.where((c) => !members.any((m) => m.email == c)).toList(); _isLoadingMembers = false; }); } } catch (e) { if (mounted) { setState(() => _isLoadingMembers = false); } } } Future _removeMember(WalletMember member) async { final confirm = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Remove Member'), content: Text('Are you sure you want to remove ${member.email} from this wallet?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white), child: const Text('Remove'), ), ], ), ); if (confirm == true) { try { await ApiRepository().removeWalletMember(widget.wallet.id, member.userId); await _fetchMembers(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Member removed.'))); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString().replaceAll('Exception: ', '')))); } } } } @override Widget build(BuildContext context) { 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('Invite to ${widget.wallet.name}', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 24), TextField( controller: _autoCompleteCtrl, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.done, style: const TextStyle(fontWeight: FontWeight.w500), onChanged: (val) { setState(() {}); }, decoration: InputDecoration( hintText: 'Enter Email Address to invite', 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)), ), ), if (_autoCompleteCtrl?.text.trim().isNotEmpty == true && (_autoCompleteCtrl?.text.length ?? 0) >= 2) Builder( builder: (context) { final query = _autoCompleteCtrl.text.trim().toLowerCase(); final matches = _knownContacts.where((c) => c.toLowerCase().contains(query)).toList(); if (matches.isEmpty || (matches.length == 1 && matches.first.toLowerCase() == query)) return const SizedBox.shrink(); return Container( margin: const EdgeInsets.only(top: 8), constraints: const BoxConstraints(maxHeight: 160), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4))], ), child: ListView.separated( shrinkWrap: true, padding: EdgeInsets.zero, itemCount: matches.length, separatorBuilder: (_, __) => const Divider(height: 1, indent: 16, endIndent: 16), itemBuilder: (ctx, index) { final email = matches[index]; return ListTile( leading: const CircleAvatar(radius: 14, backgroundColor: Color(0xFF6C63FF), child: Icon(LucideIcons.user, size: 14, color: Colors.white)), title: Text(email, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), onTap: () { setState(() { _autoCompleteCtrl.text = email; _autoCompleteCtrl.selection = TextSelection.fromPosition(TextPosition(offset: email.length)); }); FocusScope.of(context).unfocus(); }, ); }, ), ); }, ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _isInviting ? null : () => Navigator.pop(context), 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: _isInviting ? null : () async { final email = _autoCompleteCtrl.text.trim(); if (email.isNotEmpty && email.contains('@')) { setState(() => _isInviting = true); try { await ref.read(walletProvider.notifier).inviteUser(widget.wallet.id, email); if (context.mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('User invited!'))); } } catch (e) { if (context.mounted) { setState(() => _isInviting = false); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString().replaceAll('Exception: ', '')))); } } } }, 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: _isInviting ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Text('Invite', style: TextStyle(fontWeight: FontWeight.bold)), ) ], ), const SizedBox(height: 24), const Text('Existing Members', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 12), _isLoadingMembers ? const Center(child: CircularProgressIndicator()) : _members.isEmpty ? const Text('No members found.', style: TextStyle(color: Colors.grey)) : ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _members.length, itemBuilder: (context, index) { final member = _members[index]; final isOwner = member.role == 'OWNER'; return ListTile( contentPadding: EdgeInsets.zero, leading: CircleAvatar(backgroundColor: Colors.grey.shade200, child: const Icon(LucideIcons.user, color: Colors.grey)), title: Text(member.email, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), subtitle: Text(member.role, style: TextStyle(color: isOwner ? Colors.blue : Colors.grey, fontSize: 12)), trailing: !isOwner ? IconButton( icon: const Icon(LucideIcons.userMinus, color: Colors.red, size: 20), onPressed: () => _removeMember(member), ) : null, ); }, ), ], ), ), ), ), ); } }