diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/client_create_order_bloc.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/client_create_order_bloc.dart index ddb2ff8e..4ce8b483 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/client_create_order_bloc.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/client_create_order_bloc.dart @@ -8,7 +8,7 @@ import 'client_create_order_state.dart'; class ClientCreateOrderBloc extends Bloc { ClientCreateOrderBloc(this._getOrderTypesUseCase) - : super(const ClientCreateOrderInitial()) { + : super(const ClientCreateOrderInitial()) { on(_onTypesRequested); } final GetOrderTypesUseCase _getOrderTypesUseCase; diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_bloc.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_bloc.dart index c2db55cb..8ea45002 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_bloc.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_bloc.dart @@ -8,7 +8,7 @@ import 'one_time_order_state.dart'; /// BLoC for managing the multi-step one-time order creation form. class OneTimeOrderBloc extends Bloc { OneTimeOrderBloc(this._createOneTimeOrderUseCase) - : super(OneTimeOrderState.initial()) { + : super(OneTimeOrderState.initial()) { on(_onDateChanged); on(_onLocationChanged); on(_onPositionAdded); @@ -37,13 +37,14 @@ class OneTimeOrderBloc extends Bloc { Emitter emit, ) { final List newPositions = - List.from(state.positions) - ..add(const OneTimeOrderPosition( + List.from(state.positions)..add( + const OneTimeOrderPosition( role: '', count: 1, startTime: '', endTime: '', - )); + ), + ); emit(state.copyWith(positions: newPositions)); } @@ -83,10 +84,12 @@ class OneTimeOrderBloc extends Bloc { 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(), + ), + ); } } } diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_state.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_state.dart index 2ef862f6..2f286262 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_state.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/one_time_order_state.dart @@ -17,12 +17,7 @@ class OneTimeOrderState extends Equatable { date: DateTime.now(), location: '', positions: const [ - OneTimeOrderPosition( - role: '', - count: 1, - startTime: '', - endTime: '', - ), + OneTimeOrderPosition(role: '', count: 1, startTime: '', endTime: ''), ], ); } @@ -50,10 +45,10 @@ class OneTimeOrderState extends Equatable { @override List get props => [ - date, - location, - positions, - status, - errorMessage, - ]; + date, + location, + positions, + status, + errorMessage, + ]; } diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/rapid_order_bloc.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/rapid_order_bloc.dart index 820baa04..f3b3b63b 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/rapid_order_bloc.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/blocs/rapid_order_bloc.dart @@ -7,15 +7,15 @@ import 'rapid_order_state.dart'; /// BLoC for managing the rapid (urgent) order creation flow. class RapidOrderBloc extends Bloc { RapidOrderBloc(this._createRapidOrderUseCase) - : super( - const RapidOrderInitial( - examples: [ - '"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: [ + '"We had a call out. Need 2 cooks ASAP"', + '"Need 5 bartenders ASAP until 5am"', + '"Emergency! Need 3 servers right now till midnight"', + ], + ), + ) { on(_onMessageChanged); on(_onVoiceToggled); on(_onSubmitted); @@ -68,7 +68,8 @@ class RapidOrderBloc extends Bloc { try { await _createRapidOrderUseCase( - RapidOrderArguments(description: message)); + RapidOrderArguments(description: message), + ); emit(const RapidOrderSuccess()); } catch (e) { emit(RapidOrderFailure(e.toString())); diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/pages/create_order_page.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/pages/create_order_page.dart index 9660439f..641363e2 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/pages/create_order_page.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/pages/create_order_page.dart @@ -17,8 +17,9 @@ class ClientCreateOrderPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => Modular.get() - ..add(const ClientCreateOrderTypesRequested()), + create: (BuildContext context) => + Modular.get() + ..add(const ClientCreateOrderTypesRequested()), child: const CreateOrderView(), ); } diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/create_order/create_order_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/create_order/create_order_view.dart index bc007565..eb1775fb 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/create_order/create_order_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/create_order/create_order_view.dart @@ -65,59 +65,58 @@ class CreateOrderView extends StatelessWidget { ), ), Expanded( - child: - BlocBuilder( + child: BlocBuilder( 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()); + }, ), ), ], diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_header.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_header.dart index 3dbf2a38..d39f6c8b 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_header.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_header.dart @@ -54,9 +54,7 @@ class OneTimeOrderHeader extends StatelessWidget { children: [ Text( title, - style: UiTypography.headline3m.copyWith( - color: UiColors.white, - ), + style: UiTypography.headline3m.copyWith(color: UiColors.white), ), Text( subtitle, diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_position_card.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_position_card.dart index 4b24cdfb..ec2797ac 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_position_card.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_position_card.dart @@ -99,8 +99,10 @@ class OneTimeOrderPositionCard extends StatelessWidget { child: DropdownButtonHideUnderline( child: DropdownButton( 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 +114,27 @@ class OneTimeOrderPositionCard extends StatelessWidget { onUpdated(position.copyWith(role: val)); } }, - items: [ - 'Server', - 'Bartender', - 'Cook', - 'Busser', - 'Host', - 'Barista', - 'Dishwasher', - 'Event Staff' - ].map((String role) { - // Mock rates for UI matching - final int rate = _getMockRate(role); - return DropdownMenuItem( - value: role, - child: Text( - '$role - \$$rate/hr', - style: UiTypography.body2r.textPrimary, - ), - ); - }).toList(), + items: + [ + 'Server', + 'Bartender', + 'Cook', + 'Busser', + 'Host', + 'Barista', + 'Dishwasher', + 'Event Staff', + ].map((String role) { + // Mock rates for UI matching + final int rate = _getMockRate(role); + return DropdownMenuItem( + value: role, + child: Text( + '$role - \$$rate/hr', + style: UiTypography.body2r.textPrimary, + ), + ); + }).toList(), ), ), ), @@ -153,7 +156,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 +176,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 +203,13 @@ class OneTimeOrderPositionCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ GestureDetector( - onTap: () => onUpdated(position.copyWith( + onTap: () => onUpdated( + position.copyWith( count: (position.count > 1) ? position.count - 1 - : 1)), + : 1, + ), + ), child: const Icon(UiIcons.minus, size: 12), ), Text( @@ -210,7 +218,8 @@ class OneTimeOrderPositionCard extends StatelessWidget { ), GestureDetector( onTap: () => onUpdated( - position.copyWith(count: position.count + 1)), + position.copyWith(count: position.count + 1), + ), child: const Icon(UiIcons.add, size: 12), ), ], @@ -249,11 +258,16 @@ class OneTimeOrderPositionCard extends StatelessWidget { children: [ Row( children: [ - const Icon(UiIcons.mapPin, - size: 14, color: UiColors.iconSecondary), + const Icon( + UiIcons.mapPin, + size: 14, + color: UiColors.iconSecondary, + ), const SizedBox(width: UiConstants.space1), Text( - t.client_create_order.one_time + t + .client_create_order + .one_time .different_location_title, style: UiTypography.footnote1m.textSecondary, ), @@ -283,10 +297,7 @@ class OneTimeOrderPositionCard extends StatelessWidget { 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, @@ -312,38 +323,45 @@ class OneTimeOrderPositionCard extends StatelessWidget { items: >[ DropdownMenuItem( value: 0, - child: Text(t.client_create_order.one_time.no_break, - style: UiTypography.body2r.textPrimary), + child: Text( + t.client_create_order.one_time.no_break, + style: UiTypography.body2r.textPrimary, + ), ), DropdownMenuItem( value: 10, child: Text( - '10 ${t.client_create_order.one_time.paid_break}', - style: UiTypography.body2r.textPrimary), + '10 ${t.client_create_order.one_time.paid_break}', + style: UiTypography.body2r.textPrimary, + ), ), DropdownMenuItem( value: 15, child: Text( - '15 ${t.client_create_order.one_time.paid_break}', - style: UiTypography.body2r.textPrimary), + '15 ${t.client_create_order.one_time.paid_break}', + style: UiTypography.body2r.textPrimary, + ), ), DropdownMenuItem( value: 30, child: Text( - '30 ${t.client_create_order.one_time.unpaid_break}', - style: UiTypography.body2r.textPrimary), + '30 ${t.client_create_order.one_time.unpaid_break}', + style: UiTypography.body2r.textPrimary, + ), ), DropdownMenuItem( value: 45, child: Text( - '45 ${t.client_create_order.one_time.unpaid_break}', - style: UiTypography.body2r.textPrimary), + '45 ${t.client_create_order.one_time.unpaid_break}', + style: UiTypography.body2r.textPrimary, + ), ), DropdownMenuItem( value: 60, child: Text( - '60 ${t.client_create_order.one_time.unpaid_break}', - style: UiTypography.body2r.textPrimary), + '60 ${t.client_create_order.one_time.unpaid_break}', + style: UiTypography.body2r.textPrimary, + ), ), ], ), diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_section_header.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_section_header.dart index 61adb94a..a7bf2b1a 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_section_header.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_section_header.dart @@ -27,14 +27,28 @@ class OneTimeOrderSectionHeader extends StatelessWidget { children: [ 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: [ + 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 + ), + ), + ], ), ), ], diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart index 404cbb56..b8909ac6 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart @@ -58,8 +58,9 @@ class OneTimeOrderView extends StatelessWidget { ? labels.creating : labels.create_order, isLoading: state.status == OneTimeOrderStatus.loading, - onPressed: () => BlocProvider.of(context) - .add(const OneTimeOrderSubmitted()), + onPressed: () => BlocProvider.of( + context, + ).add(const OneTimeOrderSubmitted()), ), ], ), @@ -90,34 +91,34 @@ class _OneTimeOrderForm extends StatelessWidget { OneTimeOrderDatePicker( label: labels.date_label, value: state.date, - onChanged: (DateTime date) => - BlocProvider.of(context) - .add(OneTimeOrderDateChanged(date)), + onChanged: (DateTime date) => BlocProvider.of( + context, + ).add(OneTimeOrderDateChanged(date)), ), const SizedBox(height: UiConstants.space4), OneTimeOrderLocationInput( label: labels.location_label, value: state.location, - onChanged: (String location) => - BlocProvider.of(context) - .add(OneTimeOrderLocationChanged(location)), + onChanged: (String location) => BlocProvider.of( + context, + ).add(OneTimeOrderLocationChanged(location)), ), const SizedBox(height: UiConstants.space6), OneTimeOrderSectionHeader( title: labels.positions_title, actionLabel: labels.add_position, - onAction: () => BlocProvider.of(context) - .add(const OneTimeOrderPositionAdded()), + onAction: () => BlocProvider.of( + context, + ).add(const OneTimeOrderPositionAdded()), ), const SizedBox(height: UiConstants.space3), // Positions List - ...state.positions - .asMap() - .entries - .map((MapEntry entry) { + ...state.positions.asMap().entries.map(( + MapEntry entry, + ) { final int index = entry.key; final OneTimeOrderPosition position = entry.value; return Padding( @@ -133,13 +134,14 @@ class _OneTimeOrderForm extends StatelessWidget { endLabel: labels.end_label, lunchLabel: labels.lunch_break_label, onUpdated: (OneTimeOrderPosition updated) { - BlocProvider.of(context).add( - OneTimeOrderPositionUpdated(index, updated), - ); + BlocProvider.of( + context, + ).add(OneTimeOrderPositionUpdated(index, updated)); }, onRemoved: () { - BlocProvider.of(context) - .add(OneTimeOrderPositionRemoved(index)); + BlocProvider.of( + context, + ).add(OneTimeOrderPositionRemoved(index)); }, ), ); diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/order_type_card.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/order_type_card.dart index 9a6a4535..f9c92f43 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/order_type_card.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/order_type_card.dart @@ -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, ), diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_example_card.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_example_card.dart index c2ce1723..7ffac143 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_example_card.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_example_card.dart @@ -47,10 +47,7 @@ class RapidOrderExampleCard extends StatelessWidget { text: TextSpan( style: UiTypography.body2r.textPrimary, children: [ - TextSpan( - text: label, - style: UiTypography.body2b.textPrimary, - ), + TextSpan(text: label, style: UiTypography.body2b.textPrimary), TextSpan(text: ' $example'), ], ), diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_header.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_header.dart index 2eec2d55..bcb4680e 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_header.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_header.dart @@ -74,11 +74,7 @@ class RapidOrderHeader extends StatelessWidget { children: [ Row( children: [ - 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, diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_success_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_success_view.dart index e99b1bb4..1ad01b09 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_success_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_success_view.dart @@ -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, diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart index fe03182d..1f758d89 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart @@ -153,27 +153,27 @@ class _RapidOrderFormState extends State<_RapidOrderForm> { // Examples if (initialState != null) - ...initialState.examples - .asMap() - .entries - .map((MapEntry entry) { + ...initialState.examples.asMap().entries.map(( + MapEntry 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( - 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(context).add( - RapidOrderMessageChanged(value), - ); + BlocProvider.of( + 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(context).add( - const RapidOrderVoiceToggled(), - ), + onPressed: () => BlocProvider.of( + 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(context).add( - const RapidOrderSubmitted(), - ), + : () => BlocProvider.of( + context, + ).add(const RapidOrderSubmitted()), ), ), ], diff --git a/apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart b/apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart index 93bb7175..b8704f21 100644 --- a/apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart +++ b/apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart @@ -715,7 +715,8 @@ class _ViewOrderCardState extends State { } } -/// A bottom sheet for editing an existing order. +/// A sophisticated bottom sheet for editing an existing order, +/// following the Unified Order Flow prototype. class _OrderEditSheet extends StatefulWidget { const _OrderEditSheet({required this.order}); @@ -726,37 +727,94 @@ class _OrderEditSheet extends StatefulWidget { } class _OrderEditSheetState extends State<_OrderEditSheet> { - late TextEditingController _titleController; + bool _showReview = false; + bool _isLoading = false; + late TextEditingController _dateController; - late TextEditingController _locationController; - late TextEditingController _workersNeededController; + late TextEditingController _globalLocationController; + + // Local state for positions (starts with the single position from OrderItem) + late List> _positions; @override void initState() { super.initState(); - _titleController = TextEditingController(text: widget.order.title); _dateController = TextEditingController(text: widget.order.date); - _locationController = TextEditingController( + _globalLocationController = TextEditingController( text: widget.order.locationAddress, ); - _workersNeededController = TextEditingController( - text: widget.order.workersNeeded.toString(), - ); + + _positions = >[ + { + 'role': widget.order.title, + 'count': widget.order.workersNeeded, + 'start_time': widget.order.startTime, + 'end_time': widget.order.endTime, + 'location': '', // Specific location if different from global + }, + ]; } @override void dispose() { - _titleController.dispose(); _dateController.dispose(); - _locationController.dispose(); - _workersNeededController.dispose(); + _globalLocationController.dispose(); super.dispose(); } + void _addPosition() { + setState(() { + _positions.add({ + 'role': '', + 'count': 1, + 'start_time': '09:00', + 'end_time': '17:00', + 'location': '', + }); + }); + } + + void _removePosition(int index) { + if (_positions.length > 1) { + setState(() => _positions.removeAt(index)); + } + } + + void _updatePosition(int index, String key, dynamic value) { + setState(() => _positions[index][key] = value); + } + + double _calculateTotalCost() { + double total = 0; + for (final Map pos in _positions) { + double hours = 8; // Default fallback + try { + final List startParts = pos['start_time'].toString().split(':'); + final List endParts = pos['end_time'].toString().split(':'); + final double startH = + int.parse(startParts[0]) + int.parse(startParts[1]) / 60; + final double endH = + int.parse(endParts[0]) + int.parse(endParts[1]) / 60; + hours = endH - startH; + if (hours < 0) hours += 24; + } catch (_) {} + total += hours * widget.order.hourlyRate * (pos['count'] as int); + } + return total; + } + @override Widget build(BuildContext context) { + if (_isLoading && _showReview) { + return _buildSuccessView(); + } + + return _showReview ? _buildReviewView() : _buildFormView(); + } + + Widget _buildFormView() { return Container( - height: MediaQuery.of(context).size.height * 0.9, + height: MediaQuery.of(context).size.height * 0.95, decoration: const BoxDecoration( color: UiColors.bgSecondary, borderRadius: BorderRadius.vertical( @@ -819,66 +877,289 @@ class _OrderEditSheetState extends State<_OrderEditSheet> { // Content Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.all(UiConstants.space5), + padding: const EdgeInsets.all(20), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildSectionLabel('Position Title'), - UiTextField( - controller: _titleController, - hintText: 'e.g. Server, Bartender', - prefixIcon: UiIcons.briefcase, - ), - const SizedBox(height: UiConstants.space4), - - _buildSectionLabel('Date'), + _buildSectionLabel('Date *'), UiTextField( controller: _dateController, - hintText: 'Select Date', + hintText: 'mm/dd/yyyy', prefixIcon: UiIcons.calendar, readOnly: true, onTap: () { - // TODO: Show date picker + // TODO: Date picker }, ), - const SizedBox(height: UiConstants.space4), + const SizedBox(height: 16), - _buildSectionLabel('Location'), + _buildSectionLabel('Location *'), UiTextField( - controller: _locationController, + controller: _globalLocationController, hintText: 'Business address', prefixIcon: UiIcons.mapPin, ), - const SizedBox(height: UiConstants.space4), + const SizedBox(height: 24), - _buildSectionLabel('Workers Needed'), + // Positions Header Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: UiTextField( - controller: _workersNeededController, - hintText: 'Quantity', - prefixIcon: UiIcons.users, - keyboardType: TextInputType.number, + Text( + 'Positions', + style: UiTypography.title1m.copyWith( + color: UiColors.textPrimary, ), ), + UiButton.text( + leadingIcon: UiIcons.add, + text: 'Add Position', + onPressed: _addPosition, + ), ], ), - const SizedBox(height: UiConstants.space6), + const SizedBox(height: 8), - UiButton.primary( - text: 'Save Changes', - fullWidth: true, - onPressed: () { - // TODO: Implement save logic - Navigator.pop(context); - }, + ..._positions.asMap().entries.map(( + MapEntry> entry, + ) { + return _buildPositionCard(entry.key, entry.value); + }), + + const SizedBox(height: 40), + ], + ), + ), + ), + + // Footer + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: UiColors.white, + border: Border(top: BorderSide(color: UiColors.separatorPrimary)), + ), + child: SafeArea( + top: false, + child: UiButton.primary( + text: 'Review ${_positions.length} Positions', + fullWidth: true, + onPressed: () => setState(() => _showReview = true), + ), + ), + ), + ], + ), + ); + } + + Widget _buildReviewView() { + final int totalWorkers = _positions.fold( + 0, + (int sum, Map p) => sum + (p['count'] as int), + ); + final double totalCost = _calculateTotalCost(); + + return Container( + height: MediaQuery.of(context).size.height * 0.95, + decoration: const BoxDecoration( + color: UiColors.bgSecondary, + borderRadius: BorderRadius.vertical( + top: Radius.circular(UiConstants.radiusBase * 2), + ), + ), + child: Column( + children: [ + // Header + Container( + padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), + decoration: const BoxDecoration( + color: UiColors.primary, + borderRadius: BorderRadius.vertical( + top: Radius.circular(UiConstants.radiusBase * 2), + ), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => setState(() => _showReview = false), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: UiColors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Icon( + UiIcons.chevronLeft, + color: UiColors.white, + size: 24, + ), + ), ), - const SizedBox(height: UiConstants.space3), - UiButton.ghost( - text: 'Cancel', - fullWidth: true, - onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Review Order', + style: UiTypography.title1m.copyWith( + color: UiColors.white, + ), + ), + Text( + 'Confirm details before saving', + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), + ), + ], + ), + ], + ), + ), + + // Content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Summary Card + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + UiColors.primary.withValues(alpha: 0.05), + UiColors.primary.withValues(alpha: 0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: UiColors.primary.withValues(alpha: 0.2), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildSummaryItem('${_positions.length}', 'Positions'), + _buildSummaryItem('$totalWorkers', 'Workers'), + _buildSummaryItem( + '\$${totalCost.round()}', + 'Est. Cost', + ), + ], + ), + ), + const SizedBox(height: 20), + + // Order Details + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: UiColors.separatorPrimary), + ), + child: Column( + children: [ + Row( + children: [ + const Icon( + UiIcons.calendar, + size: 16, + color: UiColors.primary, + ), + const SizedBox(width: 8), + Text( + _dateController.text, + style: UiTypography.body2m.copyWith( + color: UiColors.textPrimary, + ), + ), + ], + ), + if (_globalLocationController + .text + .isNotEmpty) ...[ + const SizedBox(height: 12), + Row( + children: [ + const Icon( + UiIcons.mapPin, + size: 16, + color: UiColors.primary, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _globalLocationController.text, + style: UiTypography.body2r.copyWith( + color: UiColors.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ], + ), + ), + const SizedBox(height: 24), + + Text( + 'Positions Breakdown', + style: UiTypography.body2b.copyWith( + color: UiColors.textPrimary, + ), + ), + const SizedBox(height: 12), + + ..._positions.map( + (Map pos) => _buildReviewPositionCard(pos), + ), + + const SizedBox(height: 40), + ], + ), + ), + ), + + // Footer + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: UiColors.white, + border: Border(top: BorderSide(color: UiColors.separatorPrimary)), + ), + child: SafeArea( + top: false, + child: Row( + children: [ + Expanded( + child: UiButton.secondary( + text: 'Edit', + onPressed: () => setState(() => _showReview = false), + ), + ), + const SizedBox(width: 12), + Expanded( + child: UiButton.primary( + text: 'Confirm & Save', + onPressed: () async { + setState(() => _isLoading = true); + await Future.delayed(const Duration(seconds: 1)); + if (mounted) { + // TODO: Implement actual save logic + } + }, + ), ), ], ), @@ -889,6 +1170,298 @@ class _OrderEditSheetState extends State<_OrderEditSheet> { ); } + Widget _buildSummaryItem(String value, String label) { + return Column( + children: [ + Text( + value, + style: UiTypography.headline2m.copyWith( + color: UiColors.primary, + fontWeight: FontWeight.bold, + ), + ), + Text( + label.toUpperCase(), + style: UiTypography.titleUppercase4m.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ); + } + + Widget _buildPositionCard(int index, Map pos) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: UiColors.separatorSecondary, width: 2), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + color: UiColors.primary, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + '${index + 1}', + style: UiTypography.footnote2b.copyWith( + color: UiColors.white, + ), + ), + ), + ), + const SizedBox(width: 8), + Text( + 'Position ${index + 1}', + style: UiTypography.footnote2m.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + if (_positions.length > 1) + GestureDetector( + onTap: () => _removePosition(index), + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Color(0xFFFEF2F2), + shape: BoxShape.circle, + ), + child: const Icon( + UiIcons.close, + size: 14, + color: UiColors.destructive, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + + _buildSectionLabel('Position Title *'), + UiTextField( + controller: TextEditingController(text: pos['role']), + hintText: 'e.g. Server, Bartender', + prefixIcon: UiIcons.briefcase, + onChanged: (String val) => _updatePosition(index, 'role', val), + ), + const SizedBox(height: 12), + + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionLabel('Start Time *'), + UiTextField( + controller: TextEditingController( + text: pos['start_time'], + ), + prefixIcon: UiIcons.clock, + onTap: () {}, // Time picker + ), + ], + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionLabel('End Time *'), + UiTextField( + controller: TextEditingController(text: pos['end_time']), + prefixIcon: UiIcons.clock, + onTap: () {}, // Time picker + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + + _buildSectionLabel('Workers Needed'), + Row( + children: [ + _buildCounterBtn( + icon: UiIcons.minus, + onTap: () { + if ((pos['count'] as int) > 1) { + _updatePosition(index, 'count', (pos['count'] as int) - 1); + } + }, + ), + const SizedBox(width: 16), + Text('${pos['count']}', style: UiTypography.body1b), + const SizedBox(width: 16), + _buildCounterBtn( + icon: UiIcons.add, + onTap: () { + _updatePosition(index, 'count', (pos['count'] as int) + 1); + }, + ), + ], + ), + ], + ), + ); + } + + Widget _buildReviewPositionCard(Map pos) { + // Simplified cost calculation + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: UiColors.separatorSecondary), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + pos['role'].toString().isEmpty + ? 'Position' + : pos['role'].toString(), + style: UiTypography.body2b.copyWith( + color: UiColors.textPrimary, + ), + ), + Text( + '${pos['count']} worker${pos['count'] > 1 ? 's' : ''}', + style: UiTypography.footnote2r.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + Text( + '\$${widget.order.hourlyRate.round()}/hr', + style: UiTypography.body2b.copyWith(color: UiColors.primary), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + const Icon( + UiIcons.clock, + size: 14, + color: UiColors.iconSecondary, + ), + const SizedBox(width: 6), + Text( + '${pos['start_time']} - ${pos['end_time']}', + style: UiTypography.footnote2r.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildCounterBtn({ + required IconData icon, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: UiColors.bgSecondary, + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, size: 16, color: UiColors.primary), + ), + ); + } + + Widget _buildSuccessView() { + return Container( + width: double.infinity, + height: MediaQuery.of(context).size.height * 0.95, + decoration: const BoxDecoration( + color: UiColors.primary, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: const BoxDecoration( + color: UiColors.accent, + shape: BoxShape.circle, + ), + child: const Center( + child: Icon( + UiIcons.success, + size: 40, + color: UiColors.foreground, + ), + ), + ), + const SizedBox(height: 24), + Text( + 'Order Updated!', + style: UiTypography.headline1m.copyWith(color: UiColors.white), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Text( + 'Your shift has been updated successfully.', + textAlign: TextAlign.center, + style: UiTypography.body1r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), + ), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: UiButton.secondary( + text: 'Back to Orders', + fullWidth: true, + style: OutlinedButton.styleFrom( + backgroundColor: UiColors.white, + foregroundColor: UiColors.primary, + ), + onPressed: () => Navigator.pop(context), + ), + ), + ], + ), + ); + } + Widget _buildSectionLabel(String label) { return Padding( padding: const EdgeInsets.only(bottom: 8),