From 5865e3e59657beee28d3899f60efd52392f72848 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sat, 21 Feb 2026 21:22:53 -0500 Subject: [PATCH] refactor: modularize view orders page by extracting list, empty, and error states into dedicated widgets. --- .../presentation/pages/view_orders_page.dart | 177 ++---------------- .../widgets/view_orders_empty_state.dart | 54 ++++++ .../widgets/view_orders_error_state.dart | 45 +++++ .../widgets/view_orders_list.dart | 66 +++++++ .../view_orders_list_section_header.dart | 54 ++++++ 5 files changed, 239 insertions(+), 157 deletions(-) create mode 100644 apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_empty_state.dart create mode 100644 apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_error_state.dart create mode 100644 apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list.dart create mode 100644 apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list_section_header.dart diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/pages/view_orders_page.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/pages/view_orders_page.dart index 5a1ac589..6c0a8923 100644 --- a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/pages/view_orders_page.dart +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/pages/view_orders_page.dart @@ -2,15 +2,15 @@ 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:intl/intl.dart'; import 'package:core_localization/core_localization.dart'; -import 'package:krow_core/core.dart'; import '../blocs/view_orders_cubit.dart'; import '../blocs/view_orders_state.dart'; import 'package:krow_domain/krow_domain.dart'; -import '../widgets/view_order_card.dart'; import '../widgets/view_orders_header.dart'; +import '../widgets/view_orders_empty_state.dart'; +import '../widgets/view_orders_error_state.dart'; +import '../widgets/view_orders_list.dart'; /// The main page for viewing client orders. /// @@ -38,7 +38,7 @@ class ViewOrdersPage extends StatelessWidget { class ViewOrdersView extends StatefulWidget { /// Creates a [ViewOrdersView]. const ViewOrdersView({super.key, this.initialDate}); - + /// The initial date to display orders for. final DateTime? initialDate; @@ -69,7 +69,8 @@ class _ViewOrdersViewState extends State { @override void didUpdateWidget(ViewOrdersView oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.initialDate != oldWidget.initialDate && widget.initialDate != null) { + if (widget.initialDate != oldWidget.initialDate && + widget.initialDate != null) { _cubit?.jumpToDate(widget.initialDate!); } } @@ -91,94 +92,29 @@ class _ViewOrdersViewState extends State { final List calendarDays = state.calendarDays; final List filteredOrders = state.filteredOrders; - // Header Colors logic from prototype - String sectionTitle = ''; - Color dotColor = UiColors.transparent; - - if (state.filterTab == 'all') { - sectionTitle = t.client_view_orders.tabs.up_next; - dotColor = UiColors.primary; - } else if (state.filterTab == 'active') { - sectionTitle = t.client_view_orders.tabs.active; - dotColor = UiColors.textWarning; - } else if (state.filterTab == 'completed') { - sectionTitle = t.client_view_orders.tabs.completed; - dotColor = - UiColors.primary; // Reverting to primary blue for consistency - } - return Scaffold( body: SafeArea( child: Column( children: [ // Header + Filter + Calendar (Sticky behavior) - ViewOrdersHeader( - state: state, - calendarDays: calendarDays, - ), - + ViewOrdersHeader(state: state, calendarDays: calendarDays), + // Content List Expanded( child: state.status == ViewOrdersStatus.failure - ? _buildErrorState(context: context, state: state) + ? ViewOrdersErrorState( + errorMessage: state.errorMessage, + selectedDate: state.selectedDate, + onRetry: () => BlocProvider.of( + context, + ).jumpToDate(state.selectedDate ?? DateTime.now()), + ) : filteredOrders.isEmpty - ? _buildEmptyState(context: context, state: state) - : ListView( - padding: const EdgeInsets.fromLTRB( - UiConstants.space5, - UiConstants.space4, - UiConstants.space5, - 100, - ), - children: [ - if (filteredOrders.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - bottom: UiConstants.space3, - ), - child: Row( - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration( - color: dotColor, - shape: BoxShape.circle, - ), - ), - const SizedBox( - width: UiConstants.space2, - ), - Text( - sectionTitle.toUpperCase(), - style: UiTypography.titleUppercase2m - .copyWith( - color: UiColors.textPrimary, - ), - ), - const SizedBox( - width: UiConstants.space1, - ), - Text( - '(${filteredOrders.length})', - style: UiTypography.footnote1r - .copyWith( - color: UiColors.textSecondary, - ), - ), - ], - ), - ), - ...filteredOrders.map( - (OrderItem order) => Padding( - padding: const EdgeInsets.only( - bottom: UiConstants.space3, - ), - child: ViewOrderCard(order: order), - ), - ), - ], - ), + ? ViewOrdersEmptyState(selectedDate: state.selectedDate) + : ViewOrdersList( + orders: filteredOrders, + filterTab: state.filterTab, + ), ), ], ), @@ -187,77 +123,4 @@ class _ViewOrdersViewState extends State { }, ); } - - /// Builds the empty state view. - Widget _buildEmptyState({ - required BuildContext context, - required ViewOrdersState state, - }) { - final String dateStr = state.selectedDate != null - ? _formatDateHeader(state.selectedDate!) - : 'this date'; - - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(UiIcons.calendar, size: 48, color: UiColors.iconInactive), - const SizedBox(height: UiConstants.space3), - Text( - t.client_view_orders.no_orders(date: dateStr), - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), - ), - const SizedBox(height: UiConstants.space4), - UiButton.primary( - text: t.client_view_orders.post_order, - leadingIcon: UiIcons.add, - onPressed: () => Modular.to.toCreateOrder(), - ), - ], - ), - ); - } - - static String _formatDateHeader(DateTime date) { - final DateTime now = DateTime.now(); - final DateTime today = DateTime(now.year, now.month, now.day); - final DateTime tomorrow = today.add(const Duration(days: 1)); - final DateTime checkDate = DateTime(date.year, date.month, date.day); - - if (checkDate == today) return 'Today'; - if (checkDate == tomorrow) return 'Tomorrow'; - return DateFormat('EEE, MMM d').format(date); - } - - Widget _buildErrorState({ - required BuildContext context, - required ViewOrdersState state, - }) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - UiIcons.error, - size: 48, - color: UiColors.error, - ), - const SizedBox(height: UiConstants.space4), - Text( - state.errorMessage != null - ? translateErrorKey(state.errorMessage!) - : 'An error occurred', - style: UiTypography.body1m.textError, - textAlign: TextAlign.center, - ), - const SizedBox(height: UiConstants.space4), - UiButton.secondary( - text: 'Retry', - onPressed: () => BlocProvider.of(context) - .jumpToDate(state.selectedDate ?? DateTime.now()), - ), - ], - ), - ); - } } diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_empty_state.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_empty_state.dart new file mode 100644 index 00000000..24362270 --- /dev/null +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_empty_state.dart @@ -0,0 +1,54 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:intl/intl.dart'; + +import 'package:krow_core/core.dart'; + +/// A widget that displays an empty state when no orders are found for a specific date. +class ViewOrdersEmptyState extends StatelessWidget { + /// Creates a [ViewOrdersEmptyState]. + const ViewOrdersEmptyState({super.key, required this.selectedDate}); + + /// The currently selected date to display in the empty state message. + final DateTime? selectedDate; + + @override + Widget build(BuildContext context) { + final String dateStr = selectedDate != null + ? _formatDateHeader(selectedDate!) + : 'this date'; + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(UiIcons.calendar, size: 48, color: UiColors.iconInactive), + const SizedBox(height: UiConstants.space3), + Text( + t.client_view_orders.no_orders(date: dateStr), + style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + ), + const SizedBox(height: UiConstants.space4), + UiButton.primary( + text: t.client_view_orders.post_order, + leadingIcon: UiIcons.add, + onPressed: () => Modular.to.toCreateOrder(), + ), + ], + ), + ); + } + + static String _formatDateHeader(DateTime date) { + final DateTime now = DateTime.now(); + final DateTime today = DateTime(now.year, now.month, now.day); + final DateTime tomorrow = today.add(const Duration(days: 1)); + final DateTime checkDate = DateTime(date.year, date.month, date.day); + + if (checkDate == today) return 'Today'; + if (checkDate == tomorrow) return 'Tomorrow'; + return DateFormat('EEE, MMM d').format(date); + } +} diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_error_state.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_error_state.dart new file mode 100644 index 00000000..2ff0af22 --- /dev/null +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_error_state.dart @@ -0,0 +1,45 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays an error state when orders fail to load. +class ViewOrdersErrorState extends StatelessWidget { + /// Creates a [ViewOrdersErrorState]. + const ViewOrdersErrorState({ + super.key, + required this.errorMessage, + required this.selectedDate, + required this.onRetry, + }); + + /// The error message to display. + final String? errorMessage; + + /// The selected date to retry loading for. + final DateTime? selectedDate; + + /// Callback to trigger a retry. + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(UiIcons.error, size: 48, color: UiColors.error), + const SizedBox(height: UiConstants.space4), + Text( + errorMessage != null + ? translateErrorKey(errorMessage!) + : 'An error occurred', + style: UiTypography.body1m.textError, + textAlign: TextAlign.center, + ), + const SizedBox(height: UiConstants.space4), + UiButton.secondary(text: 'Retry', onPressed: onRetry), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list.dart new file mode 100644 index 00000000..a4a5974b --- /dev/null +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list.dart @@ -0,0 +1,66 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:krow_domain/krow_domain.dart'; + +import 'view_order_card.dart'; +import 'view_orders_list_section_header.dart'; +import 'package:core_localization/core_localization.dart'; + +/// A widget that displays the list of filtered orders. +class ViewOrdersList extends StatelessWidget { + /// Creates a [ViewOrdersList]. + const ViewOrdersList({ + super.key, + required this.orders, + required this.filterTab, + }); + + /// The list of orders to display. + final List orders; + + /// The currently selected filter tab to determine the section title and dot color. + final String filterTab; + + @override + Widget build(BuildContext context) { + if (orders.isEmpty) { + return const SizedBox.shrink(); + } + + String sectionTitle = ''; + Color dotColor = UiColors.transparent; + + if (filterTab == 'all') { + sectionTitle = t.client_view_orders.tabs.up_next; + dotColor = UiColors.primary; + } else if (filterTab == 'active') { + sectionTitle = t.client_view_orders.tabs.active; + dotColor = UiColors.textWarning; + } else if (filterTab == 'completed') { + sectionTitle = t.client_view_orders.tabs.completed; + dotColor = UiColors.primary; + } + + return ListView( + padding: const EdgeInsets.fromLTRB( + UiConstants.space5, + UiConstants.space4, + UiConstants.space5, + 100, + ), + children: [ + ViewOrdersListSectionHeader( + title: sectionTitle, + dotColor: dotColor, + count: orders.length, + ), + ...orders.map( + (OrderItem order) => Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space3), + child: ViewOrderCard(order: order), + ), + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list_section_header.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list_section_header.dart new file mode 100644 index 00000000..775ee6ba --- /dev/null +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_orders_list_section_header.dart @@ -0,0 +1,54 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays the section header for the orders list. +/// +/// Includes a status indicator dot, the section title, and the count of orders. +class ViewOrdersListSectionHeader extends StatelessWidget { + /// Creates a [ViewOrdersListSectionHeader]. + const ViewOrdersListSectionHeader({ + super.key, + required this.title, + required this.dotColor, + required this.count, + }); + + /// The title of the section (e.g., UP NEXT, ACTIVE, COMPLETED). + final String title; + + /// The color of the status indicator dot. + final Color dotColor; + + /// The number of orders in this section. + final int count; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space3), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle), + ), + const SizedBox(width: UiConstants.space2), + Text( + title.toUpperCase(), + style: UiTypography.titleUppercase2m.copyWith( + color: UiColors.textPrimary, + ), + ), + const SizedBox(width: UiConstants.space1), + Text( + '($count)', + style: UiTypography.footnote1r.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + ); + } +}