From e2e2257167dc6db2030698cad7a1cb438a1a9e17 Mon Sep 17 00:00:00 2001 From: Arseni Date: Tue, 17 Feb 2026 11:17:19 +0300 Subject: [PATCH] small fixes --- .../pshared/lib/data/mapper/wallet/ui.dart | 5 ++- .../pshared/lib/service/payment/wallets.dart | 4 +- frontend/pshared/lib/utils/money.dart | 12 ++++++ .../pweb/lib/controllers/recent_payments.dart | 11 ------ .../lib/controllers/report_operations.dart | 11 ------ frontend/pweb/lib/models/payment_state.dart | 27 ++++++++++++++ .../dashboard/buttons/balance/ledger.dart | 5 ++- .../lib/pages/dashboard/payouts/amount.dart | 9 ++++- .../multiple/panels/source_quote/helpers.dart | 5 ++- .../pweb/lib/providers/multiple_payouts.dart | 11 +++--- .../utils/payment/multiple_csv_parser.dart | 6 ++- .../pweb/lib/utils/report/payment_mapper.dart | 35 +++++++----------- .../pweb/lib/utils/report/utils/format.dart | 37 +++++++++++++++++++ 13 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 frontend/pshared/lib/utils/money.dart create mode 100644 frontend/pweb/lib/models/payment_state.dart create mode 100644 frontend/pweb/lib/utils/report/utils/format.dart diff --git a/frontend/pshared/lib/data/mapper/wallet/ui.dart b/frontend/pshared/lib/data/mapper/wallet/ui.dart index c674d6c6..e1e27aec 100644 --- a/frontend/pshared/lib/data/mapper/wallet/ui.dart +++ b/frontend/pshared/lib/data/mapper/wallet/ui.dart @@ -1,13 +1,16 @@ import 'package:pshared/models/wallet/wallet.dart' as domain; import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; extension WalletUiMapper on domain.WalletModel { Wallet toUi() => Wallet( id: walletRef, walletUserID: walletRef, - balance: double.tryParse(availableMoney?.amount ?? balance?.available?.amount ?? '0') ?? 0, + balance: parseMoneyAmount( + availableMoney?.amount ?? balance?.available?.amount, + ), currency: currencyStringToCode(asset.tokenSymbol), calculatedAt: balance?.calculatedAt ?? DateTime.now(), depositAddress: depositAddress, diff --git a/frontend/pshared/lib/service/payment/wallets.dart b/frontend/pshared/lib/service/payment/wallets.dart index ba43f16f..f1a42853 100644 --- a/frontend/pshared/lib/service/payment/wallets.dart +++ b/frontend/pshared/lib/service/payment/wallets.dart @@ -3,6 +3,7 @@ import 'package:pshared/models/describable.dart'; import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/service/wallet.dart' as shared_wallet_service; +import 'package:pshared/utils/money.dart'; abstract class WalletsService { @@ -29,8 +30,7 @@ class ApiWalletsService implements WalletsService { organizationRef: organizationRef, walletRef: walletRef, ); - final amount = balance.available?.amount; - return amount == null ? 0 : double.tryParse(amount) ?? 0; + return parseMoneyAmount(balance.available?.amount); } @override diff --git a/frontend/pshared/lib/utils/money.dart b/frontend/pshared/lib/utils/money.dart new file mode 100644 index 00000000..925f5eb5 --- /dev/null +++ b/frontend/pshared/lib/utils/money.dart @@ -0,0 +1,12 @@ +import 'package:pshared/models/money.dart'; + + +double parseMoneyAmount(String? raw, {double fallback = 0}) { + final trimmed = raw?.trim(); + if (trimmed == null || trimmed.isEmpty) return fallback; + return double.tryParse(trimmed) ?? fallback; +} + +extension MoneyAmountX on Money { + double get amountValue => parseMoneyAmount(amount); +} diff --git a/frontend/pweb/lib/controllers/recent_payments.dart b/frontend/pweb/lib/controllers/recent_payments.dart index 924aef1b..72497a65 100644 --- a/frontend/pweb/lib/controllers/recent_payments.dart +++ b/frontend/pweb/lib/controllers/recent_payments.dart @@ -17,17 +17,11 @@ class RecentPaymentsController extends ChangeNotifier { void update(PaymentsProvider provider) { if (!identical(_payments, provider)) { - _payments?.removeListener(_onPaymentsChanged); _payments = provider; - _payments?.addListener(_onPaymentsChanged); } _rebuild(); } - void _onPaymentsChanged() { - _rebuild(); - } - void _rebuild() { final operations = (_payments?.payments ?? const []) .map(mapPaymentToOperation) @@ -36,9 +30,4 @@ class RecentPaymentsController extends ChangeNotifier { notifyListeners(); } - @override - void dispose() { - _payments?.removeListener(_onPaymentsChanged); - super.dispose(); - } } diff --git a/frontend/pweb/lib/controllers/report_operations.dart b/frontend/pweb/lib/controllers/report_operations.dart index 6a81ca3c..8da4fe84 100644 --- a/frontend/pweb/lib/controllers/report_operations.dart +++ b/frontend/pweb/lib/controllers/report_operations.dart @@ -28,9 +28,7 @@ class ReportOperationsController extends ChangeNotifier { void update(PaymentsProvider provider) { if (!identical(_payments, provider)) { - _payments?.removeListener(_onPaymentsChanged); _payments = provider; - _payments?.addListener(_onPaymentsChanged); } _rebuildOperations(); } @@ -61,10 +59,6 @@ class ReportOperationsController extends ChangeNotifier { await _payments?.refresh(); } - void _onPaymentsChanged() { - _rebuildOperations(); - } - void _rebuildOperations() { final items = _payments?.payments ?? const []; _operations = items.map(mapPaymentToOperation).toList(); @@ -107,9 +101,4 @@ class ReportOperationsController extends ChangeNotifier { left.end.isAtSameMomentAs(right.end); } - @override - void dispose() { - _payments?.removeListener(_onPaymentsChanged); - super.dispose(); - } } diff --git a/frontend/pweb/lib/models/payment_state.dart b/frontend/pweb/lib/models/payment_state.dart new file mode 100644 index 00000000..814abf3c --- /dev/null +++ b/frontend/pweb/lib/models/payment_state.dart @@ -0,0 +1,27 @@ +enum PaymentState { + success, + failed, + cancelled, + processing, + unknown, +} + +PaymentState paymentStateFromRaw(String? raw) { + final trimmed = (raw ?? '').trim().toUpperCase(); + final normalized = trimmed.startsWith('PAYMENT_STATE_') + ? trimmed.substring('PAYMENT_STATE_'.length) + : trimmed; + + switch (normalized) { + case 'SUCCESS': + return PaymentState.success; + case 'FAILED': + return PaymentState.failed; + case 'CANCELLED': + return PaymentState.cancelled; + case 'PROCESSING': + return PaymentState.processing; + default: + return PaymentState.unknown; + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart index 980d2b3c..269b3539 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import 'package:pshared/controllers/balance_mask/ledger_accounts.dart'; import 'package:pshared/models/ledger/account.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; import 'package:pweb/pages/dashboard/buttons/balance/header.dart'; @@ -25,8 +26,8 @@ class LedgerAccountCard extends StatelessWidget { final money = account.balance?.balance; if (money == null) return '--'; - final amount = double.tryParse(money.amount); - if (amount == null) { + final amount = parseMoneyAmount(money.amount, fallback: double.nan); + if (amount.isNaN) { return '${money.amount} ${money.currency}'; } diff --git a/frontend/pweb/lib/pages/dashboard/payouts/amount.dart b/frontend/pweb/lib/pages/dashboard/payouts/amount.dart index 3e2b2b27..c50053de 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/amount.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/amount.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -32,7 +33,13 @@ class _PaymentAmountWidgetState extends State { super.dispose(); } - double? _parseAmount(String value) => double.tryParse(value.replaceAll(',', '.')); + double? _parseAmount(String value) { + final parsed = parseMoneyAmount( + value.replaceAll(',', '.'), + fallback: double.nan, + ); + return parsed.isNaN ? null : parsed; + } void _syncTextWithAmount(double amount) { final parsedText = _parseAmount(_controller.text); diff --git a/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart b/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart index 84e455c8..6aafc898 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart @@ -1,14 +1,15 @@ import 'package:pshared/models/asset.dart'; import 'package:pshared/models/money.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pweb/controllers/multiple_payouts.dart'; String moneyLabel(Money? money) { if (money == null) return 'N/A'; - final amount = double.tryParse(money.amount); - if (amount == null) return '${money.amount} ${money.currency}'; + final amount = parseMoneyAmount(money.amount, fallback: double.nan); + if (amount.isNaN) return '${money.amount} ${money.currency}'; try { return assetToString( Asset( diff --git a/frontend/pweb/lib/providers/multiple_payouts.dart b/frontend/pweb/lib/providers/multiple_payouts.dart index 4f376eea..46f489e5 100644 --- a/frontend/pweb/lib/providers/multiple_payouts.dart +++ b/frontend/pweb/lib/providers/multiple_payouts.dart @@ -8,6 +8,7 @@ import 'package:pshared/provider/payment/multiple/provider.dart'; import 'package:pshared/provider/payment/multiple/quotation.dart'; import 'package:pshared/provider/payment/payments.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pweb/models/multiple_payouts/csv_row.dart'; import 'package:pweb/models/multiple_payouts/state.dart'; @@ -93,8 +94,8 @@ class MultiplePayoutsProvider extends ChangeNotifier { double total = 0; for (final row in _rows) { - final value = double.tryParse(row.amount); - if (value == null) return null; + final value = parseMoneyAmount(row.amount, fallback: double.nan); + if (value.isNaN) return null; total += value; } return Money(amount: amountToString(total), currency: currency); @@ -121,10 +122,10 @@ class MultiplePayoutsProvider extends ChangeNotifier { final fee = aggregateFeeAmountFor(sourceWallet); if (debit == null || fee == null) return null; - final debitValue = double.tryParse(debit.amount); - final feeValue = double.tryParse(fee.amount); + final debitValue = parseMoneyAmount(debit.amount, fallback: double.nan); + final feeValue = parseMoneyAmount(fee.amount, fallback: double.nan); if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null; - if (debitValue == null || feeValue == null || debitValue <= 0) return null; + if (debitValue.isNaN || feeValue.isNaN || debitValue <= 0) return null; return (feeValue / debitValue) * 100; } diff --git a/frontend/pweb/lib/utils/payment/multiple_csv_parser.dart b/frontend/pweb/lib/utils/payment/multiple_csv_parser.dart index f5c180c0..1281eca0 100644 --- a/frontend/pweb/lib/utils/payment/multiple_csv_parser.dart +++ b/frontend/pweb/lib/utils/payment/multiple_csv_parser.dart @@ -1,3 +1,5 @@ +import 'package:pshared/utils/money.dart'; + import 'package:pweb/models/multiple_payouts/csv_row.dart'; @@ -79,8 +81,8 @@ class MultipleCsvParser { throw FormatException('CSV row ${i + 1}: amount is required'); } - final parsedAmount = double.tryParse(amount); - if (parsedAmount == null || parsedAmount <= 0) { + final parsedAmount = parseMoneyAmount(amount, fallback: double.nan); + if (parsedAmount.isNaN || parsedAmount <= 0) { throw FormatException( 'CSV row ${i + 1}: amount must be greater than 0', ); diff --git a/frontend/pweb/lib/utils/report/payment_mapper.dart b/frontend/pweb/lib/utils/report/payment_mapper.dart index 1dfcb640..8300b115 100644 --- a/frontend/pweb/lib/utils/report/payment_mapper.dart +++ b/frontend/pweb/lib/utils/report/payment_mapper.dart @@ -1,6 +1,9 @@ import 'package:pshared/models/payment/operation.dart'; import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/models/payment/status.dart'; +import 'package:pshared/utils/money.dart'; + +import 'package:pweb/models/payment_state.dart'; OperationItem mapPaymentToOperation(Payment payment) { @@ -8,9 +11,11 @@ OperationItem mapPaymentToOperation(Payment payment) { final settlement = payment.lastQuote?.expectedSettlementAmount; final amountMoney = debit ?? settlement; - final amount = _parseAmount(amountMoney?.amount); + final amount = parseMoneyAmount(amountMoney?.amount); final currency = amountMoney?.currency ?? ''; - final toAmount = settlement == null ? amount : _parseAmount(settlement.amount); + final toAmount = settlement == null + ? amount + : parseMoneyAmount(settlement.amount); final toCurrency = settlement?.currency ?? currency; final payId = _firstNonEmpty([ @@ -48,14 +53,15 @@ OperationItem mapPaymentToOperation(Payment payment) { } OperationStatus statusFromPayment(Payment payment) { - final normalized = _normalizePaymentState(payment.state); - switch (normalized) { - case 'SUCCESS': + final state = paymentStateFromRaw(payment.state); + switch (state) { + case PaymentState.success: return OperationStatus.success; - case 'FAILED': - case 'CANCELLED': + case PaymentState.failed: + case PaymentState.cancelled: return OperationStatus.error; - default: + case PaymentState.processing: + case PaymentState.unknown: return OperationStatus.processing; } } @@ -102,16 +108,3 @@ String? _firstNonEmpty(List values) { } return null; } - -String _normalizePaymentState(String? raw) { - final trimmed = (raw ?? '').trim().toUpperCase(); - if (trimmed.startsWith('PAYMENT_STATE_')) { - return trimmed.substring('PAYMENT_STATE_'.length); - } - return trimmed; -} - -double _parseAmount(String? amount) { - if (amount == null || amount.trim().isEmpty) return 0; - return double.tryParse(amount) ?? 0; -} diff --git a/frontend/pweb/lib/utils/report/utils/format.dart b/frontend/pweb/lib/utils/report/utils/format.dart new file mode 100644 index 00000000..f89d1428 --- /dev/null +++ b/frontend/pweb/lib/utils/report/utils/format.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/models/money.dart'; +import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/localization.dart'; +import 'package:intl/intl.dart'; + + +String formatMoney(Money? money, {String fallback = '-'}) { + final amount = money?.amount.trim(); + if (amount == null || amount.isEmpty) return fallback; + return '$amount ${money!.currency}'; +} + +String formatAmount(double amount, String currency, {String fallback = '-'}) { + final trimmed = currency.trim(); + if (trimmed.isEmpty) return amountToString(amount); + final symbol = currencySymbolFromCode(trimmed); + final suffix = symbol ?? trimmed; + return '${amountToString(amount)} $suffix'; +} + +String formatDateLabel(BuildContext context, DateTime? date, {String fallback = '-'}) { + if (date == null || date.millisecondsSinceEpoch == 0) return fallback; + return dateTimeToLocalFormat(context, date.toLocal()); +} + +String formatLongDate(BuildContext context, DateTime? date, {String fallback = '-'}) { + if (date == null || date.millisecondsSinceEpoch == 0) return fallback; + final locale = Localizations.localeOf(context).toString(); + final formatter = DateFormat('d MMMM y', locale); + return formatter.format(date.toLocal()); +} + +String collapseWhitespace(String value) { + return value.replaceAll(RegExp(r'\s+'), ' ').trim(); +}