From dd5b58b7bca8013a11cd068c1409144e16814c9e Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Fri, 30 Jan 2026 01:34:21 -0500 Subject: [PATCH] refactor: enhance invoice display logic and add empty state in BillingView --- .../src/presentation/pages/billing_page.dart | 34 ++- .../widgets/payment_method_card.dart | 226 +++++++++--------- 2 files changed, 150 insertions(+), 110 deletions(-) diff --git a/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/billing_page.dart b/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/billing_page.dart index 73decbfa..2a5774a7 100644 --- a/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/billing_page.dart +++ b/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/billing_page.dart @@ -10,7 +10,6 @@ import '../blocs/billing_state.dart'; import '../widgets/invoice_history_section.dart'; import '../widgets/payment_method_card.dart'; import '../widgets/pending_invoices_section.dart'; -import '../widgets/savings_card.dart'; import '../widgets/spending_breakdown_card.dart'; /// The entry point page for the client billing feature. @@ -199,7 +198,38 @@ class _BillingViewState extends State { ], const PaymentMethodCard(), const SpendingBreakdownCard(), - InvoiceHistorySection(invoices: state.invoiceHistory), + if (state.invoiceHistory.isEmpty) _buildEmptyState(context) + else InvoiceHistorySection(invoices: state.invoiceHistory), + ], + ), + ); + } + + Widget _buildEmptyState(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: UiConstants.space12), + Container( + padding: const EdgeInsets.all(UiConstants.space6), + decoration: BoxDecoration( + color: UiColors.bgPopup, + shape: BoxShape.circle, + border: Border.all(color: UiColors.border), + ), + child: const Icon( + UiIcons.file, + size: 48, + color: UiColors.textSecondary, + ), + ), + const SizedBox(height: UiConstants.space4), + Text( + 'No Invoices for the selected period', + style: UiTypography.body1m.textSecondary, + textAlign: TextAlign.center, + ), ], ), ); diff --git a/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/payment_method_card.dart b/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/payment_method_card.dart index 6c846212..6deda772 100644 --- a/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/payment_method_card.dart +++ b/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/payment_method_card.dart @@ -24,11 +24,13 @@ class _PaymentMethodCardState extends State { return null; } - final fdc.QueryResult result = - await dc.ExampleConnector.instance - .getAccountsByOwnerId(ownerId: businessId) - .execute(); + final fdc.QueryResult< + dc.GetAccountsByOwnerIdData, + dc.GetAccountsByOwnerIdVariables + > + result = await dc.ExampleConnector.instance + .getAccountsByOwnerId(ownerId: businessId) + .execute(); return result.data; } @@ -36,115 +38,123 @@ class _PaymentMethodCardState extends State { Widget build(BuildContext context) { return FutureBuilder( future: _accountsFuture, - builder: (BuildContext context, - AsyncSnapshot snapshot) { - final List accounts = - snapshot.data?.accounts ?? - []; - final dc.GetAccountsByOwnerIdAccounts? account = - accounts.isNotEmpty ? accounts.first : null; - final String bankLabel = - account?.bank.isNotEmpty == true ? account!.bank : '----'; - final String last4 = - account?.last4.isNotEmpty == true ? account!.last4 : '----'; - final bool isPrimary = account?.isPrimary ?? false; - final String expiryLabel = _formatExpiry(account?.expiryTime); + builder: + ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + final List accounts = + snapshot.data?.accounts ?? []; + final dc.GetAccountsByOwnerIdAccounts? account = accounts.isNotEmpty + ? accounts.first + : null; - return Container( - padding: const EdgeInsets.all(UiConstants.space4), - decoration: BoxDecoration( - color: UiColors.white, - borderRadius: UiConstants.radiusLg, - border: Border.all(color: UiColors.border), - boxShadow: [ - BoxShadow( - color: UiColors.black.withValues(alpha: 0.04), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - t.client_billing.payment_method, - style: UiTypography.title2b.textPrimary, - ), - const SizedBox.shrink(), - ], - ), - if (account != null) ...[ - const SizedBox(height: UiConstants.space3), - Container( - padding: const EdgeInsets.all(UiConstants.space3), - decoration: BoxDecoration( - color: UiColors.bgSecondary, - borderRadius: UiConstants.radiusMd, + if (account == null) { + return const SizedBox.shrink(); + } + + final String bankLabel = account.bank.isNotEmpty == true + ? account.bank + : '----'; + final String last4 = account.last4.isNotEmpty == true + ? account.last4 + : '----'; + final bool isPrimary = account.isPrimary ?? false; + final String expiryLabel = _formatExpiry(account.expiryTime); + + return Container( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + boxShadow: [ + BoxShadow( + color: UiColors.black.withValues(alpha: 0.04), + blurRadius: 8, + offset: const Offset(0, 2), ), - child: Row( + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - width: 40, - height: 28, - decoration: BoxDecoration( - color: UiColors.primary, - borderRadius: BorderRadius.circular(4), - ), - child: Center( - child: Text( - bankLabel, - style: const TextStyle( - color: UiColors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), + Text( + t.client_billing.payment_method, + style: UiTypography.title2b.textPrimary, ), - const SizedBox(width: UiConstants.space3), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '•••• $last4', - style: UiTypography.body2b.textPrimary, - ), - Text( - t.client_billing.expires(date: expiryLabel), - style: UiTypography.footnote2r.textSecondary, - ), - ], - ), - ), - if (isPrimary) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: UiColors.accent, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - t.client_billing.default_badge, - style: UiTypography.titleUppercase4b.textPrimary, - ), - ), + const SizedBox.shrink(), ], ), - ), - ], - ], - ), - ); - }, + const SizedBox(height: UiConstants.space3), + Container( + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: UiColors.bgSecondary, + borderRadius: UiConstants.radiusMd, + ), + child: Row( + children: [ + Container( + width: 40, + height: 28, + decoration: BoxDecoration( + color: UiColors.primary, + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: Text( + bankLabel, + style: const TextStyle( + color: UiColors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const SizedBox(width: UiConstants.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '•••• $last4', + style: UiTypography.body2b.textPrimary, + ), + Text( + t.client_billing.expires(date: expiryLabel), + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + ), + if (isPrimary) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: UiColors.accent, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + t.client_billing.default_badge, + style: UiTypography.titleUppercase4b.textPrimary, + ), + ), + ], + ), + ), + ], + ), + ); + }, ); }