feat: Implement UI for adding new shift manager during order creation
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:firebase_data_connect/src/core/ref.dart';
|
|
||||||
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../../domain/repositories/hubs_connector_repository.dart';
|
import '../../domain/repositories/hubs_connector_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [HubsConnectorRepository].
|
/// Implementation of [HubsConnectorRepository].
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import 'package:krow_core/core.dart';
|
|||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
import 'data/repositories_impl/client_create_order_repository_impl.dart';
|
import 'data/repositories_impl/client_create_order_repository_impl.dart';
|
||||||
|
import 'data/repositories_impl/client_order_query_repository_impl.dart';
|
||||||
import 'domain/repositories/client_create_order_repository_interface.dart';
|
import 'domain/repositories/client_create_order_repository_interface.dart';
|
||||||
|
import 'domain/repositories/client_order_query_repository_interface.dart';
|
||||||
import 'domain/usecases/create_one_time_order_usecase.dart';
|
import 'domain/usecases/create_one_time_order_usecase.dart';
|
||||||
import 'domain/usecases/create_permanent_order_usecase.dart';
|
import 'domain/usecases/create_permanent_order_usecase.dart';
|
||||||
import 'domain/usecases/create_recurring_order_usecase.dart';
|
import 'domain/usecases/create_recurring_order_usecase.dart';
|
||||||
@@ -40,6 +42,12 @@ class ClientCreateOrderModule extends Module {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i.addLazySingleton<ClientOrderQueryRepositoryInterface>(
|
||||||
|
() => ClientOrderQueryRepositoryImpl(
|
||||||
|
service: i.get<dc.DataConnectService>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
i.addLazySingleton(CreateOneTimeOrderUseCase.new);
|
i.addLazySingleton(CreateOneTimeOrderUseCase.new);
|
||||||
i.addLazySingleton(CreatePermanentOrderUseCase.new);
|
i.addLazySingleton(CreatePermanentOrderUseCase.new);
|
||||||
@@ -58,7 +66,13 @@ class ClientCreateOrderModule extends Module {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
i.add<OneTimeOrderBloc>(OneTimeOrderBloc.new);
|
i.add<OneTimeOrderBloc>(OneTimeOrderBloc.new);
|
||||||
i.add<PermanentOrderBloc>(PermanentOrderBloc.new);
|
i.add<PermanentOrderBloc>(
|
||||||
|
() => PermanentOrderBloc(
|
||||||
|
i.get<CreatePermanentOrderUseCase>(),
|
||||||
|
i.get<GetOrderDetailsForReorderUseCase>(),
|
||||||
|
i.get<ClientOrderQueryRepositoryInterface>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
i.add<RecurringOrderBloc>(RecurringOrderBloc.new);
|
i.add<RecurringOrderBloc>(RecurringOrderBloc.new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../../domain/models/order_hub.dart';
|
||||||
|
import '../../domain/models/order_manager.dart';
|
||||||
|
import '../../domain/models/order_role.dart';
|
||||||
|
import '../../domain/repositories/client_order_query_repository_interface.dart';
|
||||||
|
|
||||||
|
/// Data layer implementation of [ClientOrderQueryRepositoryInterface].
|
||||||
|
///
|
||||||
|
/// Delegates all backend calls to [dc.DataConnectService] using the
|
||||||
|
/// `_service.run()` pattern for automatic auth validation, token refresh,
|
||||||
|
/// and retry logic. Each method maps Data Connect response types to the
|
||||||
|
/// corresponding clean domain models.
|
||||||
|
class ClientOrderQueryRepositoryImpl
|
||||||
|
implements ClientOrderQueryRepositoryInterface {
|
||||||
|
/// Creates an instance backed by the given [service].
|
||||||
|
ClientOrderQueryRepositoryImpl({required dc.DataConnectService service})
|
||||||
|
: _service = service;
|
||||||
|
|
||||||
|
final dc.DataConnectService _service;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Vendor>> getVendors() async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final result = await _service.connector.listVendors().execute();
|
||||||
|
return result.data.vendors
|
||||||
|
.map(
|
||||||
|
(dc.ListVendorsVendors vendor) => Vendor(
|
||||||
|
id: vendor.id,
|
||||||
|
name: vendor.companyName,
|
||||||
|
rates: const <String, double>{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OrderRole>> getRolesByVendor(String vendorId) async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final result = await _service.connector
|
||||||
|
.listRolesByVendorId(vendorId: vendorId)
|
||||||
|
.execute();
|
||||||
|
return result.data.roles
|
||||||
|
.map(
|
||||||
|
(dc.ListRolesByVendorIdRoles role) => OrderRole(
|
||||||
|
id: role.id,
|
||||||
|
name: role.name,
|
||||||
|
costPerHour: role.costPerHour,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OrderHub>> getHubsByOwner(String ownerId) async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final result = await _service.connector
|
||||||
|
.listTeamHubsByOwnerId(ownerId: ownerId)
|
||||||
|
.execute();
|
||||||
|
return result.data.teamHubs
|
||||||
|
.map(
|
||||||
|
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => OrderHub(
|
||||||
|
id: hub.id,
|
||||||
|
name: hub.hubName,
|
||||||
|
address: hub.address,
|
||||||
|
placeId: hub.placeId,
|
||||||
|
latitude: hub.latitude,
|
||||||
|
longitude: hub.longitude,
|
||||||
|
city: hub.city,
|
||||||
|
state: hub.state,
|
||||||
|
street: hub.street,
|
||||||
|
country: hub.country,
|
||||||
|
zipCode: hub.zipCode,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OrderManager>> getManagersByHub(String hubId) async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final result = await _service.connector.listTeamMembers().execute();
|
||||||
|
return result.data.teamMembers
|
||||||
|
.where(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
member.teamHubId == hubId &&
|
||||||
|
member.role is dc.Known<dc.TeamMemberRole> &&
|
||||||
|
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
||||||
|
dc.TeamMemberRole.MANAGER,
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) => OrderManager(
|
||||||
|
id: member.id,
|
||||||
|
name: member.user.fullName ?? 'Unknown',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getBusinessId() => _service.getBusinessId();
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// A team hub (location) available for order assignment.
|
||||||
|
///
|
||||||
|
/// This domain model represents a physical hub location owned by the business.
|
||||||
|
/// It is used to populate hub selection dropdowns and to attach location
|
||||||
|
/// details when creating shifts for an order.
|
||||||
|
class OrderHub extends Equatable {
|
||||||
|
/// Creates an [OrderHub] with the required [id], [name], and [address],
|
||||||
|
/// plus optional geo-location and address component fields.
|
||||||
|
const OrderHub({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.address,
|
||||||
|
this.placeId,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
this.city,
|
||||||
|
this.state,
|
||||||
|
this.street,
|
||||||
|
this.country,
|
||||||
|
this.zipCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Unique identifier of the hub.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Human-readable display name of the hub.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// Full street address of the hub.
|
||||||
|
final String address;
|
||||||
|
|
||||||
|
/// Google Places ID, if available.
|
||||||
|
final String? placeId;
|
||||||
|
|
||||||
|
/// Geographic latitude of the hub.
|
||||||
|
final double? latitude;
|
||||||
|
|
||||||
|
/// Geographic longitude of the hub.
|
||||||
|
final double? longitude;
|
||||||
|
|
||||||
|
/// City where the hub is located.
|
||||||
|
final String? city;
|
||||||
|
|
||||||
|
/// State or province where the hub is located.
|
||||||
|
final String? state;
|
||||||
|
|
||||||
|
/// Street name portion of the address.
|
||||||
|
final String? street;
|
||||||
|
|
||||||
|
/// Country where the hub is located.
|
||||||
|
final String? country;
|
||||||
|
|
||||||
|
/// Postal / ZIP code of the hub.
|
||||||
|
final String? zipCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
address,
|
||||||
|
placeId,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
city,
|
||||||
|
state,
|
||||||
|
street,
|
||||||
|
country,
|
||||||
|
zipCode,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// A hub manager available for assignment to an order.
|
||||||
|
///
|
||||||
|
/// This domain model represents a team member with a MANAGER role at a
|
||||||
|
/// specific hub. It is used to populate the manager selection dropdown
|
||||||
|
/// when creating or editing an order.
|
||||||
|
class OrderManager extends Equatable {
|
||||||
|
/// Creates an [OrderManager] with the given [id] and [name].
|
||||||
|
const OrderManager({required this.id, required this.name});
|
||||||
|
|
||||||
|
/// Unique identifier of the manager (team member ID).
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Full display name of the manager.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name];
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// A role available for staffing positions within an order.
|
||||||
|
///
|
||||||
|
/// This domain model represents a staffing role fetched from the backend,
|
||||||
|
/// decoupled from any data layer dependencies. It carries the role identity
|
||||||
|
/// and its hourly cost so the presentation layer can populate dropdowns
|
||||||
|
/// and calculate estimates.
|
||||||
|
class OrderRole extends Equatable {
|
||||||
|
/// Creates an [OrderRole] with the given [id], [name], and [costPerHour].
|
||||||
|
const OrderRole({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.costPerHour,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Unique identifier of the role.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Human-readable display name of the role.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// Hourly cost rate for this role.
|
||||||
|
final double costPerHour;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name, costPerHour];
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../models/order_hub.dart';
|
||||||
|
import '../models/order_manager.dart';
|
||||||
|
import '../models/order_role.dart';
|
||||||
|
|
||||||
|
/// Interface for querying order-related reference data.
|
||||||
|
///
|
||||||
|
/// This repository centralises the read-only queries that the order creation
|
||||||
|
/// BLoCs need (vendors, roles, hubs, managers) so that they no longer depend
|
||||||
|
/// directly on [DataConnectService] or the `krow_data_connect` package.
|
||||||
|
///
|
||||||
|
/// Implementations live in the data layer and translate backend responses
|
||||||
|
/// into clean domain models.
|
||||||
|
abstract interface class ClientOrderQueryRepositoryInterface {
|
||||||
|
/// Returns the list of available vendors.
|
||||||
|
///
|
||||||
|
/// The returned [Vendor] objects come from the shared `krow_domain` package
|
||||||
|
/// because `Vendor` is already a clean domain entity.
|
||||||
|
Future<List<Vendor>> getVendors();
|
||||||
|
|
||||||
|
/// Returns the roles offered by the vendor identified by [vendorId].
|
||||||
|
Future<List<OrderRole>> getRolesByVendor(String vendorId);
|
||||||
|
|
||||||
|
/// Returns the team hubs owned by the business identified by [ownerId].
|
||||||
|
Future<List<OrderHub>> getHubsByOwner(String ownerId);
|
||||||
|
|
||||||
|
/// Returns the managers assigned to the hub identified by [hubId].
|
||||||
|
///
|
||||||
|
/// Only team members with the MANAGER role at the given hub are included.
|
||||||
|
Future<List<OrderManager>> getManagersByHub(String hubId);
|
||||||
|
|
||||||
|
/// Returns the current business ID from the active client session.
|
||||||
|
///
|
||||||
|
/// This allows BLoCs to resolve the business ID without depending on
|
||||||
|
/// the data layer's session store directly, keeping the presentation
|
||||||
|
/// layer free from `krow_data_connect` imports.
|
||||||
|
Future<String> getBusinessId();
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:client_create_order/src/domain/arguments/one_time_order_arguments.dart';
|
import 'package:client_create_order/src/domain/arguments/one_time_order_arguments.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||||
|
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/create_one_time_order_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/create_one_time_order_usecase.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.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';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import 'one_time_order_event.dart';
|
import 'one_time_order_event.dart';
|
||||||
@@ -18,7 +20,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
OneTimeOrderBloc(
|
OneTimeOrderBloc(
|
||||||
this._createOneTimeOrderUseCase,
|
this._createOneTimeOrderUseCase,
|
||||||
this._getOrderDetailsForReorderUseCase,
|
this._getOrderDetailsForReorderUseCase,
|
||||||
this._service,
|
this._queryRepository,
|
||||||
) : super(OneTimeOrderState.initial()) {
|
) : super(OneTimeOrderState.initial()) {
|
||||||
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
|
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
|
||||||
on<OneTimeOrderVendorChanged>(_onVendorChanged);
|
on<OneTimeOrderVendorChanged>(_onVendorChanged);
|
||||||
@@ -39,25 +41,11 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
}
|
}
|
||||||
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
|
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
|
||||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||||
final dc.DataConnectService _service;
|
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||||
|
|
||||||
Future<void> _loadVendors() async {
|
Future<void> _loadVendors() async {
|
||||||
final List<Vendor>? vendors = await handleErrorWithResult(
|
final List<Vendor>? vendors = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () => _queryRepository.getVendors(),
|
||||||
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
|
|
||||||
.connector
|
|
||||||
.listVendors()
|
|
||||||
.execute();
|
|
||||||
return result.data.vendors
|
|
||||||
.map(
|
|
||||||
(dc.ListVendorsVendors vendor) => Vendor(
|
|
||||||
id: vendor.id,
|
|
||||||
name: vendor.companyName,
|
|
||||||
rates: const <String, double>{},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
onError: (_) => add(const OneTimeOrderVendorsLoaded(<Vendor>[])),
|
onError: (_) => add(const OneTimeOrderVendorsLoaded(<Vendor>[])),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -72,19 +60,14 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
) async {
|
) async {
|
||||||
final List<OneTimeOrderRoleOption>? roles = await handleErrorWithResult(
|
final List<OneTimeOrderRoleOption>? roles = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<
|
final List<OrderRole> result =
|
||||||
dc.ListRolesByVendorIdData,
|
await _queryRepository.getRolesByVendor(vendorId);
|
||||||
dc.ListRolesByVendorIdVariables
|
return result
|
||||||
>
|
|
||||||
result = await _service.connector
|
|
||||||
.listRolesByVendorId(vendorId: vendorId)
|
|
||||||
.execute();
|
|
||||||
return result.data.roles
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListRolesByVendorIdRoles role) => OneTimeOrderRoleOption(
|
(OrderRole r) => OneTimeOrderRoleOption(
|
||||||
id: role.id,
|
id: r.id,
|
||||||
name: role.name,
|
name: r.name,
|
||||||
costPerHour: role.costPerHour,
|
costPerHour: r.costPerHour,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -101,28 +84,23 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
Future<void> _loadHubs() async {
|
Future<void> _loadHubs() async {
|
||||||
final List<OneTimeOrderHubOption>? hubs = await handleErrorWithResult(
|
final List<OneTimeOrderHubOption>? hubs = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final String businessId = await _service.getBusinessId();
|
final String businessId = await _queryRepository.getBusinessId();
|
||||||
final fdc.QueryResult<
|
final List<OrderHub> result =
|
||||||
dc.ListTeamHubsByOwnerIdData,
|
await _queryRepository.getHubsByOwner(businessId);
|
||||||
dc.ListTeamHubsByOwnerIdVariables
|
return result
|
||||||
>
|
|
||||||
result = await _service.connector
|
|
||||||
.listTeamHubsByOwnerId(ownerId: businessId)
|
|
||||||
.execute();
|
|
||||||
return result.data.teamHubs
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => OneTimeOrderHubOption(
|
(OrderHub h) => OneTimeOrderHubOption(
|
||||||
id: hub.id,
|
id: h.id,
|
||||||
name: hub.hubName,
|
name: h.name,
|
||||||
address: hub.address,
|
address: h.address,
|
||||||
placeId: hub.placeId,
|
placeId: h.placeId,
|
||||||
latitude: hub.latitude,
|
latitude: h.latitude,
|
||||||
longitude: hub.longitude,
|
longitude: h.longitude,
|
||||||
city: hub.city,
|
city: h.city,
|
||||||
state: hub.state,
|
state: h.state,
|
||||||
street: hub.street,
|
street: h.street,
|
||||||
country: hub.country,
|
country: h.country,
|
||||||
zipCode: hub.zipCode,
|
zipCode: h.zipCode,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -140,23 +118,14 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
final List<OneTimeOrderManagerOption>? managers =
|
final List<OneTimeOrderManagerOption>? managers =
|
||||||
await handleErrorWithResult(
|
await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
final List<OrderManager> result =
|
||||||
await _service.connector.listTeamMembers().execute();
|
await _queryRepository.getManagersByHub(hubId);
|
||||||
|
return result
|
||||||
return result.data.teamMembers
|
|
||||||
.where(
|
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
|
||||||
member.teamHubId == hubId &&
|
|
||||||
member.role is dc.Known<dc.TeamMemberRole> &&
|
|
||||||
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
|
||||||
dc.TeamMemberRole.MANAGER,
|
|
||||||
)
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
(OrderManager m) => OneTimeOrderManagerOption(
|
||||||
OneTimeOrderManagerOption(
|
id: m.id,
|
||||||
id: member.id,
|
name: m.name,
|
||||||
name: member.user.fullName ?? 'Unknown',
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||||
|
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/create_permanent_order_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/create_permanent_order_usecase.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.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 'package:krow_domain/krow_domain.dart' as domain;
|
||||||
|
|
||||||
import 'permanent_order_event.dart';
|
import 'permanent_order_event.dart';
|
||||||
@@ -17,7 +19,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
PermanentOrderBloc(
|
PermanentOrderBloc(
|
||||||
this._createPermanentOrderUseCase,
|
this._createPermanentOrderUseCase,
|
||||||
this._getOrderDetailsForReorderUseCase,
|
this._getOrderDetailsForReorderUseCase,
|
||||||
this._service,
|
this._queryRepository,
|
||||||
) : super(PermanentOrderState.initial()) {
|
) : super(PermanentOrderState.initial()) {
|
||||||
on<PermanentOrderVendorsLoaded>(_onVendorsLoaded);
|
on<PermanentOrderVendorsLoaded>(_onVendorsLoaded);
|
||||||
on<PermanentOrderVendorChanged>(_onVendorChanged);
|
on<PermanentOrderVendorChanged>(_onVendorChanged);
|
||||||
@@ -40,7 +42,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
|
|
||||||
final CreatePermanentOrderUseCase _createPermanentOrderUseCase;
|
final CreatePermanentOrderUseCase _createPermanentOrderUseCase;
|
||||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||||
final dc.DataConnectService _service;
|
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||||
|
|
||||||
static const List<String> _dayLabels = <String>[
|
static const List<String> _dayLabels = <String>[
|
||||||
'SUN',
|
'SUN',
|
||||||
@@ -54,21 +56,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
|
|
||||||
Future<void> _loadVendors() async {
|
Future<void> _loadVendors() async {
|
||||||
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () => _queryRepository.getVendors(),
|
||||||
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
|
|
||||||
.connector
|
|
||||||
.listVendors()
|
|
||||||
.execute();
|
|
||||||
return result.data.vendors
|
|
||||||
.map(
|
|
||||||
(dc.ListVendorsVendors vendor) => domain.Vendor(
|
|
||||||
id: vendor.id,
|
|
||||||
name: vendor.companyName,
|
|
||||||
rates: const <String, double>{},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
onError: (_) => add(const PermanentOrderVendorsLoaded(<domain.Vendor>[])),
|
onError: (_) => add(const PermanentOrderVendorsLoaded(<domain.Vendor>[])),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,19 +71,14 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
) async {
|
) async {
|
||||||
final List<PermanentOrderRoleOption>? roles = await handleErrorWithResult(
|
final List<PermanentOrderRoleOption>? roles = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<
|
final List<OrderRole> orderRoles =
|
||||||
dc.ListRolesByVendorIdData,
|
await _queryRepository.getRolesByVendor(vendorId);
|
||||||
dc.ListRolesByVendorIdVariables
|
return orderRoles
|
||||||
>
|
|
||||||
result = await _service.connector
|
|
||||||
.listRolesByVendorId(vendorId: vendorId)
|
|
||||||
.execute();
|
|
||||||
return result.data.roles
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListRolesByVendorIdRoles role) => PermanentOrderRoleOption(
|
(OrderRole r) => PermanentOrderRoleOption(
|
||||||
id: role.id,
|
id: r.id,
|
||||||
name: role.name,
|
name: r.name,
|
||||||
costPerHour: role.costPerHour,
|
costPerHour: r.costPerHour,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -112,19 +95,17 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
Future<void> _loadHubs() async {
|
Future<void> _loadHubs() async {
|
||||||
final List<PermanentOrderHubOption>? hubs = await handleErrorWithResult(
|
final List<PermanentOrderHubOption>? hubs = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final String businessId = await _service.getBusinessId();
|
final String? businessId = await _queryRepository.getBusinessId();
|
||||||
final fdc.QueryResult<
|
if (businessId == null || businessId.isEmpty) {
|
||||||
dc.ListTeamHubsByOwnerIdData,
|
return <PermanentOrderHubOption>[];
|
||||||
dc.ListTeamHubsByOwnerIdVariables
|
}
|
||||||
>
|
final List<OrderHub> orderHubs =
|
||||||
result = await _service.connector
|
await _queryRepository.getHubsByOwner(businessId);
|
||||||
.listTeamHubsByOwnerId(ownerId: businessId)
|
return orderHubs
|
||||||
.execute();
|
|
||||||
return result.data.teamHubs
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => PermanentOrderHubOption(
|
(OrderHub hub) => PermanentOrderHubOption(
|
||||||
id: hub.id,
|
id: hub.id,
|
||||||
name: hub.hubName,
|
name: hub.name,
|
||||||
address: hub.address,
|
address: hub.address,
|
||||||
placeId: hub.placeId,
|
placeId: hub.placeId,
|
||||||
latitude: hub.latitude,
|
latitude: hub.latitude,
|
||||||
@@ -219,22 +200,13 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
final List<PermanentOrderManagerOption>? managers =
|
final List<PermanentOrderManagerOption>? managers =
|
||||||
await handleErrorWithResult(
|
await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
final List<OrderManager> orderManagers =
|
||||||
await _service.connector.listTeamMembers().execute();
|
await _queryRepository.getManagersByHub(hubId);
|
||||||
|
return orderManagers
|
||||||
return result.data.teamMembers
|
|
||||||
.where(
|
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
|
||||||
member.teamHubId == hubId &&
|
|
||||||
member.role is dc.Known<dc.TeamMemberRole> &&
|
|
||||||
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
|
||||||
dc.TeamMemberRole.MANAGER,
|
|
||||||
)
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
(OrderManager m) => PermanentOrderManagerOption(
|
||||||
PermanentOrderManagerOption(
|
id: m.id,
|
||||||
id: member.id,
|
name: m.name,
|
||||||
name: member.user.fullName ?? 'Unknown',
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
|
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||||
|
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||||
|
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/create_recurring_order_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/create_recurring_order_usecase.dart';
|
||||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.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 'package:krow_domain/krow_domain.dart' as domain;
|
||||||
|
|
||||||
import 'recurring_order_event.dart';
|
import 'recurring_order_event.dart';
|
||||||
import 'recurring_order_state.dart';
|
import 'recurring_order_state.dart';
|
||||||
|
|
||||||
/// BLoC for managing the recurring order creation form.
|
/// BLoC for managing the recurring order creation form.
|
||||||
|
///
|
||||||
|
/// This BLoC delegates all backend queries to
|
||||||
|
/// [ClientOrderQueryRepositoryInterface] and order submission to
|
||||||
|
/// [CreateRecurringOrderUseCase], keeping the presentation layer free
|
||||||
|
/// from direct `krow_data_connect` imports.
|
||||||
class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||||
with
|
with
|
||||||
BlocErrorHandler<RecurringOrderState>,
|
BlocErrorHandler<RecurringOrderState>,
|
||||||
SafeBloc<RecurringOrderEvent, RecurringOrderState> {
|
SafeBloc<RecurringOrderEvent, RecurringOrderState> {
|
||||||
|
/// Creates a [RecurringOrderBloc] with the required use cases and
|
||||||
|
/// query repository.
|
||||||
RecurringOrderBloc(
|
RecurringOrderBloc(
|
||||||
this._createRecurringOrderUseCase,
|
this._createRecurringOrderUseCase,
|
||||||
this._getOrderDetailsForReorderUseCase,
|
this._getOrderDetailsForReorderUseCase,
|
||||||
this._service,
|
this._queryRepository,
|
||||||
) : super(RecurringOrderState.initial()) {
|
) : super(RecurringOrderState.initial()) {
|
||||||
on<RecurringOrderVendorsLoaded>(_onVendorsLoaded);
|
on<RecurringOrderVendorsLoaded>(_onVendorsLoaded);
|
||||||
on<RecurringOrderVendorChanged>(_onVendorChanged);
|
on<RecurringOrderVendorChanged>(_onVendorChanged);
|
||||||
@@ -41,7 +50,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
|
|
||||||
final CreateRecurringOrderUseCase _createRecurringOrderUseCase;
|
final CreateRecurringOrderUseCase _createRecurringOrderUseCase;
|
||||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||||
final dc.DataConnectService _service;
|
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||||
|
|
||||||
static const List<String> _dayLabels = <String>[
|
static const List<String> _dayLabels = <String>[
|
||||||
'SUN',
|
'SUN',
|
||||||
@@ -53,24 +62,14 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
'SAT',
|
'SAT',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Loads the list of available vendors from the query repository.
|
||||||
Future<void> _loadVendors() async {
|
Future<void> _loadVendors() async {
|
||||||
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
|
return _queryRepository.getVendors();
|
||||||
.connector
|
|
||||||
.listVendors()
|
|
||||||
.execute();
|
|
||||||
return result.data.vendors
|
|
||||||
.map(
|
|
||||||
(dc.ListVendorsVendors vendor) => domain.Vendor(
|
|
||||||
id: vendor.id,
|
|
||||||
name: vendor.companyName,
|
|
||||||
rates: const <String, double>{},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
onError: (_) => add(const RecurringOrderVendorsLoaded(<domain.Vendor>[])),
|
onError: (_) =>
|
||||||
|
add(const RecurringOrderVendorsLoaded(<domain.Vendor>[])),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vendors != null) {
|
if (vendors != null) {
|
||||||
@@ -78,25 +77,22 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads roles for the given [vendorId] and maps them to presentation
|
||||||
|
/// option models.
|
||||||
Future<void> _loadRolesForVendor(
|
Future<void> _loadRolesForVendor(
|
||||||
String vendorId,
|
String vendorId,
|
||||||
Emitter<RecurringOrderState> emit,
|
Emitter<RecurringOrderState> emit,
|
||||||
) async {
|
) async {
|
||||||
final List<RecurringOrderRoleOption>? roles = await handleErrorWithResult(
|
final List<RecurringOrderRoleOption>? roles = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<
|
final List<OrderRole> orderRoles =
|
||||||
dc.ListRolesByVendorIdData,
|
await _queryRepository.getRolesByVendor(vendorId);
|
||||||
dc.ListRolesByVendorIdVariables
|
return orderRoles
|
||||||
>
|
|
||||||
result = await _service.connector
|
|
||||||
.listRolesByVendorId(vendorId: vendorId)
|
|
||||||
.execute();
|
|
||||||
return result.data.roles
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListRolesByVendorIdRoles role) => RecurringOrderRoleOption(
|
(OrderRole r) => RecurringOrderRoleOption(
|
||||||
id: role.id,
|
id: r.id,
|
||||||
name: role.name,
|
name: r.name,
|
||||||
costPerHour: role.costPerHour,
|
costPerHour: r.costPerHour,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -110,22 +106,19 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads team hubs for the current business owner and maps them to
|
||||||
|
/// presentation option models.
|
||||||
Future<void> _loadHubs() async {
|
Future<void> _loadHubs() async {
|
||||||
final List<RecurringOrderHubOption>? hubs = await handleErrorWithResult(
|
final List<RecurringOrderHubOption>? hubs = await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final String businessId = await _service.getBusinessId();
|
final String businessId = await _queryRepository.getBusinessId();
|
||||||
final fdc.QueryResult<
|
final List<OrderHub> orderHubs =
|
||||||
dc.ListTeamHubsByOwnerIdData,
|
await _queryRepository.getHubsByOwner(businessId);
|
||||||
dc.ListTeamHubsByOwnerIdVariables
|
return orderHubs
|
||||||
>
|
|
||||||
result = await _service.connector
|
|
||||||
.listTeamHubsByOwnerId(ownerId: businessId)
|
|
||||||
.execute();
|
|
||||||
return result.data.teamHubs
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => RecurringOrderHubOption(
|
(OrderHub hub) => RecurringOrderHubOption(
|
||||||
id: hub.id,
|
id: hub.id,
|
||||||
name: hub.hubName,
|
name: hub.name,
|
||||||
address: hub.address,
|
address: hub.address,
|
||||||
placeId: hub.placeId,
|
placeId: hub.placeId,
|
||||||
latitude: hub.latitude,
|
latitude: hub.latitude,
|
||||||
@@ -213,6 +206,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
emit(state.copyWith(managers: event.managers));
|
emit(state.copyWith(managers: event.managers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads managers for the given [hubId] and maps them to presentation
|
||||||
|
/// option models.
|
||||||
Future<void> _loadManagersForHub(
|
Future<void> _loadManagersForHub(
|
||||||
String hubId,
|
String hubId,
|
||||||
Emitter<RecurringOrderState> emit,
|
Emitter<RecurringOrderState> emit,
|
||||||
@@ -220,22 +215,13 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
final List<RecurringOrderManagerOption>? managers =
|
final List<RecurringOrderManagerOption>? managers =
|
||||||
await handleErrorWithResult(
|
await handleErrorWithResult(
|
||||||
action: () async {
|
action: () async {
|
||||||
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
final List<OrderManager> orderManagers =
|
||||||
await _service.connector.listTeamMembers().execute();
|
await _queryRepository.getManagersByHub(hubId);
|
||||||
|
return orderManagers
|
||||||
return result.data.teamMembers
|
|
||||||
.where(
|
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
|
||||||
member.teamHubId == hubId &&
|
|
||||||
member.role is dc.Known<dc.TeamMemberRole> &&
|
|
||||||
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
|
||||||
dc.TeamMemberRole.MANAGER,
|
|
||||||
)
|
|
||||||
.map(
|
.map(
|
||||||
(dc.ListTeamMembersTeamMembers member) =>
|
(OrderManager m) => RecurringOrderManagerOption(
|
||||||
RecurringOrderManagerOption(
|
id: m.id,
|
||||||
id: member.id,
|
name: m.name,
|
||||||
name: member.user.fullName ?? 'Unknown',
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@@ -35,10 +35,9 @@ class HubManagerSelector extends StatelessWidget {
|
|||||||
style: UiTypography.body1m.textPrimary,
|
style: UiTypography.body1m.textPrimary,
|
||||||
),
|
),
|
||||||
if (description != null) ...<Widget>[
|
if (description != null) ...<Widget>[
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
Text(description!, style: UiTypography.body2r.textSecondary),
|
Text(description!, style: UiTypography.body2r.textSecondary),
|
||||||
],
|
],
|
||||||
const SizedBox(height: UiConstants.space2),
|
const SizedBox(height: UiConstants.space3),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => _showSelector(context),
|
onTap: () => _showSelector(context),
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
|||||||
Reference in New Issue
Block a user