@marcos_feitoza/personal-finance-frontend-feature-investments 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,340 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:intl/intl.dart';
3
+ import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
4
+ import 'package:personal_finance_frontend_core_ui/widgets/rrsp_contribution_form.dart';
5
+ import 'package:provider/provider.dart';
6
+ import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
7
+
8
+ class RrspSunLifeScreen extends StatefulWidget {
9
+ const RrspSunLifeScreen({Key? key}) : super(key: key);
10
+
11
+ @override
12
+ _RrspSunLifeScreenState createState() => _RrspSunLifeScreenState();
13
+ }
14
+
15
+ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
16
+ final TransactionService _transactionService = TransactionService();
17
+ List<Map<String, dynamic>> _contributions = [];
18
+ List<Map<String, dynamic>> _sinteticoSummary = [];
19
+ double _rrspTotalValue = 0.0;
20
+ double _rrspCashBalance = 0.0;
21
+ double _totalPortfolioBookCost = 0.0;
22
+ bool _isLoading = true;
23
+ String? _token;
24
+
25
+ @override
26
+ void initState() {
27
+ super.initState();
28
+ WidgetsBinding.instance.addPostFrameCallback((_) {
29
+ final authProvider = Provider.of<AuthProvider>(context, listen: false);
30
+ setState(() {
31
+ _token = authProvider.token;
32
+ });
33
+ _fetchData();
34
+ });
35
+ }
36
+
37
+ String currencySymbol = r'$'; // ou poderia vir de configuração, API, etc.
38
+
39
+ String _formatCurrency(double value) {
40
+ return '$currencySymbol${value.toStringAsFixed(2)}';
41
+ }
42
+
43
+ Future<void> _fetchData() async {
44
+ if (_token == null) return;
45
+ if (mounted) {
46
+ setState(() {
47
+ _isLoading = true;
48
+ });
49
+ }
50
+
51
+ try {
52
+ final contributions = await _transactionService.getRrspContributions(
53
+ investmentAccount: 'RRSP Sun Life', token: _token);
54
+ final balance =
55
+ await _transactionService.getAccountBalance('RRSP Sun Life', token: _token);
56
+ final totalPortfolioBookCost =
57
+ await _transactionService.getTotalPortfolioBookCost(token: _token);
58
+
59
+ final sinteticoSummaryData =
60
+ _calculateSinteticoSummary(contributions, totalPortfolioBookCost);
61
+
62
+ print('[DEBUG] Sintetico Summary Data: $sinteticoSummaryData');
63
+
64
+ if (!mounted) return;
65
+ setState(() {
66
+ _contributions = contributions;
67
+ _sinteticoSummary = sinteticoSummaryData['summary'];
68
+ _rrspCashBalance = balance;
69
+ _rrspTotalValue = sinteticoSummaryData['total_value'];
70
+ _totalPortfolioBookCost = totalPortfolioBookCost;
71
+ });
72
+ } catch (e) {
73
+ if (!mounted) return;
74
+ ScaffoldMessenger.of(context).showSnackBar(
75
+ SnackBar(content: Text('Error fetching RRSP data: $e')),
76
+ );
77
+ } finally {
78
+ if (mounted) {
79
+ setState(() {
80
+ _isLoading = false;
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ Map<String, dynamic> _calculateSinteticoSummary(
87
+ List<Map<String, dynamic>> contributions, double totalPortfolioBookCost) {
88
+ double totalRrsp = 0;
89
+
90
+ double totalDpsp = 0;
91
+
92
+ double totalReturn = 0;
93
+
94
+ for (var c in contributions) {
95
+ totalRrsp += double.parse(c['rrsp_amount'].toString());
96
+
97
+ totalDpsp += double.parse(c['dpsp_amount'].toString());
98
+
99
+ totalReturn += double.parse(c['return_amount']?.toString() ?? '0.0');
100
+ }
101
+
102
+ final totalPurchased = totalRrsp + totalDpsp;
103
+
104
+ final bookCost = totalPurchased + totalReturn;
105
+
106
+ final summaryData = {
107
+ 'rrsp_amount': totalRrsp,
108
+ 'dpsp_amount': totalDpsp,
109
+ 'total_purchased': totalPurchased,
110
+ 'return_amount': totalReturn,
111
+ 'percent_return':
112
+ (totalPurchased > 0) ? (totalReturn / totalPurchased) * 100 : 0.0,
113
+ 'book_cost': bookCost,
114
+ 'portfolio_allocation': (totalPortfolioBookCost > 0)
115
+ ? (bookCost / totalPortfolioBookCost) * 100
116
+ : 0.0,
117
+ };
118
+
119
+ return {
120
+ 'summary': [summaryData],
121
+ 'total_value': bookCost
122
+ };
123
+ }
124
+
125
+ @override
126
+ Widget build(BuildContext context) {
127
+ return Scaffold(
128
+ appBar: AppBar(
129
+ title: const Text('RRSP Sun Life Portfolio'),
130
+ actions: [
131
+ Center(
132
+ child: Padding(
133
+ padding: const EdgeInsets.only(right: 16.0),
134
+ child: Column(
135
+ mainAxisAlignment: MainAxisAlignment.center,
136
+ crossAxisAlignment: CrossAxisAlignment.end,
137
+ children: [
138
+ Text(
139
+ 'Total Portfolio: ${_formatCurrency(_rrspTotalValue)}',
140
+ style: const TextStyle(
141
+ fontSize: 16, fontWeight: FontWeight.bold),
142
+ ),
143
+ Text(
144
+ 'Account Balance: ${_formatCurrency(_rrspCashBalance)}',
145
+ style: const TextStyle(fontSize: 12),
146
+ ),
147
+ ],
148
+ ),
149
+ ),
150
+ ),
151
+ IconButton(
152
+ icon: const Icon(Icons.refresh),
153
+ onPressed: _fetchData,
154
+ tooltip: 'Refresh Data',
155
+ ),
156
+ ],
157
+ ),
158
+ body: _isLoading
159
+ ? const Center(child: CircularProgressIndicator())
160
+ : SingleChildScrollView(
161
+ padding: const EdgeInsets.all(16.0),
162
+ child: Column(
163
+ crossAxisAlignment: CrossAxisAlignment.stretch,
164
+ children: [
165
+ Text('Summary',
166
+ style: Theme.of(context).textTheme.titleLarge),
167
+ const SizedBox(height: 8),
168
+ _buildSinteticoTable(),
169
+ const SizedBox(height: 24),
170
+ RrspContributionForm(
171
+ investmentAccount: 'RRSP Sun Life',
172
+ onContributionLogged: () => _fetchData(),
173
+ token: _token,
174
+ ),
175
+ const SizedBox(height: 24),
176
+ Text(
177
+ 'Contribution History',
178
+ style: Theme.of(context).textTheme.titleLarge,
179
+ ),
180
+ const SizedBox(height: 8),
181
+ _buildContributionTable(),
182
+ ],
183
+ ),
184
+ ),
185
+ );
186
+ }
187
+
188
+ Widget _buildSinteticoTable() {
189
+ if (_sinteticoSummary.isEmpty) {
190
+ return const SizedBox.shrink();
191
+ }
192
+
193
+ final summary = _sinteticoSummary.first;
194
+
195
+ return DataTable(
196
+ columns: const [
197
+ DataColumn(label: Text('RRSP')),
198
+ DataColumn(label: Text('DPSP')),
199
+ DataColumn(label: Text('Total Purchased')),
200
+ DataColumn(label: Text('Return')),
201
+ DataColumn(label: Text('% Return')),
202
+ DataColumn(label: Text('Book Cost')),
203
+ ],
204
+ rows: [
205
+ DataRow(
206
+ cells: [
207
+ DataCell(Text(_formatCurrency(summary['rrsp_amount'] as double))),
208
+ DataCell(Text(_formatCurrency(summary['dpsp_amount'] as double))),
209
+ DataCell(
210
+ Text(_formatCurrency(summary['total_purchased'] as double))),
211
+ DataCell(Text(_formatCurrency(summary['return_amount'] as double))),
212
+ DataCell(Text(
213
+ '${(summary['percent_return'] as double).toStringAsFixed(2)}%')),
214
+ DataCell(Text(_formatCurrency(summary['book_cost'] as double))),
215
+ ],
216
+ ),
217
+ ],
218
+ );
219
+ }
220
+
221
+ Widget _buildContributionTable() {
222
+ if (_contributions.isEmpty) {
223
+ return const Center(
224
+ child: Padding(
225
+ padding: EdgeInsets.all(16.0),
226
+ child: Text('No contributions found for RRSP Sun Life.'),
227
+ ),
228
+ );
229
+ }
230
+
231
+ return DataTable(
232
+ columns: const [
233
+ DataColumn(label: Text('Date')),
234
+ DataColumn(label: Text('RRSP')),
235
+ DataColumn(label: Text('DPSP')),
236
+ DataColumn(label: Text('Total Purchased')),
237
+ DataColumn(label: Text('Return')),
238
+ DataColumn(label: Text('% Return')),
239
+ DataColumn(label: Text('Book Cost')),
240
+ DataColumn(label: Text('Stock Allocation')),
241
+ DataColumn(label: Text('Actions')), // New column
242
+ ],
243
+ rows: _contributions.map((c) {
244
+ final contributionId = c['id'] as int;
245
+ final rrspAmount = double.parse(c['rrsp_amount'].toString());
246
+ final dpspAmount = double.parse(c['dpsp_amount'].toString());
247
+ final totalPurchased = rrspAmount + dpspAmount;
248
+
249
+ final returnAmount =
250
+ double.parse(c['return_amount']?.toString() ?? '0.0');
251
+ final percentReturn =
252
+ totalPurchased > 0 ? (returnAmount / totalPurchased) * 100 : 0.0;
253
+
254
+ final bookCost = totalPurchased + returnAmount;
255
+
256
+ final stockAllocation = _totalPortfolioBookCost > 0
257
+ ? (bookCost / _totalPortfolioBookCost) * 100
258
+ : 0.0;
259
+
260
+ String dateStr;
261
+ final rawDate = c['date'];
262
+ if (rawDate is String) {
263
+ dateStr = DateFormat('yyyy-MM-dd').format(DateTime.parse(rawDate));
264
+ } else if (rawDate is DateTime) {
265
+ dateStr = DateFormat('yyyy-MM-dd').format(rawDate);
266
+ } else {
267
+ dateStr = '-';
268
+ }
269
+
270
+ return DataRow(
271
+ cells: [
272
+ DataCell(Text(dateStr)),
273
+ DataCell(Text(_formatCurrency(rrspAmount))),
274
+ DataCell(Text(_formatCurrency(dpspAmount))),
275
+ DataCell(Text(_formatCurrency(totalPurchased))),
276
+ DataCell(Text(_formatCurrency(returnAmount))),
277
+ DataCell(Text('${percentReturn.toStringAsFixed(2)}%')),
278
+ DataCell(Text(_formatCurrency(bookCost))),
279
+ DataCell(Text('${stockAllocation.toStringAsFixed(2)}%')),
280
+ DataCell( // New cell for the delete button
281
+ IconButton(
282
+ icon: const Icon(Icons.delete, color: Colors.red),
283
+ onPressed: () => _confirmAndDeleteContribution(contributionId),
284
+ tooltip: 'Delete Contribution',
285
+ ),
286
+ ),
287
+ ],
288
+ );
289
+ }).toList(),
290
+ );
291
+ }
292
+
293
+ Future<void> _confirmAndDeleteContribution(int contributionId) async {
294
+ final bool? confirm = await showDialog<bool>(
295
+ context: context,
296
+ builder: (BuildContext context) {
297
+ return AlertDialog(
298
+ title: const Text('Confirm Deletion'),
299
+ content: const Text('Are you sure you want to delete this contribution? This action cannot be undone.'),
300
+ actions: <Widget>[
301
+ TextButton(
302
+ onPressed: () => Navigator.of(context).pop(false),
303
+ child: const Text('Cancel'),
304
+ ),
305
+ TextButton(
306
+ onPressed: () => Navigator.of(context).pop(true),
307
+ child: const Text('Delete'),
308
+ ),
309
+ ],
310
+ );
311
+ },
312
+ );
313
+
314
+ if (confirm == true) {
315
+ if (_token == null) {
316
+ ScaffoldMessenger.of(context).showSnackBar(
317
+ const SnackBar(content: Text('Authentication token not available.')),
318
+ );
319
+ return;
320
+ }
321
+ try {
322
+ final success = await _transactionService.deleteRrspContribution(contributionId, token: _token);
323
+ if (success) {
324
+ ScaffoldMessenger.of(context).showSnackBar(
325
+ const SnackBar(content: Text('Contribution deleted successfully.')),
326
+ );
327
+ _fetchData(); // Refresh data after deletion
328
+ } else {
329
+ ScaffoldMessenger.of(context).showSnackBar(
330
+ const SnackBar(content: Text('Failed to delete contribution.')),
331
+ );
332
+ }
333
+ } catch (e) {
334
+ ScaffoldMessenger.of(context).showSnackBar(
335
+ SnackBar(content: Text('Error deleting contribution: $e')),
336
+ );
337
+ }
338
+ }
339
+ }
340
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@marcos_feitoza/personal-finance-frontend-feature-investments",
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,59 @@
1
+ name: personal_finance_frontend_feature_investments
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
+
19
+ dev_dependencies:
20
+ flutter_test:
21
+ sdk: flutter
22
+ flutter_lints: ^5.0.0
23
+
24
+ # For information on the generic Dart part of this file, see the
25
+ # following page: https://dart.dev/tools/pub/pubspec
26
+
27
+ # The following section is specific to Flutter packages.
28
+ flutter:
29
+
30
+ # To add assets to your package, add an assets section, like this:
31
+ # assets:
32
+ # - images/a_dot_burr.jpeg
33
+ # - images/a_dot_ham.jpeg
34
+ #
35
+ # For details regarding assets in packages, see
36
+ # https://flutter.dev/to/asset-from-package
37
+ #
38
+ # An image asset can refer to one or more resolution-specific "variants", see
39
+ # https://flutter.dev/to/resolution-aware-images
40
+
41
+ # To add custom fonts to your package, add a fonts section here,
42
+ # in this "flutter" section. Each entry in this list should have a
43
+ # "family" key with the font family name, and a "fonts" key with a
44
+ # list giving the asset and other descriptors for the font. For
45
+ # example:
46
+ # fonts:
47
+ # - family: Schyler
48
+ # fonts:
49
+ # - asset: fonts/Schyler-Regular.ttf
50
+ # - asset: fonts/Schyler-Italic.ttf
51
+ # style: italic
52
+ # - family: Trajan Pro
53
+ # fonts:
54
+ # - asset: fonts/TrajanPro.ttf
55
+ # - asset: fonts/TrajanPro_Bold.ttf
56
+ # weight: 700
57
+ #
58
+ # For details regarding fonts in packages, see
59
+ # 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_investments/personal_finance_frontend_feature_investments.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
+ }