Skip to content

Commit

Permalink
Merge pull request #163 from K-w-e/feature/budget_dashboard
Browse files Browse the repository at this point in the history
Feature/budget dashboard
  • Loading branch information
theperu authored Apr 11, 2024
2 parents ddfa27e + 1571680 commit 98e9e39
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 57 deletions.
7 changes: 6 additions & 1 deletion lib/custom_widgets/budget_circular_indicator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ class BudgetCircularIndicator extends ConsumerWidget with Functions {
progressColor: color,
),
const SizedBox(height: 10),
Text(title, style: Theme.of(context).textTheme.labelMedium),
Row(
children: [
perc >= 0.9 ? const Icon(Icons.error_outline, color: Colors.red, size: 15) : Container(),
const SizedBox(width: 3),
Text(title, style: Theme.of(context).textTheme.bodyLarge),
])
],
);
}
Expand Down
36 changes: 34 additions & 2 deletions lib/model/budget.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '../model/transaction.dart';
import '../database/sossoldi_database.dart';
import '../model/category_transaction.dart';
import 'base_entity.dart';
Expand Down Expand Up @@ -78,6 +79,28 @@ class Budget extends BaseEntity {
};
}

class BudgetStats extends BaseEntity {
final int idCategory;
final String? name;
final num amountLimit;
final num spent;

BudgetStats({required this.idCategory, required this.name, required this.amountLimit, required this.spent});

static BudgetStats fromJson(Map<String, Object?> json) => BudgetStats(
idCategory: json[BudgetFields.idCategory] as int,
name: json[BudgetFields.name] as String?,
amountLimit: json[BudgetFields.amountLimit] as num,
spent: json['spent'] as num);

Map<String, Object?> toJson() => {
BudgetFields.idCategory: idCategory,
BudgetFields.name: name,
BudgetFields.amountLimit: amountLimit,
'spent': spent
};
}

class BudgetMethods extends SossoldiDatabase {
Future<Budget> insert(Budget item) async {
final db = await database;
Expand All @@ -90,10 +113,8 @@ class BudgetMethods extends SossoldiDatabase {

final exists = await checkIfExists(item);
if (exists) {
print("UPDATE ${item.toJson()}");
await db.rawQuery("UPDATE $budgetTable SET amountLimit = ${item.amountLimit} WHERE idCategory = ${item.idCategory}");
} else {
print("INSERT ${item.toJson()}");
await db.insert(budgetTable, item.toJson());
}

Expand Down Expand Up @@ -147,6 +168,17 @@ class BudgetMethods extends SossoldiDatabase {
return result.map((json) => Budget.fromJson(json)).toList();
}

Future<List<BudgetStats>> selectMonthlyBudgetsStats() async {
final db = await database;
var query = "SELECT bt.*, SUM(t.amount) as spent FROM $budgetTable as bt "
+ " LEFT JOIN $categoryTransactionTable as ct ON bt.${BudgetFields.idCategory} = ct.${CategoryTransactionFields.id} "
+ " LEFT JOIN '$transactionTable' as t ON t.${TransactionFields.idCategory} = ct.${CategoryTransactionFields.id} "
+ " WHERE bt.active = 1 AND strftime('%m', t.date) = strftime('%m', 'now') AND strftime('%Y', t.date) = strftime('%Y', 'now') "
+ " GROUP BY bt.${BudgetFields.idCategory};";
final result = await db.rawQuery(query);
return result.map((json) => BudgetStats.fromJson(json)).toList();
}

Future<int> updateItem(Budget item) async {
final db = await database;

Expand Down
41 changes: 2 additions & 39 deletions lib/pages/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../pages/home_widget/budgets_home.dart';
import '../constants/functions.dart';
import '../constants/style.dart';
import '../custom_widgets/accounts_sum.dart';
import '../custom_widgets/budget_circular_indicator.dart';
import '../custom_widgets/line_chart.dart';
import '../custom_widgets/transactions_list.dart';
import '../model/bank_account.dart';
Expand Down Expand Up @@ -290,44 +290,7 @@ class _HomePageState extends ConsumerState<HomePage> with Functions {
error: (err, stack) => Text('Error: $err'),
),
const SizedBox(height: 28),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
"Your budgets",
style: Theme.of(context).textTheme.titleLarge,
),
),
),
const SizedBox(height: 16),
Container(
margin: const EdgeInsets.only(left: 16.0, right: 16.0),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BudgetCircularIndicator(
title: "TOTALE",
amount: 320,
perc: 0.25,
color: Color(0xFFEBC35F),
),
BudgetCircularIndicator(
title: "SPESE",
amount: 500,
perc: 0.5,
color: Color(0xFFD336B6),
),
BudgetCircularIndicator(
title: "SVAGO",
amount: 178.67,
perc: 0.88,
color: Color(0xFF8E5FEB),
),
],
),
),
const SizedBox(height: 50),
BudgetsSection()
],
),
),
Expand Down
78 changes: 78 additions & 0 deletions lib/pages/home_widget/budgets_home.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../constants/constants.dart';

import '../../constants/functions.dart';
import '../../custom_widgets/budget_circular_indicator.dart';
import '../../model/budget.dart';
import '../../providers/budgets_provider.dart';

class BudgetsSection extends ConsumerStatefulWidget {
const BudgetsSection({super.key});

@override
ConsumerState<BudgetsSection> createState() => _BudgetsSectionState();
}

class _BudgetsSectionState extends ConsumerState<BudgetsSection> with Functions {
@override
Widget build(BuildContext context) {
final budgets =ref.watch(budgetsProvider.notifier).getMonthlyBudgetsStats();

return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FutureBuilder<List<BudgetStats>>(
future: budgets,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final budgets = snapshot.data;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
"Your budgets",
style: Theme.of(context).textTheme.titleLarge,
),
),
),
const SizedBox(height: 16),
SizedBox(
height: 150,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: budgets!.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 13),
child: BudgetCircularIndicator(
title: budgets[index].name!,
amount: budgets[index].amountLimit - budgets[index].spent > 0 ? budgets[index].amountLimit - budgets[index].spent : 0,
perc: budgets[index].spent / budgets[index].amountLimit > 1 ? 1 : budgets[index].spent / budgets[index].amountLimit,
color: categoryColorList[index % categoryColorList.length],
),
),
),
),
],
);
}
},
),
],
),
const SizedBox(height: 50)
],
);
}
}
27 changes: 12 additions & 15 deletions lib/pages/planning_page/widget/budget_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../../../model/budget.dart';
import '../../../model/transaction.dart';
import '../../../providers/budgets_provider.dart';

import '../../../providers/currency_provider.dart';
import '../../../providers/transactions_provider.dart';
import '../manage_budget_page.dart';

Expand All @@ -24,8 +25,8 @@ class _BudgetCardState extends ConsumerState<BudgetCard> {
Widget build(BuildContext context) {

final budgets = ref.watch(budgetsProvider.notifier).getBudgets();
final transactions =
ref.watch(transactionsProvider.notifier).getMonthlyTransactions();
final transactions = ref.watch(transactionsProvider.notifier).getMonthlyTransactions();
final currencyState = ref.watch(currencyStateNotifier);

return Container(
decoration: BoxDecoration(
Expand All @@ -50,23 +51,20 @@ class _BudgetCardState extends ConsumerState<BudgetCard> {
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Composition",
style: Theme.of(context).textTheme.titleLarge),
Text("Composition", style: Theme.of(context).textTheme.titleLarge),
BudgetPieChart(budgets: budgets as List<Budget>),
Text("Progress",
style: Theme.of(context).textTheme.titleLarge),
Text("Progress", style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 10),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: budgets.length,
itemBuilder: (BuildContext context, int index) {
int spent = (transactions as List<Transaction>)
.where((t) =>
t.idCategory ==
budgets[index].idCategory)
.fold(
0, (sum, t) => sum + t.amount.toInt());
num spent = num.parse(
(transactions as List<Transaction>)
.where((t) => t.idCategory == budgets[index].idCategory)
.fold(0.0, (sum, t) => sum + t.amount)
.toStringAsFixed(2));
Budget budget = budgets.elementAt(index);
return Column(
children: [
Expand All @@ -80,7 +78,7 @@ class _BudgetCardState extends ConsumerState<BudgetCard> {
const Spacer(),
spent >= (budget.amountLimit * 0.9) ? const Icon(Icons.error_outline, color: Colors.red) : Container(),
Text(
"$spent/${budget.amountLimit}",
"${spent}${currencyState.selectedCurrency.symbol}/${budget.amountLimit}${currencyState.selectedCurrency.symbol}",
style: const TextStyle(
fontWeight: FontWeight.normal),
),
Expand All @@ -106,8 +104,7 @@ class _BudgetCardState extends ConsumerState<BudgetCard> {
],
);
},
separatorBuilder:
(BuildContext context, int index) {
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(height: 15);
},
),
Expand Down
5 changes: 5 additions & 0 deletions lib/providers/budgets_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class AsyncBudgetsNotifier extends AsyncNotifier<List<Budget>> {
return budgets;
}

Future<List<BudgetStats>> getMonthlyBudgetsStats() async {
final budgets = await BudgetMethods().selectMonthlyBudgetsStats();
return budgets;
}

Future<void> addBudget(Budget budget) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
Expand Down

0 comments on commit 98e9e39

Please sign in to comment.