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

347 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/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,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(isDark ? 0.1 : 0.2),
),
),
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: Theme.of(context).dividerColor.withOpacity(
Theme.of(context).brightness == Brightness.dark
? 0.1
: 0.2,
),
),
),
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),
],
),
),
],
),
),
);
}
}