front dev update #375
15
.gitignore
vendored
15
.gitignore
vendored
@@ -8,6 +8,19 @@ devtools_options.yaml
|
|||||||
untranslated.txt
|
untranslated.txt
|
||||||
generate_protos.sh
|
generate_protos.sh
|
||||||
update_dep.sh
|
update_dep.sh
|
||||||
|
test.sh
|
||||||
.vscode/
|
.vscode/
|
||||||
.gocache/
|
.gocache/
|
||||||
.cache/
|
.cache/
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# Air hot reload build artifacts
|
||||||
|
**/tmp/
|
||||||
|
build-errors.log
|
||||||
|
|
||||||
|
# Development environment (NEVER commit credentials!)
|
||||||
|
.env.dev
|
||||||
|
vault-keys.txt
|
||||||
|
.vault_token
|
||||||
|
|
||||||
|
CLAUDE.md
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'package:pshared/data/dto/describable.dart';
|
import 'package:pshared/data/dto/describable.dart';
|
||||||
|
import 'package:pshared/data/dto/ledger/role.dart';
|
||||||
import 'package:pshared/data/dto/ledger/type.dart';
|
import 'package:pshared/data/dto/ledger/type.dart';
|
||||||
|
|
||||||
part 'create.g.dart';
|
part 'create.g.dart';
|
||||||
@@ -11,7 +12,7 @@ class CreateLedgerAccountRequest {
|
|||||||
final Map<String, String>? metadata;
|
final Map<String, String>? metadata;
|
||||||
final String currency;
|
final String currency;
|
||||||
final bool allowNegative;
|
final bool allowNegative;
|
||||||
final bool isSettlement;
|
final LedgerAccountRoleDTO role;
|
||||||
final DescribableDTO describable;
|
final DescribableDTO describable;
|
||||||
final String? ownerRef;
|
final String? ownerRef;
|
||||||
final LedgerAccountTypeDTO accountType;
|
final LedgerAccountTypeDTO accountType;
|
||||||
@@ -20,7 +21,7 @@ class CreateLedgerAccountRequest {
|
|||||||
this.metadata,
|
this.metadata,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
required this.allowNegative,
|
required this.allowNegative,
|
||||||
required this.isSettlement,
|
required this.role,
|
||||||
required this.describable,
|
required this.describable,
|
||||||
required this.accountType,
|
required this.accountType,
|
||||||
this.ownerRef,
|
this.ownerRef,
|
||||||
|
|||||||
19
frontend/pshared/lib/api/responses/cursor_page.dart
Normal file
19
frontend/pshared/lib/api/responses/cursor_page.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/api/responses/base.dart';
|
||||||
|
import 'package:pshared/api/responses/token.dart';
|
||||||
|
|
||||||
|
part 'cursor_page.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class CursorPageResponse extends BaseAuthorizedResponse {
|
||||||
|
@JsonKey(name: 'next_cursor')
|
||||||
|
final String? nextCursor;
|
||||||
|
|
||||||
|
const CursorPageResponse({required super.accessToken, required this.nextCursor});
|
||||||
|
|
||||||
|
factory CursorPageResponse.fromJson(Map<String, dynamic> json) => _$CursorPageResponseFromJson(json);
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$CursorPageResponseToJson(this);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'package:pshared/api/responses/base.dart';
|
import 'package:pshared/api/responses/cursor_page.dart';
|
||||||
import 'package:pshared/api/responses/token.dart';
|
import 'package:pshared/api/responses/token.dart';
|
||||||
import 'package:pshared/data/dto/payment/payment.dart';
|
import 'package:pshared/data/dto/payment/payment.dart';
|
||||||
|
|
||||||
@@ -8,11 +8,14 @@ part 'payments.g.dart';
|
|||||||
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class PaymentsResponse extends BaseAuthorizedResponse {
|
class PaymentsResponse extends CursorPageResponse {
|
||||||
|
|
||||||
final List<PaymentDTO> payments;
|
final List<PaymentDTO> payments;
|
||||||
|
|
||||||
const PaymentsResponse({required super.accessToken, required this.payments});
|
const PaymentsResponse({
|
||||||
|
required super.accessToken,
|
||||||
|
required super.nextCursor,
|
||||||
|
required this.payments,
|
||||||
|
});
|
||||||
|
|
||||||
factory PaymentsResponse.fromJson(Map<String, dynamic> json) => _$PaymentsResponseFromJson(json);
|
factory PaymentsResponse.fromJson(Map<String, dynamic> json) => _$PaymentsResponseFromJson(json);
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
class CommonConstants {
|
class CommonConstants {
|
||||||
static String apiProto = 'https';
|
static String apiProto = 'https';
|
||||||
static String apiHost = 'app.sendico.io';
|
static String apiHost = 'app.sendico.io';
|
||||||
// static String apiProto = const String.fromEnvironment('API_PROTO', defaultValue: 'http');
|
|
||||||
// static String apiHost = const String.fromEnvironment('API_HOST', defaultValue: 'localhost');
|
|
||||||
// static String apiHost = 'localhost';
|
|
||||||
// static String apiHost = '10.0.2.2';
|
|
||||||
static String apiEndpoint = '/api/v1';
|
static String apiEndpoint = '/api/v1';
|
||||||
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
||||||
static String amplitudeServerZone = 'EU';
|
static String amplitudeServerZone = 'EU';
|
||||||
|
|||||||
49
frontend/pshared/lib/config/common_dev.dart
Normal file
49
frontend/pshared/lib/config/common_dev.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class CommonConstants {
|
||||||
|
static String apiProto = 'http';
|
||||||
|
static String apiHost = 'localhost:8080';
|
||||||
|
static String apiEndpoint = '/api/v1';
|
||||||
|
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
||||||
|
static String amplitudeServerZone = 'EU';
|
||||||
|
static String posthogApiKey = 'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
|
||||||
|
static String posthogHost = 'https://eu.i.posthog.com';
|
||||||
|
static Locale defaultLocale = const Locale('en');
|
||||||
|
static String defaultCurrency = 'EUR';
|
||||||
|
static int defaultDimensionLength = 500;
|
||||||
|
static String clientId = '';
|
||||||
|
static String wsProto = 'ws';
|
||||||
|
static String wsEndpoint = '/ws';
|
||||||
|
static Color themeColor = Color.fromARGB(255, 80, 63, 224);
|
||||||
|
static String nilObjectRef = '000000000000000000000000';
|
||||||
|
|
||||||
|
// Public getters for shared properties
|
||||||
|
static String get serviceUrl => '$apiProto://$apiHost';
|
||||||
|
static String get apiUrl => '$serviceUrl$apiEndpoint';
|
||||||
|
static String get wsUrl => '$wsProto://$apiHost$apiEndpoint$wsEndpoint';
|
||||||
|
static const String accessTokenStorageKey = 'access_token';
|
||||||
|
static const String refreshTokenStorageKey = 'refresh_token';
|
||||||
|
static const String currentOrgKey = 'current_org';
|
||||||
|
static const String deviceIdStorageKey = 'device_id';
|
||||||
|
|
||||||
|
// Method to apply the configuration, called by platform-specific implementations
|
||||||
|
static void applyConfiguration(Map<String, dynamic> configJson) {
|
||||||
|
apiProto = configJson['apiProto'] ?? apiProto;
|
||||||
|
apiHost = configJson['apiHost'] ?? apiHost;
|
||||||
|
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
||||||
|
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
||||||
|
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
||||||
|
posthogApiKey = configJson['posthogApiKey'] ?? posthogApiKey;
|
||||||
|
posthogHost = configJson['posthogHost'] ?? posthogHost;
|
||||||
|
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
||||||
|
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
||||||
|
wsProto = configJson['wsProto'] ?? wsProto;
|
||||||
|
wsEndpoint = configJson['wsEndpoint'] ?? wsEndpoint;
|
||||||
|
defaultDimensionLength = configJson['defaultDimensionLength'] ?? defaultDimensionLength;
|
||||||
|
clientId = configJson['clientId'] ?? clientId;
|
||||||
|
if (configJson.containsKey('themeColor')) {
|
||||||
|
themeColor = Color(int.parse(configJson['themeColor']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart';
|
|||||||
|
|
||||||
import 'package:pshared/data/dto/describable.dart';
|
import 'package:pshared/data/dto/describable.dart';
|
||||||
import 'package:pshared/data/dto/ledger/balance.dart';
|
import 'package:pshared/data/dto/ledger/balance.dart';
|
||||||
|
import 'package:pshared/data/dto/ledger/role.dart';
|
||||||
import 'package:pshared/data/dto/ledger/status.dart';
|
import 'package:pshared/data/dto/ledger/status.dart';
|
||||||
import 'package:pshared/data/dto/ledger/type.dart';
|
import 'package:pshared/data/dto/ledger/type.dart';
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ class LedgerAccountDTO {
|
|||||||
@JsonKey(fromJson: ledgerAccountStatusFromJson, toJson: ledgerAccountStatusToJson)
|
@JsonKey(fromJson: ledgerAccountStatusFromJson, toJson: ledgerAccountStatusToJson)
|
||||||
final LedgerAccountStatusDTO status;
|
final LedgerAccountStatusDTO status;
|
||||||
final bool allowNegative;
|
final bool allowNegative;
|
||||||
final bool isSettlement;
|
@JsonKey(fromJson: ledgerAccountRoleFromJson, toJson: ledgerAccountRoleToJson)
|
||||||
|
final LedgerAccountRoleDTO role;
|
||||||
final Map<String, String>? metadata;
|
final Map<String, String>? metadata;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
@@ -38,7 +40,7 @@ class LedgerAccountDTO {
|
|||||||
required this.currency,
|
required this.currency,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.allowNegative,
|
required this.allowNegative,
|
||||||
required this.isSettlement,
|
required this.role,
|
||||||
this.metadata,
|
this.metadata,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
|
|||||||
107
frontend/pshared/lib/data/dto/ledger/role.dart
Normal file
107
frontend/pshared/lib/data/dto/ledger/role.dart
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
|
||||||
|
enum LedgerAccountRoleDTO {
|
||||||
|
@JsonValue('unspecified')
|
||||||
|
unspecified,
|
||||||
|
|
||||||
|
@JsonValue('operating')
|
||||||
|
operating,
|
||||||
|
|
||||||
|
@JsonValue('hold')
|
||||||
|
hold,
|
||||||
|
|
||||||
|
@JsonValue('transit')
|
||||||
|
transit,
|
||||||
|
|
||||||
|
@JsonValue('settlement')
|
||||||
|
settlement,
|
||||||
|
|
||||||
|
@JsonValue('clearing')
|
||||||
|
clearing,
|
||||||
|
|
||||||
|
@JsonValue('pending')
|
||||||
|
pending,
|
||||||
|
|
||||||
|
@JsonValue('reserve')
|
||||||
|
reserve,
|
||||||
|
|
||||||
|
@JsonValue('liquidity')
|
||||||
|
liquidity,
|
||||||
|
|
||||||
|
@JsonValue('fee')
|
||||||
|
fee,
|
||||||
|
|
||||||
|
@JsonValue('chargeback')
|
||||||
|
chargeback,
|
||||||
|
|
||||||
|
@JsonValue('adjustment')
|
||||||
|
adjustment,
|
||||||
|
}
|
||||||
|
|
||||||
|
LedgerAccountRoleDTO ledgerAccountRoleFromJson(Object? value) {
|
||||||
|
final raw = value?.toString() ?? '';
|
||||||
|
var normalized = raw.trim().toLowerCase();
|
||||||
|
const prefix = 'account_role_';
|
||||||
|
if (normalized.startsWith(prefix)) {
|
||||||
|
normalized = normalized.substring(prefix.length);
|
||||||
|
}
|
||||||
|
switch (normalized) {
|
||||||
|
case 'operating':
|
||||||
|
return LedgerAccountRoleDTO.operating;
|
||||||
|
case 'hold':
|
||||||
|
return LedgerAccountRoleDTO.hold;
|
||||||
|
case 'transit':
|
||||||
|
return LedgerAccountRoleDTO.transit;
|
||||||
|
case 'settlement':
|
||||||
|
return LedgerAccountRoleDTO.settlement;
|
||||||
|
case 'clearing':
|
||||||
|
return LedgerAccountRoleDTO.clearing;
|
||||||
|
case 'pending':
|
||||||
|
return LedgerAccountRoleDTO.pending;
|
||||||
|
case 'reserve':
|
||||||
|
return LedgerAccountRoleDTO.reserve;
|
||||||
|
case 'liquidity':
|
||||||
|
return LedgerAccountRoleDTO.liquidity;
|
||||||
|
case 'fee':
|
||||||
|
return LedgerAccountRoleDTO.fee;
|
||||||
|
case 'chargeback':
|
||||||
|
return LedgerAccountRoleDTO.chargeback;
|
||||||
|
case 'adjustment':
|
||||||
|
return LedgerAccountRoleDTO.adjustment;
|
||||||
|
case 'unspecified':
|
||||||
|
case '':
|
||||||
|
return LedgerAccountRoleDTO.unspecified;
|
||||||
|
default:
|
||||||
|
return LedgerAccountRoleDTO.unspecified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String ledgerAccountRoleToJson(LedgerAccountRoleDTO value) {
|
||||||
|
switch (value) {
|
||||||
|
case LedgerAccountRoleDTO.operating:
|
||||||
|
return 'operating';
|
||||||
|
case LedgerAccountRoleDTO.hold:
|
||||||
|
return 'hold';
|
||||||
|
case LedgerAccountRoleDTO.transit:
|
||||||
|
return 'transit';
|
||||||
|
case LedgerAccountRoleDTO.settlement:
|
||||||
|
return 'settlement';
|
||||||
|
case LedgerAccountRoleDTO.clearing:
|
||||||
|
return 'clearing';
|
||||||
|
case LedgerAccountRoleDTO.pending:
|
||||||
|
return 'pending';
|
||||||
|
case LedgerAccountRoleDTO.reserve:
|
||||||
|
return 'reserve';
|
||||||
|
case LedgerAccountRoleDTO.liquidity:
|
||||||
|
return 'liquidity';
|
||||||
|
case LedgerAccountRoleDTO.fee:
|
||||||
|
return 'fee';
|
||||||
|
case LedgerAccountRoleDTO.chargeback:
|
||||||
|
return 'chargeback';
|
||||||
|
case LedgerAccountRoleDTO.adjustment:
|
||||||
|
return 'adjustment';
|
||||||
|
case LedgerAccountRoleDTO.unspecified:
|
||||||
|
return 'unspecified';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:pshared/data/dto/ledger/account.dart';
|
import 'package:pshared/data/dto/ledger/account.dart';
|
||||||
import 'package:pshared/data/mapper/describable.dart';
|
import 'package:pshared/data/mapper/describable.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/balance.dart';
|
import 'package:pshared/data/mapper/ledger/balance.dart';
|
||||||
|
import 'package:pshared/data/mapper/ledger/role.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/status.dart';
|
import 'package:pshared/data/mapper/ledger/status.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/type.dart';
|
import 'package:pshared/data/mapper/ledger/type.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
@@ -17,7 +18,7 @@ extension LedgerAccountDTOMapper on LedgerAccountDTO {
|
|||||||
currency: currency,
|
currency: currency,
|
||||||
status: status.toDomain(),
|
status: status.toDomain(),
|
||||||
allowNegative: allowNegative,
|
allowNegative: allowNegative,
|
||||||
isSettlement: isSettlement,
|
role: role.toDomain(),
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
@@ -36,7 +37,7 @@ extension LedgerAccountModelMapper on LedgerAccount {
|
|||||||
currency: currency,
|
currency: currency,
|
||||||
status: status.toDTO(),
|
status: status.toDTO(),
|
||||||
allowNegative: allowNegative,
|
allowNegative: allowNegative,
|
||||||
isSettlement: isSettlement,
|
role: role.toDTO(),
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
|
|||||||
65
frontend/pshared/lib/data/mapper/ledger/role.dart
Normal file
65
frontend/pshared/lib/data/mapper/ledger/role.dart
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import 'package:pshared/data/dto/ledger/role.dart';
|
||||||
|
import 'package:pshared/models/ledger/role.dart';
|
||||||
|
|
||||||
|
|
||||||
|
extension LedgerAccountRoleDTOMapper on LedgerAccountRoleDTO {
|
||||||
|
LedgerAccountRole toDomain() {
|
||||||
|
switch (this) {
|
||||||
|
case LedgerAccountRoleDTO.unspecified:
|
||||||
|
return LedgerAccountRole.unspecified;
|
||||||
|
case LedgerAccountRoleDTO.operating:
|
||||||
|
return LedgerAccountRole.operating;
|
||||||
|
case LedgerAccountRoleDTO.hold:
|
||||||
|
return LedgerAccountRole.hold;
|
||||||
|
case LedgerAccountRoleDTO.transit:
|
||||||
|
return LedgerAccountRole.transit;
|
||||||
|
case LedgerAccountRoleDTO.settlement:
|
||||||
|
return LedgerAccountRole.settlement;
|
||||||
|
case LedgerAccountRoleDTO.clearing:
|
||||||
|
return LedgerAccountRole.clearing;
|
||||||
|
case LedgerAccountRoleDTO.pending:
|
||||||
|
return LedgerAccountRole.pending;
|
||||||
|
case LedgerAccountRoleDTO.reserve:
|
||||||
|
return LedgerAccountRole.reserve;
|
||||||
|
case LedgerAccountRoleDTO.liquidity:
|
||||||
|
return LedgerAccountRole.liquidity;
|
||||||
|
case LedgerAccountRoleDTO.fee:
|
||||||
|
return LedgerAccountRole.fee;
|
||||||
|
case LedgerAccountRoleDTO.chargeback:
|
||||||
|
return LedgerAccountRole.chargeback;
|
||||||
|
case LedgerAccountRoleDTO.adjustment:
|
||||||
|
return LedgerAccountRole.adjustment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LedgerAccountRoleModelMapper on LedgerAccountRole {
|
||||||
|
LedgerAccountRoleDTO toDTO() {
|
||||||
|
switch (this) {
|
||||||
|
case LedgerAccountRole.unspecified:
|
||||||
|
return LedgerAccountRoleDTO.unspecified;
|
||||||
|
case LedgerAccountRole.operating:
|
||||||
|
return LedgerAccountRoleDTO.operating;
|
||||||
|
case LedgerAccountRole.hold:
|
||||||
|
return LedgerAccountRoleDTO.hold;
|
||||||
|
case LedgerAccountRole.transit:
|
||||||
|
return LedgerAccountRoleDTO.transit;
|
||||||
|
case LedgerAccountRole.settlement:
|
||||||
|
return LedgerAccountRoleDTO.settlement;
|
||||||
|
case LedgerAccountRole.clearing:
|
||||||
|
return LedgerAccountRoleDTO.clearing;
|
||||||
|
case LedgerAccountRole.pending:
|
||||||
|
return LedgerAccountRoleDTO.pending;
|
||||||
|
case LedgerAccountRole.reserve:
|
||||||
|
return LedgerAccountRoleDTO.reserve;
|
||||||
|
case LedgerAccountRole.liquidity:
|
||||||
|
return LedgerAccountRoleDTO.liquidity;
|
||||||
|
case LedgerAccountRole.fee:
|
||||||
|
return LedgerAccountRoleDTO.fee;
|
||||||
|
case LedgerAccountRole.chargeback:
|
||||||
|
return LedgerAccountRoleDTO.chargeback;
|
||||||
|
case LedgerAccountRole.adjustment:
|
||||||
|
return LedgerAccountRoleDTO.adjustment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,6 +90,8 @@ ChainNetwork chainNetworkFromValue(String? value) {
|
|||||||
return ChainNetwork.ethereumMainnet;
|
return ChainNetwork.ethereumMainnet;
|
||||||
case 'arbitrum_one':
|
case 'arbitrum_one':
|
||||||
return ChainNetwork.arbitrumOne;
|
return ChainNetwork.arbitrumOne;
|
||||||
|
case 'arbitrum_sepolia':
|
||||||
|
return ChainNetwork.arbitrumSepolia;
|
||||||
case 'tron_mainnet':
|
case 'tron_mainnet':
|
||||||
return ChainNetwork.tronMainnet;
|
return ChainNetwork.tronMainnet;
|
||||||
case 'tron_nile':
|
case 'tron_nile':
|
||||||
@@ -111,6 +113,8 @@ String chainNetworkToValue(ChainNetwork chain) {
|
|||||||
return 'tron_mainnet';
|
return 'tron_mainnet';
|
||||||
case ChainNetwork.tronNile:
|
case ChainNetwork.tronNile:
|
||||||
return 'tron_nile';
|
return 'tron_nile';
|
||||||
|
case ChainNetwork.arbitrumSepolia:
|
||||||
|
return 'arbitrum_sepolia';
|
||||||
case ChainNetwork.unspecified:
|
case ChainNetwork.unspecified:
|
||||||
return 'unspecified';
|
return 'unspecified';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@
|
|||||||
"description": "Label for the Arbitrum One network"
|
"description": "Label for the Arbitrum One network"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"chainNetworkArbitrumSepolia": "Arbitrum Sepolia",
|
||||||
|
"@chainNetworkArbitrumSepolia": {
|
||||||
|
"description": "Label for the Arbitrum Sepolia network"
|
||||||
|
},
|
||||||
|
|
||||||
"chainNetworkTronMainnet": "Tron Mainnet",
|
"chainNetworkTronMainnet": "Tron Mainnet",
|
||||||
"@chainNetworkTronMainnet": {
|
"@chainNetworkTronMainnet": {
|
||||||
"description": "Label for the Tron mainnet network"
|
"description": "Label for the Tron mainnet network"
|
||||||
|
|||||||
@@ -46,6 +46,11 @@
|
|||||||
"description": "Label for the Arbitrum One network"
|
"description": "Label for the Arbitrum One network"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"chainNetworkArbitrumSepolia": "Arbitrum Sepolia",
|
||||||
|
"@chainNetworkArbitrumSepolia": {
|
||||||
|
"description": "Label for the Arbitrum Sepolia network"
|
||||||
|
},
|
||||||
|
|
||||||
"chainNetworkTronMainnet": "Tron Mainnet",
|
"chainNetworkTronMainnet": "Tron Mainnet",
|
||||||
"@chainNetworkTronMainnet": {
|
"@chainNetworkTronMainnet": {
|
||||||
"description": "Label for the Tron mainnet network"
|
"description": "Label for the Tron mainnet network"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/ledger/balance.dart';
|
import 'package:pshared/models/ledger/balance.dart';
|
||||||
|
import 'package:pshared/models/ledger/role.dart';
|
||||||
import 'package:pshared/models/ledger/status.dart';
|
import 'package:pshared/models/ledger/status.dart';
|
||||||
import 'package:pshared/models/ledger/type.dart';
|
import 'package:pshared/models/ledger/type.dart';
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ class LedgerAccount implements Describable {
|
|||||||
final String currency;
|
final String currency;
|
||||||
final LedgerAccountStatus status;
|
final LedgerAccountStatus status;
|
||||||
final bool allowNegative;
|
final bool allowNegative;
|
||||||
final bool isSettlement;
|
final LedgerAccountRole role;
|
||||||
final Map<String, String>? metadata;
|
final Map<String, String>? metadata;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
@@ -35,7 +36,7 @@ class LedgerAccount implements Describable {
|
|||||||
required this.currency,
|
required this.currency,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.allowNegative,
|
required this.allowNegative,
|
||||||
required this.isSettlement,
|
required this.role,
|
||||||
this.metadata,
|
this.metadata,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
@@ -55,7 +56,7 @@ class LedgerAccount implements Describable {
|
|||||||
currency: currency,
|
currency: currency,
|
||||||
status: status,
|
status: status,
|
||||||
allowNegative: allowNegative,
|
allowNegative: allowNegative,
|
||||||
isSettlement: isSettlement,
|
role: role,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
|
|||||||
14
frontend/pshared/lib/models/ledger/role.dart
Normal file
14
frontend/pshared/lib/models/ledger/role.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
enum LedgerAccountRole {
|
||||||
|
unspecified,
|
||||||
|
operating,
|
||||||
|
hold,
|
||||||
|
transit,
|
||||||
|
settlement,
|
||||||
|
clearing,
|
||||||
|
pending,
|
||||||
|
reserve,
|
||||||
|
liquidity,
|
||||||
|
fee,
|
||||||
|
chargeback,
|
||||||
|
adjustment,
|
||||||
|
}
|
||||||
6
frontend/pshared/lib/models/pagination/cursor_page.dart
Normal file
6
frontend/pshared/lib/models/pagination/cursor_page.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class CursorPage<T> {
|
||||||
|
final List<T> items;
|
||||||
|
final String? nextCursor;
|
||||||
|
|
||||||
|
const CursorPage({required this.items, required this.nextCursor});
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ enum ChainNetwork {
|
|||||||
unspecified,
|
unspecified,
|
||||||
ethereumMainnet,
|
ethereumMainnet,
|
||||||
arbitrumOne,
|
arbitrumOne,
|
||||||
|
arbitrumSepolia,
|
||||||
tronMainnet,
|
tronMainnet,
|
||||||
tronNile
|
tronNile,
|
||||||
}
|
}
|
||||||
|
|||||||
5
frontend/pshared/lib/models/payment/page.dart
Normal file
5
frontend/pshared/lib/models/payment/page.dart
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import 'package:pshared/models/pagination/cursor_page.dart';
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
|
||||||
|
|
||||||
|
typedef PaymentPage =CursorPage<Payment>;
|
||||||
@@ -8,6 +8,7 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:pshared/models/currency.dart';
|
import 'package:pshared/models/currency.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/ledger/account.dart';
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
import 'package:pshared/models/ledger/role.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
@@ -24,7 +25,7 @@ class LedgerAccountsProvider with ChangeNotifier {
|
|||||||
Resource<List<LedgerAccount>> _resource = Resource(data: []);
|
Resource<List<LedgerAccount>> _resource = Resource(data: []);
|
||||||
Resource<List<LedgerAccount>> get resource => _resource;
|
Resource<List<LedgerAccount>> get resource => _resource;
|
||||||
|
|
||||||
List<LedgerAccount> get accounts => (_resource.data ?? []).whereNot((la)=> la.isSettlement).toList();
|
List<LedgerAccount> get accounts => (_resource.data ?? []).where((la) => la.role == LedgerAccountRole.operating).toList();
|
||||||
bool get isLoading => _resource.isLoading;
|
bool get isLoading => _resource.isLoading;
|
||||||
Exception? get error => _resource.error;
|
Exception? get error => _resource.error;
|
||||||
|
|
||||||
|
|||||||
181
frontend/pshared/lib/provider/payment/payments.dart
Normal file
181
frontend/pshared/lib/provider/payment/payments.dart
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
import 'package:pshared/provider/resource.dart';
|
||||||
|
import 'package:pshared/service/payment/service.dart';
|
||||||
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentsProvider with ChangeNotifier {
|
||||||
|
OrganizationsProvider? _organizations;
|
||||||
|
String? _loadedOrganizationRef;
|
||||||
|
|
||||||
|
Resource<List<Payment>> _resource = Resource(data: []);
|
||||||
|
bool _isLoaded = false;
|
||||||
|
bool _isLoadingMore = false;
|
||||||
|
String? _nextCursor;
|
||||||
|
|
||||||
|
int? _limit;
|
||||||
|
String? _sourceRef;
|
||||||
|
String? _destinationRef;
|
||||||
|
List<String>? _states;
|
||||||
|
|
||||||
|
int _opSeq = 0;
|
||||||
|
|
||||||
|
Resource<List<Payment>> get resource => _resource;
|
||||||
|
List<Payment> get payments => _resource.data ?? [];
|
||||||
|
bool get isLoading => _resource.isLoading;
|
||||||
|
Exception? get error => _resource.error;
|
||||||
|
bool get isReady => _isLoaded && !_resource.isLoading && _resource.error == null;
|
||||||
|
|
||||||
|
bool get isLoadingMore => _isLoadingMore;
|
||||||
|
String? get nextCursor => _nextCursor;
|
||||||
|
bool get canLoadMore => _nextCursor != null && _nextCursor!.isNotEmpty;
|
||||||
|
|
||||||
|
void update(OrganizationsProvider organizations) {
|
||||||
|
_organizations = organizations;
|
||||||
|
if (!organizations.isOrganizationSet) {
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final orgRef = organizations.current.id;
|
||||||
|
if (_loadedOrganizationRef != orgRef) {
|
||||||
|
_loadedOrganizationRef = orgRef;
|
||||||
|
unawaited(refresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh({
|
||||||
|
int? limit,
|
||||||
|
String? sourceRef,
|
||||||
|
String? destinationRef,
|
||||||
|
List<String>? states,
|
||||||
|
}) async {
|
||||||
|
final org = _organizations;
|
||||||
|
if (org == null || !org.isOrganizationSet) return;
|
||||||
|
|
||||||
|
_limit = limit;
|
||||||
|
_sourceRef = _normalize(sourceRef);
|
||||||
|
_destinationRef = _normalize(destinationRef);
|
||||||
|
_states = _normalizeStates(states);
|
||||||
|
_nextCursor = null;
|
||||||
|
_isLoadingMore = false;
|
||||||
|
|
||||||
|
final seq = ++_opSeq;
|
||||||
|
|
||||||
|
_applyResource(_resource.copyWith(isLoading: true, error: null), notify: true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final page = await PaymentService.listPage(
|
||||||
|
org.current.id,
|
||||||
|
limit: _limit,
|
||||||
|
cursor: null,
|
||||||
|
sourceRef: _sourceRef,
|
||||||
|
destinationRef: _destinationRef,
|
||||||
|
states: _states,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (seq != _opSeq) return;
|
||||||
|
|
||||||
|
_isLoaded = true;
|
||||||
|
_nextCursor = _normalize(page.nextCursor);
|
||||||
|
_applyResource(
|
||||||
|
Resource(data: page.items, isLoading: false, error: null),
|
||||||
|
notify: true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (seq != _opSeq) return;
|
||||||
|
|
||||||
|
_applyResource(
|
||||||
|
_resource.copyWith(isLoading: false, error: toException(e)),
|
||||||
|
notify: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadMore() async {
|
||||||
|
final org = _organizations;
|
||||||
|
if (org == null || !org.isOrganizationSet) return;
|
||||||
|
if (_isLoadingMore || _resource.isLoading) return;
|
||||||
|
|
||||||
|
final cursor = _normalize(_nextCursor);
|
||||||
|
if (cursor == null) return;
|
||||||
|
|
||||||
|
final seq = _opSeq;
|
||||||
|
|
||||||
|
_isLoadingMore = true;
|
||||||
|
_applyResource(_resource.copyWith(error: null), notify: false);
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final page = await PaymentService.listPage(
|
||||||
|
org.current.id,
|
||||||
|
limit: _limit,
|
||||||
|
cursor: cursor,
|
||||||
|
sourceRef: _sourceRef,
|
||||||
|
destinationRef: _destinationRef,
|
||||||
|
states: _states,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (seq != _opSeq) return;
|
||||||
|
|
||||||
|
final combined = List<Payment>.from(payments)..addAll(page.items);
|
||||||
|
_nextCursor = _normalize(page.nextCursor);
|
||||||
|
|
||||||
|
_applyResource(
|
||||||
|
_resource.copyWith(data: combined, error: null),
|
||||||
|
notify: false,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (seq != _opSeq) return;
|
||||||
|
|
||||||
|
_applyResource(
|
||||||
|
_resource.copyWith(error: toException(e)),
|
||||||
|
notify: false,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (seq == _opSeq) {
|
||||||
|
_isLoadingMore = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_opSeq++;
|
||||||
|
_isLoaded = false;
|
||||||
|
_isLoadingMore = false;
|
||||||
|
_nextCursor = null;
|
||||||
|
_limit = null;
|
||||||
|
_sourceRef = null;
|
||||||
|
_destinationRef = null;
|
||||||
|
_states = null;
|
||||||
|
_resource = Resource(data: []);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyResource(Resource<List<Payment>> newResource, {required bool notify}) {
|
||||||
|
_resource = newResource;
|
||||||
|
if (notify) notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _normalize(String? value) {
|
||||||
|
final trimmed = value?.trim();
|
||||||
|
if (trimmed == null || trimmed.isEmpty) return null;
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String>? _normalizeStates(List<String>? states) {
|
||||||
|
if (states == null || states.isEmpty) return null;
|
||||||
|
final normalized = states
|
||||||
|
.map((state) => state.trim())
|
||||||
|
.where((state) => state.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
if (normalized.isEmpty) return null;
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:pshared/provider/organizations.dart';
|
|||||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
import 'package:pshared/service/payment/service.dart';
|
import 'package:pshared/service/payment/service.dart';
|
||||||
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentProvider extends ChangeNotifier {
|
class PaymentProvider extends ChangeNotifier {
|
||||||
@@ -53,11 +54,7 @@ class PaymentProvider extends ChangeNotifier {
|
|||||||
_isLoaded = true;
|
_isLoaded = true;
|
||||||
_setResource(_payment.copyWith(data: response, isLoading: false, error: null));
|
_setResource(_payment.copyWith(data: response, isLoading: false, error: null));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_setResource(_payment.copyWith(
|
_setResource(_payment.copyWith(data: null, error: toException(e), isLoading: false));
|
||||||
data: null,
|
|
||||||
error: e is Exception ? e : Exception(e.toString()),
|
|
||||||
isLoading: false,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return _payment.data;
|
return _payment.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
|
import 'package:pshared/models/payment/asset.dart';
|
||||||
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/payment/currency_pair.dart';
|
import 'package:pshared/models/payment/currency_pair.dart';
|
||||||
import 'package:pshared/models/payment/customer.dart';
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
import 'package:pshared/models/payment/fx/intent.dart';
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
@@ -55,6 +57,10 @@ class QuotationIntentBuilder {
|
|||||||
destination: paymentData,
|
destination: paymentData,
|
||||||
source: ManagedWalletPaymentMethod(
|
source: ManagedWalletPaymentMethod(
|
||||||
managedWalletRef: selectedWallet.id,
|
managedWalletRef: selectedWallet.id,
|
||||||
|
asset: PaymentAsset(
|
||||||
|
tokenSymbol: selectedWallet.tokenSymbol ?? '',
|
||||||
|
chain: selectedWallet.network ?? ChainNetwork.unspecified,
|
||||||
|
)
|
||||||
),
|
),
|
||||||
fx: fxIntent,
|
fx: fxIntent,
|
||||||
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
|
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import 'package:pshared/models/resources.dart';
|
|||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
import 'package:pshared/service/permissions.dart';
|
import 'package:pshared/service/permissions.dart';
|
||||||
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
|
|
||||||
class PermissionsProvider extends ChangeNotifier {
|
class PermissionsProvider extends ChangeNotifier {
|
||||||
@@ -43,10 +44,7 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
await operation();
|
await operation();
|
||||||
return await load();
|
return await load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_userAccess = _userAccess.copyWith(
|
_userAccess = _userAccess.copyWith(error: toException(e), isLoading: false);
|
||||||
error: e is Exception ? e : Exception(e.toString()),
|
|
||||||
isLoading: false,
|
|
||||||
);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return _userAccess.data;
|
return _userAccess.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import 'package:pshared/api/responses/ledger/balance.dart';
|
|||||||
import 'package:pshared/data/mapper/describable.dart';
|
import 'package:pshared/data/mapper/describable.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/account.dart';
|
import 'package:pshared/data/mapper/ledger/account.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/balance.dart';
|
import 'package:pshared/data/mapper/ledger/balance.dart';
|
||||||
|
import 'package:pshared/data/mapper/ledger/role.dart';
|
||||||
import 'package:pshared/data/mapper/ledger/type.dart';
|
import 'package:pshared/data/mapper/ledger/type.dart';
|
||||||
import 'package:pshared/models/currency.dart';
|
import 'package:pshared/models/currency.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/ledger/account.dart';
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/ledger/balance.dart';
|
import 'package:pshared/models/ledger/balance.dart';
|
||||||
|
import 'package:pshared/models/ledger/role.dart';
|
||||||
import 'package:pshared/models/ledger/type.dart';
|
import 'package:pshared/models/ledger/type.dart';
|
||||||
import 'package:pshared/service/authorization/service.dart';
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
import 'package:pshared/service/services.dart';
|
import 'package:pshared/service/services.dart';
|
||||||
@@ -49,7 +51,7 @@ class LedgerService {
|
|||||||
describable: describable.toDTO(),
|
describable: describable.toDTO(),
|
||||||
ownerRef: ownerRef,
|
ownerRef: ownerRef,
|
||||||
allowNegative: false,
|
allowNegative: false,
|
||||||
isSettlement: false,
|
role: LedgerAccountRole.operating.toDTO(),
|
||||||
accountType: LedgerAccountType.asset.toDTO(),
|
accountType: LedgerAccountType.asset.toDTO(),
|
||||||
currency: currencyCodeToString(currency),
|
currency: currencyCodeToString(currency),
|
||||||
).toJson(),
|
).toJson(),
|
||||||
|
|||||||
@@ -6,16 +6,18 @@ import 'package:pshared/api/requests/payment/initiate.dart';
|
|||||||
import 'package:pshared/api/responses/payment/payment.dart';
|
import 'package:pshared/api/responses/payment/payment.dart';
|
||||||
import 'package:pshared/api/responses/payment/payments.dart';
|
import 'package:pshared/api/responses/payment/payments.dart';
|
||||||
import 'package:pshared/data/mapper/payment/payment_response.dart';
|
import 'package:pshared/data/mapper/payment/payment_response.dart';
|
||||||
|
import 'package:pshared/models/payment/page.dart';
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/service/authorization/service.dart';
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
import 'package:pshared/service/services.dart';
|
import 'package:pshared/service/services.dart';
|
||||||
|
import 'package:pshared/utils/http/params.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentService {
|
class PaymentService {
|
||||||
static final _logger = Logger('service.payment');
|
static final _logger = Logger('service.payment');
|
||||||
static const String _objectType = Services.payments;
|
static const String _objectType = Services.payments;
|
||||||
|
|
||||||
static Future<List<Payment>> list(
|
static Future<PaymentPage> listPage(
|
||||||
String organizationRef, {
|
String organizationRef, {
|
||||||
int? limit,
|
int? limit,
|
||||||
String? cursor,
|
String? cursor,
|
||||||
@@ -25,12 +27,6 @@ class PaymentService {
|
|||||||
}) async {
|
}) async {
|
||||||
_logger.fine('Listing payments for organization $organizationRef');
|
_logger.fine('Listing payments for organization $organizationRef');
|
||||||
final queryParams = <String, String>{};
|
final queryParams = <String, String>{};
|
||||||
if (limit != null) {
|
|
||||||
queryParams['limit'] = limit.toString();
|
|
||||||
}
|
|
||||||
if (cursor != null && cursor.isNotEmpty) {
|
|
||||||
queryParams['cursor'] = cursor;
|
|
||||||
}
|
|
||||||
if (sourceRef != null && sourceRef.isNotEmpty) {
|
if (sourceRef != null && sourceRef.isNotEmpty) {
|
||||||
queryParams['source_ref'] = sourceRef;
|
queryParams['source_ref'] = sourceRef;
|
||||||
}
|
}
|
||||||
@@ -41,12 +37,35 @@ class PaymentService {
|
|||||||
queryParams['state'] = states.join(',');
|
queryParams['state'] = states.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
final path = '/$organizationRef';
|
final url = cursorParamsToUriString(
|
||||||
final url = queryParams.isEmpty
|
path: '/$organizationRef',
|
||||||
? path
|
limit: limit,
|
||||||
: Uri(path: path, queryParameters: queryParams).toString();
|
cursor: cursor,
|
||||||
|
queryParams: queryParams,
|
||||||
|
);
|
||||||
final response = await AuthorizationService.getGETResponse(_objectType, url);
|
final response = await AuthorizationService.getGETResponse(_objectType, url);
|
||||||
return PaymentsResponse.fromJson(response).payments.map((payment) => payment.toDomain()).toList();
|
final parsed = PaymentsResponse.fromJson(response);
|
||||||
|
final payments = parsed.payments.map((payment) => payment.toDomain()).toList();
|
||||||
|
return PaymentPage(items: payments, nextCursor: parsed.nextCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<Payment>> list(
|
||||||
|
String organizationRef, {
|
||||||
|
int? limit,
|
||||||
|
String? cursor,
|
||||||
|
String? sourceRef,
|
||||||
|
String? destinationRef,
|
||||||
|
List<String>? states,
|
||||||
|
}) async {
|
||||||
|
final page = await listPage(
|
||||||
|
organizationRef,
|
||||||
|
limit: limit,
|
||||||
|
cursor: cursor,
|
||||||
|
sourceRef: sourceRef,
|
||||||
|
destinationRef: destinationRef,
|
||||||
|
states: states,
|
||||||
|
);
|
||||||
|
return page.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Payment> pay(
|
static Future<Payment> pay(
|
||||||
@@ -68,4 +87,5 @@ class PaymentService {
|
|||||||
);
|
);
|
||||||
return PaymentResponse.fromJson(response).payment.toDomain();
|
return PaymentResponse.fromJson(response).payment.toDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const String _limitParam = 'limit';
|
const String _limitParam = 'limit';
|
||||||
const String _offsetParam = 'offset';
|
const String _offsetParam = 'offset';
|
||||||
const String _archivedParam = 'archived';
|
const String _archivedParam = 'archived';
|
||||||
|
const String _cursorParam = 'cursor';
|
||||||
|
|
||||||
void _addIfNotNull(Map<String, String> params, String key, dynamic value) {
|
void _addIfNotNull(Map<String, String> params, String key, dynamic value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -9,6 +10,13 @@ void _addIfNotNull(Map<String, String> params, String key, dynamic value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addIfNotBlank(Map<String, String> params, String key, String? value) {
|
||||||
|
final trimmed = value?.trim();
|
||||||
|
if (trimmed != null && trimmed.isNotEmpty) {
|
||||||
|
params[key] = trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Uri paramsToUri({
|
Uri paramsToUri({
|
||||||
required String path,
|
required String path,
|
||||||
int? limit,
|
int? limit,
|
||||||
@@ -36,3 +44,32 @@ String paramsToUriString({
|
|||||||
int? offset,
|
int? offset,
|
||||||
bool? fetchArchived,
|
bool? fetchArchived,
|
||||||
}) => paramsToUri(path: path, limit: limit, offset: offset, fetchArchived: fetchArchived).toString();
|
}) => paramsToUri(path: path, limit: limit, offset: offset, fetchArchived: fetchArchived).toString();
|
||||||
|
|
||||||
|
Uri cursorParamsToUri({
|
||||||
|
required String path,
|
||||||
|
int? limit,
|
||||||
|
String? cursor,
|
||||||
|
Map<String, String> queryParams = const {},
|
||||||
|
}) {
|
||||||
|
final params = Map<String, String>.from(queryParams);
|
||||||
|
_addIfNotNull(params, _limitParam, limit);
|
||||||
|
_addIfNotBlank(params, _cursorParam, cursor);
|
||||||
|
params.removeWhere((_, value) => value.trim().isEmpty);
|
||||||
|
|
||||||
|
return Uri(
|
||||||
|
path: path,
|
||||||
|
queryParameters: params.isEmpty ? null : params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String cursorParamsToUriString({
|
||||||
|
required String path,
|
||||||
|
int? limit,
|
||||||
|
String? cursor,
|
||||||
|
Map<String, String> queryParams = const {},
|
||||||
|
}) => cursorParamsToUri(
|
||||||
|
path: path,
|
||||||
|
limit: limit,
|
||||||
|
cursor: cursor,
|
||||||
|
queryParams: queryParams,
|
||||||
|
).toString();
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ extension ChainNetworkL10n on ChainNetwork {
|
|||||||
return l10n.chainNetworkEthereumMainnet;
|
return l10n.chainNetworkEthereumMainnet;
|
||||||
case ChainNetwork.arbitrumOne:
|
case ChainNetwork.arbitrumOne:
|
||||||
return l10n.chainNetworkArbitrumOne;
|
return l10n.chainNetworkArbitrumOne;
|
||||||
|
case ChainNetwork.arbitrumSepolia:
|
||||||
|
return l10n.chainNetworkArbitrumSepolia;
|
||||||
case ChainNetwork.tronMainnet:
|
case ChainNetwork.tronMainnet:
|
||||||
return l10n.chainNetworkTronMainnet;
|
return l10n.chainNetworkTronMainnet;
|
||||||
case ChainNetwork.tronNile:
|
case ChainNetwork.tronNile:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'package:pshared/provider/recipient/provider.dart';
|
|||||||
import 'package:pshared/provider/email_verification.dart';
|
import 'package:pshared/provider/email_verification.dart';
|
||||||
import 'package:pshared/provider/ledger.dart';
|
import 'package:pshared/provider/ledger.dart';
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
import 'package:pshared/provider/payment/wallets.dart';
|
||||||
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
import 'package:pshared/provider/invitations.dart';
|
import 'package:pshared/provider/invitations.dart';
|
||||||
import 'package:pshared/service/ledger.dart';
|
import 'package:pshared/service/ledger.dart';
|
||||||
import 'package:pshared/service/payment/wallets.dart';
|
import 'package:pshared/service/payment/wallets.dart';
|
||||||
@@ -27,11 +28,9 @@ import 'package:pshared/service/payment/wallets.dart';
|
|||||||
import 'package:pweb/app/app.dart';
|
import 'package:pweb/app/app.dart';
|
||||||
import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
|
import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
|
||||||
import 'package:pweb/app/timeago.dart';
|
import 'package:pweb/app/timeago.dart';
|
||||||
import 'package:pweb/providers/operatioins.dart';
|
|
||||||
import 'package:pweb/providers/two_factor.dart';
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
import 'package:pweb/providers/upload_history.dart';
|
import 'package:pweb/providers/upload_history.dart';
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/operations.dart';
|
|
||||||
import 'package:pweb/services/payments/history.dart';
|
import 'package:pweb/services/payments/history.dart';
|
||||||
import 'package:pweb/services/posthog.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
import 'package:pweb/services/wallet_transactions.dart';
|
||||||
@@ -78,6 +77,10 @@ void main() async {
|
|||||||
create: (_) => EmployeesProvider(),
|
create: (_) => EmployeesProvider(),
|
||||||
update: (context, organizations, provider) => provider!..updateProviders(organizations),
|
update: (context, organizations, provider) => provider!..updateProviders(organizations),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProxyProvider<OrganizationsProvider, PaymentsProvider>(
|
||||||
|
create: (_) => PaymentsProvider(),
|
||||||
|
update: (context, organizations, provider) => provider!..update(organizations),
|
||||||
|
),
|
||||||
ChangeNotifierProvider(create: (_) => EmailVerificationProvider()),
|
ChangeNotifierProvider(create: (_) => EmailVerificationProvider()),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
@@ -117,9 +120,6 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(),
|
create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(
|
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: const PayApp(),
|
child: const PayApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/operation.dart';
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/models/payment/status.dart';
|
||||||
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/report/charts/distribution.dart';
|
import 'package:pweb/pages/report/charts/distribution.dart';
|
||||||
import 'package:pweb/pages/report/charts/status.dart';
|
import 'package:pweb/pages/report/charts/status.dart';
|
||||||
import 'package:pweb/pages/report/table/filters.dart';
|
import 'package:pweb/pages/report/table/filters.dart';
|
||||||
import 'package:pweb/pages/report/table/widget.dart';
|
import 'package:pweb/pages/report/table/widget.dart';
|
||||||
import 'package:pweb/providers/operatioins.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -19,18 +23,25 @@ class OperationHistoryPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||||
|
DateTimeRange? _pendingRange;
|
||||||
|
DateTimeRange? _appliedRange;
|
||||||
|
final Set<OperationStatus> _pendingStatuses = {};
|
||||||
|
Set<OperationStatus> _appliedStatuses = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<OperationProvider>().loadOperations();
|
final provider = context.read<PaymentsProvider>();
|
||||||
|
if (!provider.isReady && !provider.isLoading) {
|
||||||
|
provider.refresh();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pickRange() async {
|
Future<void> _pickRange() async {
|
||||||
final provider = context.read<OperationProvider>();
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final initial = provider.dateRange ??
|
final initial = _pendingRange ??
|
||||||
DateTimeRange(
|
DateTimeRange(
|
||||||
start: now.subtract(const Duration(days: 30)),
|
start: now.subtract(const Duration(days: 30)),
|
||||||
end: now,
|
end: now,
|
||||||
@@ -44,33 +55,157 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
provider.setDateRange(picked);
|
setState(() {
|
||||||
|
_pendingRange = picked;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleStatus(OperationStatus status) {
|
||||||
|
setState(() {
|
||||||
|
if (_pendingStatuses.contains(status)) {
|
||||||
|
_pendingStatuses.remove(status);
|
||||||
|
} else {
|
||||||
|
_pendingStatuses.add(status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyFilters() {
|
||||||
|
setState(() {
|
||||||
|
_appliedRange = _pendingRange;
|
||||||
|
_appliedStatuses = {..._pendingStatuses};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OperationItem> _mapPayments(List<Payment> payments) {
|
||||||
|
return payments.map(_mapPayment).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationItem _mapPayment(Payment payment) {
|
||||||
|
final debit = payment.lastQuote?.debitAmount;
|
||||||
|
final settlement = payment.lastQuote?.expectedSettlementAmount;
|
||||||
|
final amountMoney = debit ?? settlement;
|
||||||
|
|
||||||
|
final amount = _parseAmount(amountMoney?.amount);
|
||||||
|
final currency = amountMoney?.currency ?? '';
|
||||||
|
final toAmount = settlement == null ? amount : _parseAmount(settlement.amount);
|
||||||
|
final toCurrency = settlement?.currency ?? currency;
|
||||||
|
|
||||||
|
final payId = _firstNonEmpty([payment.paymentRef, payment.idempotencyKey]) ?? '-';
|
||||||
|
final name = _firstNonEmpty([payment.lastQuote?.quoteRef, payment.paymentRef, payment.idempotencyKey]) ?? '-';
|
||||||
|
final comment = _firstNonEmpty([payment.failureReason, payment.failureCode, payment.state]) ?? '';
|
||||||
|
|
||||||
|
return OperationItem(
|
||||||
|
status: _statusFromPaymentState(payment.state),
|
||||||
|
fileName: null,
|
||||||
|
amount: amount,
|
||||||
|
currency: currency,
|
||||||
|
toAmount: toAmount,
|
||||||
|
toCurrency: toCurrency,
|
||||||
|
payId: payId,
|
||||||
|
cardNumber: null,
|
||||||
|
name: name,
|
||||||
|
date: _resolvePaymentDate(payment),
|
||||||
|
comment: comment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OperationItem> _filterOperations(List<OperationItem> operations) {
|
||||||
|
if (_appliedRange == null && _appliedStatuses.isEmpty) {
|
||||||
|
return operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
return operations.where((op) {
|
||||||
|
final statusMatch =
|
||||||
|
_appliedStatuses.isEmpty || _appliedStatuses.contains(op.status);
|
||||||
|
|
||||||
|
final dateMatch = _appliedRange == null ||
|
||||||
|
_isUnknownDate(op.date) ||
|
||||||
|
(op.date.isAfter(_appliedRange!.start.subtract(const Duration(seconds: 1))) &&
|
||||||
|
op.date.isBefore(_appliedRange!.end.add(const Duration(seconds: 1))));
|
||||||
|
|
||||||
|
return statusMatch && dateMatch;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationStatus _statusFromPaymentState(String? raw) {
|
||||||
|
final state = raw?.trim().toLowerCase();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case 'accepted':
|
||||||
|
case 'funds_reserved':
|
||||||
|
case 'submitted':
|
||||||
|
case 'unspecified':
|
||||||
|
case null:
|
||||||
|
return OperationStatus.processing;
|
||||||
|
|
||||||
|
case 'settled':
|
||||||
|
return OperationStatus.success;
|
||||||
|
|
||||||
|
case 'failed':
|
||||||
|
case 'cancelled':
|
||||||
|
return OperationStatus.error;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Future-proof: any new backend state is treated as processing
|
||||||
|
return OperationStatus.processing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime _resolvePaymentDate(Payment payment) {
|
||||||
|
final expiresAt = payment.lastQuote?.fxQuote?.expiresAtUnixMs;
|
||||||
|
if (expiresAt != null && expiresAt > 0) {
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch(expiresAt, isUtc: true);
|
||||||
|
}
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _parseAmount(String? amount) {
|
||||||
|
if (amount == null || amount.trim().isEmpty) return 0;
|
||||||
|
return double.tryParse(amount) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _firstNonEmpty(List<String?> values) {
|
||||||
|
for (final value in values) {
|
||||||
|
final trimmed = value?.trim();
|
||||||
|
if (trimmed != null && trimmed.isNotEmpty) return trimmed;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isUnknownDate(DateTime date) => date.millisecondsSinceEpoch == 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
return Consumer<OperationProvider>(
|
return Consumer<PaymentsProvider>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, provider, child) {
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
|
final message = provider.error?.toString() ?? loc.noErrorInformation;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
Text(loc.notificationError(message)),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => provider.loadOperations(),
|
onPressed: () => provider.refresh(),
|
||||||
child: Text(loc.retry),
|
child: Text(loc.retry),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final operations = _mapPayments(provider.payments);
|
||||||
|
final filteredOperations = _filterOperations(operations);
|
||||||
|
final hasFileName = operations.any(
|
||||||
|
(operation) => (operation.fileName ?? '').trim().isNotEmpty,
|
||||||
|
);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -84,26 +219,26 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
|||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StatusChart(operations: provider.allOperations),
|
child: StatusChart(operations: operations),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PayoutDistributionChart(
|
child: PayoutDistributionChart(
|
||||||
operations: provider.allOperations,
|
operations: operations,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
OperationFilters(
|
OperationFilters(
|
||||||
selectedRange: provider.dateRange,
|
selectedRange: _pendingRange,
|
||||||
selectedStatuses: provider.selectedStatuses,
|
selectedStatuses: _pendingStatuses,
|
||||||
onPickRange: _pickRange,
|
onPickRange: _pickRange,
|
||||||
onToggleStatus: provider.toggleStatus,
|
onToggleStatus: _toggleStatus,
|
||||||
onApply: () => provider.applyFilters(context),
|
onApply: _applyFilters,
|
||||||
),
|
),
|
||||||
OperationsTable(
|
OperationsTable(
|
||||||
operations: provider.filteredOperations,
|
operations: filteredOperations,
|
||||||
showFileNameColumn: provider.hasFileName,
|
showFileNameColumn: hasFileName,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
class OperationFilters extends StatelessWidget {
|
class OperationFilters extends StatelessWidget {
|
||||||
final DateTimeRange? selectedRange;
|
final DateTimeRange? selectedRange;
|
||||||
final Set<String> selectedStatuses;
|
final Set<OperationStatus> selectedStatuses;
|
||||||
final VoidCallback onPickRange;
|
final VoidCallback onPickRange;
|
||||||
final VoidCallback onApply;
|
final VoidCallback onApply;
|
||||||
final ValueChanged<String> onToggleStatus;
|
final ValueChanged<OperationStatus> onToggleStatus;
|
||||||
|
|
||||||
const OperationFilters({
|
const OperationFilters({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -66,11 +66,12 @@ class OperationFilters extends StatelessWidget {
|
|||||||
Wrap(
|
Wrap(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: const [
|
||||||
OperationStatus.success.localized(context),
|
OperationStatus.success,
|
||||||
OperationStatus.processing.localized(context),
|
OperationStatus.processing,
|
||||||
OperationStatus.error.localized(context),
|
OperationStatus.error,
|
||||||
].map((status) {
|
].map((status) {
|
||||||
|
final label = status.localized(context);
|
||||||
final isSelected = selectedStatuses.contains(status);
|
final isSelected = selectedStatuses.contains(status);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => onToggleStatus(status),
|
onTap: () => onToggleStatus(status),
|
||||||
@@ -89,7 +90,7 @@ class OperationFilters extends StatelessWidget {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.status(status),
|
l10n.status(label),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected ? Colors.white : Colors.black87,
|
color: isSelected ? Colors.white : Colors.black87,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ import 'package:pweb/pages/report/table/badge.dart';
|
|||||||
|
|
||||||
class OperationRow {
|
class OperationRow {
|
||||||
static DataRow build(OperationItem op, BuildContext context) {
|
static DataRow build(OperationItem op, BuildContext context) {
|
||||||
|
final isUnknownDate = op.date.millisecondsSinceEpoch == 0;
|
||||||
|
final localDate = op.date.toLocal();
|
||||||
|
final dateLabel = isUnknownDate
|
||||||
|
? '-'
|
||||||
|
: '${TimeOfDay.fromDateTime(localDate).format(context)}\n'
|
||||||
|
'${localDate.toIso8601String().split("T").first}';
|
||||||
|
|
||||||
return DataRow(cells: [
|
return DataRow(cells: [
|
||||||
DataCell(OperationStatusBadge(status: op.status)),
|
DataCell(OperationStatusBadge(status: op.status)),
|
||||||
DataCell(Text(op.fileName ?? '')),
|
DataCell(Text(op.fileName ?? '')),
|
||||||
@@ -16,10 +23,7 @@ class OperationRow {
|
|||||||
DataCell(Text(op.payId)),
|
DataCell(Text(op.payId)),
|
||||||
DataCell(Text(op.cardNumber ?? '-')),
|
DataCell(Text(op.cardNumber ?? '-')),
|
||||||
DataCell(Text(op.name)),
|
DataCell(Text(op.name)),
|
||||||
DataCell(Text(
|
DataCell(Text(dateLabel)),
|
||||||
'${TimeOfDay.fromDateTime(op.date).format(context)}\n'
|
|
||||||
'${op.date.toLocal().toIso8601String().split("T").first}',
|
|
||||||
)),
|
|
||||||
DataCell(Text(op.comment)),
|
DataCell(Text(op.comment)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user