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

225 lines
9.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../transactions/data/models.dart';
import '../../../../core/theme/nature_colors.dart';
import 'day_wise_spending_chart.dart';
import 'category_spending_chart.dart';
class StatisticsTab extends StatefulWidget {
final List<Transaction> transactions;
final List<Wallet> wallets;
final List<Category> categories;
final String selectedFilter;
final DateTime startDate;
final DateTime endDate;
final VoidCallback onPickCustomDateRange;
final Function(String) onFilterChanged;
final Future<void> Function() onRefresh;
final List<String> filters;
const StatisticsTab({
super.key,
required this.transactions,
required this.wallets,
required this.categories,
required this.selectedFilter,
required this.startDate,
required this.endDate,
required this.onPickCustomDateRange,
required this.onFilterChanged,
required this.onRefresh,
required this.filters,
});
@override
State<StatisticsTab> createState() => _StatisticsTabState();
}
class _StatisticsTabState extends State<StatisticsTab> {
String _selectedNature = 'EXPENSE';
Widget _buildTotalCard(String title, double amount, Color color, IconData icon) {
return Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.grey.shade200)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
CircleAvatar(backgroundColor: color.withOpacity(0.1), radius: 16, child: Icon(icon, color: color, size: 16)),
const SizedBox(width: 8),
Expanded(child: Text(title, style: const TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis)),
],
),
const SizedBox(height: 12),
Text('Rs. ${amount.toStringAsFixed(0)}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color)),
],
),
),
);
}
@override
Widget build(BuildContext context) {
// Compute totals
final totalIncome = widget.transactions.where((t) => t.type == 'INCOME' || (t.type == 'TRANSFER' && t.fromWalletId == null)).fold(0.0, (s, t) => s + t.amount);
final totalExpense = widget.transactions.where((t) => t.type == 'EXPENSE' || (t.type == 'TRANSFER' && t.toWalletId == null)).fold(0.0, (s, t) => s + t.amount);
final totalInvestment = widget.transactions.where((t) => t.type == 'INVESTMENT').fold(0.0, (s, t) => s + t.amount);
double totalPayablesPeriod = 0.0;
double totalReceivablesPeriod = 0.0;
for (var t in widget.transactions) {
if (t.toWalletId != null) {
final w = widget.wallets.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;
}
}
}
}
Color barColor = NatureColors.getColor(_selectedNature);
return RefreshIndicator(
onRefresh: widget.onRefresh,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: widget.filters.map((f) {
final isSelected = widget.selectedFilter == f;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(f),
selected: isSelected,
onSelected: (val) {
if (val) {
if (f == 'Custom') {
widget.onPickCustomDateRange();
} else {
widget.onFilterChanged(f);
}
}
},
),
);
}).toList(),
),
),
if (widget.selectedFilter == 'Custom')
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
'${widget.startDate.day} ${widget.startDate.month} - ${widget.endDate.day} ${widget.endDate.month}',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Analytics', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
decoration: BoxDecoration(
color: barColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: DropdownButton<String>(
value: _selectedNature,
underline: const SizedBox(),
icon: Icon(Icons.arrow_drop_down, color: barColor),
style: TextStyle(color: barColor, fontWeight: FontWeight.bold),
items: const [
DropdownMenuItem(value: 'EXPENSE', child: Text('Expense')),
DropdownMenuItem(value: 'INCOME', child: Text('Income')),
DropdownMenuItem(value: 'PAYABLES', child: Text('Payables')),
DropdownMenuItem(value: 'RECEIVABLES', child: Text('Receivables')),
DropdownMenuItem(value: 'INVESTMENTS', child: Text('Investments')),
],
onChanged: (val) {
if (val != null) {
setState(() => _selectedNature = val);
}
},
),
),
],
),
const SizedBox(height: 24),
if (widget.transactions.isEmpty)
Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24), side: BorderSide(color: Colors.grey.shade200)),
child: const Padding(
padding: EdgeInsets.all(32.0),
child: Center(child: Text('No transactions in this period.', style: TextStyle(color: Colors.grey))),
),
)
else ...[
DayWiseSpendingChart(
transactions: widget.transactions,
wallets: widget.wallets,
selectedNature: _selectedNature,
startDate: widget.startDate,
endDate: widget.endDate,
),
const SizedBox(height: 16),
CategorySpendingChart(
transactions: widget.transactions,
categories: widget.categories,
selectedNature: _selectedNature,
),
],
const SizedBox(height: 32),
const Text('Summary Totals', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1.5,
children: [
_buildTotalCard('Income', totalIncome, NatureColors.getColor('INCOME'), LucideIcons.arrowDown),
_buildTotalCard('Expense', totalExpense, NatureColors.getColor('EXPENSE'), LucideIcons.arrowUp),
_buildTotalCard('Investment', totalInvestment, NatureColors.getColor('INVESTMENTS'), LucideIcons.trendingUp),
_buildTotalCard('Payables', totalPayablesPeriod, NatureColors.getColor('PAYABLES'), LucideIcons.alertCircle),
_buildTotalCard('Receivables', totalReceivablesPeriod, NatureColors.getColor('RECEIVABLES'), LucideIcons.arrowDownLeft),
],
),
const SizedBox(height: 32),
],
),
),
],
),
),
);
}
}