Files
Kifi/kifi-app/lib/features/dashboard/presentation/wallet_ledger_screen.dart

355 lines
12 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,
),
),
),
),
],
);
},
),
);
}
}