From 8d39ebbcedef71da8bd637f0d55a4981d45ff85f Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Mar 2026 17:37:38 -0400 Subject: [PATCH] feat(benefit_history): refactor benefit history page with new components and improved loading states --- .../pages/benefit_history_page.dart | 71 +++++-------------- .../benefit_history_empty_state.dart | 23 ++++++ .../benefit_history_list.dart | 53 ++++++++++++++ .../benefit_history_skeleton.dart | 33 +++++++++ .../widgets/benefit_history_page/index.dart | 3 + .../benefit_history_preview.dart | 32 ++++----- 6 files changed, 143 insertions(+), 72 deletions(-) create mode 100644 apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_empty_state.dart create mode 100644 apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_list.dart create mode 100644 apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_skeleton.dart create mode 100644 apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/index.dart diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefit_history_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefit_history_page.dart index de166a31..57698f45 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefit_history_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefit_history_page.dart @@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_domain/krow_domain.dart'; import 'package:staff_home/src/presentation/blocs/benefits_overview/benefits_overview_cubit.dart'; -import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_history_row.dart'; +import 'package:staff_home/src/presentation/widgets/benefit_history_page/index.dart'; /// Full-screen page displaying paginated benefit history. /// @@ -79,43 +79,29 @@ class _BenefitHistoryPageState extends State { final bool isLoaded = state.loadedHistoryIds.contains(widget.benefitId); final List history = - state.historyByBenefitId[widget.benefitId] ?? []; - final bool hasMore = state.hasMoreHistory[widget.benefitId] ?? true; - + state.historyByBenefitId[widget.benefitId] ?? + []; + final bool hasMore = + state.hasMoreHistory[widget.benefitId] ?? true; + // Initial loading state if (isLoading && !isLoaded) { - return _buildLoadingSkeleton(); + return const BenefitHistorySkeleton(); } - + // Empty state if (isLoaded && history.isEmpty) { - return UiEmptyState( - icon: UiIcons.clock, - title: i18n.no_history as String, - description: '', + return BenefitHistoryEmptyState( + message: i18n.no_history as String, ); } - + // Loaded list with infinite scroll - return ListView.builder( - controller: _scrollController, - padding: const EdgeInsets.symmetric( - horizontal: UiConstants.space4, - vertical: UiConstants.space4, - ), - itemCount: history.length + (hasMore ? 1 : 0), - itemBuilder: (BuildContext context, int index) { - if (index >= history.length) { - // Bottom loading indicator - return isLoading - ? const Padding( - padding: EdgeInsets.all(UiConstants.space4), - child: Center(child: CircularProgressIndicator()), - ) - : const SizedBox.shrink(); - } - return BenefitHistoryRow(history: history[index]); - }, + return BenefitHistoryList( + history: history, + hasMore: hasMore, + isLoading: isLoading, + scrollController: _scrollController, ); }, ), @@ -134,29 +120,4 @@ class _BenefitHistoryPageState extends State { cubit.loadMoreBenefitHistory(widget.benefitId); } } - - /// Builds a shimmer skeleton for the initial loading state. - Widget _buildLoadingSkeleton() { - return UiShimmer( - child: Padding( - padding: const EdgeInsets.all(UiConstants.space4), - child: Column( - children: [ - for (int i = 0; i < 8; i++) - Padding( - padding: - const EdgeInsets.symmetric(vertical: UiConstants.space2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - UiShimmerLine(width: 100, height: 14), - UiShimmerLine(width: 80, height: 14), - ], - ), - ), - ], - ), - ), - ); - } } diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_empty_state.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_empty_state.dart new file mode 100644 index 00000000..bcd9ccac --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_empty_state.dart @@ -0,0 +1,23 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// Empty state shown when a benefit has no history entries. +class BenefitHistoryEmptyState extends StatelessWidget { + /// Creates a [BenefitHistoryEmptyState]. + const BenefitHistoryEmptyState({ + required this.message, + super.key, + }); + + /// The localized message displayed as the empty-state title. + final String message; + + @override + Widget build(BuildContext context) { + return UiEmptyState( + icon: UiIcons.clock, + title: message, + description: '', + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_list.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_list.dart new file mode 100644 index 00000000..47e70470 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_list.dart @@ -0,0 +1,53 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:krow_domain/krow_domain.dart'; +import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_history_row.dart'; + +/// Scrollable list of [BenefitHistoryRow] items with a bottom loading +/// indicator for infinite-scroll pagination. +class BenefitHistoryList extends StatelessWidget { + /// Creates a [BenefitHistoryList]. + const BenefitHistoryList({ + required this.history, + required this.hasMore, + required this.isLoading, + required this.scrollController, + super.key, + }); + + /// The benefit history entries to display. + final List history; + + /// Whether additional pages are available to fetch. + final bool hasMore; + + /// Whether a page load is currently in progress. + final bool isLoading; + + /// Controller shared with the parent for infinite-scroll detection. + final ScrollController scrollController; + + @override + Widget build(BuildContext context) { + return ListView.builder( + controller: scrollController, + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space4, + ), + itemCount: history.length + (hasMore ? 1 : 0), + itemBuilder: (BuildContext context, int index) { + if (index >= history.length) { + // Bottom loading indicator + return isLoading + ? const Padding( + padding: EdgeInsets.all(UiConstants.space4), + child: Center(child: CircularProgressIndicator()), + ) + : const SizedBox.shrink(); + } + return BenefitHistoryRow(history: history[index]); + }, + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_skeleton.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_skeleton.dart new file mode 100644 index 00000000..6b3ec769 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/benefit_history_skeleton.dart @@ -0,0 +1,33 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// Shimmer skeleton shown while the initial benefit history page loads. +class BenefitHistorySkeleton extends StatelessWidget { + /// Creates a [BenefitHistorySkeleton]. + const BenefitHistorySkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return UiShimmer( + child: Padding( + padding: const EdgeInsets.all(UiConstants.space4), + child: Column( + children: [ + for (int i = 0; i < 8; i++) + Padding( + padding: + const EdgeInsets.symmetric(vertical: UiConstants.space2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + UiShimmerLine(width: 100, height: 14), + UiShimmerLine(width: 80, height: 14), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/index.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/index.dart new file mode 100644 index 00000000..ebf787a8 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefit_history_page/index.dart @@ -0,0 +1,3 @@ +export 'benefit_history_empty_state.dart'; +export 'benefit_history_list.dart'; +export 'benefit_history_skeleton.dart'; diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefits_overview/benefit_history_preview.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefits_overview/benefit_history_preview.dart index 00392ed0..449b4bf8 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefits_overview/benefit_history_preview.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/benefits_overview/benefit_history_preview.dart @@ -41,25 +41,24 @@ class _BenefitHistoryPreviewState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Divider(height: 1, color: UiColors.border), InkWell( onTap: _toggleExpanded, child: Padding( - padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - i18n.history_header as String, - style: UiTypography.footnote2b.textSecondary, - ), - Icon( - _isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown, - size: UiConstants.iconSm, - color: UiColors.iconSecondary, - ), - ], - ), + padding: const EdgeInsets.only(top: UiConstants.space4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + i18n.history_header as String, + style: UiTypography.footnote2b.textSecondary, + ), + Icon( + _isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown, + size: UiConstants.iconSm, + color: UiColors.iconSecondary, + ), + ], + ), ), ), AnimatedSize( @@ -136,7 +135,6 @@ class _BenefitHistoryPreviewState extends State { ), ), ), - const SizedBox(height: UiConstants.space1), ], ); },