Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/herald/app_design.dart';
5 : import 'package:app_finance/_classes/herald/app_locale.dart';
6 : import 'package:app_finance/_classes/herald/app_sorting.dart';
7 : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
8 : import 'package:app_finance/_classes/storage/app_data.dart';
9 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
10 : import 'package:app_finance/_classes/structure/budget_app_data.dart';
11 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
12 : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
13 : import 'package:app_finance/_configs/theme_helper.dart';
14 : import 'package:app_finance/_ext/build_context_ext.dart';
15 : import 'package:app_finance/_ext/date_time_ext.dart';
16 : import 'package:app_finance/_ext/double_ext.dart';
17 : import 'package:app_finance/components/widgets/budget_forecast_chart.dart';
18 : import 'package:app_finance/components/widgets/budget_ytd_chart.dart';
19 : import 'package:app_finance/design/generic/text_widget.dart';
20 : import 'package:app_finance/design/wrapper/table_widget.dart';
21 : import 'package:app_finance/design/wrapper/tap_widget.dart';
22 : import 'package:material_ui/material_ui.dart';
23 : import 'package:provider/provider.dart';
24 :
25 : class BudgetTab extends StatelessWidget {
26 : final AppData store;
27 :
28 0 : const BudgetTab({
29 : super.key,
30 : required this.store,
31 : });
32 :
33 0 : Widget _getField(BudgetAppData budget, List<BillAppData> bills, DateTime date) {
34 : Color? color;
35 : String? text = '-';
36 0 : if (budget.amountLimit > 0) {
37 0 : final ex = Exchange(store: store);
38 0 : double value = bills.fold(0.0, (v, e) => v + ex.reform(e.details, e.currency, ex.getDefaultCurrency()));
39 0 : value = 100 * value / ex.reform(budget.amountLimit, budget.currency, ex.getDefaultCurrency());
40 0 : text = '${value.toFixed(2)}%';
41 0 : if (value < 50) {
42 : color = Colors.green;
43 0 : } else if (value < 120) {
44 : color = Colors.yellow;
45 : } else {
46 : color = Colors.pink;
47 : }
48 : }
49 0 : return TapWidget(
50 0 : tooltip: '${budget.title}, ${date.fullMonth()}: $text',
51 0 : route: RouteSettings(
52 : name: AppRoute.billScopeViewRoute,
53 0 : arguments: {routeArguments.search: date.toString(), routeArguments.uuid: budget.uuid},
54 : ),
55 0 : child: Container(
56 : alignment: Alignment.centerRight,
57 0 : color: color?.withValues(alpha: 0.2),
58 0 : child: TextWidget(text),
59 : ),
60 : );
61 : }
62 :
63 0 : List<List<Widget>> _generateTable(int Function(dynamic, dynamic) fnSort) {
64 0 : final date = DateTime.now();
65 0 : final List<List<Widget>> result = [
66 0 : [
67 : const Icon(Icons.category),
68 0 : TextWidget(AppLocale.labels.title),
69 0 : Align(alignment: Alignment.centerRight, child: TextWidget(date.fullMonth())),
70 0 : Align(alignment: Alignment.centerRight, child: TextWidget(date.getPreviousMonth(1).fullMonth())),
71 0 : Align(alignment: Alignment.centerRight, child: TextWidget(date.getPreviousMonth(2).fullMonth())),
72 : ]
73 : ];
74 0 : final day = AppStartOfMonth.get();
75 0 : final bills = store.getStream(AppDataType.bills);
76 0 : final dateFirst = date.getStartingDay(day);
77 0 : final billFirst = bills.getTill(dateFirst.millisecondsSinceEpoch.toDouble()).cast<BillAppData>();
78 0 : final dateSecond = date.getPreviousMonth(1).getStartingDay(day);
79 0 : final billSecond = bills.getTill(dateSecond.millisecondsSinceEpoch.toDouble()).cast<BillAppData>();
80 0 : final dateThird = date.getPreviousMonth(2).getStartingDay(day);
81 0 : final billThird = bills.getTill(dateThird.millisecondsSinceEpoch.toDouble()).cast<BillAppData>();
82 0 : final items = store.getList(AppDataType.budgets);
83 0 : items.sort(fnSort);
84 0 : for (BudgetAppData budget in items) {
85 0 : bool fnCategory(bill) => bill.category == budget.uuid;
86 0 : result.add([
87 0 : Icon(budget.icon, color: budget.color),
88 0 : TextWidget(budget.title),
89 0 : _getField(budget, billFirst.where(fnCategory).toList(), dateFirst),
90 0 : _getField(budget, billSecond.where(fnCategory).toList(), dateSecond),
91 0 : _getField(budget, billThird.where(fnCategory).toList(), dateThird),
92 : ]);
93 : }
94 : return result;
95 : }
96 :
97 0 : @override
98 : Widget build(BuildContext context) {
99 0 : final sorting = Provider.of<AppSorting>(context, listen: true);
100 0 : final indent = ThemeHelper.getIndent(2);
101 0 : return SingleChildScrollView(
102 0 : child: Padding(
103 0 : padding: EdgeInsets.all(indent),
104 0 : child: Column(
105 0 : crossAxisAlignment: AppDesign.getAlignment(),
106 0 : children: [
107 : const BudgetForecastChart(),
108 : ThemeHelper.hIndent4x,
109 : const BudgetYtdChart(),
110 : ThemeHelper.hIndent4x,
111 0 : LayoutBuilder(builder: (context, constraints) {
112 0 : return TableWidget(
113 0 : shadowColor: context.colorScheme.onSurface.withValues(alpha: 0.1),
114 0 : width: constraints.maxWidth,
115 : chunk: const [20, 72, null, null, null],
116 0 : data: _generateTable(sorting.getSortFunction(context)),
117 : );
118 : }),
119 : ThemeHelper.formEndBox,
120 : ],
121 : ),
122 : ),
123 : );
124 : }
125 : }
|