Files
Kifi/kifi-app/lib/features/dashboard/presentation/dashboard_screen.dart
2026-08-24 18:38:46 +05:30

720 lines
34 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import '../../transactions/presentation/add_transaction_screen.dart';
import '../../transactions/presentation/all_transactions_screen.dart';
import '../../transactions/providers/providers.dart';
import '../../auth/presentation/profile_screen.dart';
import '../../transactions/data/models.dart';
import '../../budget/presentation/budget_screen.dart';
import '../providers/insight_provider.dart';
import '../../sales/providers/invoices_provider.dart';
import '../../sales/providers/customers_provider.dart';
import '../../sales/domain/invoice.dart';
import '../../sales/presentation/customers_list_screen.dart';
import 'widgets/swipeable_account_card.dart';
import 'widgets/budget_status_card.dart';
import '../../inventory/presentation/daily_rates_screen.dart';
import 'widgets/upcoming_dues_widget.dart';
import 'widgets/statistics_tab.dart';
import '../../../core/widgets/shimmer_loading.dart';
import '../../../core/theme/nature_colors.dart';
import 'wallet_ledger_screen.dart';
import 'maturity_dialog.dart';
import 'accounts_screen.dart';
import '../../business/providers/business_mode_provider.dart';
import '../../business/presentation/hub/business_hub_screen.dart';
class DashboardScreen extends ConsumerStatefulWidget {
const DashboardScreen({super.key});
@override
ConsumerState<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends ConsumerState<DashboardScreen> {
int _currentIndex = 0;
String _selectedFilter = 'All Time';
DateTimeRange? _customDateRange;
Timer? _notificationTimer;
final List<String> _filters = ['Today', 'Last 3 Days', 'Week', 'Month', 'Year', 'All Time', 'Custom'];
@override
void initState() {
super.initState();
_notificationTimer = Timer.periodic(const Duration(minutes: 2), (_) {
ref.invalidate(invitationProvider);
});
}
@override
void dispose() {
_notificationTimer?.cancel();
super.dispose();
}
Future<void> _onRefresh() async {
ref.invalidate(transactionProvider);
ref.invalidate(walletProvider);
ref.invalidate(categoryProvider);
ref.invalidate(budgetProvider);
ref.invalidate(invitationProvider);
ref.invalidate(invoicesProvider);
ref.invalidate(customersProvider);
await Future.delayed(const Duration(milliseconds: 500));
}
DateTimeRange _getDateRange(List<Transaction> all) {
if (_selectedFilter == 'All Time') {
DateTime start = DateTime(2000);
if (all.isNotEmpty) {
start = all.map((t) => t.date).reduce((a, b) => a.isBefore(b) ? a : b);
}
return DateTimeRange(start: start, end: DateTime.now());
}
final now = DateTime.now();
DateTime start;
DateTime end = now;
switch (_selectedFilter) {
case 'Today':
start = DateTime(now.year, now.month, now.day);
break;
case 'Last 3 Days':
start = now.subtract(const Duration(days: 3));
break;
case 'Week':
start = now.subtract(const Duration(days: 7));
break;
case 'Month':
start = DateTime(now.year, now.month - 1, now.day);
break;
case 'Year':
start = DateTime(now.year - 1, now.month, now.day);
break;
case 'Custom':
if (_customDateRange != null) {
start = _customDateRange!.start;
end = _customDateRange!.end;
} else {
start = DateTime(2000);
if (all.isNotEmpty) {
start = all.map((t) => t.date).reduce((a, b) => a.isBefore(b) ? a : b);
}
}
break;
default:
start = DateTime(2000);
}
return DateTimeRange(start: start, end: end);
}
List<Transaction> _filterTransactions(List<Transaction> all) {
if (_selectedFilter == 'All Time' || (_selectedFilter == 'Custom' && _customDateRange == null)) return all;
final range = _getDateRange(all);
final start = range.start;
final end = range.end;
return all.where((t) {
final d = t.date;
return d.isAfter(start.subtract(const Duration(seconds: 1))) &&
d.isBefore(end.add(const Duration(days: 1)));
}).toList();
}
Future<void> _pickCustomDateRange() async {
final picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
_customDateRange = picked;
_selectedFilter = 'Custom';
});
}
}
@override
Widget build(BuildContext context) {
final transState = ref.watch(transactionProvider);
final categoriesState = ref.watch(categoryProvider);
final insight = ref.watch(insightProvider);
final walletsState = ref.watch(walletProvider);
final invoicesState = ref.watch(invoicesProvider);
final customersState = ref.watch(customersProvider);
final isBusinessMode = ref.watch(businessModeProvider);
final allTransactions = transState.value ?? [];
final range = _getDateRange(allTransactions);
final startDate = range.start;
final endDate = range.end;
String title;
if (_currentIndex == 0) {
title = 'Dashboard';
} else if (isBusinessMode) {
if (_currentIndex == 1) title = 'Business Hub';
else if (_currentIndex == 2) title = 'Statistics';
else if (_currentIndex == 3) title = 'My Accounts';
else title = 'Budgets';
} else {
if (_currentIndex == 1) title = 'Statistics';
else if (_currentIndex == 2) title = 'My Accounts';
else title = 'Budgets';
}
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
if (isBusinessMode)
IconButton(
icon: const Icon(LucideIcons.trendingUp),
tooltip: 'Daily Commodity Rates',
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DailyRatesScreen()),
);
},
),
Consumer(
builder: (context, ref, child) {
final invitationsState = ref.watch(invitationProvider);
final pendingInvites = invitationsState.value?.where((inv) => inv.status == 'PENDING').toList() ?? [];
return PopupMenuButton<String>(
icon: Stack(
children: [
const Icon(LucideIcons.bell),
if (pendingInvites.isNotEmpty)
Positioned(
right: 0,
top: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: const BoxConstraints(minWidth: 12, minHeight: 12),
child: Text(
'${pendingInvites.length}',
style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
)
],
),
offset: const Offset(0, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
itemBuilder: (context) {
if (pendingInvites.isEmpty) {
return [
const PopupMenuItem(
enabled: false,
child: Text('No new notifications'),
)
];
}
return pendingInvites.map((inv) => PopupMenuItem<String>(
enabled: false,
child: Container(
width: 250,
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('You have been invited to a wallet by User ${inv.inviterId}', style: const TextStyle(fontSize: 14)),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
ref.read(invitationProvider.notifier).rejectInvitation(inv.id);
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Reject'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
ref.read(invitationProvider.notifier).acceptInvitation(inv.id);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C63FF),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text('Accept'),
),
],
)
],
),
),
)).toList();
},
);
}
),
IconButton(
icon: const Icon(LucideIcons.user),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => const ProfileScreen()));
},
),
],
),
body: transState.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: (allTransactions) {
final transactions = _filterTransactions(allTransactions);
final totalIncome = transactions.where((t) => t.type == 'INCOME').fold(0.0, (s, t) => s + t.amount);
final totalExpense = transactions.where((t) => t.type == 'EXPENSE').fold(0.0, (s, t) => s + t.amount);
final totalInvestment = transactions.where((t) => t.type == 'INVESTMENT').fold(0.0, (s, t) => s + t.amount);
double totalPayablesPeriod = 0.0;
double totalReceivablesPeriod = 0.0;
final safeWallets = walletsState.hasValue ? walletsState.value! : <Wallet>[];
for (var t in transactions) {
if (t.toWalletId != null) {
final w = safeWallets.where((w) => w.id == t.toWalletId);
if (w.isNotEmpty) {
final nature = w.first.nature;
if (nature == 'PAYABLES' || nature == 'LOAN') {
totalPayablesPeriod += t.amount;
} else if (nature == 'RECEIVABLES' || nature == 'LENDING') {
totalReceivablesPeriod += t.amount;
}
}
}
}
double cashBalance = 0;
double expenseBalance = 0;
double savingsBalance = 0;
double investmentsBalance = 0;
double payablesBalance = 0;
double receivablesBalance = 0;
if (walletsState.hasValue) {
for (var w in walletsState.value!) {
final nature = w.nature ?? 'CASH';
if (nature == 'CASH') {
cashBalance += w.balance;
} else if (nature == 'EXPENSE') {
expenseBalance += w.balance;
} else if (nature == 'SAVINGS' || nature == 'INCOME') {
savingsBalance += w.balance;
} else if (nature == 'INVESTMENTS') {
investmentsBalance += w.balance;
} else if (nature == 'LOAN' || nature == 'PAYABLES') {
payablesBalance += w.balance;
} else if (nature == 'LENDING') {
receivablesBalance += w.balance;
}
}
}
// Process invoices to get total pending
double totalPendingInvoices = 0;
Map<int, double> pendingByCustomer = {};
if (invoicesState.hasValue && customersState.hasValue) {
final invoices = invoicesState.value!;
for (var inv in invoices) {
if (inv.status != 'PAID' && inv.status != 'CANCELLED' && inv.customerId != null) {
double paidAmount = inv.amountPaid ?? 0;
double pendingAmount = inv.totalAmount - paidAmount;
if (pendingAmount > 0) {
pendingByCustomer[inv.customerId!] = (pendingByCustomer[inv.customerId!] ?? 0) + pendingAmount;
totalPendingInvoices += pendingAmount;
}
}
}
}
receivablesBalance += totalPendingInvoices;
// Build Payables Carousel Items
List<CarouselItemData> payablesItems = [];
if (walletsState.hasValue) {
final payableWallets = walletsState.value!.where((w) => w.nature == 'PAYABLES' || w.nature == 'LOAN').toList();
for (var w in payableWallets) {
if (w.balance > 0) {
payablesItems.add(CarouselItemData(
title: 'To: ${w.name}',
amount: w.balance,
color: NatureColors.getColor('PAYABLES'),
icon: LucideIcons.userMinus,
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => WalletLedgerScreen(wallet: w)));
}
));
}
}
}
// Build Receivables Carousel Items
List<CarouselItemData> receivablesItems = [];
if (invoicesState.hasValue && customersState.hasValue) {
final customers = customersState.value!;
pendingByCustomer.forEach((custId, amount) {
if (amount > 0) {
final customerName = customers.where((c) => c.id == custId).firstOrNull?.name ?? 'Unknown Customer';
receivablesItems.add(CarouselItemData(
title: 'From: $customerName',
amount: amount,
color: NatureColors.getColor('RECEIVABLES'),
icon: LucideIcons.userPlus,
onTap: () {
// Navigate to customer details or invoices in the future
}
));
}
});
}
return IndexedStack(
index: _currentIndex,
children: [
// ---------------- HOME TAB ----------------
RefreshIndicator(
onRefresh: _onRefresh,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (insight != null) ...[
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: insight.type == 'WARNING' ? Colors.orange.shade50 : (insight.type == 'SUCCESS' ? Colors.green.shade50 : Colors.blue.shade50),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: insight.type == 'WARNING' ? Colors.orange : (insight.type == 'SUCCESS' ? Colors.green : Colors.blue), width: 1),
),
child: Row(
children: [
Icon(
insight.type == 'WARNING' ? LucideIcons.alertTriangle : (insight.type == 'SUCCESS' ? LucideIcons.checkCircle : LucideIcons.info),
color: insight.type == 'WARNING' ? Colors.orange : (insight.type == 'SUCCESS' ? Colors.green : Colors.blue),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(insight.title, style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(insight.message, style: const TextStyle(fontSize: 12)),
],
),
),
],
),
),
const SizedBox(height: 24),
],
const BudgetStatusCard(),
const SizedBox(height: 24),
// Summary Cards Grid
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1.5,
children: [
_buildSummaryCard(context, 'Balance', cashBalance, NatureColors.getColor('BALANCE'), LucideIcons.wallet, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen(initialFilterNature: 'CASH')))),
_buildSummaryCard(context, 'Expense', expenseBalance, NatureColors.getColor('EXPENSE'), LucideIcons.arrowUpRight, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen(initialFilterNature: 'EXPENSE')))),
_buildSummaryCard(context, 'Savings', savingsBalance, NatureColors.getColor('SAVINGS'), LucideIcons.piggyBank, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen(initialFilterNature: 'SAVINGS')))),
_buildSummaryCard(context, 'Investments', investmentsBalance, NatureColors.getColor('INVESTMENTS'), LucideIcons.trendingUp, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen(initialFilterNature: 'INVESTMENTS')))),
_buildSummaryCard(context, 'Total Payables', payablesBalance, NatureColors.getColor('PAYABLES'), LucideIcons.alertCircle, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen(initialFilterNature: 'PAYABLES')))),
_buildSummaryCard(context, 'Total Receivables', receivablesBalance, NatureColors.getColor('RECEIVABLES'), LucideIcons.arrowDownLeft, onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => const CustomersListScreen()));
}),
],
),
const SizedBox(height: 24),
const UpcomingDuesWidget(),
if (payablesItems.isNotEmpty) ...[
const SizedBox(height: 24),
Text('Upcoming Payables', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
SwipeableAccountCard(items: payablesItems),
],
if (receivablesItems.isNotEmpty) ...[
const SizedBox(height: 24),
Text('Upcoming Receivables', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
SwipeableAccountCard(items: receivablesItems),
],
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Recent Transactions', style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold)),
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => const AllTransactionsScreen()));
},
child: const Text('See All'),
),
],
),
const SizedBox(height: 8),
allTransactions.isEmpty
? const Center(child: Padding(padding: EdgeInsets.all(16), child: Text('No transactions yet!')))
: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: allTransactions.length > 5 ? 5 : allTransactions.length,
itemBuilder: (context, index) {
final t = allTransactions[index];
final isIncome = t.type == 'INCOME' || (t.type == 'TRANSFER' && t.fromWalletId == null);
final isInvestment = t.type == 'INVESTMENT';
final isExpense = t.type == 'EXPENSE' || (t.type == 'TRANSFER' && t.toWalletId == null);
final isTransfer = t.type == 'TRANSFER' && t.fromWalletId != null && t.toWalletId != null;
Color typeColor = Colors.grey;
IconData typeIcon = LucideIcons.arrowRightLeft;
if (isIncome) {
typeColor = NatureColors.getColor('INCOME');
typeIcon = LucideIcons.arrowDownCircle;
} else if (isExpense) {
typeColor = NatureColors.getColor('EXPENSE');
typeIcon = LucideIcons.arrowUpCircle;
} else if (isInvestment) {
typeColor = NatureColors.getColor('INVESTMENTS');
typeIcon = LucideIcons.trendingUp;
} else if (isTransfer) {
typeColor = NatureColors.getColor('TRANSFER');
if (walletsState.hasValue) {
final toW = walletsState.value!.where((w) => w.id == t.toWalletId).firstOrNull;
final fromW = walletsState.value!.where((w) => w.id == t.fromWalletId).firstOrNull;
if (toW != null && (toW.nature == 'PAYABLES' || toW.nature == 'LOAN')) {
typeColor = NatureColors.getColor('PAYABLES');
typeIcon = LucideIcons.alertCircle;
} else if (toW != null && (toW.nature == 'RECEIVABLES' || toW.nature == 'LENDING')) {
typeColor = NatureColors.getColor('RECEIVABLES');
typeIcon = LucideIcons.arrowDownLeft;
} else if (fromW != null && (fromW.nature == 'PAYABLES' || fromW.nature == 'LOAN')) {
typeColor = NatureColors.getColor('PAYABLES');
typeIcon = LucideIcons.alertCircle;
} else if (fromW != null && (fromW.nature == 'RECEIVABLES' || fromW.nature == 'LENDING')) {
typeColor = NatureColors.getColor('RECEIVABLES');
typeIcon = LucideIcons.arrowDownLeft;
}
}
}
String categoryName = 'Unknown';
if (categoriesState.hasValue) {
final match = categoriesState.value!.where((c) => c.id == t.categoryId);
if (match.isNotEmpty) categoryName = match.first.name;
}
String accountName = 'Unknown';
if (walletsState.hasValue) {
if (isExpense && t.toWalletId != null) {
final match = walletsState.value!.where((w) => w.id == t.toWalletId);
if (match.isNotEmpty) accountName = match.first.name;
} else if (isIncome && (t.toWalletId != null)) {
final match = walletsState.value!.where((w) => w.id == t.toWalletId);
if (match.isNotEmpty) accountName = match.first.name;
} else if (isTransfer && t.toWalletId != null) {
final match = walletsState.value!.where((w) => w.id == t.toWalletId);
if (match.isNotEmpty) accountName = 'To ${match.first.name}';
}
}
String fromName = '';
if (t.fromWalletId != null && walletsState.hasValue) {
final match = walletsState.value!.where((w) => w.id == t.fromWalletId);
if (match.isNotEmpty) fromName = match.first.name;
}
String toName = '';
if (t.toWalletId != null && walletsState.hasValue) {
final match = walletsState.value!.where((w) => w.id == t.toWalletId);
if (match.isNotEmpty) toName = match.first.name;
}
String subtitleText = '';
if (categoryName == 'Unknown' && fromName.isNotEmpty && toName.isNotEmpty) {
subtitleText = '$fromName$toName${DateFormat('MMM dd, yyyy').format(t.date)}';
} else if (categoryName == 'Unknown' && toName.isNotEmpty) {
subtitleText = 'To $toName${DateFormat('MMM dd, yyyy').format(t.date)}';
} else if (categoryName == 'Unknown' && fromName.isNotEmpty) {
subtitleText = 'From $fromName${DateFormat('MMM dd, yyyy').format(t.date)}';
} else {
subtitleText = '$categoryName${DateFormat('MMM dd, yyyy').format(t.date)}${fromName.isNotEmpty ? ' • 💼 $fromName' : ''}';
}
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => AddTransactionScreen(transaction: t)));
},
leading: CircleAvatar(
backgroundColor: typeColor.withOpacity(0.1),
child: Icon(typeIcon, color: typeColor),
),
title: Text((t.description != null && t.description!.isNotEmpty) ? t.description! : accountName, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(subtitleText),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${isIncome ? '+' : (isTransfer ? '' : '-')}Rs. ${t.amount.toStringAsFixed(0)}',
style: TextStyle(fontWeight: FontWeight.bold, color: typeColor),
),
if (t.investmentStatus == 'OPEN' && (typeColor == NatureColors.getColor('INVESTMENTS') || typeColor == NatureColors.getColor('RECEIVABLES')))
GestureDetector(
onTap: () {
showDialog(context: context, builder: (_) => MaturityDialog(transaction: t));
},
child: const Padding(
padding: EdgeInsets.only(top: 4.0),
child: Text('Close', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 12)),
),
),
],
),
),
);
},
)
],
),
),
),
// ---------------- BUSINESS TAB ----------------
if (isBusinessMode) const BusinessHubScreen(),
// ---------------- STATS TAB ----------------
StatisticsTab(
transactions: transactions,
wallets: safeWallets,
categories: categoriesState.value ?? [],
selectedFilter: _selectedFilter,
startDate: startDate,
endDate: endDate,
filters: _filters,
onFilterChanged: (val) {
setState(() => _selectedFilter = val);
},
onPickCustomDateRange: _pickCustomDateRange,
onRefresh: _onRefresh,
),
// ---------------- WALLETS TAB ----------------
const AccountsScreen(),
// ---------------- BUDGETS TAB ----------------
const BudgetScreen(),
],
);
},
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: isBusinessMode
? (_currentIndex >= 3 ? _currentIndex + 1 : _currentIndex)
: (_currentIndex >= 2 ? _currentIndex + 1 : _currentIndex),
type: BottomNavigationBarType.fixed,
onTap: (index) {
final addIndex = isBusinessMode ? 3 : 2;
if (index == addIndex) {
Navigator.push(context, MaterialPageRoute(builder: (_) => const AddTransactionScreen()));
} else {
setState(() {
_currentIndex = index > addIndex ? index - 1 : index;
});
}
},
selectedItemColor: Theme.of(context).colorScheme.primary,
unselectedItemColor: Colors.grey,
showSelectedLabels: true,
showUnselectedLabels: true,
items: [
const BottomNavigationBarItem(icon: Icon(LucideIcons.home), label: 'Home'),
if (isBusinessMode)
const BottomNavigationBarItem(icon: Icon(LucideIcons.briefcase), label: 'Business'),
const BottomNavigationBarItem(icon: Icon(LucideIcons.pieChart), label: 'Stats'),
const BottomNavigationBarItem(icon: Icon(LucideIcons.plusCircle), label: 'Add'),
const BottomNavigationBarItem(icon: Icon(LucideIcons.wallet), label: 'Wallets'),
const BottomNavigationBarItem(icon: Icon(LucideIcons.target), label: 'Budgets'),
],
),
);
}
Widget _buildSummaryCard(BuildContext context, String title, double amount, Color color, IconData icon, {VoidCallback? onTap}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.2)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 8),
Text(title, style: TextStyle(color: color, fontWeight: FontWeight.w600)),
],
),
Text(
'Rs. ${amount.toStringAsFixed(0)}',
style: TextStyle(color: color, fontSize: 20, fontWeight: FontWeight.bold),
),
],
),
),
);
}
}