@marcos_feitoza/personal-finance-frontend-feature-dashboard 1.0.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.
@@ -0,0 +1,23 @@
1
+ version: 2.1
2
+
3
+ jobs:
4
+ release:
5
+ docker:
6
+ - image: circleci/node:latest
7
+ steps:
8
+ - checkout
9
+ - run:
10
+ name: Install dependencies
11
+ command: npm install
12
+ - run:
13
+ name: Run semantic-release
14
+ command: npx semantic-release
15
+
16
+ workflows:
17
+ build-and-release:
18
+ jobs:
19
+ - release:
20
+ filters:
21
+ branches:
22
+ only:
23
+ - main
package/.metadata ADDED
@@ -0,0 +1,10 @@
1
+ # This file tracks properties of this Flutter project.
2
+ # Used by Flutter tool to assess capabilities and perform upgrades etc.
3
+ #
4
+ # This file should be version controlled and should not be manually edited.
5
+
6
+ version:
7
+ revision: "fcf2c11572af6f390246c056bc905eca609533a0"
8
+ channel: "stable"
9
+
10
+ project_type: package
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # 1.0.0 (2025-11-22)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add currency class ([99d9217](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/99d9217d571d15e709b1a16d3845d3e72a244683))
7
+ * add date logs ([45e0a40](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/45e0a40ac9dd690b902e6c345e8bbec9247c8b09))
8
+ * add ignore and more estilo ([ad1c97a](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/ad1c97a4cc267788d92a0912696d0dba3240609a))
9
+ * add new classes ([973d2c7](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/973d2c71a0798ba1020c6456267e37dcbad936a7))
10
+ * dark mode funcionando ([f4d9417](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/f4d9417ee9ad686273397a8bd925fb81407b82ef))
11
+
12
+
13
+ ### Features
14
+
15
+ * add avatar ([a1b720a](https://github.com/MarcosOps/personal-finance-frontend-feature-dashboard/commit/a1b720aa0cd8fd3a70ffcfdac7e7049066416c8f))
16
+
17
+ ## 0.0.1
18
+
19
+ * TODO: Describe initial release.
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ TODO: Add your license here.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ <!--
2
+ This README describes the package. If you publish this package to pub.dev,
3
+ this README's contents appear on the landing page for your package.
4
+
5
+ For information about how to write a good package README, see the guide for
6
+ [writing package pages](https://dart.dev/tools/pub/writing-package-pages).
7
+
8
+ For general information about developing packages, see the Dart guide for
9
+ [creating packages](https://dart.dev/guides/libraries/create-packages)
10
+ and the Flutter guide for
11
+ [developing packages and plugins](https://flutter.dev/to/develop-packages).
12
+ -->
13
+
14
+ TODO: Put a short description of the package here that helps potential users
15
+ know whether this package might be useful for them.
16
+
17
+ ## Features
18
+
19
+ TODO: List what your package can do. Maybe include images, gifs, or videos.
20
+
21
+ ## Getting started
22
+
23
+ TODO: List prerequisites and provide or point to information on how to
24
+ start using the package.
25
+
26
+ ## Usage
27
+
28
+ TODO: Include short and useful examples for package users. Add longer examples
29
+ to `/example` folder.
30
+
31
+ ```dart
32
+ const like = 'sample';
33
+ ```
34
+
35
+ ## Additional information
36
+
37
+ TODO: Tell users more about the package: where to find more information, how to
38
+ contribute to the package, how to file issues, what response they can expect
39
+ from the package authors, and more.
@@ -0,0 +1,4 @@
1
+ include: package:flutter_lints/flutter.yaml
2
+
3
+ # Additional information about this file can be found at
4
+ # https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,5 @@
1
+ /// A Calculator.
2
+ class Calculator {
3
+ /// Returns [value] plus 1.
4
+ int addOne(int value) => value + 1;
5
+ }
@@ -0,0 +1,694 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter/services.dart';
3
+ import 'package:personal_finance_frontend_core_ui/widgets/salary_form.dart';
4
+ import 'package:personal_finance_frontend_core_ui/widgets/expense_form.dart';
5
+ import 'package:personal_finance_frontend_core_ui/widgets/investment_form.dart';
6
+ import 'package:personal_finance_frontend_feature_reports/screens/salary_report_screen.dart';
7
+ import 'package:personal_finance_frontend_feature_charts/screens/salary_chart_screen.dart';
8
+ import 'package:provider/provider.dart';
9
+ import 'package:personal_finance_frontend_feature_reports/screens/expense_report_screen.dart';
10
+ import 'package:personal_finance_frontend_feature_charts/screens/expense_chart_screen.dart';
11
+ import 'package:personal_finance_frontend_feature_reports/screens/summary_report_screen.dart';
12
+ import 'package:personal_finance_frontend_feature_management/screens/manage_transactions_screen.dart';
13
+ import 'package:personal_finance_frontend_feature_reports/screens/move_money_report_screen.dart';
14
+ import 'package:personal_finance_frontend_feature_investments/screens/investment_account_screen.dart';
15
+ import 'package:personal_finance_frontend_feature_investments/screens/rrsp_sun_life_screen.dart';
16
+ 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
+ import 'package:intl/intl.dart';
23
+ import 'package:personal_finance_frontend_feature_management/screens/reconciliation_screen.dart';
24
+ import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
25
+ import 'package:personal_finance_frontend_core_ui/utils/theme_notifier.dart';
26
+ import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
27
+ 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
+ }
35
+
36
+ final String currencySymbol = r'$';
37
+
38
+ String _formatCurrency(double value) {
39
+ final format = NumberFormat.currency(
40
+ symbol: currencySymbol,
41
+ decimalDigits: 2,
42
+ locale: 'en_US', // garante vírgula de milhar e ponto decimal no padrão US
43
+ );
44
+ return format.format(value);
45
+ }
46
+
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
+ }
85
+
86
+ @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;
97
+ final authProvider = Provider.of<AuthProvider>(context, listen: false);
98
+ final token = authProvider.token;
99
+
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.parse(item['unpaid_amount'].toString());
142
+ } else if (paymentMethod == 'BMO') {
143
+ _bmoUnpaidExpenses += double.parse(item['unpaid_amount'].toString());
144
+ }
145
+ }
146
+
147
+ _cashTransactions = otherResults[1] as List<Map<String, dynamic>>;
148
+ _rbcTransactions = otherResults[2] as List<Map<String, dynamic>>;
149
+ _bmoTransactions = otherResults[3] as List<Map<String, dynamic>>;
150
+ _investments = otherResults[4] as List<Map<String, dynamic>>;
151
+ } catch (e) {
152
+ print('Error fetching data: $e');
153
+ } finally {
154
+ if (mounted) {
155
+ setState(() {
156
+ _isLoading = false;
157
+ });
158
+ }
159
+ }
160
+ }
161
+
162
+ Widget _buildMainMenu(BuildContext context) {
163
+ return ListView(
164
+ padding: EdgeInsets.zero,
165
+ children: <Widget>[
166
+ const DrawerHeader(
167
+ decoration: BoxDecoration(
168
+ color: Colors.blue,
169
+ ),
170
+ child: Text(
171
+ 'Menu',
172
+ style: TextStyle(
173
+ color: Colors.white,
174
+ fontSize: 24,
175
+ ),
176
+ ),
177
+ ),
178
+ ListTile(
179
+ leading: const Icon(Icons.assessment),
180
+ title: const Text('Investments'),
181
+ onTap: () {
182
+ setState(() {
183
+ _selectedMenu = 'investments';
184
+ });
185
+ },
186
+ ),
187
+ ListTile(
188
+ leading: const Icon(Icons.description),
189
+ title: const Text('Reports'),
190
+ onTap: () {
191
+ setState(() {
192
+ _selectedMenu = 'reports';
193
+ });
194
+ },
195
+ ),
196
+ ListTile(
197
+ leading: const Icon(Icons.bar_chart),
198
+ title: const Text('Charts'),
199
+ onTap: () {
200
+ setState(() {
201
+ _selectedMenu = 'charts';
202
+ });
203
+ },
204
+ ),
205
+ ListTile(
206
+ leading: const Icon(Icons.settings),
207
+ title: const Text('Management'),
208
+ onTap: () {
209
+ setState(() {
210
+ _selectedMenu = 'management';
211
+ });
212
+ },
213
+ ),
214
+ ],
215
+ );
216
+ }
217
+
218
+ Widget _buildSubMenu(BuildContext context) {
219
+ List<Widget> items;
220
+ String title;
221
+
222
+ switch (_selectedMenu) {
223
+ case 'investments':
224
+ title = 'Investments';
225
+ items = [
226
+ ..._investmentAccountNames.map((accountName) {
227
+ final balance = _accountBalances[accountName] ?? 0.0;
228
+ final formattedBalance = _formatCurrency(balance);
229
+ return ListTile(
230
+ title: Text(' $accountName - $formattedBalance'),
231
+ onTap: () {
232
+ Navigator.pop(context); // Close drawer
233
+ if (accountName == 'Crypto') {
234
+ Navigator.push(
235
+ context,
236
+ MaterialPageRoute(
237
+ builder: (context) =>
238
+ CryptoAccountScreen(accountName: accountName),
239
+ ),
240
+ );
241
+ } else {
242
+ Navigator.push(
243
+ context,
244
+ MaterialPageRoute(
245
+ builder: (context) => InvestmentAccountScreen(
246
+ accountName: accountName,
247
+ showDividends: accountName != 'Crypto',
248
+ ),
249
+ ),
250
+ );
251
+ }
252
+ },
253
+ );
254
+ }).toList(),
255
+ ListTile(
256
+ title: const Text(' RRSP Sun Life'),
257
+ onTap: () {
258
+ Navigator.pop(context); // Close drawer
259
+ Navigator.push(
260
+ context,
261
+ MaterialPageRoute(
262
+ builder: (context) => const RrspSunLifeScreen()),
263
+ );
264
+ },
265
+ ),
266
+ ];
267
+ break;
268
+ case 'reports':
269
+ title = 'Reports';
270
+ items = [
271
+ ListTile(
272
+ title: const Text(' Salary Report'),
273
+ onTap: () {
274
+ Navigator.pop(context);
275
+ Navigator.push(
276
+ context,
277
+ MaterialPageRoute(
278
+ builder: (context) => const SalaryReportScreen()),
279
+ );
280
+ },
281
+ ),
282
+ ListTile(
283
+ title: const Text(' Expense Report'),
284
+ onTap: () {
285
+ Navigator.pop(context);
286
+ Navigator.push(
287
+ context,
288
+ MaterialPageRoute(
289
+ builder: (context) => const ExpenseReportScreen()),
290
+ );
291
+ },
292
+ ),
293
+ ListTile(
294
+ title: const Text(' Summary Report'),
295
+ onTap: () {
296
+ Navigator.pop(context);
297
+ Navigator.push(
298
+ context,
299
+ MaterialPageRoute(
300
+ builder: (context) => const SummaryReportScreen()),
301
+ );
302
+ },
303
+ ),
304
+ ListTile(
305
+ title: const Text(' Move Money Report'),
306
+ onTap: () {
307
+ Navigator.pop(context);
308
+ Navigator.push(
309
+ context,
310
+ MaterialPageRoute(
311
+ builder: (context) => const MovemoneyReportScreen()));
312
+ },
313
+ ),
314
+ ];
315
+ break;
316
+ case 'charts':
317
+ title = 'Charts';
318
+ items = [
319
+ ListTile(
320
+ title: const Text(' Salary Chart'),
321
+ onTap: () {
322
+ Navigator.pop(context);
323
+ Navigator.push(
324
+ context,
325
+ MaterialPageRoute(
326
+ builder: (context) => const SalaryChartScreen()),
327
+ );
328
+ },
329
+ ),
330
+ ListTile(
331
+ title: const Text(' Expense Chart'),
332
+ onTap: () {
333
+ Navigator.pop(context);
334
+ Navigator.push(
335
+ context,
336
+ MaterialPageRoute(
337
+ builder: (context) => const ExpenseChartScreen()),
338
+ );
339
+ },
340
+ ),
341
+ ];
342
+ break;
343
+ case 'management':
344
+ title = 'Management';
345
+ items = [
346
+ ListTile(
347
+ title: const Text(' Manage Transactions'),
348
+ onTap: () {
349
+ Navigator.pop(context);
350
+ Navigator.push(
351
+ context,
352
+ MaterialPageRoute(
353
+ builder: (context) => const ManageTransactionsScreen()),
354
+ ).then((_) => _fetchData());
355
+ },
356
+ ),
357
+ ListTile(
358
+ title: const Text(' Reconciliation'),
359
+ onTap: () {
360
+ Navigator.pop(context);
361
+ Navigator.push(
362
+ context,
363
+ MaterialPageRoute(
364
+ builder: (context) => const ReconciliationScreen(
365
+ paymentMethod: '')), // Pass empty string for now
366
+ );
367
+ },
368
+ ),
369
+ ];
370
+ break;
371
+ default:
372
+ items = [];
373
+ title = 'Menu';
374
+ }
375
+
376
+ return ListView(
377
+ padding: EdgeInsets.zero,
378
+ children: <Widget>[
379
+ AppBar(
380
+ title: Text(title),
381
+ backgroundColor: Theme.of(context).primaryColor,
382
+ leading: IconButton(
383
+ icon: const Icon(Icons.arrow_back),
384
+ onPressed: () {
385
+ setState(() {
386
+ _selectedMenu = null;
387
+ });
388
+ },
389
+ ),
390
+ automaticallyImplyLeading: false,
391
+ ),
392
+ ...items,
393
+ ],
394
+ );
395
+ }
396
+
397
+ @override
398
+ Widget build(BuildContext context) {
399
+ final double totalUserCash =
400
+ CurrencyInputFormatter.unformat(_rbcCashController.text) +
401
+ CurrencyInputFormatter.unformat(_wealthsimpleCashController.text);
402
+
403
+ final double appCalculatedCash = _accountBalances['CASH'] ?? 0.0;
404
+ final token = Provider.of<AuthProvider>(context, listen: false).token;
405
+
406
+ return Scaffold(
407
+ appBar: AppBar(
408
+ title: const Text('Personal Finance Dashboard'),
409
+ actions: [
410
+ const UserProfileAvatar(),
411
+ Consumer<ThemeNotifier>(
412
+ builder: (context, themeNotifier, child) {
413
+ return IconButton(
414
+ icon: Icon(themeNotifier.themeMode == ThemeMode.dark
415
+ ? Icons.dark_mode
416
+ : Icons.light_mode),
417
+ onPressed: () {
418
+ themeNotifier.setThemeMode(
419
+ themeNotifier.themeMode == ThemeMode.dark
420
+ ? ThemeMode.light
421
+ : ThemeMode.dark);
422
+ },
423
+ );
424
+ },
425
+ ),
426
+ IconButton(
427
+ icon: const Icon(Icons.logout),
428
+ onPressed: () {
429
+ Provider.of<AuthProvider>(context, listen: false).logout();
430
+ },
431
+ ),
432
+ ],
433
+ ),
434
+ drawer: Drawer(
435
+ child: _selectedMenu == null
436
+ ? _buildMainMenu(context)
437
+ : _buildSubMenu(context),
438
+ ),
439
+ body: _isLoading
440
+ ? const Center(child: CircularProgressIndicator())
441
+ : SingleChildScrollView(
442
+ padding: const EdgeInsets.all(16.0),
443
+ child: Column(
444
+ crossAxisAlignment: CrossAxisAlignment.start,
445
+ children: [
446
+ Row(
447
+ crossAxisAlignment: CrossAxisAlignment.start,
448
+ children: [
449
+ Expanded(
450
+ child: _buildCashBalanceCard(
451
+ totalUserCash, appCalculatedCash)),
452
+ const SizedBox(width: 16),
453
+ Expanded(
454
+ child: _buildCreditCardBalanceCard('RBC Balance',
455
+ _rbcCardController, _rbcUnpaidExpenses)),
456
+ const SizedBox(width: 16),
457
+ Expanded(
458
+ child: _buildCreditCardBalanceCard('BMO Balance',
459
+ _bmoCardController, _bmoUnpaidExpenses)),
460
+ ],
461
+ ),
462
+ const SizedBox(height: 16),
463
+ Row(
464
+ crossAxisAlignment: CrossAxisAlignment.start,
465
+ children: [
466
+ Expanded(
467
+ child: _buildRecentTransactionsList(
468
+ 'Cash Transactions', _cashTransactions)),
469
+ const SizedBox(width: 16),
470
+ Expanded(
471
+ child: _buildRecentTransactionsList(
472
+ 'RBC Transactions', _rbcTransactions)),
473
+ const SizedBox(width: 16),
474
+ Expanded(
475
+ child: _buildRecentTransactionsList(
476
+ 'BMO Transactions', _bmoTransactions)),
477
+ const SizedBox(width: 16),
478
+ Expanded(
479
+ child: ExpenseForm(
480
+ formKey: _expenseFormKey,
481
+ onTransactionSuccess: _fetchData,
482
+ token: token,
483
+ )),
484
+ ],
485
+ ),
486
+ const SizedBox(height: 16),
487
+ Row(
488
+ crossAxisAlignment: CrossAxisAlignment.start,
489
+ children: [
490
+ Expanded(
491
+ child: InvestmentForm(
492
+ formKey: _investmentFormKey,
493
+ accountBalances: _accountBalances,
494
+ cashBalance: appCalculatedCash,
495
+ onTransactionSuccess: _fetchData,
496
+ token: token,
497
+ )),
498
+ const SizedBox(width: 16),
499
+ Expanded(
500
+ child: SalaryForm(
501
+ formKey: _salaryFormKey,
502
+ onTransactionSuccess: _fetchData,
503
+ token: token,
504
+ )),
505
+ ],
506
+ ),
507
+ ],
508
+ ),
509
+ ),
510
+ floatingActionButton: FloatingActionButton(
511
+ onPressed: _fetchData,
512
+ tooltip: 'Refresh Data',
513
+ child: const Icon(Icons.refresh),
514
+ ),
515
+ );
516
+ }
517
+
518
+ Widget _buildCashBalanceCard(double totalUserCash, double appCalculatedCash) {
519
+ final double difference = totalUserCash - appCalculatedCash;
520
+
521
+ String differenceLabel = 'Balance is correct';
522
+ Color differenceColor = Theme.of(context).colorScheme.secondary;
523
+ String differenceText = _formatCurrency(difference);
524
+
525
+ if (difference > 0.01) {
526
+ differenceLabel = 'Missing Income:';
527
+ differenceColor = Colors.green;
528
+ differenceText = '+${_formatCurrency(difference)}';
529
+ } else if (difference < -0.01) {
530
+ differenceLabel = 'Missing Expenses:';
531
+ differenceColor = Colors.red;
532
+ }
533
+
534
+ return AppFormCard(
535
+ title: 'Cash Balance',
536
+ child: Column(
537
+ crossAxisAlignment: CrossAxisAlignment.start,
538
+ children: [
539
+ _buildBalanceTextField(_rbcCashController, 'RBC Bank Balance'),
540
+ const SizedBox(height: 12),
541
+ _buildBalanceTextField(
542
+ _wealthsimpleCashController, 'Wealthsimple Balance'),
543
+ const SizedBox(height: 16),
544
+ const Divider(),
545
+ const SizedBox(height: 10),
546
+ _buildResultRow(
547
+ 'Manual Entry Balance:', '${_formatCurrency(totalUserCash)}'),
548
+ _buildResultRow(
549
+ 'Calculated Balance:', '${_formatCurrency(appCalculatedCash)}'),
550
+ const SizedBox(height: 10),
551
+ _buildResultRow(differenceLabel, differenceText,
552
+ isHighlighted: true, color: differenceColor),
553
+ ],
554
+ ),
555
+ );
556
+ }
557
+
558
+ Widget _buildCreditCardBalanceCard(
559
+ String title, TextEditingController controller, double appExpenses) {
560
+ final double userBalance = CurrencyInputFormatter.unformat(controller.text);
561
+ final double difference = appExpenses - userBalance;
562
+
563
+ String differenceLabel = 'Balance is correct';
564
+ Color differenceColor = Theme.of(context).colorScheme.secondary;
565
+ String differenceText = '${_formatCurrency(difference)}';
566
+
567
+ if (difference > 0.01) {
568
+ differenceLabel = 'Missing Expenses:';
569
+ differenceColor = Colors.red;
570
+ differenceText = '+${_formatCurrency(difference)}';
571
+ } else if (difference < -0.01) {
572
+ differenceLabel = 'Missing Refund:';
573
+ differenceColor = Colors.green;
574
+ }
575
+
576
+ return AppFormCard(
577
+ title: title,
578
+ child: Column(
579
+ crossAxisAlignment: CrossAxisAlignment.start,
580
+ children: [
581
+ _buildBalanceTextField(controller, 'Statement Balance:'),
582
+ const SizedBox(height: 16),
583
+ const Divider(),
584
+ const SizedBox(height: 10),
585
+ _buildResultRow(
586
+ 'Manual Entry Balance:', '${_formatCurrency(userBalance)}'),
587
+ _buildResultRow(
588
+ 'Calculated Balance:', '${_formatCurrency(appExpenses)}'),
589
+ const SizedBox(height: 10),
590
+ _buildResultRow(differenceLabel, differenceText,
591
+ isHighlighted: true, color: differenceColor),
592
+ const SizedBox(height: 16),
593
+ Align(
594
+ alignment: Alignment.centerRight,
595
+ child: TextButton(
596
+ onPressed: () {
597
+ String paymentMethod = 'unknown';
598
+ if (title.toLowerCase().contains('rbc')) {
599
+ paymentMethod = 'rbc';
600
+ } else if (title.toLowerCase().contains('bmo')) {
601
+ paymentMethod = 'bmo';
602
+ }
603
+ Navigator.push(
604
+ context,
605
+ MaterialPageRoute(
606
+ builder: (context) =>
607
+ ReconciliationScreen(paymentMethod: paymentMethod),
608
+ ),
609
+ ).then((_) => _fetchData()); // Refresh data when returning
610
+ },
611
+ child: const Text('Reconcile'),
612
+ ),
613
+ ),
614
+ ],
615
+ ),
616
+ );
617
+ }
618
+
619
+ Widget _buildBalanceTextField(
620
+ TextEditingController controller, String label) {
621
+ return TextFormField(
622
+ controller: controller,
623
+ decoration: InputDecoration(
624
+ labelText: label,
625
+ border: OutlineInputBorder(
626
+ borderRadius: BorderRadius.circular(16.0),
627
+ ),
628
+ ),
629
+ keyboardType: const TextInputType.numberWithOptions(decimal: true),
630
+ inputFormatters: [CurrencyInputFormatter()],
631
+ onChanged: (value) => setState(() {}),
632
+ );
633
+ }
634
+
635
+ Widget _buildResultRow(String label, String value,
636
+ {bool isHighlighted = false, Color color = Colors.black}) {
637
+ return Row(
638
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
639
+ children: [
640
+ Text(label, style: const TextStyle(fontSize: 16)),
641
+ Text(
642
+ value,
643
+ style: TextStyle(
644
+ fontSize: 16,
645
+ fontWeight: isHighlighted ? FontWeight.bold : FontWeight.normal,
646
+ color: isHighlighted ? color : null,
647
+ ),
648
+ ),
649
+ ],
650
+ );
651
+ }
652
+
653
+ Widget _buildRecentTransactionsList(
654
+ String title, List<Map<String, dynamic>> transactions) {
655
+ return AppFormCard(
656
+ title: title,
657
+ child: transactions.isEmpty
658
+ ? const Text('No recent transactions.')
659
+ : Column(
660
+ children: transactions.map((transaction) {
661
+ final date = DateFormat('MM/dd')
662
+ .format(DateTime.parse(transaction['date']));
663
+ final amount = double.parse(transaction['amount'].toString())
664
+ .toStringAsFixed(2);
665
+ final subcategory =
666
+ transaction['subcategory'] ?? 'No subcategory';
667
+ final type = transaction['type'] == 'debit' ? '-' : '+';
668
+ final color =
669
+ transaction['type'] == 'debit' ? Colors.red : Colors.green;
670
+
671
+ return Padding(
672
+ padding: const EdgeInsets.symmetric(vertical: 4.0),
673
+ child: Row(
674
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
675
+ children: [
676
+ Expanded(
677
+ child: Text(
678
+ '$date - $subcategory',
679
+ overflow: TextOverflow.ellipsis,
680
+ ),
681
+ ),
682
+ Text(
683
+ '$type\u0024$amount',
684
+ style: TextStyle(
685
+ color: color, fontWeight: FontWeight.bold),
686
+ ),
687
+ ],
688
+ ),
689
+ );
690
+ }).toList(),
691
+ ),
692
+ );
693
+ }
694
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@marcos_feitoza/personal-finance-frontend-feature-dashboard",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "scripts": {
8
+ "release": "semantic-release"
9
+ },
10
+ "devDependencies": {
11
+ "semantic-release": "^18.0.0",
12
+ "@semantic-release/changelog": "^6.0.0",
13
+ "@semantic-release/git": "^10.0.0",
14
+ "@semantic-release/github": "^8.0.0"
15
+ },
16
+ "release": {
17
+ "branches": [
18
+ "main"
19
+ ],
20
+ "plugins": [
21
+ "@semantic-release/commit-analyzer",
22
+ "@semantic-release/release-notes-generator",
23
+ "@semantic-release/changelog",
24
+ "@semantic-release/npm",
25
+ "@semantic-release/github",
26
+ "@semantic-release/git"
27
+ ]
28
+ }
29
+ }
package/pubspec.yaml ADDED
@@ -0,0 +1,67 @@
1
+ name: personal_finance_frontend_feature_dashboard
2
+ description: "A new Flutter package project."
3
+ version: 0.0.1
4
+ homepage:
5
+
6
+ environment:
7
+ sdk: '>=2.19.0 <3.0.0'
8
+
9
+ dependencies:
10
+ flutter:
11
+ sdk: flutter
12
+ intl: ^0.17.0
13
+ provider: ^6.0.0
14
+ personal_finance_frontend_core_services:
15
+ path: ../personal-finance-frontend-core-services
16
+ personal_finance_frontend_core_ui:
17
+ path: ../personal-finance-frontend-core-ui
18
+ personal_finance_frontend_feature_reports:
19
+ path: ../personal-finance-frontend-feature-reports
20
+ personal_finance_frontend_feature_charts:
21
+ path: ../personal-finance-frontend-feature-charts
22
+ personal_finance_frontend_feature_investments:
23
+ path: ../personal-finance-frontend-feature-investments
24
+ personal_finance_frontend_feature_management:
25
+ path: ../personal-finance-frontend-feature-management
26
+
27
+ dev_dependencies:
28
+ flutter_test:
29
+ sdk: flutter
30
+ flutter_lints: ^5.0.0
31
+
32
+ # For information on the generic Dart part of this file, see the
33
+ # following page: https://dart.dev/tools/pub/pubspec
34
+
35
+ # The following section is specific to Flutter packages.
36
+ flutter:
37
+
38
+ # To add assets to your package, add an assets section, like this:
39
+ # assets:
40
+ # - images/a_dot_burr.jpeg
41
+ # - images/a_dot_ham.jpeg
42
+ #
43
+ # For details regarding assets in packages, see
44
+ # https://flutter.dev/to/asset-from-package
45
+ #
46
+ # An image asset can refer to one or more resolution-specific "variants", see
47
+ # https://flutter.dev/to/resolution-aware-images
48
+
49
+ # To add custom fonts to your package, add a fonts section here,
50
+ # in this "flutter" section. Each entry in this list should have a
51
+ # "family" key with the font family name, and a "fonts" key with a
52
+ # list giving the asset and other descriptors for the font. For
53
+ # example:
54
+ # fonts:
55
+ # - family: Schyler
56
+ # fonts:
57
+ # - asset: fonts/Schyler-Regular.ttf
58
+ # - asset: fonts/Schyler-Italic.ttf
59
+ # style: italic
60
+ # - family: Trajan Pro
61
+ # fonts:
62
+ # - asset: fonts/TrajanPro.ttf
63
+ # - asset: fonts/TrajanPro_Bold.ttf
64
+ # weight: 700
65
+ #
66
+ # For details regarding fonts in packages, see
67
+ # https://flutter.dev/to/font-from-package
@@ -0,0 +1,12 @@
1
+ import 'package:flutter_test/flutter_test.dart';
2
+
3
+ import 'package:personal_finance_frontend_feature_dashboard/personal_finance_frontend_feature_dashboard.dart';
4
+
5
+ void main() {
6
+ test('adds one to input values', () {
7
+ final calculator = Calculator();
8
+ expect(calculator.addOne(2), 3);
9
+ expect(calculator.addOne(-7), -6);
10
+ expect(calculator.addOne(0), 1);
11
+ });
12
+ }