refactor: modularize view orders page by extracting list, empty, and error states into dedicated widgets.
This commit is contained in:
@@ -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<ViewOrdersView> {
|
||||
@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<ViewOrdersView> {
|
||||
final List<DateTime> calendarDays = state.calendarDays;
|
||||
final List<OrderItem> 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: <Widget>[
|
||||
// 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<ViewOrdersCubit>(
|
||||
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: <Widget>[
|
||||
if (filteredOrders.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
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<ViewOrdersView> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 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: <Widget>[
|
||||
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: <Widget>[
|
||||
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<ViewOrdersCubit>(context)
|
||||
.jumpToDate(state.selectedDate ?? DateTime.now()),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: <Widget>[
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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: <Widget>[
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<OrderItem> 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: <Widget>[
|
||||
ViewOrdersListSectionHeader(
|
||||
title: sectionTitle,
|
||||
dotColor: dotColor,
|
||||
count: orders.length,
|
||||
),
|
||||
...orders.map(
|
||||
(OrderItem order) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: ViewOrderCard(order: order),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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: <Widget>[
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user