@marcos_feitoza/personal-finance-frontend-feature-investments 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-investments/compare/v1.1.2...v1.2.0) (2025-12-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add totals back ([0abb920](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/0abb920257d8988083f08a24f8db90ad9a24d463))
7
+
8
+
9
+ ### Features
10
+
11
+ * new dividend logic ([5aed0ed](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/5aed0edd789072894c927192fb2105dd7cf1623b))
12
+ * update dividend ([67c9dc3](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/67c9dc327a15e114c7a4acc590f797e1f89ac2ed))
13
+
1
14
  ## [1.1.2](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.1.1...v1.1.2) (2025-12-03)
2
15
 
3
16
 
@@ -139,27 +139,17 @@ class CryptoAccountScreen extends StatelessWidget {
139
139
  final idForLookup =
140
140
  (position['id_crypto'] as String? ?? symbol).toLowerCase();
141
141
  final livePrice = viewModel.livePrices[idForLookup];
142
- final shares = double.parse(position['shares'].toString());
143
- final avgPrice = double.parse(position['avg_price'].toString());
144
- final totalPurchased =
145
- double.parse(position['total_cost'].toString());
146
- final accountAllocation =
147
- (position['account_allocation'] as num?)?.toDouble() ?? 0.0;
148
- final stocksAllocation =
149
- (position['stocks_allocation'] as num?)?.toDouble() ?? 0.0;
142
+ final shares = double.tryParse(position['shares']?.toString() ?? '0.0') ?? 0.0;
143
+ final avgPrice = double.tryParse(position['avg_price']?.toString() ?? '0.0') ?? 0.0;
144
+ final totalPurchased = double.tryParse(position['total_cost']?.toString() ?? '0.0') ?? 0.0;
145
+ final accountAllocation = double.tryParse(position['account_allocation']?.toString() ?? '0.0') ?? 0.0;
146
+ final stocksAllocation = double.tryParse(position['stocks_allocation']?.toString() ?? '0.0') ?? 0.0;
150
147
 
151
- double? totalReturnValue, percentageReturn;
152
-
153
- if (livePrice != null && shares > 0) {
154
- final currentMarketValue = livePrice * shares;
155
- totalReturnValue = currentMarketValue - totalPurchased;
156
- if (totalPurchased > 0) {
157
- percentageReturn = (totalReturnValue / totalPurchased) * 100;
158
- }
159
- }
148
+ final totalReturnValue = double.tryParse(position['total_return_value']?.toString() ?? '0.0') ?? 0.0;
149
+ final percentageReturn = double.tryParse(position['percentage_return']?.toString() ?? '0.0') ?? 0.0;
160
150
 
161
151
  final returnColor =
162
- (totalReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
152
+ totalReturnValue >= 0 ? Colors.green : Colors.red;
163
153
 
164
154
  return DataRow(cells: [
165
155
  DataCell(SizedBox(
@@ -225,8 +215,8 @@ class CryptoAccountScreen extends StatelessWidget {
225
215
  DataColumn(label: Text('Actions')),
226
216
  ],
227
217
  rows: sortedTrades.map((trade) {
228
- final double shares = double.parse(trade['shares'].toString());
229
- final double price = double.parse(trade['price'].toString());
218
+ final double shares = double.tryParse(trade['shares']?.toString() ?? '0.0') ?? 0.0;
219
+ final double price = double.tryParse(trade['price']?.toString() ?? '0.0') ?? 0.0;
230
220
  final double total = shares * price;
231
221
  final tradeDate = DateTime.parse(trade['date'] as String);
232
222
  final int tradeId = trade['id'] as int;
@@ -114,12 +114,12 @@ class InvestmentAccountScreen extends StatelessWidget {
114
114
  ],
115
115
  ),
116
116
  const SizedBox(height: 24),
117
- Text('Trade History (Analítico)',
117
+ Text('Account History',
118
118
  style: Theme.of(context).textTheme.titleLarge),
119
119
  const SizedBox(height: 8),
120
120
  _buildAnaliticoFilter(context, viewModel),
121
121
  const SizedBox(height: 8),
122
- _buildAnaliticoTable(context, viewModel),
122
+ _buildHistoryTable(context, viewModel),
123
123
  ],
124
124
  ),
125
125
  ),
@@ -195,35 +195,22 @@ class InvestmentAccountScreen extends StatelessWidget {
195
195
  const DataColumn(label: Text('Market Value')),
196
196
  const DataColumn(label: Text('Unrealized P/L')),
197
197
  const DataColumn(label: Text('% P/L')),
198
- if (showDividends) ...[
199
- const DataColumn(label: Text('Dividends')),
200
- const DataColumn(label: Text('Total Return')),
201
- const DataColumn(label: Text('% Total Return')),
202
- ],
203
198
  const DataColumn(label: Text('Account %')),
204
199
  const DataColumn(label: Text('Portfolio %')),
205
200
  ],
206
201
  rows: viewModel.portfolioSummary.map((position) {
207
202
  final symbol = position['symbol'] as String;
208
- final shares = position['shares'] as double;
209
- final avgPrice = position['avg_price'] as double;
210
- final bookCost = position['book_cost'] as double;
203
+ final shares = double.tryParse(position['shares']?.toString() ?? '0.0') ?? 0.0;
204
+ final avgPrice = double.tryParse(position['avg_price']?.toString() ?? '0.0') ?? 0.0;
205
+ final bookCost = double.tryParse(position['book_cost']?.toString() ?? '0.0') ?? 0.0;
211
206
  final livePrice = viewModel.livePrices[symbol];
212
- final marketValue = position['market_value'] as double;
213
- final unrealizedPL = position['unrealized_pl'] as double;
214
- final percentUnrealizedPL =
215
- position['percent_unrealized_pl'] as double;
216
- final totalDividends = position['total_dividends'] as double;
217
- final totalReturn = position['total_return'] as double;
218
- final percentTotalReturn =
219
- position['percent_total_return'] as double;
220
- final accountAllocationPercent =
221
- position['account_allocation_percent'] as double;
222
- final portfolioAllocationPercent =
223
- position['portfolio_allocation_percent'] as double;
207
+ final marketValue = double.tryParse(position['market_value']?.toString() ?? '0.0') ?? 0.0;
208
+ final unrealizedPL = double.tryParse(position['unrealized_pl']?.toString() ?? '0.0') ?? 0.0;
209
+ final percentUnrealizedPL = double.tryParse(position['percent_unrealized_pl']?.toString() ?? '0.0') ?? 0.0;
210
+ final accountAllocationPercent = double.tryParse(position['account_allocation_percent']?.toString() ?? '0.0') ?? 0.0;
211
+ final portfolioAllocationPercent = double.tryParse(position['portfolio_allocation_percent']?.toString() ?? '0.0') ?? 0.0;
224
212
 
225
213
  final plColor = unrealizedPL >= 0 ? Colors.green : Colors.red;
226
- final totalReturnColor = totalReturn >= 0 ? Colors.green : Colors.red;
227
214
 
228
215
  return DataRow(cells: [
229
216
  DataCell(Text(symbol)),
@@ -239,13 +226,7 @@ class InvestmentAccountScreen extends StatelessWidget {
239
226
  style: TextStyle(color: plColor))),
240
227
  DataCell(Text('${percentUnrealizedPL.toStringAsFixed(2)}%',
241
228
  style: TextStyle(color: plColor))),
242
- if (showDividends) ...[
243
- DataCell(Text(_formatCurrency(totalDividends))),
244
- DataCell(Text(_formatCurrency(totalReturn),
245
- style: TextStyle(color: totalReturnColor))),
246
- DataCell(Text('${percentTotalReturn.toStringAsFixed(2)}%',
247
- style: TextStyle(color: totalReturnColor))),
248
- ],
229
+
249
230
  DataCell(Text('${accountAllocationPercent.toStringAsFixed(2)}%')),
250
231
  DataCell(Text('${portfolioAllocationPercent.toStringAsFixed(2)}%')),
251
232
  ]);
@@ -254,42 +235,34 @@ class InvestmentAccountScreen extends StatelessWidget {
254
235
  );
255
236
  }
256
237
 
257
- Widget _buildAnaliticoTable(
238
+ Widget _buildHistoryTable(
258
239
  BuildContext context, InvestmentAccountViewModel viewModel) {
259
- if (viewModel.trades.isEmpty) {
240
+ if (viewModel.unifiedHistory.isEmpty) {
260
241
  return const Center(
261
242
  child: Padding(
262
243
  padding: EdgeInsets.all(16.0),
263
- child: Text('No trades found.'),
244
+ child: Text('No history found.'),
264
245
  ),
265
246
  );
266
247
  }
267
248
 
268
- final List<Map<String, dynamic>> filteredTrades =
249
+ final List<Map<String, dynamic>> filteredHistory =
269
250
  viewModel.selectedSymbolForFilter == null
270
- ? viewModel.trades
271
- : viewModel.trades
272
- .where((trade) =>
273
- trade['asset']?['symbol'] ==
274
- viewModel.selectedSymbolForFilter)
251
+ ? viewModel.unifiedHistory
252
+ : viewModel.unifiedHistory
253
+ .where((item) =>
254
+ item['symbol'] == viewModel.selectedSymbolForFilter)
275
255
  .toList();
276
256
 
277
- if (filteredTrades.isEmpty) {
257
+ if (filteredHistory.isEmpty) {
278
258
  return const Center(
279
259
  child: Padding(
280
260
  padding: EdgeInsets.all(16.0),
281
- child: Text('No trades match the selected symbol.'),
261
+ child: Text('No history matches the selected symbol.'),
282
262
  ),
283
263
  );
284
264
  }
285
265
 
286
- final sortedTrades = List<Map<String, dynamic>>.from(filteredTrades)
287
- ..sort((a, b) {
288
- final dateA = DateTime.parse(a['date'] as String);
289
- final dateB = DateTime.parse(b['date'] as String);
290
- return dateB.compareTo(dateA);
291
- });
292
-
293
266
  return DataTable(
294
267
  columns: const [
295
268
  DataColumn(label: Text('Date')),
@@ -298,71 +271,36 @@ class InvestmentAccountScreen extends StatelessWidget {
298
271
  DataColumn(label: Text('Type')),
299
272
  DataColumn(label: Text('Shares')),
300
273
  DataColumn(label: Text('Price')),
301
- DataColumn(label: Text('Live')),
302
274
  DataColumn(label: Text('Total')),
303
- DataColumn(label: Text('Return')),
304
- DataColumn(label: Text('% Return')),
305
- DataColumn(label: Text('Book Cost')),
306
275
  DataColumn(label: Text('Actions')),
307
276
  ],
308
- rows: sortedTrades.map((trade) {
309
- final double shares = double.parse(trade['shares'].toString());
310
- final double price = double.parse(trade['price'].toString());
311
- final double total = shares * price;
312
- final tradeDate = DateTime.parse(trade['date'] as String);
313
- final tradeAge = DateTime.now().difference(tradeDate);
314
- final symbol = trade['asset']?['symbol'] ?? '';
315
- final livePrice = viewModel.livePrices[symbol];
316
- final int tradeId = trade['id'] as int;
277
+ rows: filteredHistory.map((item) {
278
+ final isTrade = item['history_type'] == 'trade';
279
+ final isDividend = item['history_type'] == 'dividend';
317
280
 
318
- double? tradeReturnValue, tradePercentageReturn, tradeBookCost;
319
-
320
- if (livePrice != null && shares > 0 && trade['trade_type'] == 'buy') {
321
- final currentMarketValue = livePrice * shares;
322
- tradeReturnValue = currentMarketValue - total;
323
- tradeBookCost = currentMarketValue;
324
- if (total > 0) {
325
- tradePercentageReturn = (tradeReturnValue / total) * 100;
326
- }
327
- }
281
+ final tradeDate = DateTime.parse(item['date'] as String);
282
+ final tradeAge = DateTime.now().difference(tradeDate);
283
+ final symbol = item['symbol'] as String? ?? 'N/A';
284
+ final type = item['type'] as String? ?? 'N/A';
285
+ final int id = item['id'] as int;
328
286
 
329
- final returnColor =
330
- (tradeReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
287
+ final double shares = isTrade ? (double.tryParse(item['shares']?.toString() ?? '0.0') ?? 0.0) : 0.0;
288
+ final double price = isTrade ? (double.tryParse(item['price']?.toString() ?? '0.0') ?? 0.0) : 0.0;
289
+ final double total = isTrade ? (shares * price) : (double.tryParse(item['total']?.toString() ?? '0.0') ?? 0.0);
331
290
 
332
291
  return DataRow(cells: [
333
292
  DataCell(Text(DateFormat('yyyy-MM-dd').format(tradeDate))),
334
293
  DataCell(Text(_formatDuration(tradeAge))),
335
294
  DataCell(Text(symbol)),
336
- DataCell(Text(trade['trade_type'] ?? 'N/A')),
337
- DataCell(Text(shares.toString())),
338
- DataCell(Text(_formatCurrency(price))),
339
- DataCell(livePrice != null
340
- ? Text(_formatCurrency(livePrice))
341
- : const Text('N/A')),
295
+ DataCell(Text(type)),
296
+ DataCell(isTrade ? Text(shares.toStringAsFixed(4)) : const Text('-')),
297
+ DataCell(isTrade ? Text(_formatCurrency(price)) : const Text('-')),
342
298
  DataCell(Text(_formatCurrency(total))),
343
- DataCell(
344
- tradeReturnValue != null
345
- ? Text(_formatCurrency(tradeReturnValue),
346
- style: TextStyle(color: returnColor))
347
- : const Text('N/A'),
348
- ),
349
- DataCell(
350
- tradePercentageReturn != null
351
- ? Text('${tradePercentageReturn.toStringAsFixed(2)}%',
352
- style: TextStyle(color: returnColor))
353
- : const Text('N/A'),
354
- ),
355
- DataCell(
356
- tradeBookCost != null
357
- ? Text(_formatCurrency(tradeBookCost),
358
- style: TextStyle(color: returnColor))
359
- : const Text('N/A'),
360
- ),
361
299
  DataCell(
362
300
  IconButton(
363
301
  icon: const Icon(Icons.delete, color: Colors.red),
364
- onPressed: () => _confirmAndDeleteTrade(context, viewModel, tradeId),
365
- tooltip: 'Delete Trade',
302
+ onPressed: () => _confirmAndDeleteItem(context, viewModel, id, isTrade),
303
+ tooltip: 'Delete Item',
366
304
  ),
367
305
  ),
368
306
  ]);
@@ -370,19 +308,23 @@ class InvestmentAccountScreen extends StatelessWidget {
370
308
  );
371
309
  }
372
310
 
373
- Future<void> _confirmAndDeleteTrade(BuildContext context,
374
- InvestmentAccountViewModel viewModel, int tradeId) async {
311
+ Future<void> _confirmAndDeleteItem(BuildContext context,
312
+ InvestmentAccountViewModel viewModel, int itemId, bool isTrade) async {
313
+ final itemType = isTrade ? 'trade' : 'dividend';
375
314
  final bool? confirm =
376
- await AppDialogs.showDeleteConfirmationDialog(context, 'this trade');
315
+ await AppDialogs.showDeleteConfirmationDialog(context, 'this $itemType');
377
316
 
378
317
  if (confirm == true) {
379
- final success = await viewModel.deleteTrade(tradeId);
318
+ final success = isTrade
319
+ ? await viewModel.deleteTrade(itemId)
320
+ : await viewModel.deleteDividend(itemId);
321
+
380
322
  if (ScaffoldMessenger.of(context).mounted) {
381
323
  ScaffoldMessenger.of(context).showSnackBar(
382
324
  SnackBar(
383
325
  content: Text(success
384
- ? 'Trade deleted successfully.'
385
- : 'Failed to delete trade.'),
326
+ ? 'The $itemType was deleted successfully.'
327
+ : 'Failed to delete the $itemType.'),
386
328
  ),
387
329
  );
388
330
  }
@@ -96,7 +96,7 @@ class RrspSunLifeScreen extends StatelessWidget {
96
96
  }
97
97
 
98
98
  final summary = viewModel.sinteticoSummary.first;
99
- final double unrealizedPL = summary['unrealized_pl'];
99
+ final double unrealizedPL = double.tryParse(summary['unrealized_pl']?.toString() ?? '0.0') ?? 0.0;
100
100
  final plColor = (unrealizedPL >= 0) ? Colors.green : Colors.red;
101
101
 
102
102
  return DataTable(
@@ -113,22 +113,22 @@ class RrspSunLifeScreen extends StatelessWidget {
113
113
  DataRow(
114
114
  cells: [
115
115
  DataCell(
116
- Text(_formatCurrency(summary['user_contribution'] as double))),
116
+ Text(_formatCurrency(double.tryParse(summary['user_contribution']?.toString() ?? '0.0') ?? 0.0))),
117
117
  DataCell(Text(
118
- _formatCurrency(summary['company_contribution'] as double))),
118
+ _formatCurrency(double.tryParse(summary['company_contribution']?.toString() ?? '0.0') ?? 0.0))),
119
119
  DataCell(
120
- Text(_formatCurrency(summary['total_contributed'] as double))),
120
+ Text(_formatCurrency(double.tryParse(summary['total_contributed']?.toString() ?? '0.0') ?? 0.0))),
121
121
  DataCell(Text(_formatCurrency(unrealizedPL),
122
122
  style: TextStyle(color: plColor))),
123
123
  DataCell(
124
- Text('${(summary['percent_return'] as double).toStringAsFixed(2)}%',
124
+ Text('${(double.tryParse(summary['percent_return']?.toString() ?? '0.0') ?? 0.0).toStringAsFixed(2)}%',
125
125
  style: TextStyle(color: plColor)),
126
126
  ),
127
127
  DataCell(
128
- Text(_formatCurrency(summary['market_value'] as double))),
128
+ Text(_formatCurrency(double.tryParse(summary['market_value']?.toString() ?? '0.0') ?? 0.0))),
129
129
  DataCell(
130
130
  Text(
131
- '${(summary['portfolio_allocation_percent'] as double).toStringAsFixed(2)}%'),
131
+ '${(double.tryParse(summary['portfolio_allocation_percent']?.toString() ?? '0.0') ?? 0.0).toStringAsFixed(2)}%'),
132
132
  ),
133
133
  ],
134
134
  ),
@@ -161,12 +161,11 @@ class RrspSunLifeScreen extends StatelessWidget {
161
161
  ],
162
162
  rows: viewModel.contributions.map((c) {
163
163
  final contributionId = c['id'] as int;
164
- final rrspAmount = double.parse(c['rrsp_amount'].toString());
165
- final dpspAmount = double.parse(c['dpsp_amount'].toString());
164
+ final rrspAmount = double.tryParse(c['rrsp_amount']?.toString() ?? '0.0') ?? 0.0;
165
+ final dpspAmount = double.tryParse(c['dpsp_amount']?.toString() ?? '0.0') ?? 0.0;
166
166
  final totalContributed = rrspAmount + dpspAmount;
167
167
 
168
- final returnAmount =
169
- double.parse(c['return_amount']?.toString() ?? '0.0');
168
+ final returnAmount = double.tryParse(c['return_amount']?.toString() ?? '0.0') ?? 0.0;
170
169
  final percentReturn = totalContributed > 0
171
170
  ? (returnAmount / totalContributed) * 100
172
171
  : 0.0;
@@ -54,9 +54,9 @@ class CryptoAccountViewModel extends ChangeNotifier {
54
54
  final results = await Future.wait(futures);
55
55
 
56
56
  _trades = results[0] as List<Map<String, dynamic>>;
57
- _cashBalance = results[1] as double;
57
+ _cashBalance = double.tryParse(results[1]?.toString() ?? '0.0') ?? 0.0;
58
58
  _assets = results[2] as List<Map<String, dynamic>>;
59
- final totalPortfolioBookCost = results[3] as double;
59
+ final totalPortfolioBookCost = double.tryParse(results[3]?.toString() ?? '0.0') ?? 0.0;
60
60
 
61
61
  _calculateAndApplyPortfolioSummary(totalPortfolioBookCost);
62
62
 
@@ -104,8 +104,8 @@ class CryptoAccountViewModel extends ChangeNotifier {
104
104
  if (asset == null || asset['symbol'] == null) continue;
105
105
  String symbol = asset['symbol'];
106
106
  String idCrypto = asset['id_crypto'] ?? symbol;
107
- double shares = double.parse(trade['shares'].toString());
108
- double price = double.parse(trade['price'].toString());
107
+ double shares = double.tryParse(trade['shares']?.toString() ?? '0.0') ?? 0.0;
108
+ double price = double.tryParse(trade['price']?.toString() ?? '0.0') ?? 0.0;
109
109
  String tradeType = trade['trade_type'];
110
110
 
111
111
  if (!summary.containsKey(symbol)) {
@@ -151,14 +151,26 @@ class CryptoAccountViewModel extends ChangeNotifier {
151
151
  void _recalculateMarketValue() {
152
152
  double newTotalValue = _cashBalance;
153
153
  for (final position in _portfolioSummary) {
154
- final shares = double.parse(position['shares'].toString());
154
+ final shares = double.tryParse(position['shares']?.toString() ?? '0.0') ?? 0.0;
155
+ final totalCost = double.tryParse(position['total_cost']?.toString() ?? '0.0') ?? 0.0;
155
156
  final idForLookup = (position['id_crypto'] as String? ?? position['symbol'] as String).toLowerCase();
156
157
  final livePrice = _livePrices[idForLookup];
158
+
159
+ double currentMarketValue;
157
160
  if (livePrice != null) {
158
- newTotalValue += shares * livePrice;
161
+ currentMarketValue = shares * livePrice;
162
+ newTotalValue += currentMarketValue;
159
163
  } else {
160
- newTotalValue += (position['total_cost'] as num?)?.toDouble() ?? 0.0;
164
+ currentMarketValue = totalCost;
165
+ newTotalValue += totalCost;
161
166
  }
167
+
168
+ double totalReturnValue = currentMarketValue - totalCost;
169
+ double percentageReturn = (totalCost > 0) ? (totalReturnValue / totalCost) * 100 : 0.0;
170
+
171
+ position['market_value'] = currentMarketValue;
172
+ position['total_return_value'] = totalReturnValue;
173
+ position['percentage_return'] = percentageReturn;
162
174
  }
163
175
  _accountTotalValue = newTotalValue;
164
176
  }
@@ -35,6 +35,47 @@ class InvestmentAccountViewModel extends ChangeNotifier {
35
35
  String? get selectedSymbolForFilter => _selectedSymbolForFilter;
36
36
  String? get token => _token;
37
37
 
38
+ List<Map<String, dynamic>> get unifiedHistory {
39
+ List<Map<String, dynamic>> history = [];
40
+
41
+ // Add trades
42
+ for (var trade in _trades) {
43
+ history.add({
44
+ ...trade,
45
+ 'history_type': 'trade',
46
+ 'symbol': trade['asset']?['symbol'] ?? 'N/A', // Normalize symbol
47
+ 'type': trade['trade_type'] as String? ?? 'N/A', // Normalize type
48
+ });
49
+ }
50
+
51
+ // Add dividends
52
+ if (showDividends) {
53
+ for (var dividend in _dividends) {
54
+ history.add({
55
+ ...dividend,
56
+ 'history_type': 'dividend',
57
+ // Normalize fields for the table
58
+ 'total': double.tryParse(dividend['amount']?.toString() ?? '0.0') ?? 0.0,
59
+ 'type': 'Dividend',
60
+ 'symbol': dividend['asset']?['symbol'] ?? 'N/A',
61
+ });
62
+ }
63
+ }
64
+
65
+ // Sort by date descending
66
+ history.sort((a, b) {
67
+ try {
68
+ final dateA = DateTime.parse(a['date'] as String);
69
+ final dateB = DateTime.parse(b['date'] as String);
70
+ return dateB.compareTo(dateA);
71
+ } catch (e) {
72
+ return 0;
73
+ }
74
+ });
75
+
76
+ return history;
77
+ }
78
+
38
79
  InvestmentAccountViewModel({required this.accountName, required this.showDividends, required String? token}) {
39
80
  _token = token;
40
81
  fetchData();
@@ -68,10 +109,10 @@ class InvestmentAccountViewModel extends ChangeNotifier {
68
109
  final results = await Future.wait(futures);
69
110
 
70
111
  _trades = results[0] as List<Map<String, dynamic>>;
71
- _cashBalance = results[1] as double;
112
+ _cashBalance = double.tryParse(results[1]?.toString() ?? '0.0') ?? 0.0;
72
113
  _assets = results[2] as List<Map<String, dynamic>>;
73
- _totalPortfolioBookCost = results[3] as double;
74
- _dividends = showDividends ? results[4] as List<Map<String, dynamic>> : <Map<String, dynamic>>[];
114
+ _totalPortfolioBookCost = double.tryParse(results[3]?.toString() ?? '0.0') ?? 0.0;
115
+ _dividends = showDividends && results.length > 4 ? results[4] as List<Map<String, dynamic>> : <Map<String, dynamic>>[];
75
116
 
76
117
  _calculateAndApplyPortfolioSummary();
77
118
 
@@ -121,7 +162,7 @@ class InvestmentAccountViewModel extends ChangeNotifier {
121
162
  final asset = dividend['asset'];
122
163
  if (asset != null && asset['symbol'] != null) {
123
164
  String symbol = asset['symbol'];
124
- double amount = double.parse(dividend['amount'].toString());
165
+ double amount = double.tryParse(dividend['amount']?.toString() ?? '0.0') ?? 0.0;
125
166
  dividendSummary.update(symbol, (value) => value + amount, ifAbsent: () => amount);
126
167
  }
127
168
  }
@@ -132,8 +173,8 @@ class InvestmentAccountViewModel extends ChangeNotifier {
132
173
  if (asset == null || asset['symbol'] == null) continue;
133
174
  String symbol = asset['symbol'];
134
175
 
135
- double shares = double.parse(trade['shares'].toString());
136
- double price = double.parse(trade['price'].toString());
176
+ double shares = double.tryParse(trade['shares']?.toString() ?? '0.0') ?? 0.0;
177
+ double price = double.tryParse(trade['price']?.toString() ?? '0.0') ?? 0.0;
137
178
  String tradeType = trade['trade_type'];
138
179
 
139
180
  if (!summary.containsKey(symbol)) {
@@ -143,7 +184,6 @@ class InvestmentAccountViewModel extends ChangeNotifier {
143
184
  'industry': asset['industry'] ?? 'N/A',
144
185
  'shares': 0.0,
145
186
  'book_cost': 0.0,
146
- 'total_dividends': dividendSummary[symbol] ?? 0.0,
147
187
  };
148
188
  }
149
189
 
@@ -184,20 +224,16 @@ class InvestmentAccountViewModel extends ChangeNotifier {
184
224
  double newTotalValue = _cashBalance;
185
225
  for (final position in _portfolioSummary) {
186
226
  final symbol = position['symbol'];
187
- final shares = position['shares'] as double;
188
- final bookCost = position['book_cost'] as double;
189
- final totalDividends = position['total_dividends'] as double;
227
+ final shares = double.tryParse(position['shares']?.toString() ?? '0.0') ?? 0.0;
228
+ final bookCost = double.tryParse(position['book_cost']?.toString() ?? '0.0') ?? 0.0;
190
229
 
191
230
  final livePrice = _livePrices[symbol];
192
231
  double marketValue = livePrice != null ? shares * livePrice : bookCost;
193
232
  double unrealizedPL = marketValue - bookCost;
194
- double totalReturn = unrealizedPL + totalDividends;
195
233
 
196
234
  position['market_value'] = marketValue;
197
235
  position['unrealized_pl'] = unrealizedPL;
198
- position['total_return'] = totalReturn;
199
236
  position['percent_unrealized_pl'] = (bookCost > 0) ? (unrealizedPL / bookCost) * 100 : 0.0;
200
- position['percent_total_return'] = (bookCost > 0) ? (totalReturn / bookCost) * 100 : 0.0;
201
237
 
202
238
  newTotalValue += marketValue;
203
239
  }
@@ -218,4 +254,19 @@ class InvestmentAccountViewModel extends ChangeNotifier {
218
254
  return false;
219
255
  }
220
256
  }
257
+
258
+ Future<bool> deleteDividend(int dividendId) async {
259
+ if (_token == null) return false;
260
+
261
+ try {
262
+ final success = await _transactionService.deleteDividend(dividendId, token: _token);
263
+ if (success) {
264
+ fetchData(); // Refresh data
265
+ }
266
+ return success;
267
+ } catch (e) {
268
+ debugPrint("Error deleting dividend: $e");
269
+ return false;
270
+ }
271
+ }
221
272
  }
@@ -45,8 +45,8 @@ class RrspSunLifeViewModel extends ChangeNotifier {
45
45
  final results = await Future.wait([contributionsFuture, balanceFuture, totalPortfolioBookCostFuture]);
46
46
 
47
47
  _contributions = results[0] as List<Map<String, dynamic>>;
48
- _rrspCashBalance = results[1] as double;
49
- _totalPortfolioBookCost = results[2] as double;
48
+ _rrspCashBalance = double.tryParse(results[1]?.toString() ?? '0.0') ?? 0.0;
49
+ _totalPortfolioBookCost = double.tryParse(results[2]?.toString() ?? '0.0') ?? 0.0;
50
50
 
51
51
  _calculateAndApplySinteticoSummary();
52
52
 
@@ -64,9 +64,9 @@ class RrspSunLifeViewModel extends ChangeNotifier {
64
64
  double totalUnrealizedPL = 0;
65
65
 
66
66
  for (var c in _contributions) {
67
- totalUserContribution += double.parse(c['rrsp_amount'].toString());
68
- totalCompanyContribution += double.parse(c['dpsp_amount'].toString());
69
- totalUnrealizedPL += double.parse(c['return_amount']?.toString() ?? '0.0');
67
+ totalUserContribution += double.tryParse(c['rrsp_amount']?.toString() ?? '0.0') ?? 0.0;
68
+ totalCompanyContribution += double.tryParse(c['dpsp_amount']?.toString() ?? '0.0') ?? 0.0;
69
+ totalUnrealizedPL += double.tryParse(c['return_amount']?.toString() ?? '0.0') ?? 0.0;
70
70
  }
71
71
 
72
72
  final totalContributed = totalUserContribution + totalCompanyContribution;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcos_feitoza/personal-finance-frontend-feature-investments",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },