Files
Kifi/kifi-app/lib/features/dashboard/presentation/wallet_ledger_screen.dart
2026-08-16 20:13:21 +05:30

250 lines
9.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../../transactions/providers/providers.dart';
import '../../transactions/data/models.dart';
import '../../../core/theme/nature_colors.dart';
class WalletLedgerScreen extends ConsumerStatefulWidget {
final Wallet wallet;
const WalletLedgerScreen({super.key, required this.wallet});
@override
ConsumerState<WalletLedgerScreen> createState() => _WalletLedgerScreenState();
}
class _WalletLedgerScreenState extends ConsumerState<WalletLedgerScreen> {
String _selectedFilter = 'All Time';
DateTimeRange? _customDateRange;
void _showFilterSheet() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
builder: (ctx) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Filter Ledger', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
ListTile(
title: const Text('Today'),
onTap: () {
setState(() => _selectedFilter = 'Today');
Navigator.pop(ctx);
},
),
ListTile(
title: const Text('Current Month'),
onTap: () {
setState(() => _selectedFilter = 'Current Month');
Navigator.pop(ctx);
},
),
ListTile(
title: const Text('All Time'),
onTap: () {
setState(() => _selectedFilter = 'All Time');
Navigator.pop(ctx);
},
),
ListTile(
title: const Text('Custom Range'),
onTap: () async {
Navigator.pop(ctx);
final range = await showDateRangePicker(
context: context,
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (range != null) {
setState(() {
_selectedFilter = 'Custom Range';
_customDateRange = range;
});
}
},
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
final transactionsState = ref.watch(transactionProvider);
final walletsState = ref.watch(walletProvider);
return Scaffold(
appBar: AppBar(
title: Text('${widget.wallet.name} Ledger'),
actions: [
IconButton(
icon: const Icon(Icons.filter_list),
onPressed: _showFilterSheet,
),
],
),
body: transactionsState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
data: (transactions) {
// 1. Filter all transactions related to this wallet
final walletTxs = transactions.where((t) => t.fromWalletId == widget.wallet.id || t.toWalletId == widget.wallet.id).toList();
// Sort chronologically (oldest first) to compute running balance
walletTxs.sort((a, b) => a.date.compareTo(b.date));
// 2. Determine date range
DateTime? startDate;
DateTime? endDate;
final now = DateTime.now();
if (_selectedFilter == 'Today') {
startDate = DateTime(now.year, now.month, now.day);
endDate = DateTime(now.year, now.month, now.day, 23, 59, 59);
} else if (_selectedFilter == 'Current Month') {
startDate = DateTime(now.year, now.month, 1);
endDate = DateTime(now.year, now.month + 1, 0, 23, 59, 59);
} else if (_selectedFilter == 'Custom Range' && _customDateRange != null) {
startDate = _customDateRange!.start;
endDate = DateTime(_customDateRange!.end.year, _customDateRange!.end.month, _customDateRange!.end.day, 23, 59, 59);
}
// 3. Calculate opening balance (sum of all transactions BEFORE start date)
double openingBalance = 0.0;
List<Transaction> visibleTxs = [];
for (var t in walletTxs) {
double impact = 0;
if (t.toWalletId == widget.wallet.id) {
impact = t.amount;
} else if (t.fromWalletId == widget.wallet.id) {
impact = -t.amount;
}
if (startDate != null && t.date.isBefore(startDate)) {
openingBalance += impact;
} else if (endDate != null && t.date.isAfter(endDate)) {
// skip
} else {
visibleTxs.add(t);
}
}
// 4. Generate Ledger Rows
double runningBalance = openingBalance;
List<DataRow> rows = [];
// Add Opening Balance Row if we have a date filter
if (startDate != null) {
rows.add(DataRow(
cells: [
const DataCell(Text('-')),
DataCell(Text(DateFormat('dd MMM yyyy').format(startDate))),
DataCell(Text('Rs. ${openingBalance.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold))),
const DataCell(Text('-')),
const DataCell(Text('-')),
DataCell(Text('Rs. ${runningBalance.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold))),
],
));
}
for (int i = 0; i < visibleTxs.length; i++) {
final t = visibleTxs[i];
bool isIncome = t.toWalletId == widget.wallet.id && t.fromWalletId == null;
bool isExpense = t.fromWalletId == widget.wallet.id && t.toWalletId == null;
bool isTransferIn = t.toWalletId == widget.wallet.id && t.fromWalletId != null;
bool isTransferOut = t.fromWalletId == widget.wallet.id && t.toWalletId != null;
double amount = t.amount;
if (isExpense || isTransferOut) {
runningBalance -= amount;
} else {
runningBalance += amount;
}
String fromName = '-';
String toName = '-';
if (isIncome || isTransferIn) {
if (t.fromWalletId != null && walletsState.hasValue) {
fromName = walletsState.value!.where((w) => w.id == t.fromWalletId).firstOrNull?.name ?? 'Unknown';
} else {
fromName = 'External';
}
toName = widget.wallet.name;
} else if (isExpense || isTransferOut) {
fromName = widget.wallet.name;
if (t.toWalletId != null && walletsState.hasValue) {
toName = walletsState.value!.where((w) => w.id == t.toWalletId).firstOrNull?.name ?? 'Unknown';
} else {
toName = 'External';
}
}
Color amountColor = (isExpense || isTransferOut) ? Colors.red : Colors.green.shade700;
String amountPrefix = (isExpense || isTransferOut) ? '-' : '+';
rows.add(DataRow(
cells: [
DataCell(Text('${startDate != null ? i + 1 : i + 1}')),
DataCell(Text(DateFormat('dd MMM yyyy').format(t.date))),
DataCell(Text('$amountPrefix Rs. ${amount.toStringAsFixed(2)}', style: TextStyle(color: amountColor, fontWeight: FontWeight.w600))),
DataCell(Text(fromName)),
DataCell(Text(toName)),
DataCell(Text('Rs. ${runningBalance.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold))),
],
));
}
if (rows.isEmpty) {
return const Center(child: Text('No transactions found for this period.'));
}
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Filter: $_selectedFilter', style: TextStyle(color: Colors.grey.shade600)),
Text('Closing Balance: Rs. ${runningBalance.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
],
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
headingRowColor: WidgetStateProperty.resolveWith((states) => Colors.grey.shade100),
columnSpacing: 24,
columns: const [
DataColumn(label: Text('S.No', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Date', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Amount', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('From', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('To', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Balance', style: TextStyle(fontWeight: FontWeight.bold))),
],
rows: rows,
),
),
),
),
],
);
},
),
);
}
}