diff --git a/apps/mobile/packages/features/client/billing/lib/src/billing_module.dart b/apps/mobile/packages/features/client/billing/lib/src/billing_module.dart index 3a594e44..e1315aa8 100644 --- a/apps/mobile/packages/features/client/billing/lib/src/billing_module.dart +++ b/apps/mobile/packages/features/client/billing/lib/src/billing_module.dart @@ -22,8 +22,6 @@ import 'presentation/pages/pending_invoices_page.dart'; class BillingModule extends Module { @override void binds(Injector i) { - - // Repositories i.addSingleton(BillingRepositoryImpl.new); @@ -54,9 +52,22 @@ class BillingModule extends Module { @override void routes(RouteManager r) { - r.child(ClientPaths.childRoute(ClientPaths.billing, ClientPaths.billing), child: (_) => const BillingPage()); - r.child('/completion-review', child: (_) => ShiftCompletionReviewPage(invoice: r.args.data as BillingInvoice?)); - r.child('/invoice-ready', child: (_) => const InvoiceReadyPage()); - r.child('/awaiting-approval', child: (_) => const PendingInvoicesPage()); + r.child( + ClientPaths.childRoute(ClientPaths.billing, ClientPaths.billing), + child: (_) => const BillingPage(), + ); + r.child( + ClientPaths.childRoute(ClientPaths.billing, ClientPaths.completionReview), + child: (_) => + ShiftCompletionReviewPage(invoice: r.args.data as BillingInvoice?), + ); + r.child( + ClientPaths.childRoute(ClientPaths.billing, ClientPaths.invoiceReady), + child: (_) => const InvoiceReadyPage(), + ); + r.child( + ClientPaths.childRoute(ClientPaths.billing, ClientPaths.awaitingApproval), + child: (_) => const PendingInvoicesPage(), + ); } } diff --git a/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/pending_invoices_page.dart b/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/pending_invoices_page.dart index ce5b40ea..246e2d08 100644 --- a/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/pending_invoices_page.dart +++ b/apps/mobile/packages/features/client/billing/lib/src/presentation/pages/pending_invoices_page.dart @@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:krow_core/core.dart'; + import '../blocs/billing_bloc.dart'; import '../blocs/billing_state.dart'; import '../widgets/pending_invoices_section.dart'; @@ -13,112 +13,77 @@ class PendingInvoicesPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF8FAFC), - body: BlocBuilder( - bloc: Modular.get(), - builder: (context, state) { - return CustomScrollView( - slivers: [ - _buildHeader(context, state.pendingInvoices.length), - if (state.status == BillingStatus.loading) - const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), - ) - else if (state.pendingInvoices.isEmpty) - _buildEmptyState() - else - SliverPadding( - padding: const EdgeInsets.fromLTRB( - UiConstants.space5, - UiConstants.space5, - UiConstants.space5, - 100, // Bottom padding for scroll clearance - ), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return Padding( - padding: const EdgeInsets.only(bottom: UiConstants.space4), - child: PendingInvoiceCard(invoice: state.pendingInvoices[index]), - ); - }, - childCount: state.pendingInvoices.length, - ), - ), - ), - ], - ); - }, - ), + return BlocBuilder( + bloc: Modular.get(), + builder: (BuildContext context, BillingState state) { + return Scaffold( + appBar: UiAppBar( + title: t.client_billing.awaiting_approval, + showBackButton: true, + ), + body: _buildBody(context, state), + ); + }, ); } - Widget _buildHeader(BuildContext context, int count) { - return SliverAppBar( - pinned: true, - expandedHeight: 140.0, - backgroundColor: UiColors.primary, - elevation: 0, - leadingWidth: 72, - leading: Center( - child: UiIconButton( - icon: UiIcons.arrowLeft, - backgroundColor: UiColors.white.withOpacity(0.15), - iconColor: UiColors.white, - useBlur: true, - size: 40, - onTap: () => Navigator.of(context).pop(), - ), - ), - flexibleSpace: FlexibleSpaceBar( - centerTitle: true, - title: Text( - t.client_billing.awaiting_approval, - style: UiTypography.headline4b.copyWith(color: UiColors.white), - ), - background: Center( - child: Padding( - padding: const EdgeInsets.only(top: 40), - child: Opacity( - opacity: 0.1, - child: Icon(UiIcons.clock, size: 100, color: UiColors.white), - ), - ), - ), + Widget _buildBody(BuildContext context, BillingState state) { + if (state.status == BillingStatus.loading) { + return const Center(child: CircularProgressIndicator()); + } + + if (state.pendingInvoices.isEmpty) { + return _buildEmptyState(); + } + + return ListView.builder( + padding: const EdgeInsets.fromLTRB( + UiConstants.space5, + UiConstants.space5, + UiConstants.space5, + 100, // Bottom padding for scroll clearance ), + itemCount: state.pendingInvoices.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space4), + child: PendingInvoiceCard(invoice: state.pendingInvoices[index]), + ); + }, ); } Widget _buildEmptyState() { - return SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(UiConstants.space6), - decoration: BoxDecoration( - color: UiColors.bgPopup, - shape: BoxShape.circle, - ), - child: const Icon(UiIcons.checkCircle, size: 48, color: UiColors.success), + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(UiConstants.space6), + decoration: const BoxDecoration( + color: UiColors.bgPopup, + shape: BoxShape.circle, ), - const SizedBox(height: UiConstants.space4), - Text( - t.client_billing.all_caught_up, - style: UiTypography.body1m.textPrimary, + child: const Icon( + UiIcons.checkCircle, + size: 48, + color: UiColors.success, ), - Text( - t.client_billing.no_pending_invoices, - style: UiTypography.body2r.textSecondary, - ), - ], - ), + ), + const SizedBox(height: UiConstants.space4), + Text( + t.client_billing.all_caught_up, + style: UiTypography.body1m.textPrimary, + ), + Text( + t.client_billing.no_pending_invoices, + style: UiTypography.body2r.textSecondary, + ), + ], ), ); } } -// We need to export the card widget from the section file if we want to reuse it, +// We need to export the card widget from the section file if we want to reuse it, // or move it to its own file. I'll move it to a shared file or just make it public in the section file. diff --git a/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/pending_invoices_section.dart b/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/pending_invoices_section.dart index 767d61af..2905f6b8 100644 --- a/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/pending_invoices_section.dart +++ b/apps/mobile/packages/features/client/billing/lib/src/presentation/widgets/pending_invoices_section.dart @@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; + import '../models/billing_invoice_model.dart'; /// Section showing a banner for invoices awaiting approval. @@ -56,7 +57,10 @@ class PendingInvoicesSection extends StatelessWidget { ), const SizedBox(width: UiConstants.space2), Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), decoration: const BoxDecoration( color: UiColors.accent, shape: BoxShape.circle, @@ -104,14 +108,7 @@ class PendingInvoiceCard extends StatelessWidget { decoration: BoxDecoration( color: UiColors.white, borderRadius: UiConstants.radiusLg, - border: Border.all(color: UiColors.border.withOpacity(0.5)), - boxShadow: [ - BoxShadow( - color: UiColors.black.withValues(alpha: 0.04), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], + border: Border.all(color: UiColors.border, width: 0.5), ), child: Padding( padding: const EdgeInsets.all(UiConstants.space5), @@ -187,7 +184,11 @@ class PendingInvoiceCard extends StatelessWidget { t.client_billing.stats.total, ), ), - Container(width: 1, height: 32, color: UiColors.border.withOpacity(0.3)), + Container( + width: 1, + height: 32, + color: UiColors.border.withOpacity(0.3), + ), Expanded( child: _buildStatItem( UiIcons.users, @@ -195,11 +196,15 @@ class PendingInvoiceCard extends StatelessWidget { t.client_billing.stats.workers, ), ), - Container(width: 1, height: 32, color: UiColors.border.withOpacity(0.3)), + Container( + width: 1, + height: 32, + color: UiColors.border.withOpacity(0.3), + ), Expanded( child: _buildStatItem( UiIcons.clock, - '${invoice.totalHours.toStringAsFixed(1)}', + invoice.totalHours.toStringAsFixed(1), t.client_billing.stats.hrs, ), ), @@ -210,14 +215,12 @@ class PendingInvoiceCard extends StatelessWidget { const SizedBox(height: UiConstants.space5), SizedBox( width: double.infinity, - child: UiButton.primary( + child: UiButton.secondary( text: t.client_billing.review_and_approve, leadingIcon: UiIcons.checkCircle, - onPressed: () => Modular.to.toCompletionReview(arguments: invoice), + onPressed: () => + Modular.to.toCompletionReview(arguments: invoice), size: UiButtonSize.large, - style: ElevatedButton.styleFrom( - textStyle: UiTypography.body1b.copyWith(fontSize: 16), - ), ), ), ], diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart index 0b7eb44b..74c38c2e 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart @@ -86,7 +86,7 @@ class _ActionCard extends StatelessWidget { decoration: BoxDecoration( color: color, borderRadius: UiConstants.radiusLg, - border: Border.all(color: borderColor), + border: Border.all(color: borderColor, width: 0.5), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart index a85eca69..ac6fe59b 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart'; /// A dashboard widget that displays today's coverage status. class CoverageDashboard extends StatelessWidget { - /// Creates a [CoverageDashboard]. const CoverageDashboard({ super.key, required this.shifts, required this.applications, }); + /// The list of shifts for today. final List shifts; @@ -23,7 +23,8 @@ class CoverageDashboard extends StatelessWidget { double todayCost = 0; for (final dynamic s in shifts) { - final int needed = (s as Map)['workersNeeded'] as int? ?? 0; + final int needed = + (s as Map)['workersNeeded'] as int? ?? 0; final int confirmed = s['filled'] as int? ?? 0; final double rate = s['hourlyRate'] as double? ?? 0.0; final double hours = s['hours'] as double? ?? 0.0; @@ -39,7 +40,9 @@ class CoverageDashboard extends StatelessWidget { final int unfilledPositions = totalNeeded - totalConfirmed; final int checkedInCount = applications - .where((dynamic a) => (a as Map)['checkInTime'] != null) + .where( + (dynamic a) => (a as Map)['checkInTime'] != null, + ) .length; final int lateWorkersCount = applications .where((dynamic a) => (a as Map)['status'] == 'LATE') @@ -58,7 +61,7 @@ class CoverageDashboard extends StatelessWidget { decoration: BoxDecoration( color: UiColors.white, borderRadius: UiConstants.radiusLg, - border: Border.all(color: UiColors.border), + border: Border.all(color: UiColors.border, width: 0.5), boxShadow: [ BoxShadow( color: UiColors.black.withValues(alpha: 0.02), @@ -145,7 +148,6 @@ class CoverageDashboard extends StatelessWidget { } class _StatusCard extends StatelessWidget { - const _StatusCard({ required this.label, required this.value, diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_widget.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_widget.dart index d90c7f6d..63f9c95e 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_widget.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/coverage_widget.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; /// A widget that displays the daily coverage metrics. class CoverageWidget extends StatelessWidget { - /// Creates a [CoverageWidget]. const CoverageWidget({ super.key, @@ -13,6 +12,7 @@ class CoverageWidget extends StatelessWidget { this.coveragePercent = 0, this.subtitle, }); + /// The total number of shifts needed. final int totalNeeded; @@ -65,8 +65,10 @@ class CoverageWidget extends StatelessWidget { color: backgroundColor, borderRadius: UiConstants.radiusLg, ), - child: Text( - t.client_home.dashboard.percent_covered(percent: coveragePercent), + child: Text( + t.client_home.dashboard.percent_covered( + percent: coveragePercent, + ), style: UiTypography.footnote2b.copyWith(color: textColor), ), ), @@ -115,7 +117,6 @@ class CoverageWidget extends StatelessWidget { } class _MetricCard extends StatelessWidget { - const _MetricCard({ required this.icon, required this.iconColor, @@ -136,7 +137,7 @@ class _MetricCard extends StatelessWidget { decoration: BoxDecoration( color: UiColors.cardViewBackground, borderRadius: UiConstants.radiusLg, - border: Border.all(color: UiColors.border), + border: Border.all(color: UiColors.border, width: 0.5), boxShadow: [ BoxShadow( color: UiColors.black.withValues(alpha: 0.02),