diff --git a/Makefile b/Makefile index 71d0b18d..4a029884 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,8 @@ help: @echo " make dataconnect-deploy [ENV=dev] Deploy Data Connect schemas (dev/staging)" @echo " make dataconnect-sql-migrate [ENV=dev] Apply pending SQL migrations" @echo " make dataconnect-generate-sdk [ENV=dev] Regenerate Data Connect client SDK" - @echo " make dataconnect-sync [ENV=dev] Full sync: deploy + migrate + generate SDK" + @echo " make dataconnect-sync [ENV=dev] Fast sync: deploy connector + generate SDK" + @echo " make dataconnect-sync-full [ENV=dev] Full sync: deploy + migrate + generate SDK" @echo " make dataconnect-seed [ENV=dev] Seed database with test data" @echo " make dataconnect-clean [ENV=dev] Delete all data from Data Connect" @echo " make dataconnect-test [ENV=dev] Test Data Connect deployment (dry-run)" @@ -101,6 +102,6 @@ help: @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo " 💡 Tip: Run 'make mobile-install' first for mobile development" - @echo " 💡 Tip: Use 'make dataconnect-sync' after schema changes" + @echo " 💡 Tip: Use 'make dataconnect-sync-full' after schema changes" @echo " 💡 Tip: Default ENV=dev, use ENV=staging for staging environment" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m b/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m index 6edfc720..241fcf3b 100644 --- a/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m +++ b/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m @@ -36,10 +36,10 @@ @import image_picker_ios; #endif -#if __has_include() -#import +#if __has_include() +#import #else -@import record_darwin; +@import record_ios; #endif #if __has_include() @@ -62,7 +62,7 @@ [FLTFirebaseAuthPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseAuthPlugin"]]; [FLTFirebaseCorePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseCorePlugin"]]; [FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]]; - [RecordPlugin registerWithRegistrar:[registry registrarForPlugin:@"RecordPlugin"]]; + [RecordIosPlugin registerWithRegistrar:[registry registrarForPlugin:@"RecordIosPlugin"]]; [SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]]; [URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]]; } diff --git a/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift index 71798f05..1dea22d7 100644 --- a/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,7 +10,7 @@ import file_selector_macos import firebase_app_check import firebase_auth import firebase_core -import record_darwin +import record_macos import shared_preferences_foundation import url_launcher_macos @@ -20,7 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart index 4f7062da..d3c1bfdd 100644 --- a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart +++ b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart @@ -139,7 +139,7 @@ extension StaffNavigator on IModularNavigator { /// /// Manage personal information, documents, and preferences. void toProfile() { - pushNamedAndRemoveUntil(StaffPaths.profile, (_) => false); + navigate(StaffPaths.profile); } // ========================================================================== @@ -189,7 +189,7 @@ extension StaffNavigator on IModularNavigator { /// /// Record previous work experience and qualifications. void toExperience() { - pushNamed(StaffPaths.experience); + navigate(StaffPaths.experience); } /// Pushes the attire preferences page. diff --git a/apps/mobile/packages/core/pubspec.yaml b/apps/mobile/packages/core/pubspec.yaml index 83f084cd..15f91f58 100644 --- a/apps/mobile/packages/core/pubspec.yaml +++ b/apps/mobile/packages/core/pubspec.yaml @@ -27,4 +27,3 @@ dependencies: file_picker: ^8.1.7 record: ^6.2.0 firebase_auth: ^6.1.4 - diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart index dfb5784c..d5a51815 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart @@ -9,6 +9,7 @@ import 'domain/usecases/create_permanent_order_usecase.dart'; import 'domain/usecases/create_recurring_order_usecase.dart'; import 'domain/usecases/create_rapid_order_usecase.dart'; import 'domain/usecases/get_order_details_for_reorder_usecase.dart'; +import 'domain/usecases/parse_rapid_order_usecase.dart'; import 'domain/usecases/transcribe_rapid_order_usecase.dart'; import 'presentation/blocs/index.dart'; import 'presentation/pages/create_order_page.dart'; @@ -24,7 +25,7 @@ import 'presentation/pages/recurring_order_page.dart'; /// presentation layer BLoCs. class ClientCreateOrderModule extends Module { @override - List get imports => [DataConnectModule(), CoreModule()]; + List get imports => [DataConnectModule()]; @override void binds(Injector i) { @@ -39,13 +40,14 @@ class ClientCreateOrderModule extends Module { i.addLazySingleton(CreateRecurringOrderUseCase.new); i.addLazySingleton(CreateRapidOrderUseCase.new); i.addLazySingleton(TranscribeRapidOrderUseCase.new); + i.addLazySingleton(ParseRapidOrderTextToOrderUseCase.new); i.addLazySingleton(GetOrderDetailsForReorderUseCase.new); // BLoCs i.add( (Injector i) => RapidOrderBloc( - i.get(), i.get(), + i.get(), i.get(), ), ); diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart index b0c62a14..645ce1df 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart @@ -1,6 +1,5 @@ import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:intl/intl.dart'; -import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart' as domain; import '../../domain/repositories/client_create_order_repository_interface.dart'; @@ -14,14 +13,10 @@ import '../../domain/repositories/client_create_order_repository_interface.dart' /// on delegation and data mapping, without business logic. class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInterface { - ClientCreateOrderRepositoryImpl({ - required dc.DataConnectService service, - required RapidOrderService rapidOrderService, - }) : _service = service, - _rapidOrderService = rapidOrderService; + ClientCreateOrderRepositoryImpl({required dc.DataConnectService service}) + : _service = service; final dc.DataConnectService _service; - final RapidOrderService _rapidOrderService; @override Future createOneTimeOrder(domain.OneTimeOrder order) async { @@ -372,6 +367,44 @@ class ClientCreateOrderRepositoryImpl throw UnimplementedError('Rapid order IA is not connected yet.'); } + @override + Future parseRapidOrder(String text) async { + final RapidOrderParseResponse response = await _rapidOrderService.parseText( + text: text, + ); + final RapidOrderParsedData data = response.parsed; + + final DateTime startAt = + DateTime.tryParse(data.startAt ?? '') ?? DateTime.now(); + final DateTime endAt = + DateTime.tryParse(data.endAt ?? '') ?? + startAt.add(const Duration(hours: 8)); + + final String startTimeStr = DateFormat('hh:mm a').format(startAt); + final String endTimeStr = DateFormat('hh:mm a').format(endAt); + + return domain.OneTimeOrder( + date: startAt, + location: data.locationHint ?? '', + eventName: data.notes ?? '', + hub: data.locationHint != null + ? domain.OneTimeOrderHubDetails( + id: '', + name: data.locationHint!, + address: '', + ) + : null, + positions: data.positions.map((RapidOrderPosition p) { + return domain.OneTimeOrderPosition( + role: p.role, + count: p.count, + startTime: startTimeStr, + endTime: endTimeStr, + ); + }).toList(), + ); + } + @override Future transcribeRapidOrder(String audioPath) async { final RapidOrderTranscriptionResponse response = await _rapidOrderService diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart index d373c9ce..84124804 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart @@ -29,6 +29,11 @@ abstract interface class ClientCreateOrderRepositoryInterface { /// [audioPath] is the local path to the recorded audio file. Future transcribeRapidOrder(String audioPath); + /// Parses the text description for a rapid order into a structured draft. + /// + /// [text] is the text message describing the need. + Future parseRapidOrder(String text); + /// Reorders an existing staffing order with a new date. /// /// [previousOrderId] is the ID of the order to reorder. diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/parse_rapid_order_usecase.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/parse_rapid_order_usecase.dart new file mode 100644 index 00000000..17113b2a --- /dev/null +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/parse_rapid_order_usecase.dart @@ -0,0 +1,15 @@ +import 'package:krow_domain/krow_domain.dart'; +import '../repositories/client_create_order_repository_interface.dart'; + +/// Use case for parsing rapid order text into a structured OneTimeOrder. +class ParseRapidOrderTextToOrderUseCase { + ParseRapidOrderTextToOrderUseCase({ + required ClientCreateOrderRepositoryInterface repository, + }) : _repository = repository; + + final ClientCreateOrderRepositoryInterface _repository; + + Future call(String text) async { + return _repository.parseRapidOrder(text); + } +} diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_bloc.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_bloc.dart index a255fe7d..7c3a4435 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_bloc.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_bloc.dart @@ -136,9 +136,7 @@ class OneTimeOrderBloc extends Bloc } } - Future _loadManagersForHub( - String hubId, - ) async { + Future _loadManagersForHub(String hubId) async { final List? managers = await handleErrorWithResult( action: () async { @@ -163,7 +161,9 @@ class OneTimeOrderBloc extends Bloc .toList(); }, onError: (_) { - add(const OneTimeOrderManagersLoaded([])); + add( + const OneTimeOrderManagersLoaded([]), + ); }, ); @@ -172,7 +172,6 @@ class OneTimeOrderBloc extends Bloc } } - Future _onVendorsLoaded( OneTimeOrderVendorsLoaded event, Emitter emit, @@ -216,7 +215,6 @@ class OneTimeOrderBloc extends Bloc } } - void _onHubChanged( OneTimeOrderHubChanged event, Emitter emit, @@ -239,7 +237,6 @@ class OneTimeOrderBloc extends Bloc emit(state.copyWith(managers: event.managers)); } - void _onEventNameChanged( OneTimeOrderEventNameChanged event, Emitter emit, @@ -349,6 +346,45 @@ class OneTimeOrderBloc extends Bloc final DateTime? startDate = data['startDate'] as DateTime?; final String? orderId = data['orderId']?.toString(); + // Handle Rapid Order Draft + if (data['isRapidDraft'] == true) { + final OneTimeOrder? order = data['order'] as OneTimeOrder?; + if (order != null) { + emit( + state.copyWith( + eventName: order.eventName ?? '', + date: order.date, + positions: order.positions, + location: order.location, + isRapidDraft: true, + ), + ); + + // Try to match vendor if available + if (order.vendorId != null) { + final Vendor? vendor = state.vendors + .where((Vendor v) => v.id == order.vendorId) + .firstOrNull; + if (vendor != null) { + emit(state.copyWith(selectedVendor: vendor)); + await _loadRolesForVendor(vendor.id, emit); + } + } + + // Try to match hub if available + if (order.hub != null) { + final OneTimeOrderHubOption? hub = state.hubs + .where((OneTimeOrderHubOption h) => h.id == order.hub?.id) + .firstOrNull; + if (hub != null) { + emit(state.copyWith(selectedHub: hub)); + await _loadManagersForHub(hub.id); + } + } + return; + } + } + emit(state.copyWith(eventName: title, date: startDate ?? DateTime.now())); if (orderId == null || orderId.isEmpty) return; diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_state.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_state.dart index b48b9134..c2964f35 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_state.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/one_time_order/one_time_order_state.dart @@ -18,6 +18,7 @@ class OneTimeOrderState extends Equatable { this.roles = const [], this.managers = const [], this.selectedManager, + this.isRapidDraft = false, }); factory OneTimeOrderState.initial() { @@ -47,6 +48,7 @@ class OneTimeOrderState extends Equatable { final List roles; final List managers; final OneTimeOrderManagerOption? selectedManager; + final bool isRapidDraft; OneTimeOrderState copyWith({ DateTime? date, @@ -62,6 +64,7 @@ class OneTimeOrderState extends Equatable { List? roles, List? managers, OneTimeOrderManagerOption? selectedManager, + bool? isRapidDraft, }) { return OneTimeOrderState( date: date ?? this.date, @@ -77,6 +80,7 @@ class OneTimeOrderState extends Equatable { roles: roles ?? this.roles, managers: managers ?? this.managers, selectedManager: selectedManager ?? this.selectedManager, + isRapidDraft: isRapidDraft ?? this.isRapidDraft, ); } @@ -109,6 +113,7 @@ class OneTimeOrderState extends Equatable { roles, managers, selectedManager, + isRapidDraft, ]; } @@ -171,10 +176,7 @@ class OneTimeOrderRoleOption extends Equatable { } class OneTimeOrderManagerOption extends Equatable { - const OneTimeOrderManagerOption({ - required this.id, - required this.name, - }); + const OneTimeOrderManagerOption({required this.id, required this.name}); final String id; final String name; @@ -182,4 +184,3 @@ class OneTimeOrderManagerOption extends Equatable { @override List get props => [id, name]; } - diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart index 7967b166..3b957100 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart @@ -1,8 +1,8 @@ -import 'package:client_create_order/src/domain/arguments/rapid_order_arguments.dart'; -import 'package:client_create_order/src/domain/usecases/create_rapid_order_usecase.dart'; +import 'package:client_create_order/src/domain/usecases/parse_rapid_order_usecase.dart'; import 'package:client_create_order/src/domain/usecases/transcribe_rapid_order_usecase.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; import 'rapid_order_event.dart'; import 'rapid_order_state.dart'; @@ -11,8 +11,8 @@ import 'rapid_order_state.dart'; class RapidOrderBloc extends Bloc with BlocErrorHandler { RapidOrderBloc( - this._createRapidOrderUseCase, this._transcribeRapidOrderUseCase, + this._parseRapidOrderUseCase, this._audioRecorderService, ) : super( const RapidOrderInitial( @@ -28,8 +28,8 @@ class RapidOrderBloc extends Bloc on(_onSubmitted); on(_onExampleSelected); } - final CreateRapidOrderUseCase _createRapidOrderUseCase; final TranscribeRapidOrderUseCase _transcribeRapidOrderUseCase; + final ParseRapidOrderTextToOrderUseCase _parseRapidOrderUseCase; final AudioRecorderService _audioRecorderService; void _onMessageChanged( @@ -47,39 +47,21 @@ class RapidOrderBloc extends Bloc ) async { if (state is RapidOrderInitial) { final RapidOrderInitial currentState = state as RapidOrderInitial; - final bool alreadyListening = currentState.isListening; + final bool newListeningState = !currentState.isListening; - if (!alreadyListening) { - // Start recording - await handleError( - emit: (RapidOrderState s) => emit(s), - action: () async { - await _audioRecorderService.startRecording(); - emit(currentState.copyWith(isListening: true)); - }, - onError: (String errorKey) => RapidOrderFailure(errorKey), - ); - } else { - // Stop and transcribe - emit(currentState.copyWith(isListening: false)); + emit(currentState.copyWith(isListening: newListeningState)); - await handleError( - emit: (RapidOrderState s) => emit(s), - action: () async { - final String? path = await _audioRecorderService.stopRecording(); - if (path != null) { - final String transcript = await _transcribeRapidOrderUseCase( - path, - ); - if (state is RapidOrderInitial) { - emit( - (state as RapidOrderInitial).copyWith(message: transcript), - ); - } - } - }, - onError: (String errorKey) => RapidOrderFailure(errorKey), - ); + // Simulate voice recognition + if (newListeningState) { + await Future.delayed(const Duration(seconds: 2)); + if (state is RapidOrderInitial) { + emit( + (state as RapidOrderInitial).copyWith( + message: 'Need 2 servers for a banquet right now.', + isListening: false, + ), + ); + } } } } @@ -96,10 +78,8 @@ class RapidOrderBloc extends Bloc await handleError( emit: emit.call, action: () async { - await _createRapidOrderUseCase( - RapidOrderArguments(description: message), - ); - emit(const RapidOrderSuccess()); + final OneTimeOrder order = await _parseRapidOrderUseCase(message); + emit(RapidOrderParsed(order)); }, onError: (String errorKey) => RapidOrderFailure(errorKey), ); @@ -116,3 +96,4 @@ class RapidOrderBloc extends Bloc } } } + diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_state.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_state.dart index 6c752b92..88396c09 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_state.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_state.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/krow_domain.dart'; abstract class RapidOrderState extends Equatable { const RapidOrderState(); @@ -48,3 +49,11 @@ class RapidOrderFailure extends RapidOrderState { @override List get props => [error]; } + +class RapidOrderParsed extends RapidOrderState { + const RapidOrderParsed(this.order); + final OneTimeOrder order; + + @override + List get props => [order]; +} diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/pages/one_time_order_page.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/pages/one_time_order_page.dart index 3c611062..11d8e1d7 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/pages/one_time_order_page.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/pages/one_time_order_page.dart @@ -53,6 +53,7 @@ class OneTimeOrderPage extends StatelessWidget { : null, hubManagers: state.managers.map(_mapManager).toList(), isValid: state.isValid, + title: state.isRapidDraft ? 'Rapid Order : Verify the order' : null, onEventNameChanged: (String val) => bloc.add(OneTimeOrderEventNameChanged(val)), onVendorChanged: (Vendor val) => diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart index a3c81660..f306e312 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart @@ -72,6 +72,13 @@ class _RapidOrderFormState extends State<_RapidOrderForm> { TextPosition(offset: _messageController.text.length), ); } + } else if (state is RapidOrderParsed) { + Modular.to.toCreateOrderOneTime( + arguments: { + 'order': state.order, + 'isRapidDraft': true, + }, + ); } else if (state is RapidOrderFailure) { UiSnackbar.show( context, diff --git a/apps/mobile/packages/features/client/orders/orders_common/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart b/apps/mobile/packages/features/client/orders/orders_common/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart index 4abe0eae..bb80fcc4 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart +++ b/apps/mobile/packages/features/client/orders/orders_common/lib/src/presentation/widgets/one_time_order/one_time_order_view.dart @@ -38,6 +38,7 @@ class OneTimeOrderView extends StatelessWidget { required this.onSubmit, required this.onDone, required this.onBack, + this.title, super.key, }); @@ -54,6 +55,7 @@ class OneTimeOrderView extends StatelessWidget { final List hubManagers; final OrderManagerUiModel? selectedHubManager; final bool isValid; + final String? title; final ValueChanged onEventNameChanged; final ValueChanged onVendorChanged; @@ -61,7 +63,8 @@ class OneTimeOrderView extends StatelessWidget { final ValueChanged onHubChanged; final ValueChanged onHubManagerChanged; 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 VoidCallback onSubmit; final VoidCallback onDone; @@ -98,7 +101,7 @@ class OneTimeOrderView extends StatelessWidget { body: Column( children: [ OneTimeOrderHeader( - title: labels.title, + title: title ?? labels.title, subtitle: labels.subtitle, onBack: onBack, ), @@ -136,7 +139,7 @@ class OneTimeOrderView extends StatelessWidget { body: Column( children: [ OneTimeOrderHeader( - title: labels.title, + title: title ?? labels.title, subtitle: labels.subtitle, onBack: onBack, ), @@ -220,7 +223,8 @@ class _OneTimeOrderForm extends StatelessWidget { final ValueChanged onHubChanged; final ValueChanged onHubManagerChanged; 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; @override @@ -317,10 +321,7 @@ class _OneTimeOrderForm extends StatelessWidget { items: hubs.map((OrderHubUiModel hub) { return DropdownMenuItem( value: hub, - child: Text( - hub.name, - style: UiTypography.body2m.textPrimary, - ), + child: Text(hub.name, style: UiTypography.body2m.textPrimary), ); }).toList(), ), diff --git a/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift index 5cf9ab86..3eb92bc4 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,7 +10,7 @@ import file_selector_macos import firebase_app_check import firebase_auth import firebase_core -import record_darwin +import record_macos import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -19,6 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/docs/BACKEND/DATACONNECT_GUIDES/DOCUMENTS/backend_manual.md b/docs/BACKEND/DATACONNECT_GUIDES/DOCUMENTS/backend_manual.md index c31b0f8c..90e1dead 100644 --- a/docs/BACKEND/DATACONNECT_GUIDES/DOCUMENTS/backend_manual.md +++ b/docs/BACKEND/DATACONNECT_GUIDES/DOCUMENTS/backend_manual.md @@ -186,7 +186,8 @@ client.createInvoice(); | dataconnect-deploy | Deploy schemas | | dataconnect-sql-migrate | Apply DB migrations | | dataconnect-generate-sdk | Generate SDK | -| dataconnect-sync | Full backend update | +| dataconnect-sync | Fast connector + SDK sync | +| dataconnect-sync-full | Full backend update | | dataconnect-test | Test without breaking | | dataconnect-seed | Insert seed data | | dataconnect-bootstrap-db | Create Cloud SQL | @@ -195,7 +196,7 @@ client.createInvoice(); ## 9. Correct Backend Workflow -### Production Flow +### Query/Connector Flow ```bash make dataconnect-sync @@ -203,9 +204,22 @@ make dataconnect-sync Steps: -1. Deploy schema -2. Run SQL migrations -3. Generate SDK +1. Deploy connector +2. Generate SDK + +--- + +### Full Schema Flow + +```bash +make dataconnect-sync-full +``` + +Steps: + +1. Deploy schema +2. Run SQL migrations +3. Generate SDK --- diff --git a/internal/launchpad/assets/documents/data connect/backend_manual.md b/internal/launchpad/assets/documents/data connect/backend_manual.md index c31b0f8c..90e1dead 100644 --- a/internal/launchpad/assets/documents/data connect/backend_manual.md +++ b/internal/launchpad/assets/documents/data connect/backend_manual.md @@ -186,7 +186,8 @@ client.createInvoice(); | dataconnect-deploy | Deploy schemas | | dataconnect-sql-migrate | Apply DB migrations | | dataconnect-generate-sdk | Generate SDK | -| dataconnect-sync | Full backend update | +| dataconnect-sync | Fast connector + SDK sync | +| dataconnect-sync-full | Full backend update | | dataconnect-test | Test without breaking | | dataconnect-seed | Insert seed data | | dataconnect-bootstrap-db | Create Cloud SQL | @@ -195,7 +196,7 @@ client.createInvoice(); ## 9. Correct Backend Workflow -### Production Flow +### Query/Connector Flow ```bash make dataconnect-sync @@ -203,9 +204,22 @@ make dataconnect-sync Steps: -1. Deploy schema -2. Run SQL migrations -3. Generate SDK +1. Deploy connector +2. Generate SDK + +--- + +### Full Schema Flow + +```bash +make dataconnect-sync-full +``` + +Steps: + +1. Deploy schema +2. Run SQL migrations +3. Generate SDK --- diff --git a/makefiles/dataconnect.mk b/makefiles/dataconnect.mk index 3ae3410e..4c9f7ef9 100644 --- a/makefiles/dataconnect.mk +++ b/makefiles/dataconnect.mk @@ -2,11 +2,14 @@ # Usage examples: # make dataconnect-sync DC_ENV=dev +# make dataconnect-sync-full DC_ENV=dev # make dataconnect-seed DC_ENV=validation # make dataconnect-clean DC_ENV=validation # make dataconnect-generate-sdk DC_ENV=dev # DC_ENV ?= dev +DC_LOCATION ?= us-central1 +DC_CONNECTOR_ID ?= example DC_SERVICE_DEV := krow-workforce-db DC_SERVICE_VALIDATION := krow-workforce-db-validation @@ -19,7 +22,7 @@ else $(error Invalid DC_ENV '$(DC_ENV)'. Use DC_ENV=dev or DC_ENV=validation) endif -.PHONY: dataconnect-enable-apis dataconnect-init dataconnect-deploy dataconnect-sql-migrate dataconnect-generate-sdk dataconnect-sync dataconnect-bootstrap-db check-gcloud-beta dataconnect-clean dataconnect-bootstrap-validation-db dataconnect-file dataconnect-file-validation dataconnect-file-dev dataconnect-seed dataconnect-test +.PHONY: dataconnect-enable-apis dataconnect-init dataconnect-deploy dataconnect-sql-migrate dataconnect-generate-sdk dataconnect-sync dataconnect-sync-full dataconnect-bootstrap-db check-gcloud-beta dataconnect-clean dataconnect-bootstrap-validation-db dataconnect-file dataconnect-file-validation dataconnect-file-dev dataconnect-seed dataconnect-test #creation dataconnect file dataconnect-file: @@ -79,15 +82,23 @@ dataconnect-generate-sdk: dataconnect-file @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) @echo "✅ Data Connect SDK generation completed for [$(DC_ENV)]." -# Unified backend schema update workflow (schema -> deploy -> SDK) +# Fast sync workflow for connector/query changes (deploy connector -> SDK) dataconnect-sync: dataconnect-file - @echo "--> [1/3] Deploying Data Connect [$(DC_SERVICE)]..." - @firebase deploy --only dataconnect:$(DC_SERVICE) --project=$(FIREBASE_ALIAS) + @echo "--> [1/2] Deploying Data Connect connector [$(DC_SERVICE):$(DC_CONNECTOR_ID)]..." + @firebase deploy --only dataconnect:$(DC_SERVICE):$(DC_CONNECTOR_ID) --project=$(FIREBASE_ALIAS) --force + @echo "--> [2/2] Regenerating SDK [$(DC_SERVICE)]..." + @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) + @echo "✅ Data Connect connector + SDK sync completed for [$(DC_ENV)]." + +# Full backend schema update workflow (schema -> migrate -> SDK) +dataconnect-sync-full: dataconnect-file + @echo "--> [1/3] Deploying full Data Connect service [$(DC_SERVICE)]..." + @firebase deploy --only dataconnect:$(DC_SERVICE) --project=$(FIREBASE_ALIAS) --force @echo "--> [2/3] Applying SQL migrations [$(DC_SERVICE)]..." - @firebase dataconnect:sql:migrate --project=$(FIREBASE_ALIAS) + @firebase dataconnect:sql:migrate --project=$(FIREBASE_ALIAS) --service $(DC_SERVICE) --location $(DC_LOCATION) --force @echo "--> [3/3] Regenerating SDK [$(DC_SERVICE)]..." @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) - @echo "✅ Data Connect SQL, deploy, and SDK generation [$(ENV)]." + @echo "✅ Data Connect full schema sync completed for [$(DC_ENV)]." # Execute seed in Firebase Data Connect dataconnect-seed: dataconnect-file @@ -268,4 +279,4 @@ dataconnect-bootstrap-validation-database: dataconnect-file-validation @echo "⚠️ Generating Data Connect SDK ($(DC_SERVICE))..." @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) - @echo "🎉 Validation Cloud SQL + Data Connect bootstrap completed successfully!" \ No newline at end of file + @echo "🎉 Validation Cloud SQL + Data Connect bootstrap completed successfully!"