@marcos_feitoza/personal-finance-frontend-feature-dashboard 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [1.2.0](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/compare/v1.1.2...v1.2.0) (2026-01-29)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add revoke token ([9d4febb](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/9d4febb18f932dae3c853656cb08ba9e3ab53ca5))
7
+
8
+
9
+ ### Features
10
+
11
+ * add useer profile ([f3e410a](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/f3e410aea7852115e5be40199d3cd24e95205812))
12
+ * new AppTheme process ([5538715](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/55387150f8271f898e8309dfc0c3f1dc8ee66661))
13
+
1
14
  ## [1.1.2](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/compare/v1.1.1...v1.1.2) (2025-12-06)
2
15
 
3
16
 
@@ -1,5 +1,4 @@
1
1
  import 'package:flutter/material.dart';
2
- import 'package:flutter/services.dart';
3
2
  import 'package:personal_finance_frontend_core_ui/widgets/salary_form.dart';
4
3
  import 'package:personal_finance_frontend_core_ui/widgets/expense_form.dart';
5
4
  import 'package:personal_finance_frontend_core_ui/widgets/investment_form.dart';
@@ -14,24 +13,16 @@ import 'package:personal_finance_frontend_feature_reports/screens/move_money_rep
14
13
  import 'package:personal_finance_frontend_feature_investments/screens/investment_account_screen.dart';
15
14
  import 'package:personal_finance_frontend_feature_investments/screens/rrsp_sun_life_screen.dart';
16
15
  import 'package:personal_finance_frontend_feature_investments/screens/crypto_account_screen.dart';
17
- import 'package:personal_finance_frontend_feature_charts/viewmodels/salary_chart_viewmodel.dart';
18
- import 'package:personal_finance_frontend_feature_reports/viewmodels/expense_report_viewmodel.dart';
19
- import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
20
- import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
21
- import 'package:personal_finance_frontend_core_services/models/payment_method.dart';
22
16
  import 'package:intl/intl.dart';
23
17
  import 'package:personal_finance_frontend_feature_management/screens/reconciliation_screen.dart';
24
18
  import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
25
19
  import 'package:personal_finance_frontend_core_ui/utils/theme_notifier.dart';
26
20
  import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
27
21
  import 'package:personal_finance_frontend_core_ui/widgets/user_profile_avatar.dart';
28
-
29
- class HomeScreen extends StatefulWidget {
30
- const HomeScreen({Key? key}) : super(key: key);
31
-
32
- @override
33
- _HomeScreenState createState() => _HomeScreenState();
34
- }
22
+ import 'package:personal_finance_frontend_feature_profile/screens/profile_screen.dart';
23
+ import '../viewmodels/dashboard_viewmodel.dart';
24
+ import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
25
+ import 'package:personal_finance_frontend_core_ui/theme/app_colors.dart';
35
26
 
36
27
  final String currencySymbol = r'$';
37
28
 
@@ -39,131 +30,174 @@ String _formatCurrency(double value) {
39
30
  final format = NumberFormat.currency(
40
31
  symbol: currencySymbol,
41
32
  decimalDigits: 2,
42
- locale: 'en_US', // garante vírgula de milhar e ponto decimal no padrão US
33
+ locale: 'en_US',
43
34
  );
44
35
  return format.format(value);
45
36
  }
46
37
 
47
- class _HomeScreenState extends State<HomeScreen> {
48
- final _salaryFormKey = GlobalKey<FormState>();
49
- final _expenseFormKey = GlobalKey<FormState>();
50
- final _investmentFormKey = GlobalKey<FormState>();
51
-
52
- final _rbcCashController = TextEditingController();
53
- final _wealthsimpleCashController = TextEditingController();
54
- final _rbcCardController = TextEditingController();
55
- final _bmoCardController = TextEditingController();
56
-
57
- final TransactionService _transactionService = TransactionService();
58
- double _totalIncome = 0.0;
59
- double _totalExpenses = 0.0;
60
- double _rbcUnpaidExpenses = 0.0;
61
- double _bmoUnpaidExpenses = 0.0;
62
-
63
- List<Map<String, dynamic>> _cashTransactions = [];
64
- List<Map<String, dynamic>> _rbcTransactions = [];
65
- List<Map<String, dynamic>> _bmoTransactions = [];
66
- List<Map<String, dynamic>> _investments = [];
67
- Map<String, double> _accountBalances = {};
68
-
69
- bool _isLoading = true;
70
- String? _selectedMenu;
71
-
72
- final List<String> _investmentAccountNames = [
73
- 'TFSA',
74
- 'RRSP Wealthsimple',
75
- 'Non-Registered',
76
- 'Crypto'
77
- ];
78
-
79
- @override
80
- void initState() {
81
- super.initState();
82
- // Fetch data after the first frame is built
83
- WidgetsBinding.instance.addPostFrameCallback((_) => _fetchData());
84
- }
38
+ class HomeScreen extends StatelessWidget {
39
+ const HomeScreen({Key? key}) : super(key: key);
85
40
 
86
41
  @override
87
- void dispose() {
88
- _rbcCashController.dispose();
89
- _wealthsimpleCashController.dispose();
90
- _rbcCardController.dispose();
91
- _bmoCardController.dispose();
92
- super.dispose();
93
- }
94
-
95
- Future<void> _fetchData() async {
96
- if (!mounted) return;
42
+ Widget build(BuildContext context) {
97
43
  final authProvider = Provider.of<AuthProvider>(context, listen: false);
98
44
  final token = authProvider.token;
99
45
 
100
- setState(() {
101
- _isLoading = true;
102
- });
103
- try {
104
- // Fetch all balances, including the definitive cash balance from the backend
105
- final cashBalanceFuture =
106
- _transactionService.getAccountBalance('CASH', token: token);
107
- final investmentBalanceFutures = _investmentAccountNames
108
- .map((name) =>
109
- _transactionService.getAccountBalance(name, token: token))
110
- .toList();
111
-
112
- final otherFutures = <Future<dynamic>>[
113
- _transactionService.getSummaryByPaymentMethod(token: token),
114
- _transactionService.fetchTransactions(
115
- paymentMethod: 'CASH', limit: 10, token: token),
116
- _transactionService.fetchTransactions(
117
- paymentMethod: 'RBC', limit: 10, token: token),
118
- _transactionService.fetchTransactions(
119
- paymentMethod: 'BMO', limit: 10, token: token),
120
- _transactionService.getInvestments(token: token),
121
- ];
122
-
123
- // Await all futures
124
- final allInvestmentBalances = await Future.wait(investmentBalanceFutures);
125
- final cashBalance = await cashBalanceFuture;
126
- final otherResults = await Future.wait(otherFutures);
127
-
128
- // Safely build the account balances map
129
- _accountBalances = {'CASH': cashBalance};
130
- for (int i = 0; i < _investmentAccountNames.length; i++) {
131
- _accountBalances[_investmentAccountNames[i]] = allInvestmentBalances[i];
132
- }
133
-
134
- final pmSummary = otherResults[0] as List<Map<String, dynamic>>;
135
- _rbcUnpaidExpenses = 0.0;
136
- _bmoUnpaidExpenses = 0.0;
137
- for (var item in pmSummary) {
138
- final paymentMethod =
139
- (item['payment_method'] as String?)?.toUpperCase();
140
- if (paymentMethod == 'RBC') {
141
- _rbcUnpaidExpenses += double.tryParse(item['unpaid_amount']?.toString() ?? '0.0') ?? 0.0;
142
- } else if (paymentMethod == 'BMO') {
143
- _bmoUnpaidExpenses += double.tryParse(item['unpaid_amount']?.toString() ?? '0.0') ?? 0.0;
144
- } }
145
-
146
- _cashTransactions = otherResults[1] as List<Map<String, dynamic>>;
147
- _rbcTransactions = otherResults[2] as List<Map<String, dynamic>>;
148
- _bmoTransactions = otherResults[3] as List<Map<String, dynamic>>;
149
- _investments = otherResults[4] as List<Map<String, dynamic>>;
150
- } catch (e) {
151
- print('Error fetching data: $e');
152
- } finally {
153
- if (mounted) {
154
- setState(() {
155
- _isLoading = false;
156
- });
157
- }
158
- }
46
+ return ChangeNotifierProvider(
47
+ create: (_) => DashboardViewModel(token),
48
+ child: Consumer<DashboardViewModel>(
49
+ builder: (context, viewModel, child) {
50
+ return Scaffold(
51
+ appBar: AppBar(
52
+ title: const Text('Personal Finance Dashboard'),
53
+ actions: [
54
+ PopupMenuButton<String>(
55
+ onSelected: (value) {
56
+ if (value == 'profile') {
57
+ Navigator.push(
58
+ context,
59
+ MaterialPageRoute(
60
+ builder: (context) => const ProfileScreen()),
61
+ );
62
+ } else if (value == 'theme') {
63
+ final themeNotifier =
64
+ Provider.of<ThemeNotifier>(context, listen: false);
65
+ final newTheme =
66
+ themeNotifier.themeMode == ThemeMode.dark
67
+ ? ThemeMode.light
68
+ : ThemeMode.dark;
69
+ themeNotifier.setThemeMode(newTheme);
70
+ } else if (value == 'logout') {
71
+ Provider.of<AuthProvider>(context, listen: false)
72
+ .logout();
73
+ Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
74
+ }
75
+ },
76
+ itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
77
+ const PopupMenuItem<String>(
78
+ value: 'profile',
79
+ child: Text('My Profile'),
80
+ ),
81
+ const PopupMenuItem<String>(
82
+ value: 'theme',
83
+ child: Text('Toggle Theme'),
84
+ ),
85
+ const PopupMenuItem<String>(
86
+ value: 'logout',
87
+ child: Text('Logout'),
88
+ ),
89
+ ],
90
+ child: const UserProfileAvatar(),
91
+ ),
92
+ const SizedBox(width: 10),
93
+ ],
94
+ ),
95
+ drawer: Drawer(
96
+ child: viewModel.selectedMenu == null
97
+ ? _buildMainMenu(context, viewModel)
98
+ : _buildSubMenu(context, viewModel),
99
+ ),
100
+ body: viewModel.isLoading
101
+ ? const Center(child: CircularProgressIndicator())
102
+ : SingleChildScrollView(
103
+ padding: const EdgeInsets.all(16.0),
104
+ child: Column(
105
+ crossAxisAlignment: CrossAxisAlignment.start,
106
+ children: [
107
+ Row(
108
+ crossAxisAlignment: CrossAxisAlignment.start,
109
+ children: [
110
+ Expanded(
111
+ child: _buildCashBalanceCard(
112
+ context, viewModel)),
113
+ const SizedBox(width: 16),
114
+ Expanded(
115
+ child: _buildCreditCardBalanceCard(
116
+ context,
117
+ viewModel,
118
+ 'RBC Balance',
119
+ viewModel.rbcCardController,
120
+ viewModel.rbcUnpaidExpenses)),
121
+ const SizedBox(width: 16),
122
+ Expanded(
123
+ child: _buildCreditCardBalanceCard(
124
+ context,
125
+ viewModel,
126
+ 'BMO Balance',
127
+ viewModel.bmoCardController,
128
+ viewModel.bmoUnpaidExpenses)),
129
+ ],
130
+ ),
131
+ const SizedBox(height: 16),
132
+ Row(
133
+ crossAxisAlignment: CrossAxisAlignment.start,
134
+ children: [
135
+ Expanded(
136
+ child: _buildRecentTransactionsList(
137
+ 'Cash Transactions',
138
+ viewModel.cashTransactions)),
139
+ const SizedBox(width: 16),
140
+ Expanded(
141
+ child: _buildRecentTransactionsList(
142
+ 'RBC Transactions',
143
+ viewModel.rbcTransactions)),
144
+ const SizedBox(width: 16),
145
+ Expanded(
146
+ child: _buildRecentTransactionsList(
147
+ 'BMO Transactions',
148
+ viewModel.bmoTransactions)),
149
+ const SizedBox(width: 16),
150
+ Expanded(
151
+ child: ExpenseForm(
152
+ formKey: viewModel.expenseFormKey,
153
+ onTransactionSuccess: viewModel.fetchData,
154
+ token: token,
155
+ )),
156
+ ],
157
+ ),
158
+ const SizedBox(height: 16),
159
+ Row(
160
+ crossAxisAlignment: CrossAxisAlignment.start,
161
+ children: [
162
+ Expanded(
163
+ child: InvestmentForm(
164
+ formKey: viewModel.investmentFormKey,
165
+ accountBalances: viewModel.accountBalances,
166
+ cashBalance: viewModel.appCalculatedCash,
167
+ onTransactionSuccess: viewModel.fetchData,
168
+ token: token,
169
+ )),
170
+ const SizedBox(width: 16),
171
+ Expanded(
172
+ child: SalaryForm(
173
+ formKey: viewModel.salaryFormKey,
174
+ onTransactionSuccess: viewModel.fetchData,
175
+ token: token,
176
+ )),
177
+ ],
178
+ ),
179
+ ],
180
+ ),
181
+ ),
182
+ floatingActionButton: FloatingActionButton(
183
+ onPressed: viewModel.fetchData,
184
+ tooltip: 'Refresh Data',
185
+ child: const Icon(Icons.refresh),
186
+ ),
187
+ );
188
+ },
189
+ ),
190
+ );
159
191
  }
160
192
 
161
- Widget _buildMainMenu(BuildContext context) {
193
+ Widget _buildMainMenu(BuildContext context, DashboardViewModel viewModel) {
162
194
  return ListView(
163
195
  padding: EdgeInsets.zero,
164
196
  children: <Widget>[
165
197
  const DrawerHeader(
166
198
  decoration: BoxDecoration(
199
+ // TODO(UI): replace hardcoded color with a theme token (e.g. theme.colorScheme.primary)
200
+ // or a semantic token in AppColors if needed.
167
201
  color: Colors.blue,
168
202
  ),
169
203
  child: Text(
@@ -177,53 +211,37 @@ class _HomeScreenState extends State<HomeScreen> {
177
211
  ListTile(
178
212
  leading: const Icon(Icons.assessment),
179
213
  title: const Text('Investments'),
180
- onTap: () {
181
- setState(() {
182
- _selectedMenu = 'investments';
183
- });
184
- },
214
+ onTap: () => viewModel.setMenu('investments'),
185
215
  ),
186
216
  ListTile(
187
217
  leading: const Icon(Icons.description),
188
218
  title: const Text('Reports'),
189
- onTap: () {
190
- setState(() {
191
- _selectedMenu = 'reports';
192
- });
193
- },
219
+ onTap: () => viewModel.setMenu('reports'),
194
220
  ),
195
221
  ListTile(
196
222
  leading: const Icon(Icons.bar_chart),
197
223
  title: const Text('Charts'),
198
- onTap: () {
199
- setState(() {
200
- _selectedMenu = 'charts';
201
- });
202
- },
224
+ onTap: () => viewModel.setMenu('charts'),
203
225
  ),
204
226
  ListTile(
205
227
  leading: const Icon(Icons.settings),
206
228
  title: const Text('Management'),
207
- onTap: () {
208
- setState(() {
209
- _selectedMenu = 'management';
210
- });
211
- },
229
+ onTap: () => viewModel.setMenu('management'),
212
230
  ),
213
231
  ],
214
232
  );
215
233
  }
216
234
 
217
- Widget _buildSubMenu(BuildContext context) {
235
+ Widget _buildSubMenu(BuildContext context, DashboardViewModel viewModel) {
218
236
  List<Widget> items;
219
237
  String title;
220
238
 
221
- switch (_selectedMenu) {
239
+ switch (viewModel.selectedMenu) {
222
240
  case 'investments':
223
241
  title = 'Investments';
224
242
  items = [
225
- ..._investmentAccountNames.map((accountName) {
226
- final balance = _accountBalances[accountName] ?? 0.0;
243
+ ...viewModel.investmentAccountNames.map((accountName) {
244
+ final balance = viewModel.accountBalances[accountName] ?? 0.0;
227
245
  final formattedBalance = _formatCurrency(balance);
228
246
  return ListTile(
229
247
  title: Text(' $accountName - $formattedBalance'),
@@ -350,7 +368,7 @@ class _HomeScreenState extends State<HomeScreen> {
350
368
  context,
351
369
  MaterialPageRoute(
352
370
  builder: (context) => const ManageTransactionsScreen()),
353
- ).then((_) => _fetchData());
371
+ ).then((_) => viewModel.fetchData());
354
372
  },
355
373
  ),
356
374
  ListTile(
@@ -380,11 +398,7 @@ class _HomeScreenState extends State<HomeScreen> {
380
398
  backgroundColor: Theme.of(context).primaryColor,
381
399
  leading: IconButton(
382
400
  icon: const Icon(Icons.arrow_back),
383
- onPressed: () {
384
- setState(() {
385
- _selectedMenu = null;
386
- });
387
- },
401
+ onPressed: () => viewModel.setMenu(null),
388
402
  ),
389
403
  automaticallyImplyLeading: false,
390
404
  ),
@@ -393,129 +407,10 @@ class _HomeScreenState extends State<HomeScreen> {
393
407
  );
394
408
  }
395
409
 
396
- @override
397
- Widget build(BuildContext context) {
398
- final double totalUserCash =
399
- CurrencyInputFormatter.unformat(_rbcCashController.text) +
400
- CurrencyInputFormatter.unformat(_wealthsimpleCashController.text);
401
-
402
- final double appCalculatedCash = _accountBalances['CASH'] ?? 0.0;
403
- final token = Provider.of<AuthProvider>(context, listen: false).token;
404
-
405
- return Scaffold(
406
- appBar: AppBar(
407
- title: const Text('Personal Finance Dashboard'),
408
- actions: [
409
- const UserProfileAvatar(),
410
- Consumer<ThemeNotifier>(
411
- builder: (context, themeNotifier, child) {
412
- return IconButton(
413
- icon: Icon(themeNotifier.themeMode == ThemeMode.dark
414
- ? Icons.dark_mode
415
- : Icons.light_mode),
416
- onPressed: () {
417
- themeNotifier.setThemeMode(
418
- themeNotifier.themeMode == ThemeMode.dark
419
- ? ThemeMode.light
420
- : ThemeMode.dark);
421
- },
422
- );
423
- },
424
- ),
425
- IconButton(
426
- icon: const Icon(Icons.logout),
427
- onPressed: () {
428
- Provider.of<AuthProvider>(context, listen: false).logout();
429
- },
430
- ),
431
- ],
432
- ),
433
- drawer: Drawer(
434
- child: _selectedMenu == null
435
- ? _buildMainMenu(context)
436
- : _buildSubMenu(context),
437
- ),
438
- body: _isLoading
439
- ? const Center(child: CircularProgressIndicator())
440
- : SingleChildScrollView(
441
- padding: const EdgeInsets.all(16.0),
442
- child: Column(
443
- crossAxisAlignment: CrossAxisAlignment.start,
444
- children: [
445
- Row(
446
- crossAxisAlignment: CrossAxisAlignment.start,
447
- children: [
448
- Expanded(
449
- child: _buildCashBalanceCard(
450
- totalUserCash, appCalculatedCash)),
451
- const SizedBox(width: 16),
452
- Expanded(
453
- child: _buildCreditCardBalanceCard('RBC Balance',
454
- _rbcCardController, _rbcUnpaidExpenses)),
455
- const SizedBox(width: 16),
456
- Expanded(
457
- child: _buildCreditCardBalanceCard('BMO Balance',
458
- _bmoCardController, _bmoUnpaidExpenses)),
459
- ],
460
- ),
461
- const SizedBox(height: 16),
462
- Row(
463
- crossAxisAlignment: CrossAxisAlignment.start,
464
- children: [
465
- Expanded(
466
- child: _buildRecentTransactionsList(
467
- 'Cash Transactions', _cashTransactions)),
468
- const SizedBox(width: 16),
469
- Expanded(
470
- child: _buildRecentTransactionsList(
471
- 'RBC Transactions', _rbcTransactions)),
472
- const SizedBox(width: 16),
473
- Expanded(
474
- child: _buildRecentTransactionsList(
475
- 'BMO Transactions', _bmoTransactions)),
476
- const SizedBox(width: 16),
477
- Expanded(
478
- child: ExpenseForm(
479
- formKey: _expenseFormKey,
480
- onTransactionSuccess: _fetchData,
481
- token: token,
482
- )),
483
- ],
484
- ),
485
- const SizedBox(height: 16),
486
- Row(
487
- crossAxisAlignment: CrossAxisAlignment.start,
488
- children: [
489
- Expanded(
490
- child: InvestmentForm(
491
- formKey: _investmentFormKey,
492
- accountBalances: _accountBalances,
493
- cashBalance: appCalculatedCash,
494
- onTransactionSuccess: _fetchData,
495
- token: token,
496
- )),
497
- const SizedBox(width: 16),
498
- Expanded(
499
- child: SalaryForm(
500
- formKey: _salaryFormKey,
501
- onTransactionSuccess: _fetchData,
502
- token: token,
503
- )),
504
- ],
505
- ),
506
- ],
507
- ),
508
- ),
509
- floatingActionButton: FloatingActionButton(
510
- onPressed: _fetchData,
511
- tooltip: 'Refresh Data',
512
- child: const Icon(Icons.refresh),
513
- ),
514
- );
515
- }
516
-
517
- Widget _buildCashBalanceCard(double totalUserCash, double appCalculatedCash) {
518
- final double difference = totalUserCash - appCalculatedCash;
410
+ Widget _buildCashBalanceCard(
411
+ BuildContext context, DashboardViewModel viewModel) {
412
+ final double difference =
413
+ viewModel.totalUserCash - viewModel.appCalculatedCash;
519
414
 
520
415
  String differenceLabel = 'Balance is correct';
521
416
  Color differenceColor = Theme.of(context).colorScheme.secondary;
@@ -523,11 +418,12 @@ class _HomeScreenState extends State<HomeScreen> {
523
418
 
524
419
  if (difference > 0.01) {
525
420
  differenceLabel = 'Missing Income:';
526
- differenceColor = Colors.green;
421
+ // Use semantic color token instead of hardcoded Colors.green
422
+ differenceColor = AppColors.success;
527
423
  differenceText = '+${_formatCurrency(difference)}';
528
424
  } else if (difference < -0.01) {
529
425
  differenceLabel = 'Missing Expenses:';
530
- differenceColor = Colors.red;
426
+ differenceColor = AppColors.error;
531
427
  }
532
428
 
533
429
  return AppFormCard(
@@ -535,17 +431,17 @@ class _HomeScreenState extends State<HomeScreen> {
535
431
  child: Column(
536
432
  crossAxisAlignment: CrossAxisAlignment.start,
537
433
  children: [
538
- _buildBalanceTextField(_rbcCashController, 'RBC Bank Balance'),
434
+ _buildBalanceTextField(viewModel.rbcCashController, 'RBC Bank Balance'),
539
435
  const SizedBox(height: 12),
540
436
  _buildBalanceTextField(
541
- _wealthsimpleCashController, 'Wealthsimple Balance'),
437
+ viewModel.wealthsimpleCashController, 'Wealthsimple Balance'),
542
438
  const SizedBox(height: 16),
543
439
  const Divider(),
544
440
  const SizedBox(height: 10),
545
- _buildResultRow(
546
- 'Manual Entry Balance:', '${_formatCurrency(totalUserCash)}'),
547
- _buildResultRow(
548
- 'Calculated Balance:', '${_formatCurrency(appCalculatedCash)}'),
441
+ _buildResultRow('Manual Entry Balance:',
442
+ '${_formatCurrency(viewModel.totalUserCash)}'),
443
+ _buildResultRow('Calculated Balance:',
444
+ '${_formatCurrency(viewModel.appCalculatedCash)}'),
549
445
  const SizedBox(height: 10),
550
446
  _buildResultRow(differenceLabel, differenceText,
551
447
  isHighlighted: true, color: differenceColor),
@@ -555,7 +451,11 @@ class _HomeScreenState extends State<HomeScreen> {
555
451
  }
556
452
 
557
453
  Widget _buildCreditCardBalanceCard(
558
- String title, TextEditingController controller, double appExpenses) {
454
+ BuildContext context,
455
+ DashboardViewModel viewModel,
456
+ String title,
457
+ TextEditingController controller,
458
+ double appExpenses) {
559
459
  final double userBalance = CurrencyInputFormatter.unformat(controller.text);
560
460
  final double difference = appExpenses - userBalance;
561
461
 
@@ -565,11 +465,11 @@ class _HomeScreenState extends State<HomeScreen> {
565
465
 
566
466
  if (difference > 0.01) {
567
467
  differenceLabel = 'Missing Refund:';
568
- differenceColor = Colors.green;
468
+ differenceColor = AppColors.success;
569
469
  differenceText = '+${_formatCurrency(difference)}';
570
470
  } else if (difference < -0.01) {
571
471
  differenceLabel = 'Missing Expenses:';
572
- differenceColor = Colors.red;
472
+ differenceColor = AppColors.error;
573
473
  }
574
474
 
575
475
  return AppFormCard(
@@ -605,7 +505,8 @@ class _HomeScreenState extends State<HomeScreen> {
605
505
  builder: (context) =>
606
506
  ReconciliationScreen(paymentMethod: paymentMethod),
607
507
  ),
608
- ).then((_) => _fetchData()); // Refresh data when returning
508
+ ).then((_) =>
509
+ viewModel.fetchData()); // Refresh data when returning
609
510
  },
610
511
  child: const Text('Reconcile'),
611
512
  ),
@@ -627,7 +528,6 @@ class _HomeScreenState extends State<HomeScreen> {
627
528
  ),
628
529
  keyboardType: const TextInputType.numberWithOptions(decimal: true),
629
530
  inputFormatters: [CurrencyInputFormatter()],
630
- onChanged: (value) => setState(() {}),
631
531
  );
632
532
  }
633
533
 
@@ -659,13 +559,14 @@ class _HomeScreenState extends State<HomeScreen> {
659
559
  children: transactions.map((transaction) {
660
560
  final date = DateFormat('MM/dd')
661
561
  .format(DateTime.parse(transaction['date']));
662
- final amount = (double.tryParse(transaction['amount']?.toString() ?? '0.0') ?? 0.0)
663
- .toStringAsFixed(2);
562
+ final amount =
563
+ (double.tryParse(transaction['amount']?.toString() ?? '0.0') ?? 0.0)
564
+ .toStringAsFixed(2);
664
565
  final subcategory =
665
566
  transaction['subcategory'] ?? 'No subcategory';
666
567
  final type = transaction['type'] == 'debit' ? '-' : '+';
667
568
  final color =
668
- transaction['type'] == 'debit' ? Colors.red : Colors.green;
569
+ transaction['type'] == 'debit' ? AppColors.error : AppColors.success;
669
570
 
670
571
  return Padding(
671
572
  padding: const EdgeInsets.symmetric(vertical: 4.0),
@@ -12,6 +12,11 @@ class DashboardViewModel extends ChangeNotifier {
12
12
  final rbcCardController = TextEditingController();
13
13
  final bmoCardController = TextEditingController();
14
14
 
15
+ // Form Keys
16
+ final expenseFormKey = GlobalKey<FormState>();
17
+ final investmentFormKey = GlobalKey<FormState>();
18
+ final salaryFormKey = GlobalKey<FormState>();
19
+
15
20
  // State variables
16
21
  bool _isLoading = true;
17
22
  String? _selectedMenu;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcos_feitoza/personal-finance-frontend-feature-dashboard",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/pubspec.yaml CHANGED
@@ -23,6 +23,8 @@ dependencies:
23
23
  path: ../personal-finance-frontend-feature-investments
24
24
  personal_finance_frontend_feature_management:
25
25
  path: ../personal-finance-frontend-feature-management
26
+ personal_finance_frontend_feature_profile:
27
+ path: ../personal-finance-frontend-feature-profile
26
28
 
27
29
  dev_dependencies:
28
30
  flutter_test: