Merge branch '208-p0-auth-05-get-started-screen' into rapid_order_client
This commit is contained in:
@@ -8,7 +8,7 @@ import 'client_create_order_state.dart';
|
||||
class ClientCreateOrderBloc
|
||||
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
|
||||
ClientCreateOrderBloc(this._getOrderTypesUseCase)
|
||||
: super(const ClientCreateOrderInitial()) {
|
||||
: super(const ClientCreateOrderInitial()) {
|
||||
on<ClientCreateOrderTypesRequested>(_onTypesRequested);
|
||||
}
|
||||
final GetOrderTypesUseCase _getOrderTypesUseCase;
|
||||
|
||||
@@ -8,16 +8,71 @@ import 'one_time_order_state.dart';
|
||||
/// BLoC for managing the multi-step one-time order creation form.
|
||||
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
OneTimeOrderBloc(this._createOneTimeOrderUseCase)
|
||||
: super(OneTimeOrderState.initial()) {
|
||||
: super(OneTimeOrderState.initial()) {
|
||||
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
|
||||
on<OneTimeOrderVendorChanged>(_onVendorChanged);
|
||||
on<OneTimeOrderDateChanged>(_onDateChanged);
|
||||
on<OneTimeOrderLocationChanged>(_onLocationChanged);
|
||||
on<OneTimeOrderPositionAdded>(_onPositionAdded);
|
||||
on<OneTimeOrderPositionRemoved>(_onPositionRemoved);
|
||||
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
|
||||
on<OneTimeOrderSubmitted>(_onSubmitted);
|
||||
|
||||
// Initial load of mock vendors
|
||||
add(
|
||||
const OneTimeOrderVendorsLoaded(<Vendor>[
|
||||
Vendor(
|
||||
id: 'v1',
|
||||
name: 'Elite Staffing',
|
||||
rates: <String, double>{
|
||||
'Server': 25.0,
|
||||
'Bartender': 30.0,
|
||||
'Cook': 28.0,
|
||||
'Busser': 18.0,
|
||||
'Host': 20.0,
|
||||
'Barista': 22.0,
|
||||
'Dishwasher': 17.0,
|
||||
'Event Staff': 19.0,
|
||||
},
|
||||
),
|
||||
Vendor(
|
||||
id: 'v2',
|
||||
name: 'Premier Workforce',
|
||||
rates: <String, double>{
|
||||
'Server': 22.0,
|
||||
'Bartender': 28.0,
|
||||
'Cook': 25.0,
|
||||
'Busser': 16.0,
|
||||
'Host': 18.0,
|
||||
'Barista': 20.0,
|
||||
'Dishwasher': 15.0,
|
||||
'Event Staff': 18.0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
|
||||
|
||||
void _onVendorsLoaded(
|
||||
OneTimeOrderVendorsLoaded event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
vendors: event.vendors,
|
||||
selectedVendor: event.vendors.isNotEmpty ? event.vendors.first : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onVendorChanged(
|
||||
OneTimeOrderVendorChanged event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) {
|
||||
emit(state.copyWith(selectedVendor: event.vendor));
|
||||
}
|
||||
|
||||
void _onDateChanged(
|
||||
OneTimeOrderDateChanged event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
@@ -37,13 +92,14 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) {
|
||||
final List<OneTimeOrderPosition> newPositions =
|
||||
List<OneTimeOrderPosition>.from(state.positions)
|
||||
..add(const OneTimeOrderPosition(
|
||||
List<OneTimeOrderPosition>.from(state.positions)..add(
|
||||
const OneTimeOrderPosition(
|
||||
role: '',
|
||||
count: 1,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
));
|
||||
startTime: '09:00',
|
||||
endTime: '17:00',
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(positions: newPositions));
|
||||
}
|
||||
|
||||
@@ -79,14 +135,17 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
date: state.date,
|
||||
location: state.location,
|
||||
positions: state.positions,
|
||||
// In a real app, we'd pass the vendorId here
|
||||
);
|
||||
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.success));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: OneTimeOrderStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OneTimeOrderStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,22 @@ abstract class OneTimeOrderEvent extends Equatable {
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class OneTimeOrderVendorsLoaded extends OneTimeOrderEvent {
|
||||
const OneTimeOrderVendorsLoaded(this.vendors);
|
||||
final List<Vendor> vendors;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[vendors];
|
||||
}
|
||||
|
||||
class OneTimeOrderVendorChanged extends OneTimeOrderEvent {
|
||||
const OneTimeOrderVendorChanged(this.vendor);
|
||||
final Vendor vendor;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[vendor];
|
||||
}
|
||||
|
||||
class OneTimeOrderDateChanged extends OneTimeOrderEvent {
|
||||
const OneTimeOrderDateChanged(this.date);
|
||||
final DateTime date;
|
||||
|
||||
@@ -10,6 +10,8 @@ class OneTimeOrderState extends Equatable {
|
||||
required this.positions,
|
||||
this.status = OneTimeOrderStatus.initial,
|
||||
this.errorMessage,
|
||||
this.vendors = const <Vendor>[],
|
||||
this.selectedVendor,
|
||||
});
|
||||
|
||||
factory OneTimeOrderState.initial() {
|
||||
@@ -17,13 +19,9 @@ class OneTimeOrderState extends Equatable {
|
||||
date: DateTime.now(),
|
||||
location: '',
|
||||
positions: const <OneTimeOrderPosition>[
|
||||
OneTimeOrderPosition(
|
||||
role: '',
|
||||
count: 1,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
),
|
||||
OneTimeOrderPosition(role: '', count: 1, startTime: '', endTime: ''),
|
||||
],
|
||||
vendors: const <Vendor>[],
|
||||
);
|
||||
}
|
||||
final DateTime date;
|
||||
@@ -31,6 +29,8 @@ class OneTimeOrderState extends Equatable {
|
||||
final List<OneTimeOrderPosition> positions;
|
||||
final OneTimeOrderStatus status;
|
||||
final String? errorMessage;
|
||||
final List<Vendor> vendors;
|
||||
final Vendor? selectedVendor;
|
||||
|
||||
OneTimeOrderState copyWith({
|
||||
DateTime? date,
|
||||
@@ -38,6 +38,8 @@ class OneTimeOrderState extends Equatable {
|
||||
List<OneTimeOrderPosition>? positions,
|
||||
OneTimeOrderStatus? status,
|
||||
String? errorMessage,
|
||||
List<Vendor>? vendors,
|
||||
Vendor? selectedVendor,
|
||||
}) {
|
||||
return OneTimeOrderState(
|
||||
date: date ?? this.date,
|
||||
@@ -45,15 +47,19 @@ class OneTimeOrderState extends Equatable {
|
||||
positions: positions ?? this.positions,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
vendors: vendors ?? this.vendors,
|
||||
selectedVendor: selectedVendor ?? this.selectedVendor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
date,
|
||||
location,
|
||||
positions,
|
||||
status,
|
||||
errorMessage,
|
||||
];
|
||||
date,
|
||||
location,
|
||||
positions,
|
||||
status,
|
||||
errorMessage,
|
||||
vendors,
|
||||
selectedVendor,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import 'rapid_order_state.dart';
|
||||
/// BLoC for managing the rapid (urgent) order creation flow.
|
||||
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||
RapidOrderBloc(this._createRapidOrderUseCase)
|
||||
: super(
|
||||
const RapidOrderInitial(
|
||||
examples: <String>[
|
||||
'"We had a call out. Need 2 cooks ASAP"',
|
||||
'"Need 5 bartenders ASAP until 5am"',
|
||||
'"Emergency! Need 3 servers right now till midnight"',
|
||||
],
|
||||
),
|
||||
) {
|
||||
: super(
|
||||
const RapidOrderInitial(
|
||||
examples: <String>[
|
||||
'"We had a call out. Need 2 cooks ASAP"',
|
||||
'"Need 5 bartenders ASAP until 5am"',
|
||||
'"Emergency! Need 3 servers right now till midnight"',
|
||||
],
|
||||
),
|
||||
) {
|
||||
on<RapidOrderMessageChanged>(_onMessageChanged);
|
||||
on<RapidOrderVoiceToggled>(_onVoiceToggled);
|
||||
on<RapidOrderSubmitted>(_onSubmitted);
|
||||
@@ -68,7 +68,8 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||
|
||||
try {
|
||||
await _createRapidOrderUseCase(
|
||||
RapidOrderArguments(description: message));
|
||||
RapidOrderArguments(description: message),
|
||||
);
|
||||
emit(const RapidOrderSuccess());
|
||||
} catch (e) {
|
||||
emit(RapidOrderFailure(e.toString()));
|
||||
|
||||
@@ -17,8 +17,9 @@ class ClientCreateOrderPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientCreateOrderBloc>(
|
||||
create: (BuildContext context) => Modular.get<ClientCreateOrderBloc>()
|
||||
..add(const ClientCreateOrderTypesRequested()),
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<ClientCreateOrderBloc>()
|
||||
..add(const ClientCreateOrderTypesRequested()),
|
||||
child: const CreateOrderView(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,59 +65,58 @@ class CreateOrderView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocBuilder<ClientCreateOrderBloc, ClientCreateOrderState>(
|
||||
child: BlocBuilder<ClientCreateOrderBloc, ClientCreateOrderState>(
|
||||
builder:
|
||||
(BuildContext context, ClientCreateOrderState state) {
|
||||
if (state is ClientCreateOrderLoadSuccess) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: UiConstants.space4,
|
||||
crossAxisSpacing: UiConstants.space4,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: state.orderTypes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final OrderType type = state.orderTypes[index];
|
||||
final OrderTypeUiMetadata ui =
|
||||
OrderTypeUiMetadata.fromId(id: type.id);
|
||||
if (state is ClientCreateOrderLoadSuccess) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: UiConstants.space4,
|
||||
crossAxisSpacing: UiConstants.space4,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: state.orderTypes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final OrderType type = state.orderTypes[index];
|
||||
final OrderTypeUiMetadata ui =
|
||||
OrderTypeUiMetadata.fromId(id: type.id);
|
||||
|
||||
return OrderTypeCard(
|
||||
icon: ui.icon,
|
||||
title: _getTranslation(key: type.titleKey),
|
||||
description: _getTranslation(
|
||||
key: type.descriptionKey,
|
||||
),
|
||||
backgroundColor: ui.backgroundColor,
|
||||
borderColor: ui.borderColor,
|
||||
iconBackgroundColor: ui.iconBackgroundColor,
|
||||
iconColor: ui.iconColor,
|
||||
textColor: ui.textColor,
|
||||
descriptionColor: ui.descriptionColor,
|
||||
onTap: () {
|
||||
switch (type.id) {
|
||||
case 'rapid':
|
||||
Modular.to.pushRapidOrder();
|
||||
break;
|
||||
case 'one-time':
|
||||
Modular.to.pushOneTimeOrder();
|
||||
break;
|
||||
case 'recurring':
|
||||
Modular.to.pushRecurringOrder();
|
||||
break;
|
||||
case 'permanent':
|
||||
Modular.to.pushPermanentOrder();
|
||||
break;
|
||||
}
|
||||
return OrderTypeCard(
|
||||
icon: ui.icon,
|
||||
title: _getTranslation(key: type.titleKey),
|
||||
description: _getTranslation(
|
||||
key: type.descriptionKey,
|
||||
),
|
||||
backgroundColor: ui.backgroundColor,
|
||||
borderColor: ui.borderColor,
|
||||
iconBackgroundColor: ui.iconBackgroundColor,
|
||||
iconColor: ui.iconColor,
|
||||
textColor: ui.textColor,
|
||||
descriptionColor: ui.descriptionColor,
|
||||
onTap: () {
|
||||
switch (type.id) {
|
||||
case 'rapid':
|
||||
Modular.to.pushRapidOrder();
|
||||
break;
|
||||
case 'one-time':
|
||||
Modular.to.pushOneTimeOrder();
|
||||
break;
|
||||
case 'recurring':
|
||||
Modular.to.pushRecurringOrder();
|
||||
break;
|
||||
case 'permanent':
|
||||
Modular.to.pushPermanentOrder();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -54,9 +54,7 @@ class OneTimeOrderHeader extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.headline3m.copyWith(
|
||||
color: UiColors.white,
|
||||
),
|
||||
style: UiTypography.headline3m.copyWith(color: UiColors.white),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
|
||||
@@ -19,6 +19,7 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
required this.startLabel,
|
||||
required this.endLabel,
|
||||
required this.lunchLabel,
|
||||
this.vendor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -55,6 +56,9 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
/// Label for the lunch break.
|
||||
final String lunchLabel;
|
||||
|
||||
/// The current selected vendor to determine rates.
|
||||
final Vendor? vendor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -99,8 +103,10 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
isExpanded: true,
|
||||
hint:
|
||||
Text(roleLabel, style: UiTypography.body2r.textPlaceholder),
|
||||
hint: Text(
|
||||
roleLabel,
|
||||
style: UiTypography.body2r.textPlaceholder,
|
||||
),
|
||||
value: position.role.isEmpty ? null : position.role,
|
||||
icon: const Icon(
|
||||
UiIcons.chevronDown,
|
||||
@@ -112,26 +118,26 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
onUpdated(position.copyWith(role: val));
|
||||
}
|
||||
},
|
||||
items: <String>[
|
||||
'Server',
|
||||
'Bartender',
|
||||
'Cook',
|
||||
'Busser',
|
||||
'Host',
|
||||
'Barista',
|
||||
'Dishwasher',
|
||||
'Event Staff'
|
||||
].map((String role) {
|
||||
// Mock rates for UI matching
|
||||
final int rate = _getMockRate(role);
|
||||
return DropdownMenuItem<String>(
|
||||
value: role,
|
||||
child: Text(
|
||||
'$role - \$$rate/hr',
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
items:
|
||||
<String>{
|
||||
...(vendor?.rates.keys ?? <String>[]),
|
||||
if (position.role.isNotEmpty &&
|
||||
!(vendor?.rates.keys.contains(position.role) ??
|
||||
false))
|
||||
position.role,
|
||||
}.map((String role) {
|
||||
final double? rate = vendor?.rates[role];
|
||||
final String label = rate == null
|
||||
? role
|
||||
: '$role - \$${rate.toStringAsFixed(0)}/hr';
|
||||
return DropdownMenuItem<String>(
|
||||
value: role,
|
||||
child: Text(
|
||||
label,
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -153,7 +159,8 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
);
|
||||
if (picked != null && context.mounted) {
|
||||
onUpdated(
|
||||
position.copyWith(startTime: picked.format(context)));
|
||||
position.copyWith(startTime: picked.format(context)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -172,7 +179,8 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
);
|
||||
if (picked != null && context.mounted) {
|
||||
onUpdated(
|
||||
position.copyWith(endTime: picked.format(context)));
|
||||
position.copyWith(endTime: picked.format(context)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -198,10 +206,13 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => onUpdated(position.copyWith(
|
||||
count: (position.count > 1)
|
||||
? position.count - 1
|
||||
: 1)),
|
||||
onTap: () {
|
||||
if (position.count > 1) {
|
||||
onUpdated(
|
||||
position.copyWith(count: position.count - 1),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Icon(UiIcons.minus, size: 12),
|
||||
),
|
||||
Text(
|
||||
@@ -209,8 +220,11 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => onUpdated(
|
||||
position.copyWith(count: position.count + 1)),
|
||||
onTap: () {
|
||||
onUpdated(
|
||||
position.copyWith(count: position.count + 1),
|
||||
);
|
||||
},
|
||||
child: const Icon(UiIcons.add, size: 12),
|
||||
),
|
||||
],
|
||||
@@ -223,74 +237,12 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Optional Location Override
|
||||
if (position.location == null)
|
||||
GestureDetector(
|
||||
onTap: () => onUpdated(position.copyWith(location: '')),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.mapPin, size: 14, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
t.client_create_order.one_time.different_location,
|
||||
style: UiTypography.footnote1m.copyWith(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.mapPin,
|
||||
size: 14, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
t.client_create_order.one_time
|
||||
.different_location_title,
|
||||
style: UiTypography.footnote1m.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => onUpdated(position.copyWith(location: null)),
|
||||
child: const Icon(
|
||||
UiIcons.close,
|
||||
size: 14,
|
||||
color: UiColors.destructive,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
_PositionLocationInput(
|
||||
value: position.location ?? '',
|
||||
onChanged: (String val) =>
|
||||
onUpdated(position.copyWith(location: val)),
|
||||
hintText:
|
||||
t.client_create_order.one_time.different_location_hint,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Lunch Break
|
||||
Text(
|
||||
lunchLabel,
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
Text(lunchLabel, style: UiTypography.footnote2r.textSecondary),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
border: Border.all(color: UiColors.border),
|
||||
@@ -309,43 +261,15 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
onUpdated(position.copyWith(lunchBreak: val));
|
||||
}
|
||||
},
|
||||
items: <DropdownMenuItem<int>>[
|
||||
DropdownMenuItem<int>(
|
||||
value: 0,
|
||||
child: Text(t.client_create_order.one_time.no_break,
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 10,
|
||||
items: <int>[0, 15, 30, 45, 60].map((int mins) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: mins,
|
||||
child: Text(
|
||||
'10 ${t.client_create_order.one_time.paid_break}',
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 15,
|
||||
child: Text(
|
||||
'15 ${t.client_create_order.one_time.paid_break}',
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 30,
|
||||
child: Text(
|
||||
'30 ${t.client_create_order.one_time.unpaid_break}',
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 45,
|
||||
child: Text(
|
||||
'45 ${t.client_create_order.one_time.unpaid_break}',
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 60,
|
||||
child: Text(
|
||||
'60 ${t.client_create_order.one_time.unpaid_break}',
|
||||
style: UiTypography.body2r.textPrimary),
|
||||
),
|
||||
],
|
||||
mins == 0 ? 'No Break' : '$mins mins',
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -360,83 +284,37 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
required String value,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return UiTextField(
|
||||
label: label,
|
||||
controller: TextEditingController(text: value),
|
||||
readOnly: true,
|
||||
onTap: onTap,
|
||||
hintText: '--:--',
|
||||
);
|
||||
}
|
||||
|
||||
int _getMockRate(String role) {
|
||||
switch (role) {
|
||||
case 'Server':
|
||||
return 18;
|
||||
case 'Bartender':
|
||||
return 22;
|
||||
case 'Cook':
|
||||
return 20;
|
||||
case 'Busser':
|
||||
return 16;
|
||||
case 'Host':
|
||||
return 17;
|
||||
case 'Barista':
|
||||
return 16;
|
||||
case 'Dishwasher':
|
||||
return 15;
|
||||
case 'Event Staff':
|
||||
return 20;
|
||||
default:
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PositionLocationInput extends StatefulWidget {
|
||||
const _PositionLocationInput({
|
||||
required this.value,
|
||||
required this.hintText,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final String value;
|
||||
final String hintText;
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
@override
|
||||
State<_PositionLocationInput> createState() => _PositionLocationInputState();
|
||||
}
|
||||
|
||||
class _PositionLocationInputState extends State<_PositionLocationInput> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(text: widget.value);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_PositionLocationInput oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.value != _controller.text) {
|
||||
_controller.text = widget.value;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiTextField(
|
||||
controller: _controller,
|
||||
onChanged: widget.onChanged,
|
||||
hintText: widget.hintText,
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(label, style: UiTypography.footnote2r.textSecondary),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
value.isEmpty ? '--:--' : value,
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
),
|
||||
const Icon(
|
||||
UiIcons.clock,
|
||||
size: 14,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,28 @@ class OneTimeOrderSectionHeader extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(title, style: UiTypography.headline4m.textPrimary),
|
||||
if (actionLabel != null && onAction != null)
|
||||
UiButton.text(
|
||||
TextButton(
|
||||
onPressed: onAction,
|
||||
leadingIcon: UiIcons.add,
|
||||
text: actionLabel!,
|
||||
iconSize: 16,
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: const Size(0, 24),
|
||||
maximumSize: const Size(0, 24),
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.add, size: 16, color: Color(0xFF0032A0)),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
actionLabel!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF0032A0),
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
FontWeight.w500, // Added to match typical button text
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -35,6 +35,47 @@ class OneTimeOrderView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
if (state.vendors.isEmpty &&
|
||||
state.status != OneTimeOrderStatus.loading) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.bgPrimary,
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
OneTimeOrderHeader(
|
||||
title: labels.title,
|
||||
subtitle: labels.subtitle,
|
||||
onBack: () => Modular.to.pop(),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.search,
|
||||
size: 64,
|
||||
color: UiColors.iconInactive,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
'No Vendors Available',
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
'There are no staffing vendors associated with your account.',
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.bgPrimary,
|
||||
body: Column(
|
||||
@@ -58,8 +99,9 @@ class OneTimeOrderView extends StatelessWidget {
|
||||
? labels.creating
|
||||
: labels.create_order,
|
||||
isLoading: state.status == OneTimeOrderStatus.loading,
|
||||
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(context)
|
||||
.add(const OneTimeOrderSubmitted()),
|
||||
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(const OneTimeOrderSubmitted()),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -87,37 +129,78 @@ class _OneTimeOrderForm extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Vendor Selection
|
||||
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
|
||||
const SizedBox(height: 8),
|
||||
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: state.selectedVendor,
|
||||
icon: const Icon(
|
||||
UiIcons.chevronDown,
|
||||
size: 18,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onChanged: (Vendor? vendor) {
|
||||
if (vendor != null) {
|
||||
BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(OneTimeOrderVendorChanged(vendor));
|
||||
}
|
||||
},
|
||||
items: state.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: state.date,
|
||||
onChanged: (DateTime date) =>
|
||||
BlocProvider.of<OneTimeOrderBloc>(context)
|
||||
.add(OneTimeOrderDateChanged(date)),
|
||||
onChanged: (DateTime date) => BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(OneTimeOrderDateChanged(date)),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
OneTimeOrderLocationInput(
|
||||
label: labels.location_label,
|
||||
value: state.location,
|
||||
onChanged: (String location) =>
|
||||
BlocProvider.of<OneTimeOrderBloc>(context)
|
||||
.add(OneTimeOrderLocationChanged(location)),
|
||||
onChanged: (String location) => BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(OneTimeOrderLocationChanged(location)),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
OneTimeOrderSectionHeader(
|
||||
title: labels.positions_title,
|
||||
actionLabel: labels.add_position,
|
||||
onAction: () => BlocProvider.of<OneTimeOrderBloc>(context)
|
||||
.add(const OneTimeOrderPositionAdded()),
|
||||
onAction: () => BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(const OneTimeOrderPositionAdded()),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Positions List
|
||||
...state.positions
|
||||
.asMap()
|
||||
.entries
|
||||
.map((MapEntry<int, OneTimeOrderPosition> entry) {
|
||||
...state.positions.asMap().entries.map((
|
||||
MapEntry<int, OneTimeOrderPosition> entry,
|
||||
) {
|
||||
final int index = entry.key;
|
||||
final OneTimeOrderPosition position = entry.value;
|
||||
return Padding(
|
||||
@@ -132,14 +215,16 @@ class _OneTimeOrderForm extends StatelessWidget {
|
||||
startLabel: labels.start_label,
|
||||
endLabel: labels.end_label,
|
||||
lunchLabel: labels.lunch_break_label,
|
||||
vendor: state.selectedVendor,
|
||||
onUpdated: (OneTimeOrderPosition updated) {
|
||||
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||
OneTimeOrderPositionUpdated(index, updated),
|
||||
);
|
||||
BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(OneTimeOrderPositionUpdated(index, updated));
|
||||
},
|
||||
onRemoved: () {
|
||||
BlocProvider.of<OneTimeOrderBloc>(context)
|
||||
.add(OneTimeOrderPositionRemoved(index));
|
||||
BlocProvider.of<OneTimeOrderBloc>(
|
||||
context,
|
||||
).add(OneTimeOrderPositionRemoved(index));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -73,16 +73,14 @@ class OrderTypeCard extends StatelessWidget {
|
||||
),
|
||||
child: Icon(icon, color: iconColor, size: 24),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.body2b.copyWith(color: textColor),
|
||||
),
|
||||
Text(title, style: UiTypography.body2b.copyWith(color: textColor)),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Expanded(
|
||||
child: Text(
|
||||
description,
|
||||
style:
|
||||
UiTypography.footnote1r.copyWith(color: descriptionColor),
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: descriptionColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -47,10 +47,7 @@ class RapidOrderExampleCard extends StatelessWidget {
|
||||
text: TextSpan(
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
children: <InlineSpan>[
|
||||
TextSpan(
|
||||
text: label,
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
),
|
||||
TextSpan(text: label, style: UiTypography.body2b.textPrimary),
|
||||
TextSpan(text: ' $example'),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -74,11 +74,7 @@ class RapidOrderHeader extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.zap,
|
||||
color: UiColors.accent,
|
||||
size: 18,
|
||||
),
|
||||
const Icon(UiIcons.zap, color: UiColors.accent, size: 18),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
title,
|
||||
|
||||
@@ -42,8 +42,9 @@ class RapidOrderSuccessView extends StatelessWidget {
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: Container(
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: UiConstants.space10),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space10,
|
||||
),
|
||||
padding: const EdgeInsets.all(UiConstants.space8),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
@@ -75,10 +76,7 @@ class RapidOrderSuccessView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.headline1m.textPrimary,
|
||||
),
|
||||
Text(title, style: UiTypography.headline1m.textPrimary),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Text(
|
||||
message,
|
||||
|
||||
@@ -153,27 +153,27 @@ class _RapidOrderFormState extends State<_RapidOrderForm> {
|
||||
|
||||
// Examples
|
||||
if (initialState != null)
|
||||
...initialState.examples
|
||||
.asMap()
|
||||
.entries
|
||||
.map((MapEntry<int, String> entry) {
|
||||
...initialState.examples.asMap().entries.map((
|
||||
MapEntry<int, String> entry,
|
||||
) {
|
||||
final int index = entry.key;
|
||||
final String example = entry.value;
|
||||
final bool isHighlighted = index == 0;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space2),
|
||||
bottom: UiConstants.space2,
|
||||
),
|
||||
child: RapidOrderExampleCard(
|
||||
example: example,
|
||||
isHighlighted: isHighlighted,
|
||||
label: labels.example,
|
||||
onTap: () =>
|
||||
BlocProvider.of<RapidOrderBloc>(
|
||||
context)
|
||||
.add(
|
||||
RapidOrderExampleSelected(example),
|
||||
),
|
||||
context,
|
||||
).add(
|
||||
RapidOrderExampleSelected(example),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
@@ -184,9 +184,9 @@ class _RapidOrderFormState extends State<_RapidOrderForm> {
|
||||
controller: _messageController,
|
||||
maxLines: 4,
|
||||
onChanged: (String value) {
|
||||
BlocProvider.of<RapidOrderBloc>(context).add(
|
||||
RapidOrderMessageChanged(value),
|
||||
);
|
||||
BlocProvider.of<RapidOrderBloc>(
|
||||
context,
|
||||
).add(RapidOrderMessageChanged(value));
|
||||
},
|
||||
hintText: labels.hint,
|
||||
),
|
||||
@@ -197,7 +197,8 @@ class _RapidOrderFormState extends State<_RapidOrderForm> {
|
||||
labels: labels,
|
||||
isSubmitting: isSubmitting,
|
||||
isListening: initialState?.isListening ?? false,
|
||||
isMessageEmpty: initialState != null &&
|
||||
isMessageEmpty:
|
||||
initialState != null &&
|
||||
initialState.message.trim().isEmpty,
|
||||
),
|
||||
],
|
||||
@@ -242,11 +243,7 @@ class _AnimatedZapIcon extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
UiIcons.zap,
|
||||
color: UiColors.white,
|
||||
size: 32,
|
||||
),
|
||||
child: const Icon(UiIcons.zap, color: UiColors.white, size: 32),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -271,9 +268,9 @@ class _RapidOrderActions extends StatelessWidget {
|
||||
child: UiButton.secondary(
|
||||
text: isListening ? labels.listening : labels.speak,
|
||||
leadingIcon: UiIcons.bell, // Placeholder for mic
|
||||
onPressed: () => BlocProvider.of<RapidOrderBloc>(context).add(
|
||||
const RapidOrderVoiceToggled(),
|
||||
),
|
||||
onPressed: () => BlocProvider.of<RapidOrderBloc>(
|
||||
context,
|
||||
).add(const RapidOrderVoiceToggled()),
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: isListening
|
||||
? UiColors.destructive.withValues(alpha: 0.05)
|
||||
@@ -291,9 +288,9 @@ class _RapidOrderActions extends StatelessWidget {
|
||||
trailingIcon: UiIcons.arrowRight,
|
||||
onPressed: isSubmitting || isMessageEmpty
|
||||
? null
|
||||
: () => BlocProvider.of<RapidOrderBloc>(context).add(
|
||||
const RapidOrderSubmitted(),
|
||||
),
|
||||
: () => BlocProvider.of<RapidOrderBloc>(
|
||||
context,
|
||||
).add(const RapidOrderSubmitted()),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user