multiple payout page and small fixes
This commit is contained in:
@@ -6,10 +6,11 @@ import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
|
||||
import 'package:pweb/models/dashboard_payment_mode.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/balance.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/controller.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/buttons.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/title.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/widgets/title.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/widget.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/single/widget.dart';
|
||||
import 'package:pweb/pages/loader.dart';
|
||||
@@ -42,19 +43,19 @@ class DashboardPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DashboardPageState extends State<DashboardPage> {
|
||||
bool _showContainerSingle = true;
|
||||
bool _showContainerMultiple = false;
|
||||
DashboardPayoutMode _payoutMode = DashboardPayoutMode.single;
|
||||
|
||||
void _setActive(bool single) {
|
||||
void _setActive(DashboardPayoutMode mode) {
|
||||
setState(() {
|
||||
_showContainerSingle = single;
|
||||
_showContainerMultiple = !single;
|
||||
_payoutMode = mode;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final showSingle = _payoutMode == DashboardPayoutMode.single;
|
||||
final showMultiple = _payoutMode == DashboardPayoutMode.multiple;
|
||||
return PageViewLoader(
|
||||
child: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
@@ -66,8 +67,8 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(true),
|
||||
isActive: _showContainerSingle,
|
||||
onTap: () => _setActive(DashboardPayoutMode.single),
|
||||
isActive: showSingle,
|
||||
label: l10n.sendSingle,
|
||||
icon: Icons.person_add,
|
||||
),
|
||||
@@ -76,8 +77,8 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(false),
|
||||
isActive: _showContainerMultiple,
|
||||
onTap: () => _setActive(DashboardPayoutMode.multiple),
|
||||
isActive: showMultiple,
|
||||
label: l10n.sendMultiple,
|
||||
icon: Icons.group_add,
|
||||
),
|
||||
@@ -93,14 +94,14 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.small),
|
||||
if (_showContainerMultiple) TitleMultiplePayout(),
|
||||
if (showMultiple) TitleMultiplePayout(),
|
||||
const SizedBox(height: AppSpacing.medium),
|
||||
if (_showContainerSingle)
|
||||
if (showSingle)
|
||||
SinglePayoutForm(
|
||||
onRecipientSelected: widget.onRecipientSelected,
|
||||
onGoToPayment: widget.onGoToPaymentWithoutRecipient,
|
||||
),
|
||||
if (_showContainerMultiple) MultiplePayoutForm(),
|
||||
if (showMultiple) MultiplePayoutForm(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/source_quote_panel.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/upload_panel.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadCSVSection extends StatelessWidget {
|
||||
const UploadCSVSection({super.key});
|
||||
|
||||
static const double _verticalSpacing = 10;
|
||||
static const double _iconTextSpacing = 5;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = context.watch<MultiplePayoutsController>();
|
||||
final walletsController = context.watch<WalletsController>();
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.upload),
|
||||
const SizedBox(width: _iconTextSpacing),
|
||||
Text(
|
||||
l10n.uploadCSV,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: _verticalSpacing),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: theme.colorScheme.outline),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final useHorizontal = constraints.maxWidth >= 760;
|
||||
if (!useHorizontal) {
|
||||
return Column(
|
||||
children: [
|
||||
UploadPanel(
|
||||
controller: controller,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SourceQuotePanel(
|
||||
controller: controller,
|
||||
walletsController: walletsController,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: UploadPanel(
|
||||
controller: controller,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: SourceQuotePanel(
|
||||
controller: controller,
|
||||
walletsController: walletsController,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
class MultiplePayoutRow {
|
||||
final String pan;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final int expMonth;
|
||||
final int expYear;
|
||||
final String amount;
|
||||
|
||||
const MultiplePayoutRow({
|
||||
required this.pan,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.expMonth,
|
||||
required this.expYear,
|
||||
required this.amount,
|
||||
});
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/payments.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadHistorySection extends StatelessWidget {
|
||||
const UploadHistorySection({super.key});
|
||||
|
||||
static const double _smallBox = 5;
|
||||
static const double _radius = 6;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<PaymentsProvider>();
|
||||
final theme = Theme.of(context);
|
||||
final l10 = AppLocalizations.of(context)!;
|
||||
final dateFormat = DateFormat.yMMMd().add_Hm();
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (provider.error != null) {
|
||||
return Text(
|
||||
l10.notificationError(provider.error ?? l10.noErrorInformation),
|
||||
);
|
||||
}
|
||||
final items = List.of(provider.payments);
|
||||
items.sort((a, b) {
|
||||
final left = a.createdAt;
|
||||
final right = b.createdAt;
|
||||
if (left == null && right == null) return 0;
|
||||
if (left == null) return 1;
|
||||
if (right == null) return -1;
|
||||
return right.compareTo(left);
|
||||
});
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.history),
|
||||
const SizedBox(width: _smallBox),
|
||||
Text(l10.uploadHistory, style: theme.textTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (items.isEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
l10.walletHistoryEmpty,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
)
|
||||
else
|
||||
DataTable(
|
||||
columns: [
|
||||
DataColumn(label: Text(l10.fileNameColumn)),
|
||||
DataColumn(label: Text(l10.rowsColumn)),
|
||||
DataColumn(label: Text(l10.dateColumn)),
|
||||
DataColumn(label: Text(l10.amountColumn)),
|
||||
DataColumn(label: Text(l10.statusColumn)),
|
||||
],
|
||||
rows: items.map((payment) {
|
||||
final metadata = payment.metadata;
|
||||
final state = payment.state ?? '-';
|
||||
final statusColor =
|
||||
payment.isFailure ? Colors.red : Colors.green;
|
||||
final fileName = metadata?['upload_filename'];
|
||||
final fileNameText =
|
||||
(fileName == null || fileName.isEmpty) ? '-' : fileName;
|
||||
final rows = metadata?['upload_rows'];
|
||||
final rowsText = (rows == null || rows.isEmpty) ? '-' : rows;
|
||||
final createdAt = payment.createdAt;
|
||||
final dateText = createdAt == null
|
||||
? '-'
|
||||
: dateFormat.format(createdAt.toLocal());
|
||||
final amountValue = metadata?['upload_amount'];
|
||||
final amountCurrency = metadata?['upload_currency'];
|
||||
final fallbackAmount = payment.lastQuote?.debitAmount;
|
||||
final amountText = (amountValue == null || amountValue.isEmpty)
|
||||
? (fallbackAmount == null
|
||||
? '-'
|
||||
: '${fallbackAmount.amount} ${fallbackAmount.currency}')
|
||||
: (amountCurrency == null || amountCurrency.isEmpty
|
||||
? amountValue
|
||||
: '$amountValue $amountCurrency');
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(fileNameText)),
|
||||
DataCell(Text(rowsText)),
|
||||
DataCell(Text(dateText)),
|
||||
DataCell(Text(amountText)),
|
||||
DataCell(
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(_radius),
|
||||
),
|
||||
child: Text(state, style: TextStyle(color: statusColor)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SourceQuotePanelHeader extends StatelessWidget {
|
||||
const SourceQuotePanelHeader({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
l10n.sourceOfFunds,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.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}';
|
||||
try {
|
||||
return assetToString(
|
||||
Asset(
|
||||
currency: currencyStringToCode(money.currency),
|
||||
amount: amount,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return '${money.amount} ${money.currency}';
|
||||
}
|
||||
}
|
||||
|
||||
String sentAmountLabel(MultiplePayoutsController controller) {
|
||||
final requested = controller.requestedSentAmount;
|
||||
final sourceDebit = controller.aggregateDebitAmount;
|
||||
|
||||
if (requested == null && sourceDebit == null) return 'N/A';
|
||||
if (sourceDebit != null) return moneyLabel(sourceDebit);
|
||||
return moneyLabel(requested);
|
||||
}
|
||||
|
||||
String feeLabel(MultiplePayoutsController controller) {
|
||||
final feeLabelText = moneyLabel(controller.aggregateFeeAmount);
|
||||
final percent = controller.aggregateFeePercent;
|
||||
if (percent == null) return feeLabelText;
|
||||
return '$feeLabelText (${percent.toStringAsFixed(2)}%)';
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SourceWalletSelector extends StatelessWidget {
|
||||
const SourceWalletSelector({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.walletsController,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final WalletsController walletsController;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final wallets = walletsController.wallets;
|
||||
final selectedWalletRef = walletsController.selectedWalletRef;
|
||||
|
||||
if (wallets.isEmpty) {
|
||||
return Text(l10n.noWalletsAvailable, style: theme.textTheme.bodySmall);
|
||||
}
|
||||
|
||||
return DropdownButtonFormField<String>(
|
||||
initialValue: selectedWalletRef,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.whereGetMoney,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
items: wallets
|
||||
.map(
|
||||
(wallet) => DropdownMenuItem<String>(
|
||||
value: wallet.id,
|
||||
child: Text(
|
||||
'${wallet.name} - ${amountToString(wallet.balance)} ${currencyCodeToString(wallet.currency)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
onChanged: controller.isBusy
|
||||
? null
|
||||
: (value) {
|
||||
if (value == null) return;
|
||||
walletsController.selectWalletByRef(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/models/summary_values.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||
|
||||
|
||||
class SourceQuoteSummary extends StatelessWidget {
|
||||
const SourceQuoteSummary({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.spacing,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final double spacing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PaymentSummary(
|
||||
spacing: spacing,
|
||||
values: PaymentSummaryValues(
|
||||
sentAmount: sentAmountLabel(controller),
|
||||
fee: feeLabel(controller),
|
||||
recipientReceives: moneyLabel(controller.aggregateSettlementAmount),
|
||||
total: moneyLabel(controller.aggregateDebitAmount),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/header.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/selector.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/widgets/quote_status.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
|
||||
|
||||
class SourceQuotePanel extends StatelessWidget {
|
||||
const SourceQuotePanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.walletsController,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final WalletsController walletsController;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SourceQuotePanelHeader(theme: theme, l10n: l10n),
|
||||
const SizedBox(height: 8),
|
||||
SourceWalletSelector(
|
||||
controller: controller,
|
||||
walletsController: walletsController,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 12),
|
||||
SourceQuoteSummary(controller: controller, spacing: 12),
|
||||
const SizedBox(height: 12),
|
||||
MultipleQuoteStatusCard(controller: controller),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadPanelActions extends StatelessWidget {
|
||||
const UploadPanelActions({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.l10n,
|
||||
required this.onSend,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final AppLocalizations l10n;
|
||||
final VoidCallback onSend;
|
||||
|
||||
static const double _buttonVerticalPadding = 12;
|
||||
static const double _buttonHorizontalPadding = 24;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasFile = controller.selectedFileName != null;
|
||||
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: !hasFile || controller.isBusy
|
||||
? null
|
||||
: () => controller.removeUploadedFile(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _buttonHorizontalPadding,
|
||||
vertical: _buttonVerticalPadding,
|
||||
),
|
||||
),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: controller.canSend ? onSend : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _buttonHorizontalPadding,
|
||||
vertical: _buttonVerticalPadding,
|
||||
),
|
||||
),
|
||||
child: Text(l10n.send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadDropZone extends StatelessWidget {
|
||||
const UploadDropZone({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
static const double _panelRadius = 12;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasFile = controller.selectedFileName != null;
|
||||
|
||||
return InkWell(
|
||||
onTap: controller.isBusy
|
||||
? null
|
||||
: () => controller.pickAndQuote(),
|
||||
borderRadius: BorderRadius.circular(_panelRadius),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
borderRadius: BorderRadius.circular(_panelRadius),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.upload_file,
|
||||
size: 34,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
hasFile ? controller.selectedFileName! : l10n.uploadCSV,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (!hasFile) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withValues(alpha: 0.12),
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.primary.withValues(alpha: 0.5),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.touch_app,
|
||||
size: 16,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
l10n.upload,
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
l10n.hintUpload,
|
||||
style: theme.textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (hasFile) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${l10n.payout}: ${controller.rows.length}',
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/widgets/dialogs/payment_status_dialog.dart';
|
||||
|
||||
|
||||
Future<void> handleUploadSend(
|
||||
BuildContext context,
|
||||
MultiplePayoutsController controller,
|
||||
) async {
|
||||
final outcome = await controller.sendAndStorePayments();
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
await showPaymentStatusDialog(
|
||||
context,
|
||||
isSuccess: outcome == MultiplePayoutSendOutcome.success,
|
||||
);
|
||||
|
||||
controller.removeUploadedFile();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class UploadQuoteProgress extends StatelessWidget {
|
||||
const UploadQuoteProgress({
|
||||
super.key,
|
||||
required this.isQuoting,
|
||||
required this.theme,
|
||||
});
|
||||
|
||||
final bool isQuoting;
|
||||
final ThemeData theme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!isQuoting) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 5,
|
||||
color: theme.colorScheme.primary,
|
||||
backgroundColor: theme.colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadPanelStatus extends StatelessWidget {
|
||||
const UploadPanelStatus({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller.sentCount <= 0 && controller.error == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (controller.sentCount > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${l10n.payout}: ${controller.sentCount}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (controller.error != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
controller.error.toString(),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/drop_zone.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/actions.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/helpers.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/status.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/progress.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadPanel extends StatelessWidget {
|
||||
const UploadPanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
UploadDropZone(controller: controller, theme: theme, l10n: l10n),
|
||||
UploadQuoteProgress(isQuoting: controller.isQuoting, theme: theme),
|
||||
const SizedBox(height: 12),
|
||||
UploadPanelActions(
|
||||
controller: controller,
|
||||
l10n: l10n,
|
||||
onSend: () => handleUploadSend(context, controller),
|
||||
),
|
||||
UploadPanelStatus(controller: controller, theme: theme, l10n: l10n),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/file/downloaded_file.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/form.dart';
|
||||
import 'package:pweb/utils/download.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class FileFormatSampleSection extends StatelessWidget {
|
||||
const FileFormatSampleSection({super.key});
|
||||
|
||||
static final List<MultiplePayoutRow> sampleRows = [
|
||||
MultiplePayoutRow(
|
||||
pan: "9022****11",
|
||||
firstName: "Alex",
|
||||
lastName: "Ivanov",
|
||||
expMonth: 12,
|
||||
expYear: 27,
|
||||
amount: "500",
|
||||
),
|
||||
MultiplePayoutRow(
|
||||
pan: "9022****12",
|
||||
firstName: "Maria",
|
||||
lastName: "Sokolova",
|
||||
expMonth: 7,
|
||||
expYear: 26,
|
||||
amount: "100",
|
||||
),
|
||||
MultiplePayoutRow(
|
||||
pan: "9022****13",
|
||||
firstName: "Dmitry",
|
||||
lastName: "Smirnov",
|
||||
expMonth: 3,
|
||||
expYear: 28,
|
||||
amount: "120",
|
||||
),
|
||||
];
|
||||
|
||||
static const String _sampleFileName = 'sample.csv';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
final titleStyle = theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
final linkStyle = theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.filter_list),
|
||||
const SizedBox(width: 5),
|
||||
Text(l10n.exampleTitle, style: titleStyle),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildDataTable(l10n),
|
||||
const SizedBox(height: 10),
|
||||
TextButton(
|
||||
onPressed: _downloadSampleCsv,
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
child: Text(l10n.downloadSampleCSV, style: linkStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataTable(AppLocalizations l10n) {
|
||||
return DataTable(
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
DataColumn(label: Text(l10n.cardNumberColumn)),
|
||||
DataColumn(label: Text(l10n.firstName)),
|
||||
DataColumn(label: Text(l10n.lastName)),
|
||||
DataColumn(label: Text(l10n.expiryDate)),
|
||||
DataColumn(label: Text(l10n.amount)),
|
||||
],
|
||||
rows: sampleRows.map((row) {
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(row.pan)),
|
||||
DataCell(Text(row.firstName)),
|
||||
DataCell(Text(row.lastName)),
|
||||
DataCell(
|
||||
Text('${row.expMonth.toString().padLeft(2, '0')}/${row.expYear}'),
|
||||
),
|
||||
DataCell(Text(row.amount)),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _downloadSampleCsv() async {
|
||||
final rows = <String>[
|
||||
'pan,first_name,last_name,exp_month,exp_year,amount',
|
||||
...sampleRows.map(
|
||||
(row) =>
|
||||
'${row.pan},${row.firstName},${row.lastName},${row.expMonth},${row.expYear},${row.amount}',
|
||||
),
|
||||
];
|
||||
final content = rows.join('\n');
|
||||
|
||||
await downloadFile(
|
||||
DownloadedFile(
|
||||
bytes: utf8.encode(content),
|
||||
filename: _sampleFileName,
|
||||
mimeType: 'text/csv;charset=utf-8',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadHistoryHeader extends StatelessWidget {
|
||||
const UploadHistoryHeader({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
static const double _smallBox = 5;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.history),
|
||||
const SizedBox(width: _smallBox),
|
||||
Text(l10n.uploadHistory, style: theme.textTheme.bodyLarge),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class StatusView {
|
||||
final String label;
|
||||
final Color color;
|
||||
|
||||
const StatusView(this.label, this.color);
|
||||
}
|
||||
|
||||
StatusView statusView(AppLocalizations l10n, String? raw) {
|
||||
final trimmed = (raw ?? '').trim();
|
||||
final upper = trimmed.toUpperCase();
|
||||
final normalized = upper.startsWith('PAYMENT_STATE_')
|
||||
? upper.substring('PAYMENT_STATE_'.length)
|
||||
: upper;
|
||||
|
||||
switch (normalized) {
|
||||
case 'SETTLED':
|
||||
return StatusView(l10n.paymentStatusPending, Colors.yellow);
|
||||
case 'SUCCESS':
|
||||
return StatusView(l10n.paymentStatusSuccessful, Colors.green);
|
||||
case 'FUNDS_RESERVED':
|
||||
return StatusView(l10n.paymentStatusReserved, Colors.blue);
|
||||
case 'ACCEPTED':
|
||||
return StatusView(l10n.paymentStatusProcessing, Colors.yellow);
|
||||
case 'SUBMITTED':
|
||||
return StatusView(l10n.paymentStatusProcessing, Colors.blue);
|
||||
case 'FAILED':
|
||||
return StatusView(l10n.paymentStatusFailed, Colors.red);
|
||||
case 'CANCELLED':
|
||||
return StatusView(l10n.paymentStatusCancelled, Colors.grey);
|
||||
case 'UNSPECIFIED':
|
||||
case '':
|
||||
default:
|
||||
return StatusView(l10n.paymentStatusPending, Colors.grey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/helpers.dart';
|
||||
|
||||
|
||||
class HistoryStatusBadge extends StatelessWidget {
|
||||
const HistoryStatusBadge({
|
||||
super.key,
|
||||
required this.statusView,
|
||||
});
|
||||
|
||||
final StatusView statusView;
|
||||
|
||||
static const double _radius = 6;
|
||||
static const double _statusBgOpacity = 0.12;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: statusView.color.withValues(alpha: _statusBgOpacity),
|
||||
borderRadius: BorderRadius.circular(_radius),
|
||||
),
|
||||
child: Text(
|
||||
statusView.label,
|
||||
style: TextStyle(
|
||||
color: statusView.color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:pshared/models/payment/payment.dart';
|
||||
|
||||
import 'package:pweb/controllers/upload_history_table.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/helpers.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/status_badge.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadHistoryTable extends StatelessWidget {
|
||||
const UploadHistoryTable({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.dateFormat,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final List<Payment> items;
|
||||
final DateFormat dateFormat;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
static const int _maxVisibleItems = 10;
|
||||
static const UploadHistoryTableController _controller =
|
||||
UploadHistoryTableController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final visibleItems = items.take(_maxVisibleItems).toList(growable: false);
|
||||
|
||||
return DataTable(
|
||||
columns: [
|
||||
DataColumn(label: Text(l10n.fileNameColumn)),
|
||||
DataColumn(label: Text(l10n.rowsColumn)),
|
||||
DataColumn(label: Text(l10n.dateColumn)),
|
||||
DataColumn(label: Text(l10n.amountColumn)),
|
||||
DataColumn(label: Text(l10n.statusColumn)),
|
||||
],
|
||||
rows: visibleItems.map((payment) {
|
||||
final metadata = payment.metadata;
|
||||
final status = statusView(l10n, payment.state);
|
||||
final fileName = metadata?['upload_filename'];
|
||||
final fileNameText =
|
||||
(fileName == null || fileName.isEmpty) ? '-' : fileName;
|
||||
final rows = metadata?['upload_rows'];
|
||||
final rowsText = (rows == null || rows.isEmpty) ? '-' : rows;
|
||||
final createdAt = payment.createdAt;
|
||||
final dateText = createdAt == null
|
||||
? '-'
|
||||
: dateFormat.format(createdAt.toLocal());
|
||||
final amountText = _controller.amountText(payment);
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(fileNameText)),
|
||||
DataCell(Text(rowsText)),
|
||||
DataCell(Text(dateText)),
|
||||
DataCell(Text(amountText)),
|
||||
DataCell(HistoryStatusBadge(statusView: status)),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/payments.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/header.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/table.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadHistorySection extends StatelessWidget {
|
||||
const UploadHistorySection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<PaymentsProvider>();
|
||||
final theme = Theme.of(context);
|
||||
final l10 = AppLocalizations.of(context)!;
|
||||
final dateFormat = DateFormat.yMMMd().add_Hm();
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (provider.error != null) {
|
||||
return Text(
|
||||
l10.notificationError(provider.error ?? l10.noErrorInformation),
|
||||
);
|
||||
}
|
||||
final items = List.of(provider.payments);
|
||||
items.sort((a, b) {
|
||||
final left = a.createdAt;
|
||||
final right = b.createdAt;
|
||||
if (left == null && right == null) return 0;
|
||||
if (left == null) return 1;
|
||||
if (right == null) return -1;
|
||||
return right.compareTo(left);
|
||||
});
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
UploadHistoryHeader(theme: theme, l10n: l10),
|
||||
const SizedBox(height: 8),
|
||||
if (items.isEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
l10.walletHistoryEmpty,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
UploadHistoryTable(
|
||||
items: items,
|
||||
dateFormat: dateFormat,
|
||||
l10n: l10,
|
||||
),
|
||||
//TODO redirect to Reports page
|
||||
// if (hasMore) ...[
|
||||
// const SizedBox(height: 8),
|
||||
// Align(
|
||||
// alignment: Alignment.centerLeft,
|
||||
// child: TextButton.icon(
|
||||
// onPressed: () => context.goNamed(PayoutRoutes.reports),
|
||||
// icon: const Icon(Icons.open_in_new, size: 16),
|
||||
// label: Text(l10.viewWholeHistory),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:pweb/models/multiple_payouts/csv_row.dart';
|
||||
|
||||
|
||||
const String sampleFileName = 'sample.csv';
|
||||
|
||||
final List<CsvPayoutRow> sampleRows = [
|
||||
CsvPayoutRow(
|
||||
pan: "9022****11",
|
||||
firstName: "Alex",
|
||||
lastName: "Ivanov",
|
||||
expMonth: 12,
|
||||
expYear: 27,
|
||||
amount: "500",
|
||||
),
|
||||
CsvPayoutRow(
|
||||
pan: "9022****12",
|
||||
firstName: "Maria",
|
||||
lastName: "Sokolova",
|
||||
expMonth: 7,
|
||||
expYear: 26,
|
||||
amount: "100",
|
||||
),
|
||||
CsvPayoutRow(
|
||||
pan: "9022****13",
|
||||
firstName: "Dmitry",
|
||||
lastName: "Smirnov",
|
||||
expMonth: 3,
|
||||
expYear: 28,
|
||||
amount: "120",
|
||||
),
|
||||
];
|
||||
|
||||
String buildSampleCsvContent() {
|
||||
final rows = <String>[
|
||||
'pan,first_name,last_name,exp_month,exp_year,amount',
|
||||
...sampleRows.map(
|
||||
(row) =>
|
||||
'${row.pan},${row.firstName},${row.lastName},${row.expMonth},${row.expYear},${row.amount}',
|
||||
),
|
||||
];
|
||||
return rows.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class FileFormatSampleDownloadButton extends StatelessWidget {
|
||||
const FileFormatSampleDownloadButton({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final linkStyle = theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
);
|
||||
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
child: Text(l10n.downloadSampleCSV, style: linkStyle),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class FileFormatSampleHeader extends StatelessWidget {
|
||||
const FileFormatSampleHeader({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final titleStyle = theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.filter_list),
|
||||
const SizedBox(width: 5),
|
||||
Text(l10n.exampleTitle, style: titleStyle),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/models/multiple_payouts/csv_row.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class FileFormatSampleTable extends StatelessWidget {
|
||||
const FileFormatSampleTable({
|
||||
super.key,
|
||||
required this.l10n,
|
||||
required this.rows,
|
||||
});
|
||||
|
||||
final AppLocalizations l10n;
|
||||
final List<CsvPayoutRow> rows;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DataTable(
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
DataColumn(label: Text(l10n.cardNumberColumn)),
|
||||
DataColumn(label: Text(l10n.firstName)),
|
||||
DataColumn(label: Text(l10n.lastName)),
|
||||
DataColumn(label: Text(l10n.expiryDate)),
|
||||
DataColumn(label: Text(l10n.amount)),
|
||||
],
|
||||
rows: rows.map((row) {
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(row.pan)),
|
||||
DataCell(Text(row.firstName)),
|
||||
DataCell(Text(row.lastName)),
|
||||
DataCell(
|
||||
Text('${row.expMonth.toString().padLeft(2, '0')}/${row.expYear}'),
|
||||
),
|
||||
DataCell(Text(row.amount)),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/file/downloaded_file.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/sample/data.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/sample/download_button.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/sample/header.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/sample/table.dart';
|
||||
import 'package:pweb/utils/download.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class FileFormatSampleSection extends StatelessWidget {
|
||||
const FileFormatSampleSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FileFormatSampleHeader(theme: theme, l10n: l10n),
|
||||
const SizedBox(height: 12),
|
||||
FileFormatSampleTable(l10n: l10n, rows: sampleRows),
|
||||
const SizedBox(height: 10),
|
||||
FileFormatSampleDownloadButton(
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
onPressed: _downloadSampleCsv,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _downloadSampleCsv() async {
|
||||
await downloadFile(
|
||||
DownloadedFile(
|
||||
bytes: utf8.encode(buildSampleCsvContent()),
|
||||
filename: sampleFileName,
|
||||
mimeType: 'text/csv;charset=utf-8',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
class UploadCsvHeader extends StatelessWidget {
|
||||
const UploadCsvHeader({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
static const double _iconTextSpacing = 5;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.upload),
|
||||
const SizedBox(width: _iconTextSpacing),
|
||||
Text(
|
||||
l10n.uploadCSV,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/widget.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/widget.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/panel_card.dart';
|
||||
|
||||
|
||||
class UploadCsvLayout extends StatelessWidget {
|
||||
const UploadCsvLayout({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.walletsController,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final WalletsController walletsController;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final useHorizontal = constraints.maxWidth >= 760;
|
||||
if (!useHorizontal) {
|
||||
return Column(
|
||||
children: [
|
||||
PanelCard(
|
||||
theme: theme,
|
||||
child: UploadPanel(
|
||||
controller: controller,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SourceQuotePanel(
|
||||
controller: controller,
|
||||
walletsController: walletsController,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: PanelCard(
|
||||
theme: theme,
|
||||
child: UploadPanel(
|
||||
controller: controller,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: SourceQuotePanel(
|
||||
controller: controller,
|
||||
walletsController: walletsController,
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class PanelCard extends StatelessWidget {
|
||||
const PanelCard({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/header.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/layout.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UploadCSVSection extends StatelessWidget {
|
||||
const UploadCSVSection({super.key});
|
||||
|
||||
static const double _verticalSpacing = 10;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = context.watch<MultiplePayoutsController>();
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UploadCsvHeader(theme: theme, l10n: l10n),
|
||||
const SizedBox(height: _verticalSpacing),
|
||||
UploadCsvLayout(
|
||||
controller: controller,
|
||||
walletsController: context.watch(),
|
||||
theme: theme,
|
||||
l10n: l10n,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SourceQuotePanel extends StatelessWidget {
|
||||
const SourceQuotePanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.walletsController,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final WalletsController walletsController;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final wallets = walletsController.wallets;
|
||||
final selectedWalletRef = walletsController.selectedWalletRef;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.sourceOfFunds,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (wallets.isEmpty)
|
||||
Text(l10n.noWalletsAvailable, style: theme.textTheme.bodySmall)
|
||||
else
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: selectedWalletRef,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.whereGetMoney,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
items: wallets
|
||||
.map(
|
||||
(wallet) => DropdownMenuItem<String>(
|
||||
value: wallet.id,
|
||||
child: Text(
|
||||
'${wallet.name} · ${amountToString(wallet.balance)} ${currencyCodeToString(wallet.currency)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
onChanged: controller.isBusy
|
||||
? null
|
||||
: (value) {
|
||||
if (value == null) return;
|
||||
walletsController.selectWalletByRef(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
controller.aggregateDebitAmount == null
|
||||
? l10n.quoteUnavailable
|
||||
: l10n.quoteActive,
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.sentAmount(_sentAmountLabel(controller)),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
l10n.recipientsWillReceive(
|
||||
_moneyLabel(controller.aggregateSettlementAmount),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
controller.aggregateFeePercent == null
|
||||
? l10n.fee(_moneyLabel(controller.aggregateFeeAmount))
|
||||
: '${l10n.fee(_moneyLabel(controller.aggregateFeeAmount))} (${controller.aggregateFeePercent!.toStringAsFixed(2)}%)',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _moneyLabel(Money? money) {
|
||||
if (money == null) return '-';
|
||||
return '${money.amount} ${money.currency}';
|
||||
}
|
||||
|
||||
String _sentAmountLabel(MultiplePayoutsController controller) {
|
||||
final requested = controller.requestedSentAmount;
|
||||
final sourceDebit = controller.aggregateDebitAmount;
|
||||
|
||||
if (requested == null && sourceDebit == null) return '-';
|
||||
if (requested == null) return _moneyLabel(sourceDebit);
|
||||
if (sourceDebit == null) return _moneyLabel(requested);
|
||||
|
||||
if (requested.currency.toUpperCase() ==
|
||||
sourceDebit.currency.toUpperCase()) {
|
||||
return _moneyLabel(sourceDebit);
|
||||
}
|
||||
return '${_moneyLabel(requested)} (${_moneyLabel(sourceDebit)})';
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/payments.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
//TODO this file is too long
|
||||
class UploadPanel extends StatelessWidget {
|
||||
const UploadPanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.theme,
|
||||
required this.l10n,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
final ThemeData theme;
|
||||
final AppLocalizations l10n;
|
||||
|
||||
static const double _buttonVerticalPadding = 12;
|
||||
static const double _buttonHorizontalPadding = 24;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(Icons.upload_file, size: 36, color: theme.colorScheme.primary),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: controller.isBusy
|
||||
? null
|
||||
: () => context
|
||||
.read<MultiplePayoutsController>()
|
||||
.pickAndQuote(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _buttonHorizontalPadding,
|
||||
vertical: _buttonVerticalPadding,
|
||||
),
|
||||
),
|
||||
child: Text(l10n.upload),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: controller.canSend
|
||||
? () => _handleSend(context)
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _buttonHorizontalPadding,
|
||||
vertical: _buttonVerticalPadding,
|
||||
),
|
||||
),
|
||||
child: Text(l10n.send),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.hintUpload,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (controller.isQuoting || controller.isSending) ...[
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
],
|
||||
if (controller.selectedFileName != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'${controller.selectedFileName} · ${controller.rows.length}',
|
||||
style: theme.textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: l10n.close,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: controller.isBusy
|
||||
? null
|
||||
: () => context
|
||||
.read<MultiplePayoutsController>()
|
||||
.removeUploadedFile(),
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (controller.sentCount > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${l10n.payout}: ${controller.sentCount}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (controller.error != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
controller.error.toString(),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleSend(BuildContext context) async {
|
||||
final paymentsProvider = context.read<PaymentsProvider>();
|
||||
final result = await controller.send();
|
||||
paymentsProvider.addPayments(result);
|
||||
await paymentsProvider.refresh();
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
final isSuccess = controller.error == null && result.isNotEmpty;
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(
|
||||
isSuccess
|
||||
? l10n.paymentStatusSuccessTitle
|
||||
: l10n.paymentStatusFailureTitle,
|
||||
),
|
||||
content: Text(
|
||||
isSuccess
|
||||
? l10n.paymentStatusSuccessMessage
|
||||
: l10n.paymentStatusFailureMessage,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(l10n.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
controller.removeUploadedFile();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/csv.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/history.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sample.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/widget.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/history/widget.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/sections/sample/widget.dart';
|
||||
|
||||
|
||||
class MultiplePayoutForm extends StatelessWidget {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/quote/status_type.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/quote_status/widgets/card.dart';
|
||||
import 'package:pweb/utils/quote_duration_format.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class MultipleQuoteStatusCard extends StatelessWidget {
|
||||
const MultipleQuoteStatusCard({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final MultiplePayoutsController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final isLoading = controller.quoteIsLoading;
|
||||
final statusType = controller.quoteStatusType;
|
||||
final timeLeft = controller.quoteTimeLeft;
|
||||
|
||||
String statusText;
|
||||
String? helperText;
|
||||
switch (statusType) {
|
||||
case QuoteStatusType.loading:
|
||||
statusText = loc.quoteUpdating;
|
||||
break;
|
||||
case QuoteStatusType.error:
|
||||
statusText = loc.quoteErrorGeneric;
|
||||
break;
|
||||
case QuoteStatusType.missing:
|
||||
statusText = loc.quoteUnavailable;
|
||||
break;
|
||||
case QuoteStatusType.expired:
|
||||
statusText = loc.quoteExpired;
|
||||
break;
|
||||
case QuoteStatusType.active:
|
||||
statusText = timeLeft == null
|
||||
? loc.quoteActive
|
||||
: loc.quoteExpiresIn(formatQuoteDuration(timeLeft));
|
||||
break;
|
||||
}
|
||||
|
||||
return QuoteStatusCard(
|
||||
statusType: statusType,
|
||||
statusText: statusText,
|
||||
helperText: helperText,
|
||||
isLoading: isLoading,
|
||||
canRefresh: false,
|
||||
showPrimaryRefresh: false,
|
||||
onRefresh: () {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,21 @@ import 'package:pshared/utils/currency.dart';
|
||||
class PaymentSummaryRow extends StatelessWidget {
|
||||
final String Function(String) labelFactory;
|
||||
final Asset? asset;
|
||||
final String? value;
|
||||
final TextStyle? style;
|
||||
|
||||
const PaymentSummaryRow({
|
||||
super.key,
|
||||
required this.labelFactory,
|
||||
required this.asset,
|
||||
this.value,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Text(
|
||||
labelFactory(asset == null ? 'N/A' : assetToString(asset!)),
|
||||
style: style,
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
final formatted = value ??
|
||||
(asset == null ? 'N/A' : assetToString(asset!));
|
||||
return Text(labelFactory(formatted), style: style);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,86 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/models/summary_values.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/fee.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/recipient_receives.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/sent_amount.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/total.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentSummary extends StatelessWidget {
|
||||
final double spacing;
|
||||
final PaymentSummaryValues? values;
|
||||
|
||||
const PaymentSummary({super.key, required this.spacing});
|
||||
const PaymentSummary({
|
||||
super.key,
|
||||
required this.spacing,
|
||||
this.values,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
PaymentSentAmountRow(currency: currencyStringToCode(context.read<WalletsController>().selectedWallet?.tokenSymbol ?? 'USDT')),
|
||||
const PaymentFeeRow(),
|
||||
const PaymentRecipientReceivesRow(),
|
||||
SizedBox(height: spacing),
|
||||
const PaymentTotalRow(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) {
|
||||
final resolvedValues = values;
|
||||
if (resolvedValues != null) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
PaymentSummaryRow(
|
||||
labelFactory: loc.sentAmount,
|
||||
asset: null,
|
||||
value: resolvedValues.sentAmount,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
PaymentSummaryRow(
|
||||
labelFactory: loc.fee,
|
||||
asset: null,
|
||||
value: resolvedValues.fee,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
PaymentSummaryRow(
|
||||
labelFactory: loc.recipientWillReceive,
|
||||
asset: null,
|
||||
value: resolvedValues.recipientReceives,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
SizedBox(height: spacing),
|
||||
PaymentSummaryRow(
|
||||
labelFactory: loc.total,
|
||||
asset: null,
|
||||
value: resolvedValues.total,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
PaymentSentAmountRow(
|
||||
currency: currencyStringToCode(
|
||||
context.read<WalletsController>().selectedWallet?.tokenSymbol ??
|
||||
'USDT',
|
||||
),
|
||||
),
|
||||
const PaymentFeeRow(),
|
||||
const PaymentRecipientReceivesRow(),
|
||||
SizedBox(height: spacing),
|
||||
const PaymentTotalRow(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user