225 lines
9.5 KiB
Dart
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),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|