Done Category, Product Catalogue, Customer, Vendor, Purchase Invoice Module

This commit is contained in:
2026-08-28 11:30:18 +05:30
parent 0d13833679
commit 189f49ed07
118 changed files with 12624 additions and 4004 deletions

View File

@@ -38,11 +38,21 @@ class StatisticsTab extends StatefulWidget {
class _StatisticsTabState extends State<StatisticsTab> {
String _selectedNature = 'EXPENSE';
Widget _buildTotalCard(String title, double amount, Color color, IconData icon) {
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))),
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(
@@ -51,13 +61,35 @@ class _StatisticsTabState extends State<StatisticsTab> {
children: [
Row(
children: [
CircleAvatar(backgroundColor: color.withOpacity(0.1), radius: 16, child: Icon(icon, color: color, size: 16)),
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)),
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)),
Text(
'Rs. ${amount.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
),
@@ -67,13 +99,27 @@ class _StatisticsTabState extends State<StatisticsTab> {
@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);
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);
@@ -124,13 +170,19 @@ class _StatisticsTabState extends State<StatisticsTab> {
),
if (widget.selectedFilter == 'Custom')
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
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),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -139,9 +191,18 @@ class _StatisticsTabState extends State<StatisticsTab> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Analytics', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const Text(
'Analytics',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 0,
),
decoration: BoxDecoration(
color: barColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
@@ -150,13 +211,31 @@ class _StatisticsTabState extends State<StatisticsTab> {
value: _selectedNature,
underline: const SizedBox(),
icon: Icon(Icons.arrow_drop_down, color: barColor),
style: TextStyle(color: barColor, fontWeight: FontWeight.bold),
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')),
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) {
@@ -168,19 +247,33 @@ class _StatisticsTabState extends State<StatisticsTab> {
],
),
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))),
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))),
child: Center(
child: Text(
'No transactions in this period.',
style: TextStyle(color: Colors.grey),
),
),
),
)
else ...[
DayWiseSpendingChart(
transactions: widget.transactions,
transactions: widget.transactions,
wallets: widget.wallets,
selectedNature: _selectedNature,
startDate: widget.startDate,
@@ -188,16 +281,19 @@ class _StatisticsTabState extends State<StatisticsTab> {
),
const SizedBox(height: 16),
CategorySpendingChart(
transactions: widget.transactions,
transactions: widget.transactions,
categories: widget.categories,
selectedNature: _selectedNature,
),
],
const SizedBox(height: 32),
const Text('Summary Totals', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const Text(
'Summary Totals',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12,
@@ -206,11 +302,36 @@ class _StatisticsTabState extends State<StatisticsTab> {
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),
_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),