feat: Add forms and UI components for one-time, permanent, and recurring orders
- Implemented OneTimeOrderForm widget for creating one-time orders with fields for event name, vendor selection, date, hub, hub manager, and positions. - Created OrderBottomActionButton for a consistent bottom action button across order views. - Developed PermanentOrderForm for permanent orders, including event name, vendor selection, start date, permanent days, hub, hub manager, and positions. - Added RecurringOrderForm for recurring orders with fields for event name, vendor selection, start/end dates, recurring days, hub, hub manager, and positions. - Introduced PermanentOrderDaysSelector and RecurringOrderDaysSelector for selecting days of the week in permanent and recurring orders respectively.
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
// UI Models
|
// UI Models
|
||||||
export 'src/presentation/widgets/order_ui_models.dart';
|
export 'src/presentation/widgets/order_ui_models.dart';
|
||||||
|
|
||||||
|
// Shared Widgets
|
||||||
|
export 'src/presentation/widgets/order_bottom_action_button.dart';
|
||||||
|
|
||||||
// One Time Order Widgets
|
// One Time Order Widgets
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_date_picker.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_date_picker.dart';
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_event_name_input.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_event_name_input.dart';
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_header.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_form.dart';
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_location_input.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_location_input.dart';
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_position_card.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_position_card.dart';
|
||||||
export 'src/presentation/widgets/one_time_order/one_time_order_section_header.dart';
|
export 'src/presentation/widgets/one_time_order/one_time_order_section_header.dart';
|
||||||
@@ -13,8 +16,9 @@ export 'src/presentation/widgets/one_time_order/one_time_order_view.dart';
|
|||||||
|
|
||||||
// Permanent Order Widgets
|
// Permanent Order Widgets
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_date_picker.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_date_picker.dart';
|
||||||
|
export 'src/presentation/widgets/permanent_order/permanent_order_days_selector.dart';
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_event_name_input.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_event_name_input.dart';
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_header.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_form.dart';
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_position_card.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_position_card.dart';
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_section_header.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_section_header.dart';
|
||||||
export 'src/presentation/widgets/permanent_order/permanent_order_success_view.dart';
|
export 'src/presentation/widgets/permanent_order/permanent_order_success_view.dart';
|
||||||
@@ -22,8 +26,9 @@ export 'src/presentation/widgets/permanent_order/permanent_order_view.dart';
|
|||||||
|
|
||||||
// Recurring Order Widgets
|
// Recurring Order Widgets
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_date_picker.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_date_picker.dart';
|
||||||
|
export 'src/presentation/widgets/recurring_order/recurring_order_days_selector.dart';
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_event_name_input.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_event_name_input.dart';
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_header.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_form.dart';
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_position_card.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_position_card.dart';
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_section_header.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_section_header.dart';
|
||||||
export 'src/presentation/widgets/recurring_order/recurring_order_success_view.dart';
|
export 'src/presentation/widgets/recurring_order/recurring_order_success_view.dart';
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
|
import '../order_ui_models.dart';
|
||||||
|
import 'one_time_order_date_picker.dart';
|
||||||
|
import 'one_time_order_event_name_input.dart';
|
||||||
|
import 'one_time_order_position_card.dart';
|
||||||
|
import 'one_time_order_section_header.dart';
|
||||||
|
|
||||||
|
/// The scrollable form body for the one-time order creation flow.
|
||||||
|
///
|
||||||
|
/// Displays fields for event name, vendor selection, date, hub, hub manager,
|
||||||
|
/// and a dynamic list of position cards.
|
||||||
|
class OneTimeOrderForm extends StatelessWidget {
|
||||||
|
/// Creates a [OneTimeOrderForm].
|
||||||
|
const OneTimeOrderForm({
|
||||||
|
required this.eventName,
|
||||||
|
required this.selectedVendor,
|
||||||
|
required this.vendors,
|
||||||
|
required this.date,
|
||||||
|
required this.selectedHub,
|
||||||
|
required this.hubs,
|
||||||
|
required this.selectedHubManager,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.positions,
|
||||||
|
required this.roles,
|
||||||
|
required this.onEventNameChanged,
|
||||||
|
required this.onVendorChanged,
|
||||||
|
required this.onDateChanged,
|
||||||
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
|
required this.onPositionAdded,
|
||||||
|
required this.onPositionUpdated,
|
||||||
|
required this.onPositionRemoved,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The current event name value.
|
||||||
|
final String eventName;
|
||||||
|
|
||||||
|
/// The currently selected vendor, if any.
|
||||||
|
final Vendor? selectedVendor;
|
||||||
|
|
||||||
|
/// The list of available vendors to choose from.
|
||||||
|
final List<Vendor> vendors;
|
||||||
|
|
||||||
|
/// The selected date for the one-time order.
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
/// The currently selected hub, if any.
|
||||||
|
final OrderHubUiModel? selectedHub;
|
||||||
|
|
||||||
|
/// The list of available hubs to choose from.
|
||||||
|
final List<OrderHubUiModel> hubs;
|
||||||
|
|
||||||
|
/// The currently selected hub manager, if any.
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
|
||||||
|
/// The list of available hub managers for the selected hub.
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
|
||||||
|
/// The list of position entries in the order.
|
||||||
|
final List<OrderPositionUiModel> positions;
|
||||||
|
|
||||||
|
/// The list of available roles for position assignment.
|
||||||
|
final List<OrderRoleUiModel> roles;
|
||||||
|
|
||||||
|
/// Called when the event name text changes.
|
||||||
|
final ValueChanged<String> onEventNameChanged;
|
||||||
|
|
||||||
|
/// Called when a vendor is selected.
|
||||||
|
final ValueChanged<Vendor> onVendorChanged;
|
||||||
|
|
||||||
|
/// Called when the date is changed.
|
||||||
|
final ValueChanged<DateTime> onDateChanged;
|
||||||
|
|
||||||
|
/// Called when a hub is selected.
|
||||||
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
|
||||||
|
/// Called when a hub manager is selected or cleared.
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
|
|
||||||
|
/// Called when the user requests adding a new position.
|
||||||
|
final VoidCallback onPositionAdded;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is updated with new values.
|
||||||
|
final void Function(int index, OrderPositionUiModel position)
|
||||||
|
onPositionUpdated;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is removed.
|
||||||
|
final void Function(int index) onPositionRemoved;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn labels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
children: <Widget>[
|
||||||
|
OneTimeOrderEventNameInput(
|
||||||
|
label: 'ORDER NAME',
|
||||||
|
value: eventName,
|
||||||
|
onChanged: onEventNameChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Vendor Selection
|
||||||
|
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<Vendor>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedVendor,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (Vendor? vendor) {
|
||||||
|
if (vendor != null) {
|
||||||
|
onVendorChanged(vendor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: vendors.map((Vendor vendor) {
|
||||||
|
return DropdownMenuItem<Vendor>(
|
||||||
|
value: vendor,
|
||||||
|
child: Text(
|
||||||
|
vendor.name,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
OneTimeOrderDatePicker(
|
||||||
|
label: labels.date_label,
|
||||||
|
value: date,
|
||||||
|
onChanged: onDateChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<OrderHubUiModel>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedHub,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (OrderHubUiModel? hub) {
|
||||||
|
if (hub != null) {
|
||||||
|
onHubChanged(hub);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: hubs.map((OrderHubUiModel hub) {
|
||||||
|
return DropdownMenuItem<OrderHubUiModel>(
|
||||||
|
value: hub,
|
||||||
|
child: Text(hub.name, style: UiTypography.body2m.textPrimary),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: labels.hub_manager_label,
|
||||||
|
description: labels.hub_manager_desc,
|
||||||
|
hintText: labels.hub_manager_hint,
|
||||||
|
noManagersText: labels.hub_manager_empty,
|
||||||
|
noneText: labels.hub_manager_none,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
OneTimeOrderSectionHeader(
|
||||||
|
title: labels.positions_title,
|
||||||
|
actionLabel: labels.add_position,
|
||||||
|
onAction: onPositionAdded,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
|
||||||
|
// Positions List
|
||||||
|
...positions.asMap().entries.map((
|
||||||
|
MapEntry<int, OrderPositionUiModel> entry,
|
||||||
|
) {
|
||||||
|
final int index = entry.key;
|
||||||
|
final OrderPositionUiModel position = entry.value;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||||
|
child: OneTimeOrderPositionCard(
|
||||||
|
index: index,
|
||||||
|
position: position,
|
||||||
|
isRemovable: positions.length > 1,
|
||||||
|
positionLabel: labels.positions_title,
|
||||||
|
roleLabel: labels.select_role,
|
||||||
|
workersLabel: labels.workers_label,
|
||||||
|
startLabel: labels.start_label,
|
||||||
|
endLabel: labels.end_label,
|
||||||
|
lunchLabel: labels.lunch_break_label,
|
||||||
|
roles: roles,
|
||||||
|
onUpdated: (OrderPositionUiModel updated) {
|
||||||
|
onPositionUpdated(index, updated);
|
||||||
|
},
|
||||||
|
onRemoved: () {
|
||||||
|
onPositionRemoved(index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// A header widget for the one-time order flow with a colored background.
|
|
||||||
class OneTimeOrderHeader extends StatelessWidget {
|
|
||||||
/// Creates a [OneTimeOrderHeader].
|
|
||||||
const OneTimeOrderHeader({
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
required this.onBack,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The title of the page.
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
/// The subtitle or description.
|
|
||||||
final String subtitle;
|
|
||||||
|
|
||||||
/// Callback when the back button is pressed.
|
|
||||||
final VoidCallback onBack;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: MediaQuery.of(context).padding.top + UiConstants.space5,
|
|
||||||
bottom: UiConstants.space5,
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
),
|
|
||||||
color: UiColors.primary,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: onBack,
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.2),
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
UiIcons.chevronLeft,
|
|
||||||
color: UiColors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: UiTypography.headline3m.copyWith(color: UiColors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: UiTypography.footnote2r.copyWith(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,10 @@ import 'package:core_localization/core_localization.dart';
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../order_bottom_action_button.dart';
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
import '../hub_manager_selector.dart';
|
import 'one_time_order_form.dart';
|
||||||
import 'one_time_order_date_picker.dart';
|
|
||||||
import 'one_time_order_event_name_input.dart';
|
|
||||||
import 'one_time_order_header.dart';
|
|
||||||
import 'one_time_order_position_card.dart';
|
|
||||||
import 'one_time_order_section_header.dart';
|
|
||||||
import 'one_time_order_success_view.dart';
|
import 'one_time_order_success_view.dart';
|
||||||
|
|
||||||
/// The main content of the One-Time Order page as a dumb widget.
|
/// The main content of the One-Time Order page as a dumb widget.
|
||||||
@@ -98,322 +95,92 @@ class OneTimeOrderView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: UiAppBar(
|
||||||
|
showBackButton: true,
|
||||||
|
onLeadingPressed: onBack,
|
||||||
|
title: title ?? labels.title,
|
||||||
|
subtitle: subtitle ?? labels.subtitle,
|
||||||
|
),
|
||||||
|
body: _buildBody(context, labels),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the main body of the One-Time Order page, showing either the form or a loading indicator.
|
||||||
|
Widget _buildBody(
|
||||||
|
BuildContext context,
|
||||||
|
TranslationsClientCreateOrderOneTimeEn labels,
|
||||||
|
) {
|
||||||
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Expanded(
|
||||||
OneTimeOrderHeader(
|
child: Center(
|
||||||
title: title ?? labels.title,
|
child: Column(
|
||||||
subtitle: subtitle ?? labels.subtitle,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onBack: onBack,
|
children: <Widget>[
|
||||||
),
|
const Icon(
|
||||||
Expanded(
|
UiIcons.search,
|
||||||
child: Center(
|
size: 64,
|
||||||
child: Column(
|
color: UiColors.iconInactive,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: <Widget>[
|
const SizedBox(height: UiConstants.space4),
|
||||||
const Icon(
|
Text(
|
||||||
UiIcons.search,
|
'No Vendors Available',
|
||||||
size: 64,
|
style: UiTypography.headline3m.textPrimary,
|
||||||
color: UiColors.iconInactive,
|
),
|
||||||
),
|
const SizedBox(height: UiConstants.space2),
|
||||||
const SizedBox(height: UiConstants.space4),
|
Text(
|
||||||
Text(
|
'There are no staffing vendors associated with your account.',
|
||||||
'No Vendors Available',
|
style: UiTypography.body2r.textSecondary,
|
||||||
style: UiTypography.headline3m.textPrimary,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space2),
|
],
|
||||||
Text(
|
|
||||||
'There are no staffing vendors associated with your account.',
|
|
||||||
style: UiTypography.body2r.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
OneTimeOrderHeader(
|
|
||||||
title: title ?? labels.title,
|
|
||||||
subtitle: subtitle ?? labels.subtitle,
|
|
||||||
onBack: onBack,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
_OneTimeOrderForm(
|
|
||||||
eventName: eventName,
|
|
||||||
selectedVendor: selectedVendor,
|
|
||||||
vendors: vendors,
|
|
||||||
date: date,
|
|
||||||
selectedHub: selectedHub,
|
|
||||||
hubs: hubs,
|
|
||||||
selectedHubManager: selectedHubManager,
|
|
||||||
hubManagers: hubManagers,
|
|
||||||
positions: positions,
|
|
||||||
roles: roles,
|
|
||||||
onEventNameChanged: onEventNameChanged,
|
|
||||||
onVendorChanged: onVendorChanged,
|
|
||||||
onDateChanged: onDateChanged,
|
|
||||||
onHubChanged: onHubChanged,
|
|
||||||
onHubManagerChanged: onHubManagerChanged,
|
|
||||||
onPositionAdded: onPositionAdded,
|
|
||||||
onPositionUpdated: onPositionUpdated,
|
|
||||||
onPositionRemoved: onPositionRemoved,
|
|
||||||
),
|
|
||||||
if (status == OrderFormStatus.loading)
|
|
||||||
const Center(child: CircularProgressIndicator()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_BottomActionButton(
|
|
||||||
label: status == OrderFormStatus.loading
|
|
||||||
? labels.creating
|
|
||||||
: labels.create_order,
|
|
||||||
isLoading: status == OrderFormStatus.loading,
|
|
||||||
onPressed: isValid ? onSubmit : null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _OneTimeOrderForm extends StatelessWidget {
|
|
||||||
const _OneTimeOrderForm({
|
|
||||||
required this.eventName,
|
|
||||||
required this.selectedVendor,
|
|
||||||
required this.vendors,
|
|
||||||
required this.date,
|
|
||||||
required this.selectedHub,
|
|
||||||
required this.hubs,
|
|
||||||
required this.selectedHubManager,
|
|
||||||
required this.hubManagers,
|
|
||||||
required this.positions,
|
|
||||||
required this.roles,
|
|
||||||
required this.onEventNameChanged,
|
|
||||||
required this.onVendorChanged,
|
|
||||||
required this.onDateChanged,
|
|
||||||
required this.onHubChanged,
|
|
||||||
required this.onHubManagerChanged,
|
|
||||||
required this.onPositionAdded,
|
|
||||||
required this.onPositionUpdated,
|
|
||||||
required this.onPositionRemoved,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String eventName;
|
|
||||||
final Vendor? selectedVendor;
|
|
||||||
final List<Vendor> vendors;
|
|
||||||
final DateTime date;
|
|
||||||
final OrderHubUiModel? selectedHub;
|
|
||||||
final List<OrderHubUiModel> hubs;
|
|
||||||
final OrderManagerUiModel? selectedHubManager;
|
|
||||||
final List<OrderManagerUiModel> hubManagers;
|
|
||||||
final List<OrderPositionUiModel> positions;
|
|
||||||
final List<OrderRoleUiModel> roles;
|
|
||||||
|
|
||||||
final ValueChanged<String> onEventNameChanged;
|
|
||||||
final ValueChanged<Vendor> onVendorChanged;
|
|
||||||
final ValueChanged<DateTime> onDateChanged;
|
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
|
||||||
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
|
||||||
final VoidCallback onPositionAdded;
|
|
||||||
final void Function(int index, OrderPositionUiModel position)
|
|
||||||
onPositionUpdated;
|
|
||||||
final void Function(int index) onPositionRemoved;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TranslationsClientCreateOrderOneTimeEn labels =
|
|
||||||
t.client_create_order.one_time;
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Expanded(
|
||||||
labels.create_your_order,
|
child: Stack(
|
||||||
style: UiTypography.headline3m.textPrimary,
|
children: <Widget>[
|
||||||
),
|
OneTimeOrderForm(
|
||||||
const SizedBox(height: UiConstants.space4),
|
eventName: eventName,
|
||||||
|
selectedVendor: selectedVendor,
|
||||||
OneTimeOrderEventNameInput(
|
vendors: vendors,
|
||||||
label: 'ORDER NAME',
|
date: date,
|
||||||
value: eventName,
|
selectedHub: selectedHub,
|
||||||
onChanged: onEventNameChanged,
|
hubs: hubs,
|
||||||
),
|
selectedHubManager: selectedHubManager,
|
||||||
const SizedBox(height: UiConstants.space4),
|
hubManagers: hubManagers,
|
||||||
|
positions: positions,
|
||||||
// Vendor Selection
|
roles: roles,
|
||||||
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
onEventNameChanged: onEventNameChanged,
|
||||||
const SizedBox(height: UiConstants.space2),
|
onVendorChanged: onVendorChanged,
|
||||||
Container(
|
onDateChanged: onDateChanged,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
onHubChanged: onHubChanged,
|
||||||
height: 48,
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
decoration: BoxDecoration(
|
onPositionAdded: onPositionAdded,
|
||||||
color: UiColors.white,
|
onPositionUpdated: onPositionUpdated,
|
||||||
borderRadius: UiConstants.radiusMd,
|
onPositionRemoved: onPositionRemoved,
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton<Vendor>(
|
|
||||||
isExpanded: true,
|
|
||||||
value: selectedVendor,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
),
|
||||||
onChanged: (Vendor? vendor) {
|
if (status == OrderFormStatus.loading)
|
||||||
if (vendor != null) {
|
const Center(child: CircularProgressIndicator()),
|
||||||
onVendorChanged(vendor);
|
],
|
||||||
}
|
|
||||||
},
|
|
||||||
items: vendors.map((Vendor vendor) {
|
|
||||||
return DropdownMenuItem<Vendor>(
|
|
||||||
value: vendor,
|
|
||||||
child: Text(
|
|
||||||
vendor.name,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
OrderBottomActionButton(
|
||||||
|
label: status == OrderFormStatus.loading
|
||||||
OneTimeOrderDatePicker(
|
? labels.creating
|
||||||
label: labels.date_label,
|
: labels.create_order,
|
||||||
value: date,
|
isLoading: status == OrderFormStatus.loading,
|
||||||
onChanged: onDateChanged,
|
onPressed: isValid ? onSubmit : null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton<OrderHubUiModel>(
|
|
||||||
isExpanded: true,
|
|
||||||
value: selectedHub,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
|
||||||
onChanged: (OrderHubUiModel? hub) {
|
|
||||||
if (hub != null) {
|
|
||||||
onHubChanged(hub);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: hubs.map((OrderHubUiModel hub) {
|
|
||||||
return DropdownMenuItem<OrderHubUiModel>(
|
|
||||||
value: hub,
|
|
||||||
child: Text(hub.name, style: UiTypography.body2m.textPrimary),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
HubManagerSelector(
|
|
||||||
label: labels.hub_manager_label,
|
|
||||||
description: labels.hub_manager_desc,
|
|
||||||
hintText: labels.hub_manager_hint,
|
|
||||||
noManagersText: labels.hub_manager_empty,
|
|
||||||
noneText: labels.hub_manager_none,
|
|
||||||
managers: hubManagers,
|
|
||||||
selectedManager: selectedHubManager,
|
|
||||||
onChanged: onHubManagerChanged,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
|
|
||||||
OneTimeOrderSectionHeader(
|
|
||||||
title: labels.positions_title,
|
|
||||||
actionLabel: labels.add_position,
|
|
||||||
onAction: onPositionAdded,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
|
|
||||||
// Positions List
|
|
||||||
...positions.asMap().entries.map((
|
|
||||||
MapEntry<int, OrderPositionUiModel> entry,
|
|
||||||
) {
|
|
||||||
final int index = entry.key;
|
|
||||||
final OrderPositionUiModel position = entry.value;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
|
||||||
child: OneTimeOrderPositionCard(
|
|
||||||
index: index,
|
|
||||||
position: position,
|
|
||||||
isRemovable: positions.length > 1,
|
|
||||||
positionLabel: labels.positions_title,
|
|
||||||
roleLabel: labels.select_role,
|
|
||||||
workersLabel: labels.workers_label,
|
|
||||||
startLabel: labels.start_label,
|
|
||||||
endLabel: labels.end_label,
|
|
||||||
lunchLabel: labels.lunch_break_label,
|
|
||||||
roles: roles,
|
|
||||||
onUpdated: (OrderPositionUiModel updated) {
|
|
||||||
onPositionUpdated(index, updated);
|
|
||||||
},
|
|
||||||
onRemoved: () {
|
|
||||||
onPositionRemoved(index);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BottomActionButton extends StatelessWidget {
|
|
||||||
const _BottomActionButton({
|
|
||||||
required this.label,
|
|
||||||
required this.onPressed,
|
|
||||||
this.isLoading = false,
|
|
||||||
});
|
|
||||||
final String label;
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
final bool isLoading;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
top: UiConstants.space5,
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space5,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
border: Border(top: BorderSide(color: UiColors.border)),
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: UiButton.primary(
|
|
||||||
text: label,
|
|
||||||
onPressed: isLoading ? null : onPressed,
|
|
||||||
size: UiButtonSize.large,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A bottom-pinned action button used across all order type views.
|
||||||
|
///
|
||||||
|
/// Renders a full-width primary button with safe-area padding at the bottom
|
||||||
|
/// and a top border separator. Disables the button while [isLoading] is true.
|
||||||
|
class OrderBottomActionButton extends StatelessWidget {
|
||||||
|
/// Creates an [OrderBottomActionButton].
|
||||||
|
const OrderBottomActionButton({
|
||||||
|
required this.label,
|
||||||
|
required this.onPressed,
|
||||||
|
this.isLoading = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The text displayed on the button.
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// Callback invoked when the button is pressed. Pass `null` to disable.
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
/// Whether the form is currently submitting. Disables the button when true.
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: UiConstants.space5,
|
||||||
|
right: UiConstants.space5,
|
||||||
|
top: UiConstants.space5,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space5,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
border: Border(top: BorderSide(color: UiColors.border, width: 0.5)),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: UiButton.primary(
|
||||||
|
text: label,
|
||||||
|
onPressed: isLoading ? null : onPressed,
|
||||||
|
size: UiButtonSize.large,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A horizontal row of circular day-of-week toggle buttons for permanent orders.
|
||||||
|
///
|
||||||
|
/// Displays seven circles labeled S, M, T, W, T, F, S representing the days
|
||||||
|
/// of the week. Selected days are highlighted with the primary color.
|
||||||
|
class PermanentOrderDaysSelector extends StatelessWidget {
|
||||||
|
/// Creates a [PermanentOrderDaysSelector].
|
||||||
|
const PermanentOrderDaysSelector({
|
||||||
|
required this.selectedDays,
|
||||||
|
required this.onToggle,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The list of currently selected day abbreviations (e.g. 'MON', 'TUE').
|
||||||
|
final List<String> selectedDays;
|
||||||
|
|
||||||
|
/// Called when a day circle is tapped, with the day index (0 = Sunday).
|
||||||
|
final ValueChanged<int> onToggle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const List<String> labelsShort = <String>[
|
||||||
|
'S',
|
||||||
|
'M',
|
||||||
|
'T',
|
||||||
|
'W',
|
||||||
|
'T',
|
||||||
|
'F',
|
||||||
|
'S',
|
||||||
|
];
|
||||||
|
const List<String> labelsLong = <String>[
|
||||||
|
'SUN',
|
||||||
|
'MON',
|
||||||
|
'TUE',
|
||||||
|
'WED',
|
||||||
|
'THU',
|
||||||
|
'FRI',
|
||||||
|
'SAT',
|
||||||
|
];
|
||||||
|
return Wrap(
|
||||||
|
spacing: UiConstants.space2,
|
||||||
|
children: List<Widget>.generate(labelsShort.length, (int index) {
|
||||||
|
final bool isSelected = selectedDays.contains(labelsLong[index]);
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onToggle(index),
|
||||||
|
child: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? UiColors.primary : UiColors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
labelsShort[index],
|
||||||
|
style: UiTypography.body2m.copyWith(
|
||||||
|
color: isSelected ? UiColors.white : UiColors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
||||||
|
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
|
import '../order_ui_models.dart';
|
||||||
|
import 'permanent_order_date_picker.dart';
|
||||||
|
import 'permanent_order_days_selector.dart';
|
||||||
|
import 'permanent_order_event_name_input.dart';
|
||||||
|
import 'permanent_order_position_card.dart';
|
||||||
|
import 'permanent_order_section_header.dart';
|
||||||
|
|
||||||
|
/// The scrollable form body for the permanent order creation flow.
|
||||||
|
///
|
||||||
|
/// Displays fields for event name, vendor selection, start date,
|
||||||
|
/// permanent day toggles, hub, hub manager, and a dynamic list of
|
||||||
|
/// position cards.
|
||||||
|
class PermanentOrderForm extends StatelessWidget {
|
||||||
|
/// Creates a [PermanentOrderForm].
|
||||||
|
const PermanentOrderForm({
|
||||||
|
required this.eventName,
|
||||||
|
required this.selectedVendor,
|
||||||
|
required this.vendors,
|
||||||
|
required this.startDate,
|
||||||
|
required this.permanentDays,
|
||||||
|
required this.selectedHub,
|
||||||
|
required this.hubs,
|
||||||
|
required this.positions,
|
||||||
|
required this.roles,
|
||||||
|
required this.onEventNameChanged,
|
||||||
|
required this.onVendorChanged,
|
||||||
|
required this.onStartDateChanged,
|
||||||
|
required this.onDayToggled,
|
||||||
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
|
required this.onPositionAdded,
|
||||||
|
required this.onPositionUpdated,
|
||||||
|
required this.onPositionRemoved,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The current event name value.
|
||||||
|
final String eventName;
|
||||||
|
|
||||||
|
/// The currently selected vendor, if any.
|
||||||
|
final Vendor? selectedVendor;
|
||||||
|
|
||||||
|
/// The list of available vendors to choose from.
|
||||||
|
final List<Vendor> vendors;
|
||||||
|
|
||||||
|
/// The start date for the permanent order.
|
||||||
|
final DateTime startDate;
|
||||||
|
|
||||||
|
/// The list of selected permanent day abbreviations (e.g. 'MON', 'TUE').
|
||||||
|
final List<String> permanentDays;
|
||||||
|
|
||||||
|
/// The currently selected hub, if any.
|
||||||
|
final OrderHubUiModel? selectedHub;
|
||||||
|
|
||||||
|
/// The list of available hubs to choose from.
|
||||||
|
final List<OrderHubUiModel> hubs;
|
||||||
|
|
||||||
|
/// The list of position entries in the order.
|
||||||
|
final List<OrderPositionUiModel> positions;
|
||||||
|
|
||||||
|
/// The list of available roles for position assignment.
|
||||||
|
final List<OrderRoleUiModel> roles;
|
||||||
|
|
||||||
|
/// Called when the event name text changes.
|
||||||
|
final ValueChanged<String> onEventNameChanged;
|
||||||
|
|
||||||
|
/// Called when a vendor is selected.
|
||||||
|
final ValueChanged<Vendor> onVendorChanged;
|
||||||
|
|
||||||
|
/// Called when the start date is changed.
|
||||||
|
final ValueChanged<DateTime> onStartDateChanged;
|
||||||
|
|
||||||
|
/// Called when a day-of-week toggle is tapped, with the day index (0=Sun).
|
||||||
|
final ValueChanged<int> onDayToggled;
|
||||||
|
|
||||||
|
/// Called when a hub is selected.
|
||||||
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
|
||||||
|
/// Called when a hub manager is selected or cleared.
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
|
|
||||||
|
/// Called when the user requests adding a new position.
|
||||||
|
final VoidCallback onPositionAdded;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is updated with new values.
|
||||||
|
final void Function(int index, OrderPositionUiModel position)
|
||||||
|
onPositionUpdated;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is removed.
|
||||||
|
final void Function(int index) onPositionRemoved;
|
||||||
|
|
||||||
|
/// The list of available hub managers for the selected hub.
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
|
||||||
|
/// The currently selected hub manager, if any.
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderPermanentEn labels =
|
||||||
|
t.client_create_order.permanent;
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn oneTimeLabels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
labels.title,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
PermanentOrderEventNameInput(
|
||||||
|
label: 'ORDER NAME',
|
||||||
|
value: eventName,
|
||||||
|
onChanged: onEventNameChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Vendor Selection
|
||||||
|
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<Vendor>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedVendor,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (Vendor? vendor) {
|
||||||
|
if (vendor != null) {
|
||||||
|
onVendorChanged(vendor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: vendors.map((Vendor vendor) {
|
||||||
|
return DropdownMenuItem<Vendor>(
|
||||||
|
value: vendor,
|
||||||
|
child: Text(
|
||||||
|
vendor.name,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
PermanentOrderDatePicker(
|
||||||
|
label: 'Start Date',
|
||||||
|
value: startDate,
|
||||||
|
onChanged: onStartDateChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
Text('Permanent Days', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
PermanentOrderDaysSelector(
|
||||||
|
selectedDays: permanentDays,
|
||||||
|
onToggle: onDayToggled,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<OrderHubUiModel>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedHub,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (OrderHubUiModel? hub) {
|
||||||
|
if (hub != null) {
|
||||||
|
onHubChanged(hub);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: hubs.map((OrderHubUiModel hub) {
|
||||||
|
return DropdownMenuItem<OrderHubUiModel>(
|
||||||
|
value: hub,
|
||||||
|
child: Text(
|
||||||
|
hub.name,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: oneTimeLabels.hub_manager_label,
|
||||||
|
description: oneTimeLabels.hub_manager_desc,
|
||||||
|
hintText: oneTimeLabels.hub_manager_hint,
|
||||||
|
noManagersText: oneTimeLabels.hub_manager_empty,
|
||||||
|
noneText: oneTimeLabels.hub_manager_none,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
PermanentOrderSectionHeader(
|
||||||
|
title: oneTimeLabels.positions_title,
|
||||||
|
actionLabel: oneTimeLabels.add_position,
|
||||||
|
onAction: onPositionAdded,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
|
||||||
|
// Positions List
|
||||||
|
...positions.asMap().entries.map((
|
||||||
|
MapEntry<int, OrderPositionUiModel> entry,
|
||||||
|
) {
|
||||||
|
final int index = entry.key;
|
||||||
|
final OrderPositionUiModel position = entry.value;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||||
|
child: PermanentOrderPositionCard(
|
||||||
|
index: index,
|
||||||
|
position: position,
|
||||||
|
isRemovable: positions.length > 1,
|
||||||
|
positionLabel: oneTimeLabels.positions_title,
|
||||||
|
roleLabel: oneTimeLabels.select_role,
|
||||||
|
workersLabel: oneTimeLabels.workers_label,
|
||||||
|
startLabel: oneTimeLabels.start_label,
|
||||||
|
endLabel: oneTimeLabels.end_label,
|
||||||
|
lunchLabel: oneTimeLabels.lunch_break_label,
|
||||||
|
roles: roles,
|
||||||
|
onUpdated: (OrderPositionUiModel updated) {
|
||||||
|
onPositionUpdated(index, updated);
|
||||||
|
},
|
||||||
|
onRemoved: () {
|
||||||
|
onPositionRemoved(index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// A header widget for the permanent order flow with a colored background.
|
|
||||||
class PermanentOrderHeader extends StatelessWidget {
|
|
||||||
/// Creates a [PermanentOrderHeader].
|
|
||||||
const PermanentOrderHeader({
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
required this.onBack,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The title of the page.
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
/// The subtitle or description.
|
|
||||||
final String subtitle;
|
|
||||||
|
|
||||||
/// Callback when the back button is pressed.
|
|
||||||
final VoidCallback onBack;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: MediaQuery.of(context).padding.top + UiConstants.space5,
|
|
||||||
bottom: UiConstants.space5,
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
),
|
|
||||||
color: UiColors.primary,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: onBack,
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.2),
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
UiIcons.chevronLeft,
|
|
||||||
color: UiColors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: UiTypography.headline3m.copyWith(color: UiColors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: UiTypography.footnote2r.copyWith(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,10 @@ import 'package:core_localization/core_localization.dart';
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
||||||
|
|
||||||
|
import '../order_bottom_action_button.dart';
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
import '../hub_manager_selector.dart';
|
import 'permanent_order_form.dart';
|
||||||
import 'permanent_order_date_picker.dart';
|
|
||||||
import 'permanent_order_event_name_input.dart';
|
|
||||||
import 'permanent_order_header.dart';
|
|
||||||
import 'permanent_order_position_card.dart';
|
|
||||||
import 'permanent_order_section_header.dart';
|
|
||||||
import 'permanent_order_success_view.dart';
|
import 'permanent_order_success_view.dart';
|
||||||
|
|
||||||
/// The main content of the Permanent Order page.
|
/// The main content of the Permanent Order page.
|
||||||
@@ -65,7 +62,8 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position)
|
||||||
|
onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
final VoidCallback onSubmit;
|
final VoidCallback onSubmit;
|
||||||
final VoidCallback onDone;
|
final VoidCallback onDone;
|
||||||
@@ -98,398 +96,95 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: UiAppBar(
|
||||||
|
showBackButton: true,
|
||||||
|
onLeadingPressed: onBack,
|
||||||
|
title: labels.title,
|
||||||
|
subtitle: labels.subtitle,
|
||||||
|
),
|
||||||
|
body: _buildBody(context, labels, oneTimeLabels),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the main body of the Permanent Order page based on the current state.
|
||||||
|
Widget _buildBody(
|
||||||
|
BuildContext context,
|
||||||
|
TranslationsClientCreateOrderPermanentEn labels,
|
||||||
|
TranslationsClientCreateOrderOneTimeEn oneTimeLabels,
|
||||||
|
) {
|
||||||
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Expanded(
|
||||||
PermanentOrderHeader(
|
child: Center(
|
||||||
title: labels.title,
|
child: Column(
|
||||||
subtitle: labels.subtitle,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onBack: onBack,
|
children: <Widget>[
|
||||||
),
|
const Icon(
|
||||||
Expanded(
|
UiIcons.search,
|
||||||
child: Center(
|
size: 64,
|
||||||
child: Column(
|
color: UiColors.iconInactive,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: <Widget>[
|
const SizedBox(height: UiConstants.space4),
|
||||||
const Icon(
|
Text(
|
||||||
UiIcons.search,
|
'No Vendors Available',
|
||||||
size: 64,
|
style: UiTypography.headline3m.textPrimary,
|
||||||
color: UiColors.iconInactive,
|
),
|
||||||
),
|
const SizedBox(height: UiConstants.space2),
|
||||||
const SizedBox(height: UiConstants.space4),
|
Text(
|
||||||
Text(
|
'There are no staffing vendors associated with your account.',
|
||||||
'No Vendors Available',
|
style: UiTypography.body2r.textSecondary,
|
||||||
style: UiTypography.headline3m.textPrimary,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space2),
|
],
|
||||||
Text(
|
|
||||||
'There are no staffing vendors associated with your account.',
|
|
||||||
style: UiTypography.body2r.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
PermanentOrderHeader(
|
|
||||||
title: labels.title,
|
|
||||||
subtitle: labels.subtitle,
|
|
||||||
onBack: onBack,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
_PermanentOrderForm(
|
|
||||||
eventName: eventName,
|
|
||||||
selectedVendor: selectedVendor,
|
|
||||||
vendors: vendors,
|
|
||||||
startDate: startDate,
|
|
||||||
permanentDays: permanentDays,
|
|
||||||
selectedHub: selectedHub,
|
|
||||||
hubs: hubs,
|
|
||||||
positions: positions,
|
|
||||||
roles: roles,
|
|
||||||
onEventNameChanged: onEventNameChanged,
|
|
||||||
onVendorChanged: onVendorChanged,
|
|
||||||
onStartDateChanged: onStartDateChanged,
|
|
||||||
onDayToggled: onDayToggled,
|
|
||||||
onHubChanged: onHubChanged,
|
|
||||||
onHubManagerChanged: onHubManagerChanged,
|
|
||||||
onPositionAdded: onPositionAdded,
|
|
||||||
onPositionUpdated: onPositionUpdated,
|
|
||||||
onPositionRemoved: onPositionRemoved,
|
|
||||||
hubManagers: hubManagers,
|
|
||||||
selectedHubManager: selectedHubManager,
|
|
||||||
),
|
|
||||||
if (status == OrderFormStatus.loading)
|
|
||||||
const Center(child: CircularProgressIndicator()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_BottomActionButton(
|
|
||||||
label: status == OrderFormStatus.loading
|
|
||||||
? oneTimeLabels.creating
|
|
||||||
: oneTimeLabels.create_order,
|
|
||||||
isLoading: status == OrderFormStatus.loading,
|
|
||||||
onPressed: isValid ? onSubmit : null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PermanentOrderForm extends StatelessWidget {
|
|
||||||
const _PermanentOrderForm({
|
|
||||||
required this.eventName,
|
|
||||||
required this.selectedVendor,
|
|
||||||
required this.vendors,
|
|
||||||
required this.startDate,
|
|
||||||
required this.permanentDays,
|
|
||||||
required this.selectedHub,
|
|
||||||
required this.hubs,
|
|
||||||
required this.positions,
|
|
||||||
required this.roles,
|
|
||||||
required this.onEventNameChanged,
|
|
||||||
required this.onVendorChanged,
|
|
||||||
required this.onStartDateChanged,
|
|
||||||
required this.onDayToggled,
|
|
||||||
required this.onHubChanged,
|
|
||||||
required this.onHubManagerChanged,
|
|
||||||
required this.onPositionAdded,
|
|
||||||
required this.onPositionUpdated,
|
|
||||||
required this.onPositionRemoved,
|
|
||||||
required this.hubManagers,
|
|
||||||
required this.selectedHubManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String eventName;
|
|
||||||
final Vendor? selectedVendor;
|
|
||||||
final List<Vendor> vendors;
|
|
||||||
final DateTime startDate;
|
|
||||||
final List<String> permanentDays;
|
|
||||||
final OrderHubUiModel? selectedHub;
|
|
||||||
final List<OrderHubUiModel> hubs;
|
|
||||||
final List<OrderPositionUiModel> positions;
|
|
||||||
final List<OrderRoleUiModel> roles;
|
|
||||||
|
|
||||||
final ValueChanged<String> onEventNameChanged;
|
|
||||||
final ValueChanged<Vendor> onVendorChanged;
|
|
||||||
final ValueChanged<DateTime> onStartDateChanged;
|
|
||||||
final ValueChanged<int> onDayToggled;
|
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
|
||||||
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
|
||||||
final VoidCallback onPositionAdded;
|
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
|
||||||
final void Function(int index) onPositionRemoved;
|
|
||||||
|
|
||||||
final List<OrderManagerUiModel> hubManagers;
|
|
||||||
final OrderManagerUiModel? selectedHubManager;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TranslationsClientCreateOrderPermanentEn labels =
|
|
||||||
t.client_create_order.permanent;
|
|
||||||
final TranslationsClientCreateOrderOneTimeEn oneTimeLabels =
|
|
||||||
t.client_create_order.one_time;
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Expanded(
|
||||||
labels.title,
|
child: Stack(
|
||||||
style: UiTypography.headline3m.textPrimary,
|
children: <Widget>[
|
||||||
),
|
PermanentOrderForm(
|
||||||
const SizedBox(height: UiConstants.space4),
|
eventName: eventName,
|
||||||
|
selectedVendor: selectedVendor,
|
||||||
PermanentOrderEventNameInput(
|
vendors: vendors,
|
||||||
label: 'ORDER NAME',
|
startDate: startDate,
|
||||||
value: eventName,
|
permanentDays: permanentDays,
|
||||||
onChanged: onEventNameChanged,
|
selectedHub: selectedHub,
|
||||||
),
|
hubs: hubs,
|
||||||
const SizedBox(height: UiConstants.space4),
|
positions: positions,
|
||||||
|
roles: roles,
|
||||||
// Vendor Selection
|
onEventNameChanged: onEventNameChanged,
|
||||||
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
onVendorChanged: onVendorChanged,
|
||||||
const SizedBox(height: UiConstants.space2),
|
onStartDateChanged: onStartDateChanged,
|
||||||
Container(
|
onDayToggled: onDayToggled,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
onHubChanged: onHubChanged,
|
||||||
height: 48,
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
decoration: BoxDecoration(
|
onPositionAdded: onPositionAdded,
|
||||||
color: UiColors.white,
|
onPositionUpdated: onPositionUpdated,
|
||||||
borderRadius: UiConstants.radiusMd,
|
onPositionRemoved: onPositionRemoved,
|
||||||
border: Border.all(color: UiColors.border),
|
hubManagers: hubManagers,
|
||||||
),
|
selectedHubManager: selectedHubManager,
|
||||||
child: DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton<Vendor>(
|
|
||||||
isExpanded: true,
|
|
||||||
value: selectedVendor,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
),
|
||||||
onChanged: (Vendor? vendor) {
|
if (status == OrderFormStatus.loading)
|
||||||
if (vendor != null) {
|
const Center(child: CircularProgressIndicator()),
|
||||||
onVendorChanged(vendor);
|
],
|
||||||
}
|
|
||||||
},
|
|
||||||
items: vendors.map((Vendor vendor) {
|
|
||||||
return DropdownMenuItem<Vendor>(
|
|
||||||
value: vendor,
|
|
||||||
child: Text(
|
|
||||||
vendor.name,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
OrderBottomActionButton(
|
||||||
|
label: status == OrderFormStatus.loading
|
||||||
PermanentOrderDatePicker(
|
? oneTimeLabels.creating
|
||||||
label: 'Start Date',
|
: oneTimeLabels.create_order,
|
||||||
value: startDate,
|
isLoading: status == OrderFormStatus.loading,
|
||||||
onChanged: onStartDateChanged,
|
onPressed: isValid ? onSubmit : null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
Text('Permanent Days', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
_PermanentDaysSelector(
|
|
||||||
selectedDays: permanentDays,
|
|
||||||
onToggle: onDayToggled,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton<OrderHubUiModel>(
|
|
||||||
isExpanded: true,
|
|
||||||
value: selectedHub,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
|
||||||
onChanged: (OrderHubUiModel? hub) {
|
|
||||||
if (hub != null) {
|
|
||||||
onHubChanged(hub);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: hubs.map((OrderHubUiModel hub) {
|
|
||||||
return DropdownMenuItem<OrderHubUiModel>(
|
|
||||||
value: hub,
|
|
||||||
child: Text(
|
|
||||||
hub.name,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
HubManagerSelector(
|
|
||||||
label: oneTimeLabels.hub_manager_label,
|
|
||||||
description: oneTimeLabels.hub_manager_desc,
|
|
||||||
hintText: oneTimeLabels.hub_manager_hint,
|
|
||||||
noManagersText: oneTimeLabels.hub_manager_empty,
|
|
||||||
noneText: oneTimeLabels.hub_manager_none,
|
|
||||||
managers: hubManagers,
|
|
||||||
selectedManager: selectedHubManager,
|
|
||||||
onChanged: onHubManagerChanged,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
|
|
||||||
PermanentOrderSectionHeader(
|
|
||||||
title: oneTimeLabels.positions_title,
|
|
||||||
actionLabel: oneTimeLabels.add_position,
|
|
||||||
onAction: onPositionAdded,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
|
|
||||||
// Positions List
|
|
||||||
...positions.asMap().entries.map((
|
|
||||||
MapEntry<int, OrderPositionUiModel> entry,
|
|
||||||
) {
|
|
||||||
final int index = entry.key;
|
|
||||||
final OrderPositionUiModel position = entry.value;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
|
||||||
child: PermanentOrderPositionCard(
|
|
||||||
index: index,
|
|
||||||
position: position,
|
|
||||||
isRemovable: positions.length > 1,
|
|
||||||
positionLabel: oneTimeLabels.positions_title,
|
|
||||||
roleLabel: oneTimeLabels.select_role,
|
|
||||||
workersLabel: oneTimeLabels.workers_label,
|
|
||||||
startLabel: oneTimeLabels.start_label,
|
|
||||||
endLabel: oneTimeLabels.end_label,
|
|
||||||
lunchLabel: oneTimeLabels.lunch_break_label,
|
|
||||||
roles: roles,
|
|
||||||
onUpdated: (OrderPositionUiModel updated) {
|
|
||||||
onPositionUpdated(index, updated);
|
|
||||||
},
|
|
||||||
onRemoved: () {
|
|
||||||
onPositionRemoved(index);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PermanentDaysSelector extends StatelessWidget {
|
|
||||||
const _PermanentDaysSelector({
|
|
||||||
required this.selectedDays,
|
|
||||||
required this.onToggle,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<String> selectedDays;
|
|
||||||
final ValueChanged<int> onToggle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const List<String> labelsShort = <String>[
|
|
||||||
'S',
|
|
||||||
'M',
|
|
||||||
'T',
|
|
||||||
'W',
|
|
||||||
'T',
|
|
||||||
'F',
|
|
||||||
'S',
|
|
||||||
];
|
|
||||||
const List<String> labelsLong = <String>[
|
|
||||||
'SUN',
|
|
||||||
'MON',
|
|
||||||
'TUE',
|
|
||||||
'WED',
|
|
||||||
'THU',
|
|
||||||
'FRI',
|
|
||||||
'SAT',
|
|
||||||
];
|
|
||||||
return Wrap(
|
|
||||||
spacing: UiConstants.space2,
|
|
||||||
children: List<Widget>.generate(labelsShort.length, (int index) {
|
|
||||||
final bool isSelected = selectedDays.contains(labelsLong[index]);
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onToggle(index),
|
|
||||||
child: Container(
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? UiColors.primary : UiColors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
labelsShort[index],
|
|
||||||
style: UiTypography.body2m.copyWith(
|
|
||||||
color: isSelected ? UiColors.white : UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BottomActionButton extends StatelessWidget {
|
|
||||||
const _BottomActionButton({
|
|
||||||
required this.label,
|
|
||||||
required this.onPressed,
|
|
||||||
this.isLoading = false,
|
|
||||||
});
|
|
||||||
final String label;
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
final bool isLoading;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
top: UiConstants.space5,
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space5,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
border: Border(top: BorderSide(color: UiColors.border)),
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: UiButton.primary(
|
|
||||||
text: label,
|
|
||||||
onPressed: isLoading ? null : onPressed,
|
|
||||||
size: UiButtonSize.large,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A horizontal row of circular day-of-week toggle buttons for recurring orders.
|
||||||
|
///
|
||||||
|
/// Displays seven circles labeled S, M, T, W, T, F, S representing the days
|
||||||
|
/// of the week. Selected days are highlighted with the primary color.
|
||||||
|
class RecurringOrderDaysSelector extends StatelessWidget {
|
||||||
|
/// Creates a [RecurringOrderDaysSelector].
|
||||||
|
const RecurringOrderDaysSelector({
|
||||||
|
required this.selectedDays,
|
||||||
|
required this.onToggle,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The list of currently selected day abbreviations (e.g. 'MON', 'TUE').
|
||||||
|
final List<String> selectedDays;
|
||||||
|
|
||||||
|
/// Called when a day circle is tapped, with the day index (0 = Sunday).
|
||||||
|
final ValueChanged<int> onToggle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const List<String> labelsShort = <String>[
|
||||||
|
'S',
|
||||||
|
'M',
|
||||||
|
'T',
|
||||||
|
'W',
|
||||||
|
'T',
|
||||||
|
'F',
|
||||||
|
'S',
|
||||||
|
];
|
||||||
|
const List<String> labelsLong = <String>[
|
||||||
|
'SUN',
|
||||||
|
'MON',
|
||||||
|
'TUE',
|
||||||
|
'WED',
|
||||||
|
'THU',
|
||||||
|
'FRI',
|
||||||
|
'SAT',
|
||||||
|
];
|
||||||
|
return Wrap(
|
||||||
|
spacing: UiConstants.space2,
|
||||||
|
children: List<Widget>.generate(labelsShort.length, (int index) {
|
||||||
|
final bool isSelected = selectedDays.contains(labelsLong[index]);
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onToggle(index),
|
||||||
|
child: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? UiColors.primary : UiColors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
labelsShort[index],
|
||||||
|
style: UiTypography.body2m.copyWith(
|
||||||
|
color: isSelected ? UiColors.white : UiColors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
||||||
|
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
|
import '../order_ui_models.dart';
|
||||||
|
import 'recurring_order_date_picker.dart';
|
||||||
|
import 'recurring_order_days_selector.dart';
|
||||||
|
import 'recurring_order_event_name_input.dart';
|
||||||
|
import 'recurring_order_position_card.dart';
|
||||||
|
import 'recurring_order_section_header.dart';
|
||||||
|
|
||||||
|
/// The scrollable form body for the recurring order creation flow.
|
||||||
|
///
|
||||||
|
/// Displays fields for event name, vendor selection, start/end dates,
|
||||||
|
/// recurring day toggles, hub, hub manager, and a dynamic list of
|
||||||
|
/// position cards.
|
||||||
|
class RecurringOrderForm extends StatelessWidget {
|
||||||
|
/// Creates a [RecurringOrderForm].
|
||||||
|
const RecurringOrderForm({
|
||||||
|
required this.eventName,
|
||||||
|
required this.selectedVendor,
|
||||||
|
required this.vendors,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
required this.recurringDays,
|
||||||
|
required this.selectedHub,
|
||||||
|
required this.hubs,
|
||||||
|
required this.positions,
|
||||||
|
required this.roles,
|
||||||
|
required this.onEventNameChanged,
|
||||||
|
required this.onVendorChanged,
|
||||||
|
required this.onStartDateChanged,
|
||||||
|
required this.onEndDateChanged,
|
||||||
|
required this.onDayToggled,
|
||||||
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
|
required this.onPositionAdded,
|
||||||
|
required this.onPositionUpdated,
|
||||||
|
required this.onPositionRemoved,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The current event name value.
|
||||||
|
final String eventName;
|
||||||
|
|
||||||
|
/// The currently selected vendor, if any.
|
||||||
|
final Vendor? selectedVendor;
|
||||||
|
|
||||||
|
/// The list of available vendors to choose from.
|
||||||
|
final List<Vendor> vendors;
|
||||||
|
|
||||||
|
/// The start date for the recurring period.
|
||||||
|
final DateTime startDate;
|
||||||
|
|
||||||
|
/// The end date for the recurring period.
|
||||||
|
final DateTime endDate;
|
||||||
|
|
||||||
|
/// The list of selected recurring day abbreviations (e.g. 'MON', 'TUE').
|
||||||
|
final List<String> recurringDays;
|
||||||
|
|
||||||
|
/// The currently selected hub, if any.
|
||||||
|
final OrderHubUiModel? selectedHub;
|
||||||
|
|
||||||
|
/// The list of available hubs to choose from.
|
||||||
|
final List<OrderHubUiModel> hubs;
|
||||||
|
|
||||||
|
/// The list of position entries in the order.
|
||||||
|
final List<OrderPositionUiModel> positions;
|
||||||
|
|
||||||
|
/// The list of available roles for position assignment.
|
||||||
|
final List<OrderRoleUiModel> roles;
|
||||||
|
|
||||||
|
/// Called when the event name text changes.
|
||||||
|
final ValueChanged<String> onEventNameChanged;
|
||||||
|
|
||||||
|
/// Called when a vendor is selected.
|
||||||
|
final ValueChanged<Vendor> onVendorChanged;
|
||||||
|
|
||||||
|
/// Called when the start date is changed.
|
||||||
|
final ValueChanged<DateTime> onStartDateChanged;
|
||||||
|
|
||||||
|
/// Called when the end date is changed.
|
||||||
|
final ValueChanged<DateTime> onEndDateChanged;
|
||||||
|
|
||||||
|
/// Called when a day-of-week toggle is tapped, with the day index (0=Sun).
|
||||||
|
final ValueChanged<int> onDayToggled;
|
||||||
|
|
||||||
|
/// Called when a hub is selected.
|
||||||
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
|
||||||
|
/// Called when a hub manager is selected or cleared.
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
|
|
||||||
|
/// Called when the user requests adding a new position.
|
||||||
|
final VoidCallback onPositionAdded;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is updated with new values.
|
||||||
|
final void Function(int index, OrderPositionUiModel position)
|
||||||
|
onPositionUpdated;
|
||||||
|
|
||||||
|
/// Called when a position at [index] is removed.
|
||||||
|
final void Function(int index) onPositionRemoved;
|
||||||
|
|
||||||
|
/// The list of available hub managers for the selected hub.
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
|
||||||
|
/// The currently selected hub manager, if any.
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderRecurringEn labels =
|
||||||
|
t.client_create_order.recurring;
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn oneTimeLabels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
labels.title,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
RecurringOrderEventNameInput(
|
||||||
|
label: 'ORDER NAME',
|
||||||
|
value: eventName,
|
||||||
|
onChanged: onEventNameChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Vendor Selection
|
||||||
|
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<Vendor>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedVendor,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (Vendor? vendor) {
|
||||||
|
if (vendor != null) {
|
||||||
|
onVendorChanged(vendor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: vendors.map((Vendor vendor) {
|
||||||
|
return DropdownMenuItem<Vendor>(
|
||||||
|
value: vendor,
|
||||||
|
child: Text(
|
||||||
|
vendor.name,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
RecurringOrderDatePicker(
|
||||||
|
label: 'Start Date',
|
||||||
|
value: startDate,
|
||||||
|
onChanged: onStartDateChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
RecurringOrderDatePicker(
|
||||||
|
label: 'End Date',
|
||||||
|
value: endDate,
|
||||||
|
onChanged: onEndDateChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
Text('Recurring Days', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
RecurringOrderDaysSelector(
|
||||||
|
selectedDays: recurringDays,
|
||||||
|
onToggle: onDayToggled,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<OrderHubUiModel>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedHub,
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.chevronDown,
|
||||||
|
size: 18,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
onChanged: (OrderHubUiModel? hub) {
|
||||||
|
if (hub != null) {
|
||||||
|
onHubChanged(hub);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: hubs.map((OrderHubUiModel hub) {
|
||||||
|
return DropdownMenuItem<OrderHubUiModel>(
|
||||||
|
value: hub,
|
||||||
|
child: Text(
|
||||||
|
hub.name,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: oneTimeLabels.hub_manager_label,
|
||||||
|
description: oneTimeLabels.hub_manager_desc,
|
||||||
|
hintText: oneTimeLabels.hub_manager_hint,
|
||||||
|
noManagersText: oneTimeLabels.hub_manager_empty,
|
||||||
|
noneText: oneTimeLabels.hub_manager_none,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
RecurringOrderSectionHeader(
|
||||||
|
title: oneTimeLabels.positions_title,
|
||||||
|
actionLabel: oneTimeLabels.add_position,
|
||||||
|
onAction: onPositionAdded,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
|
||||||
|
// Positions List
|
||||||
|
...positions.asMap().entries.map((
|
||||||
|
MapEntry<int, OrderPositionUiModel> entry,
|
||||||
|
) {
|
||||||
|
final int index = entry.key;
|
||||||
|
final OrderPositionUiModel position = entry.value;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||||
|
child: RecurringOrderPositionCard(
|
||||||
|
index: index,
|
||||||
|
position: position,
|
||||||
|
isRemovable: positions.length > 1,
|
||||||
|
positionLabel: oneTimeLabels.positions_title,
|
||||||
|
roleLabel: oneTimeLabels.select_role,
|
||||||
|
workersLabel: oneTimeLabels.workers_label,
|
||||||
|
startLabel: oneTimeLabels.start_label,
|
||||||
|
endLabel: oneTimeLabels.end_label,
|
||||||
|
lunchLabel: oneTimeLabels.lunch_break_label,
|
||||||
|
roles: roles,
|
||||||
|
onUpdated: (OrderPositionUiModel updated) {
|
||||||
|
onPositionUpdated(index, updated);
|
||||||
|
},
|
||||||
|
onRemoved: () {
|
||||||
|
onPositionRemoved(index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// A header widget for the recurring order flow with a colored background.
|
|
||||||
class RecurringOrderHeader extends StatelessWidget {
|
|
||||||
/// Creates a [RecurringOrderHeader].
|
|
||||||
const RecurringOrderHeader({
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
required this.onBack,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The title of the page.
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
/// The subtitle or description.
|
|
||||||
final String subtitle;
|
|
||||||
|
|
||||||
/// Callback when the back button is pressed.
|
|
||||||
final VoidCallback onBack;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: MediaQuery.of(context).padding.top + UiConstants.space5,
|
|
||||||
bottom: UiConstants.space5,
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
),
|
|
||||||
color: UiColors.primary,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: onBack,
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.2),
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
UiIcons.chevronLeft,
|
|
||||||
color: UiColors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: UiTypography.headline3m.copyWith(color: UiColors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: UiTypography.footnote2r.copyWith(
|
|
||||||
color: UiColors.white.withValues(alpha: 0.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import 'package:core_localization/core_localization.dart';
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
||||||
|
|
||||||
|
import '../order_bottom_action_button.dart';
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
import '../hub_manager_selector.dart';
|
import 'recurring_order_form.dart';
|
||||||
import 'recurring_order_date_picker.dart';
|
|
||||||
import 'recurring_order_event_name_input.dart';
|
|
||||||
import 'recurring_order_header.dart';
|
|
||||||
import 'recurring_order_position_card.dart';
|
|
||||||
import 'recurring_order_section_header.dart';
|
|
||||||
import 'recurring_order_success_view.dart';
|
import 'recurring_order_success_view.dart';
|
||||||
|
|
||||||
/// The main content of the Recurring Order page.
|
/// The main content of the Recurring Order page.
|
||||||
@@ -69,7 +66,8 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position)
|
||||||
|
onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
final VoidCallback onSubmit;
|
final VoidCallback onSubmit;
|
||||||
final VoidCallback onDone;
|
final VoidCallback onDone;
|
||||||
@@ -105,412 +103,97 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: UiAppBar(
|
||||||
|
showBackButton: true,
|
||||||
|
onLeadingPressed: onBack,
|
||||||
|
title: labels.title,
|
||||||
|
subtitle: labels.subtitle,
|
||||||
|
),
|
||||||
|
body: _buildBody(context, labels, oneTimeLabels),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the main body of the Recurring Order page, including the form and handling empty vendor state.
|
||||||
|
Widget _buildBody(
|
||||||
|
BuildContext context,
|
||||||
|
TranslationsClientCreateOrderRecurringEn labels,
|
||||||
|
TranslationsClientCreateOrderOneTimeEn oneTimeLabels,
|
||||||
|
) {
|
||||||
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
if (vendors.isEmpty && status != OrderFormStatus.loading) {
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Expanded(
|
||||||
RecurringOrderHeader(
|
child: Center(
|
||||||
title: labels.title,
|
child: Column(
|
||||||
subtitle: labels.subtitle,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onBack: onBack,
|
children: <Widget>[
|
||||||
),
|
const Icon(
|
||||||
Expanded(
|
UiIcons.search,
|
||||||
child: Center(
|
size: 64,
|
||||||
child: Column(
|
color: UiColors.iconInactive,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: <Widget>[
|
const SizedBox(height: UiConstants.space4),
|
||||||
const Icon(
|
Text(
|
||||||
UiIcons.search,
|
'No Vendors Available',
|
||||||
size: 64,
|
style: UiTypography.headline3m.textPrimary,
|
||||||
color: UiColors.iconInactive,
|
),
|
||||||
),
|
const SizedBox(height: UiConstants.space2),
|
||||||
const SizedBox(height: UiConstants.space4),
|
Text(
|
||||||
Text(
|
'There are no staffing vendors associated with your account.',
|
||||||
'No Vendors Available',
|
style: UiTypography.body2r.textSecondary,
|
||||||
style: UiTypography.headline3m.textPrimary,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space2),
|
],
|
||||||
Text(
|
|
||||||
'There are no staffing vendors associated with your account.',
|
|
||||||
style: UiTypography.body2r.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Column(
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
RecurringOrderHeader(
|
|
||||||
title: labels.title,
|
|
||||||
subtitle: labels.subtitle,
|
|
||||||
onBack: onBack,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
_RecurringOrderForm(
|
|
||||||
eventName: eventName,
|
|
||||||
selectedVendor: selectedVendor,
|
|
||||||
vendors: vendors,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
recurringDays: recurringDays,
|
|
||||||
selectedHub: selectedHub,
|
|
||||||
hubs: hubs,
|
|
||||||
positions: positions,
|
|
||||||
roles: roles,
|
|
||||||
onEventNameChanged: onEventNameChanged,
|
|
||||||
onVendorChanged: onVendorChanged,
|
|
||||||
onStartDateChanged: onStartDateChanged,
|
|
||||||
onEndDateChanged: onEndDateChanged,
|
|
||||||
onDayToggled: onDayToggled,
|
|
||||||
onHubChanged: onHubChanged,
|
|
||||||
onHubManagerChanged: onHubManagerChanged,
|
|
||||||
onPositionAdded: onPositionAdded,
|
|
||||||
onPositionUpdated: onPositionUpdated,
|
|
||||||
onPositionRemoved: onPositionRemoved,
|
|
||||||
hubManagers: hubManagers,
|
|
||||||
selectedHubManager: selectedHubManager,
|
|
||||||
),
|
|
||||||
if (status == OrderFormStatus.loading)
|
|
||||||
const Center(child: CircularProgressIndicator()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_BottomActionButton(
|
|
||||||
label: status == OrderFormStatus.loading
|
|
||||||
? oneTimeLabels.creating
|
|
||||||
: oneTimeLabels.create_order,
|
|
||||||
isLoading: status == OrderFormStatus.loading,
|
|
||||||
onPressed: isValid ? onSubmit : null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecurringOrderForm extends StatelessWidget {
|
|
||||||
const _RecurringOrderForm({
|
|
||||||
required this.eventName,
|
|
||||||
required this.selectedVendor,
|
|
||||||
required this.vendors,
|
|
||||||
required this.startDate,
|
|
||||||
required this.endDate,
|
|
||||||
required this.recurringDays,
|
|
||||||
required this.selectedHub,
|
|
||||||
required this.hubs,
|
|
||||||
required this.positions,
|
|
||||||
required this.roles,
|
|
||||||
required this.onEventNameChanged,
|
|
||||||
required this.onVendorChanged,
|
|
||||||
required this.onStartDateChanged,
|
|
||||||
required this.onEndDateChanged,
|
|
||||||
required this.onDayToggled,
|
|
||||||
required this.onHubChanged,
|
|
||||||
required this.onHubManagerChanged,
|
|
||||||
required this.onPositionAdded,
|
|
||||||
required this.onPositionUpdated,
|
|
||||||
required this.onPositionRemoved,
|
|
||||||
required this.hubManagers,
|
|
||||||
required this.selectedHubManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String eventName;
|
|
||||||
final Vendor? selectedVendor;
|
|
||||||
final List<Vendor> vendors;
|
|
||||||
final DateTime startDate;
|
|
||||||
final DateTime endDate;
|
|
||||||
final List<String> recurringDays;
|
|
||||||
final OrderHubUiModel? selectedHub;
|
|
||||||
final List<OrderHubUiModel> hubs;
|
|
||||||
final List<OrderPositionUiModel> positions;
|
|
||||||
final List<OrderRoleUiModel> roles;
|
|
||||||
|
|
||||||
final ValueChanged<String> onEventNameChanged;
|
|
||||||
final ValueChanged<Vendor> onVendorChanged;
|
|
||||||
final ValueChanged<DateTime> onStartDateChanged;
|
|
||||||
final ValueChanged<DateTime> onEndDateChanged;
|
|
||||||
final ValueChanged<int> onDayToggled;
|
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
|
||||||
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
|
||||||
final VoidCallback onPositionAdded;
|
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
|
||||||
final void Function(int index) onPositionRemoved;
|
|
||||||
|
|
||||||
final List<OrderManagerUiModel> hubManagers;
|
|
||||||
final OrderManagerUiModel? selectedHubManager;
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TranslationsClientCreateOrderRecurringEn labels =
|
|
||||||
t.client_create_order.recurring;
|
|
||||||
final TranslationsClientCreateOrderOneTimeEn oneTimeLabels =
|
|
||||||
t.client_create_order.one_time;
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Expanded(
|
||||||
labels.title,
|
child: Stack(
|
||||||
style: UiTypography.headline3m.textPrimary,
|
children: <Widget>[
|
||||||
),
|
RecurringOrderForm(
|
||||||
const SizedBox(height: UiConstants.space4),
|
eventName: eventName,
|
||||||
|
selectedVendor: selectedVendor,
|
||||||
RecurringOrderEventNameInput(
|
vendors: vendors,
|
||||||
label: 'ORDER NAME',
|
startDate: startDate,
|
||||||
value: eventName,
|
endDate: endDate,
|
||||||
onChanged: onEventNameChanged,
|
recurringDays: recurringDays,
|
||||||
),
|
selectedHub: selectedHub,
|
||||||
const SizedBox(height: UiConstants.space4),
|
hubs: hubs,
|
||||||
|
positions: positions,
|
||||||
// Vendor Selection
|
roles: roles,
|
||||||
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
onEventNameChanged: onEventNameChanged,
|
||||||
const SizedBox(height: UiConstants.space2),
|
onVendorChanged: onVendorChanged,
|
||||||
Container(
|
onStartDateChanged: onStartDateChanged,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
onEndDateChanged: onEndDateChanged,
|
||||||
height: 48,
|
onDayToggled: onDayToggled,
|
||||||
decoration: BoxDecoration(
|
onHubChanged: onHubChanged,
|
||||||
color: UiColors.white,
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
borderRadius: UiConstants.radiusMd,
|
onPositionAdded: onPositionAdded,
|
||||||
border: Border.all(color: UiColors.border),
|
onPositionUpdated: onPositionUpdated,
|
||||||
),
|
onPositionRemoved: onPositionRemoved,
|
||||||
child: DropdownButtonHideUnderline(
|
hubManagers: hubManagers,
|
||||||
child: DropdownButton<Vendor>(
|
selectedHubManager: selectedHubManager,
|
||||||
isExpanded: true,
|
|
||||||
value: selectedVendor,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
),
|
||||||
onChanged: (Vendor? vendor) {
|
if (status == OrderFormStatus.loading)
|
||||||
if (vendor != null) {
|
const Center(child: CircularProgressIndicator()),
|
||||||
onVendorChanged(vendor);
|
],
|
||||||
}
|
|
||||||
},
|
|
||||||
items: vendors.map((Vendor vendor) {
|
|
||||||
return DropdownMenuItem<Vendor>(
|
|
||||||
value: vendor,
|
|
||||||
child: Text(
|
|
||||||
vendor.name,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
OrderBottomActionButton(
|
||||||
|
label: status == OrderFormStatus.loading
|
||||||
RecurringOrderDatePicker(
|
? oneTimeLabels.creating
|
||||||
label: 'Start Date',
|
: oneTimeLabels.create_order,
|
||||||
value: startDate,
|
isLoading: status == OrderFormStatus.loading,
|
||||||
onChanged: onStartDateChanged,
|
onPressed: isValid ? onSubmit : null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
RecurringOrderDatePicker(
|
|
||||||
label: 'End Date',
|
|
||||||
value: endDate,
|
|
||||||
onChanged: onEndDateChanged,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
Text('Recurring Days', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
_RecurringDaysSelector(
|
|
||||||
selectedDays: recurringDays,
|
|
||||||
onToggle: onDayToggled,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
Text('HUB', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton<OrderHubUiModel>(
|
|
||||||
isExpanded: true,
|
|
||||||
value: selectedHub,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.chevronDown,
|
|
||||||
size: 18,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
|
||||||
onChanged: (OrderHubUiModel? hub) {
|
|
||||||
if (hub != null) {
|
|
||||||
onHubChanged(hub);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: hubs.map((OrderHubUiModel hub) {
|
|
||||||
return DropdownMenuItem<OrderHubUiModel>(
|
|
||||||
value: hub,
|
|
||||||
child: Text(
|
|
||||||
hub.name,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
HubManagerSelector(
|
|
||||||
label: oneTimeLabels.hub_manager_label,
|
|
||||||
description: oneTimeLabels.hub_manager_desc,
|
|
||||||
hintText: oneTimeLabels.hub_manager_hint,
|
|
||||||
noManagersText: oneTimeLabels.hub_manager_empty,
|
|
||||||
noneText: oneTimeLabels.hub_manager_none,
|
|
||||||
managers: hubManagers,
|
|
||||||
selectedManager: selectedHubManager,
|
|
||||||
onChanged: onHubManagerChanged,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
|
|
||||||
RecurringOrderSectionHeader(
|
|
||||||
title: oneTimeLabels.positions_title,
|
|
||||||
actionLabel: oneTimeLabels.add_position,
|
|
||||||
onAction: onPositionAdded,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
|
|
||||||
// Positions List
|
|
||||||
...positions.asMap().entries.map((
|
|
||||||
MapEntry<int, OrderPositionUiModel> entry,
|
|
||||||
) {
|
|
||||||
final int index = entry.key;
|
|
||||||
final OrderPositionUiModel position = entry.value;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
|
||||||
child: RecurringOrderPositionCard(
|
|
||||||
index: index,
|
|
||||||
position: position,
|
|
||||||
isRemovable: positions.length > 1,
|
|
||||||
positionLabel: oneTimeLabels.positions_title,
|
|
||||||
roleLabel: oneTimeLabels.select_role,
|
|
||||||
workersLabel: oneTimeLabels.workers_label,
|
|
||||||
startLabel: oneTimeLabels.start_label,
|
|
||||||
endLabel: oneTimeLabels.end_label,
|
|
||||||
lunchLabel: oneTimeLabels.lunch_break_label,
|
|
||||||
roles: roles,
|
|
||||||
onUpdated: (OrderPositionUiModel updated) {
|
|
||||||
onPositionUpdated(index, updated);
|
|
||||||
},
|
|
||||||
onRemoved: () {
|
|
||||||
onPositionRemoved(index);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecurringDaysSelector extends StatelessWidget {
|
|
||||||
const _RecurringDaysSelector({
|
|
||||||
required this.selectedDays,
|
|
||||||
required this.onToggle,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<String> selectedDays;
|
|
||||||
final ValueChanged<int> onToggle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const List<String> labelsShort = <String>[
|
|
||||||
'S',
|
|
||||||
'M',
|
|
||||||
'T',
|
|
||||||
'W',
|
|
||||||
'T',
|
|
||||||
'F',
|
|
||||||
'S',
|
|
||||||
];
|
|
||||||
const List<String> labelsLong = <String>[
|
|
||||||
'SUN',
|
|
||||||
'MON',
|
|
||||||
'TUE',
|
|
||||||
'WED',
|
|
||||||
'THU',
|
|
||||||
'FRI',
|
|
||||||
'SAT',
|
|
||||||
];
|
|
||||||
return Wrap(
|
|
||||||
spacing: UiConstants.space2,
|
|
||||||
children: List<Widget>.generate(labelsShort.length, (int index) {
|
|
||||||
final bool isSelected = selectedDays.contains(labelsLong[index]);
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onToggle(index),
|
|
||||||
child: Container(
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? UiColors.primary : UiColors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
labelsShort[index],
|
|
||||||
style: UiTypography.body2m.copyWith(
|
|
||||||
color: isSelected ? UiColors.white : UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BottomActionButton extends StatelessWidget {
|
|
||||||
const _BottomActionButton({
|
|
||||||
required this.label,
|
|
||||||
required this.onPressed,
|
|
||||||
this.isLoading = false,
|
|
||||||
});
|
|
||||||
final String label;
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
final bool isLoading;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: UiConstants.space5,
|
|
||||||
right: UiConstants.space5,
|
|
||||||
top: UiConstants.space5,
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space5,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
border: Border(top: BorderSide(color: UiColors.border)),
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: UiButton.primary(
|
|
||||||
text: label,
|
|
||||||
onPressed: isLoading ? null : onPressed,
|
|
||||||
size: UiButtonSize.large,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user