Merge pull request #388 from Oloodi/feature/centralized-data-error-handling
Fix: Resolve critical bugs and linting issues (concurrency, syntax, dead code)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_current_bill_amount.dart';
|
||||
import '../../domain/usecases/get_invoice_history.dart';
|
||||
@@ -11,7 +12,8 @@ import 'billing_event.dart';
|
||||
import 'billing_state.dart';
|
||||
|
||||
/// BLoC for managing billing state and data loading.
|
||||
class BillingBloc extends Bloc<BillingEvent, BillingState> {
|
||||
class BillingBloc extends Bloc<BillingEvent, BillingState>
|
||||
with BlocErrorHandler<BillingState> {
|
||||
/// Creates a [BillingBloc] with the given use cases.
|
||||
BillingBloc({
|
||||
required GetCurrentBillAmountUseCase getCurrentBillAmount,
|
||||
@@ -40,82 +42,82 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
|
||||
Emitter<BillingState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: BillingStatus.loading));
|
||||
try {
|
||||
final List<dynamic> results = await Future.wait<dynamic>(<Future<dynamic>>[
|
||||
_getCurrentBillAmount.call(),
|
||||
_getSavingsAmount.call(),
|
||||
_getPendingInvoices.call(),
|
||||
_getInvoiceHistory.call(),
|
||||
_getSpendingBreakdown.call(state.period),
|
||||
]);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<dynamic> results =
|
||||
await Future.wait<dynamic>(<Future<dynamic>>[
|
||||
_getCurrentBillAmount.call(),
|
||||
_getSavingsAmount.call(),
|
||||
_getPendingInvoices.call(),
|
||||
_getInvoiceHistory.call(),
|
||||
_getSpendingBreakdown.call(state.period),
|
||||
]);
|
||||
|
||||
final double currentBill = results[0] as double;
|
||||
final double savings = results[1] as double;
|
||||
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
||||
final List<Invoice> invoiceHistory = results[3] as List<Invoice>;
|
||||
final List<InvoiceItem> spendingItems = results[4] as List<InvoiceItem>;
|
||||
final double currentBill = results[0] as double;
|
||||
final double savings = results[1] as double;
|
||||
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
||||
final List<Invoice> invoiceHistory = results[3] as List<Invoice>;
|
||||
final List<InvoiceItem> spendingItems = results[4] as List<InvoiceItem>;
|
||||
|
||||
// Map Domain Entities to Presentation Models
|
||||
final List<BillingInvoice> uiPendingInvoices = pendingInvoices
|
||||
.map(_mapInvoiceToUiModel)
|
||||
.toList();
|
||||
final List<BillingInvoice> uiInvoiceHistory = invoiceHistory
|
||||
.map(_mapInvoiceToUiModel)
|
||||
.toList();
|
||||
final List<SpendingBreakdownItem> uiSpendingBreakdown = _mapSpendingItemsToUiModel(spendingItems);
|
||||
final double periodTotal = uiSpendingBreakdown.fold(
|
||||
0.0,
|
||||
(double sum, SpendingBreakdownItem item) => sum + item.amount,
|
||||
);
|
||||
// Map Domain Entities to Presentation Models
|
||||
final List<BillingInvoice> uiPendingInvoices =
|
||||
pendingInvoices.map(_mapInvoiceToUiModel).toList();
|
||||
final List<BillingInvoice> uiInvoiceHistory =
|
||||
invoiceHistory.map(_mapInvoiceToUiModel).toList();
|
||||
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
||||
_mapSpendingItemsToUiModel(spendingItems);
|
||||
final double periodTotal = uiSpendingBreakdown.fold(
|
||||
0.0,
|
||||
(double sum, SpendingBreakdownItem item) => sum + item.amount,
|
||||
);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BillingStatus.success,
|
||||
currentBill: periodTotal,
|
||||
savings: savings,
|
||||
pendingInvoices: uiPendingInvoices,
|
||||
invoiceHistory: uiInvoiceHistory,
|
||||
spendingBreakdown: uiSpendingBreakdown,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BillingStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BillingStatus.success,
|
||||
currentBill: periodTotal,
|
||||
savings: savings,
|
||||
pendingInvoices: uiPendingInvoices,
|
||||
invoiceHistory: uiInvoiceHistory,
|
||||
spendingBreakdown: uiSpendingBreakdown,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: BillingStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPeriodChanged(
|
||||
BillingPeriodChanged event,
|
||||
Emitter<BillingState> emit,
|
||||
) async {
|
||||
try {
|
||||
final List<InvoiceItem> spendingItems =
|
||||
await _getSpendingBreakdown.call(event.period);
|
||||
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
||||
_mapSpendingItemsToUiModel(spendingItems);
|
||||
final double periodTotal = uiSpendingBreakdown.fold(
|
||||
0.0,
|
||||
(double sum, SpendingBreakdownItem item) => sum + item.amount,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
period: event.period,
|
||||
spendingBreakdown: uiSpendingBreakdown,
|
||||
currentBill: periodTotal,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BillingStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<InvoiceItem> spendingItems =
|
||||
await _getSpendingBreakdown.call(event.period);
|
||||
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
||||
_mapSpendingItemsToUiModel(spendingItems);
|
||||
final double periodTotal = uiSpendingBreakdown.fold(
|
||||
0.0,
|
||||
(double sum, SpendingBreakdownItem item) => sum + item.amount,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
period: event.period,
|
||||
spendingBreakdown: uiSpendingBreakdown,
|
||||
currentBill: periodTotal,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: BillingStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BillingInvoice _mapInvoiceToUiModel(Invoice invoice) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/arguments/get_coverage_stats_arguments.dart';
|
||||
import '../../domain/arguments/get_shifts_for_date_arguments.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import '../../domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
@@ -13,14 +14,15 @@ import 'coverage_state.dart';
|
||||
/// - Loading shifts for a specific date
|
||||
/// - Loading coverage statistics
|
||||
/// - Refreshing coverage data
|
||||
class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
with BlocErrorHandler<CoverageState> {
|
||||
/// Creates a [CoverageBloc].
|
||||
CoverageBloc({
|
||||
required GetShiftsForDateUseCase getShiftsForDate,
|
||||
required GetCoverageStatsUseCase getCoverageStats,
|
||||
}) : _getShiftsForDate = getShiftsForDate,
|
||||
_getCoverageStats = getCoverageStats,
|
||||
super(const CoverageState()) {
|
||||
}) : _getShiftsForDate = getShiftsForDate,
|
||||
_getCoverageStats = getCoverageStats,
|
||||
super(const CoverageState()) {
|
||||
on<CoverageLoadRequested>(_onLoadRequested);
|
||||
on<CoverageRefreshRequested>(_onRefreshRequested);
|
||||
}
|
||||
@@ -40,31 +42,31 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
// Fetch shifts and stats concurrently
|
||||
final List<Object> results = await Future.wait<Object>(<Future<Object>>[
|
||||
_getShiftsForDate(GetShiftsForDateArguments(date: event.date)),
|
||||
_getCoverageStats(GetCoverageStatsArguments(date: event.date)),
|
||||
]);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
// Fetch shifts and stats concurrently
|
||||
final List<Object> results = await Future.wait<Object>(<Future<Object>>[
|
||||
_getShiftsForDate(GetShiftsForDateArguments(date: event.date)),
|
||||
_getCoverageStats(GetCoverageStatsArguments(date: event.date)),
|
||||
]);
|
||||
|
||||
final List<CoverageShift> shifts = results[0] as List<CoverageShift>;
|
||||
final CoverageStats stats = results[1] as CoverageStats;
|
||||
final List<CoverageShift> shifts = results[0] as List<CoverageShift>;
|
||||
final CoverageStats stats = results[1] as CoverageStats;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CoverageStatus.success,
|
||||
shifts: shifts,
|
||||
stats: stats,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CoverageStatus.failure,
|
||||
errorMessage: error.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CoverageStatus.success,
|
||||
shifts: shifts,
|
||||
stats: stats,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: CoverageStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the refresh requested event.
|
||||
@@ -78,3 +80,4 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
add(CoverageLoadRequested(date: state.selectedDate!));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,9 +128,7 @@ class ClientCreateOrderRepositoryImpl
|
||||
final double rate = order.roleRates[position.role] ?? 0;
|
||||
final double totalValue = rate * hours * position.count;
|
||||
|
||||
print(
|
||||
'CreateOneTimeOrder shiftRole: start=${start.toIso8601String()} end=${normalizedEnd.toIso8601String()}',
|
||||
);
|
||||
|
||||
|
||||
await executeProtected(() => _dataConnect
|
||||
.createShiftRole(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_order_types_usecase.dart';
|
||||
import 'client_create_order_event.dart';
|
||||
@@ -6,7 +7,8 @@ import 'client_create_order_state.dart';
|
||||
|
||||
/// BLoC for managing the list of available order types.
|
||||
class ClientCreateOrderBloc
|
||||
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
|
||||
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState>
|
||||
with BlocErrorHandler<ClientCreateOrderState> {
|
||||
ClientCreateOrderBloc(this._getOrderTypesUseCase)
|
||||
: super(const ClientCreateOrderInitial()) {
|
||||
on<ClientCreateOrderTypesRequested>(_onTypesRequested);
|
||||
@@ -17,7 +19,14 @@ class ClientCreateOrderBloc
|
||||
ClientCreateOrderTypesRequested event,
|
||||
Emitter<ClientCreateOrderState> emit,
|
||||
) async {
|
||||
final List<OrderType> types = await _getOrderTypesUseCase();
|
||||
emit(ClientCreateOrderLoadSuccess(types));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<OrderType> types = await _getOrderTypesUseCase();
|
||||
emit(ClientCreateOrderLoadSuccess(types));
|
||||
},
|
||||
onError: (String errorKey) => ClientCreateOrderLoadFailure(errorKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,3 +24,13 @@ class ClientCreateOrderLoadSuccess extends ClientCreateOrderState {
|
||||
@override
|
||||
List<Object?> get props => <Object?>[orderTypes];
|
||||
}
|
||||
|
||||
/// State representing a failure to load order types.
|
||||
class ClientCreateOrderLoadFailure extends ClientCreateOrderState {
|
||||
const ClientCreateOrderLoadFailure(this.error);
|
||||
|
||||
final String error;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[error];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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 '../../domain/arguments/one_time_order_arguments.dart';
|
||||
@@ -8,7 +9,8 @@ import 'one_time_order_event.dart';
|
||||
import 'one_time_order_state.dart';
|
||||
|
||||
/// BLoC for managing the multi-step one-time order creation form.
|
||||
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
with BlocErrorHandler<OneTimeOrderState> {
|
||||
OneTimeOrderBloc(this._createOneTimeOrderUseCase, this._dataConnect)
|
||||
: super(OneTimeOrderState.initial()) {
|
||||
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
|
||||
@@ -29,79 +31,93 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
|
||||
Future<void> _loadVendors() async {
|
||||
try {
|
||||
final QueryResult<dc.ListVendorsData, void> result = await _dataConnect.listVendors().execute();
|
||||
final List<Vendor> vendors = result.data.vendors
|
||||
.map(
|
||||
(dc.ListVendorsVendors vendor) => Vendor(
|
||||
id: vendor.id,
|
||||
name: vendor.companyName,
|
||||
rates: const <String, double>{},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final List<Vendor>? vendors = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final QueryResult<dc.ListVendorsData, void> result =
|
||||
await _dataConnect.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>[])),
|
||||
);
|
||||
|
||||
if (vendors != null) {
|
||||
add(OneTimeOrderVendorsLoaded(vendors));
|
||||
} catch (_) {
|
||||
add(const OneTimeOrderVendorsLoaded(<Vendor>[]));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRolesForVendor(String vendorId) async {
|
||||
try {
|
||||
final QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables> result = await _dataConnect.listRolesByVendorId(
|
||||
vendorId: vendorId,
|
||||
).execute();
|
||||
final List<OneTimeOrderRoleOption> roles = result.data.roles
|
||||
.map(
|
||||
(dc.ListRolesByVendorIdRoles role) => OneTimeOrderRoleOption(
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
costPerHour: role.costPerHour,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
Future<void> _loadRolesForVendor(String vendorId, Emitter<OneTimeOrderState> emit) async {
|
||||
final List<OneTimeOrderRoleOption>? roles = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables>
|
||||
result = await _dataConnect.listRolesByVendorId(vendorId: vendorId).execute();
|
||||
return result.data.roles
|
||||
.map(
|
||||
(dc.ListRolesByVendorIdRoles role) => OneTimeOrderRoleOption(
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
costPerHour: role.costPerHour,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
onError: (_) => emit(state.copyWith(roles: const <OneTimeOrderRoleOption>[])),
|
||||
);
|
||||
|
||||
if (roles != null) {
|
||||
emit(state.copyWith(roles: roles));
|
||||
} catch (_) {
|
||||
emit(state.copyWith(roles: const <OneTimeOrderRoleOption>[]));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadHubs() async {
|
||||
try {
|
||||
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
add(const OneTimeOrderHubsLoaded(<OneTimeOrderHubOption>[]));
|
||||
return;
|
||||
}
|
||||
final QueryResult<dc.ListTeamHubsByOwnerIdData, dc.ListTeamHubsByOwnerIdVariables>
|
||||
result = await _dataConnect.listTeamHubsByOwnerId(ownerId: businessId).execute();
|
||||
final List<OneTimeOrderHubOption> hubs = result.data.teamHubs
|
||||
.map(
|
||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => OneTimeOrderHubOption(
|
||||
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();
|
||||
final List<OneTimeOrderHubOption>? hubs = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final String? businessId =
|
||||
dc.ClientSessionStore.instance.session?.business?.id;
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
return <OneTimeOrderHubOption>[];
|
||||
}
|
||||
final QueryResult<dc.ListTeamHubsByOwnerIdData, dc.ListTeamHubsByOwnerIdVariables>
|
||||
result = await _dataConnect
|
||||
.listTeamHubsByOwnerId(ownerId: businessId)
|
||||
.execute();
|
||||
return result.data.teamHubs
|
||||
.map(
|
||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => OneTimeOrderHubOption(
|
||||
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();
|
||||
},
|
||||
onError: (_) => add(const OneTimeOrderHubsLoaded(<OneTimeOrderHubOption>[])),
|
||||
);
|
||||
|
||||
if (hubs != null) {
|
||||
add(OneTimeOrderHubsLoaded(hubs));
|
||||
} catch (_) {
|
||||
add(const OneTimeOrderHubsLoaded(<OneTimeOrderHubOption>[]));
|
||||
}
|
||||
}
|
||||
|
||||
void _onVendorsLoaded(
|
||||
Future<void> _onVendorsLoaded(
|
||||
OneTimeOrderVendorsLoaded event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) {
|
||||
) async {
|
||||
final Vendor? selectedVendor =
|
||||
event.vendors.isNotEmpty ? event.vendors.first : null;
|
||||
emit(
|
||||
@@ -111,16 +127,16 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
),
|
||||
);
|
||||
if (selectedVendor != null) {
|
||||
_loadRolesForVendor(selectedVendor.id);
|
||||
await _loadRolesForVendor(selectedVendor.id, emit);
|
||||
}
|
||||
}
|
||||
|
||||
void _onVendorChanged(
|
||||
Future<void> _onVendorChanged(
|
||||
OneTimeOrderVendorChanged event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) {
|
||||
) async {
|
||||
emit(state.copyWith(selectedVendor: event.vendor));
|
||||
_loadRolesForVendor(event.vendor.id);
|
||||
await _loadRolesForVendor(event.vendor.id, emit);
|
||||
}
|
||||
|
||||
void _onHubsLoaded(
|
||||
@@ -207,44 +223,45 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.loading));
|
||||
try {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final OneTimeOrderRoleOption role in state.roles) role.id: role.costPerHour,
|
||||
};
|
||||
final OneTimeOrderHubOption? selectedHub = state.selectedHub;
|
||||
if (selectedHub == null) {
|
||||
throw Exception('Hub is missing.');
|
||||
}
|
||||
final OneTimeOrder order = OneTimeOrder(
|
||||
date: state.date,
|
||||
location: selectedHub.name,
|
||||
positions: state.positions,
|
||||
hub: OneTimeOrderHubDetails(
|
||||
id: selectedHub.id,
|
||||
name: selectedHub.name,
|
||||
address: selectedHub.address,
|
||||
placeId: selectedHub.placeId,
|
||||
latitude: selectedHub.latitude,
|
||||
longitude: selectedHub.longitude,
|
||||
city: selectedHub.city,
|
||||
state: selectedHub.state,
|
||||
street: selectedHub.street,
|
||||
country: selectedHub.country,
|
||||
zipCode: selectedHub.zipCode,
|
||||
),
|
||||
eventName: state.eventName,
|
||||
vendorId: state.selectedVendor?.id,
|
||||
roleRates: roleRates,
|
||||
);
|
||||
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.success));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OneTimeOrderStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final OneTimeOrderRoleOption role in state.roles)
|
||||
role.id: role.costPerHour,
|
||||
};
|
||||
final OneTimeOrderHubOption? selectedHub = state.selectedHub;
|
||||
if (selectedHub == null) {
|
||||
throw const OrderMissingHubException();
|
||||
}
|
||||
final OneTimeOrder order = OneTimeOrder(
|
||||
date: state.date,
|
||||
location: selectedHub.name,
|
||||
positions: state.positions,
|
||||
hub: OneTimeOrderHubDetails(
|
||||
id: selectedHub.id,
|
||||
name: selectedHub.name,
|
||||
address: selectedHub.address,
|
||||
placeId: selectedHub.placeId,
|
||||
latitude: selectedHub.latitude,
|
||||
longitude: selectedHub.longitude,
|
||||
city: selectedHub.city,
|
||||
state: selectedHub.state,
|
||||
street: selectedHub.street,
|
||||
country: selectedHub.country,
|
||||
zipCode: selectedHub.zipCode,
|
||||
),
|
||||
eventName: state.eventName,
|
||||
vendorId: state.selectedVendor?.id,
|
||||
roleRates: roleRates,
|
||||
);
|
||||
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.success));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: OneTimeOrderStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../domain/arguments/rapid_order_arguments.dart';
|
||||
import '../../domain/usecases/create_rapid_order_usecase.dart';
|
||||
import 'rapid_order_event.dart';
|
||||
import 'rapid_order_state.dart';
|
||||
|
||||
/// BLoC for managing the rapid (urgent) order creation flow.
|
||||
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
|
||||
with BlocErrorHandler<RapidOrderState> {
|
||||
RapidOrderBloc(this._createRapidOrderUseCase)
|
||||
: super(
|
||||
const RapidOrderInitial(
|
||||
@@ -64,19 +66,18 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||
final RapidOrderState currentState = state;
|
||||
if (currentState is RapidOrderInitial) {
|
||||
final String message = currentState.message;
|
||||
print('RapidOrder submit: message="$message"');
|
||||
emit(const RapidOrderSubmitting());
|
||||
|
||||
try {
|
||||
await _createRapidOrderUseCase(
|
||||
RapidOrderArguments(description: message),
|
||||
);
|
||||
print('RapidOrder submit: success');
|
||||
emit(const RapidOrderSuccess());
|
||||
} catch (e) {
|
||||
print('RapidOrder submit: error=$e');
|
||||
emit(RapidOrderFailure(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _createRapidOrderUseCase(
|
||||
RapidOrderArguments(description: message),
|
||||
);
|
||||
emit(const RapidOrderSuccess());
|
||||
},
|
||||
onError: (String errorKey) => RapidOrderFailure(errorKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,3 +91,4 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:client_home/src/domain/repositories/home_repository_interface.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_dashboard_data_usecase.dart';
|
||||
import '../../domain/usecases/get_recent_reorders_usecase.dart';
|
||||
@@ -8,8 +9,8 @@ import 'client_home_event.dart';
|
||||
import 'client_home_state.dart';
|
||||
|
||||
/// BLoC responsible for managing the state and business logic of the client home dashboard.
|
||||
class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
|
||||
class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState>
|
||||
with BlocErrorHandler<ClientHomeState> {
|
||||
ClientHomeBloc({
|
||||
required GetDashboardDataUseCase getDashboardDataUseCase,
|
||||
required GetRecentReordersUseCase getRecentReordersUseCase,
|
||||
@@ -35,31 +36,31 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
Emitter<ClientHomeState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientHomeStatus.loading));
|
||||
try {
|
||||
// Get session data
|
||||
final UserSessionData sessionData = _getUserSessionDataUseCase();
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
// Get session data
|
||||
final UserSessionData sessionData = _getUserSessionDataUseCase();
|
||||
|
||||
// Get dashboard data
|
||||
final HomeDashboardData data = await _getDashboardDataUseCase();
|
||||
final List<ReorderItem> reorderItems = await _getRecentReordersUseCase();
|
||||
// Get dashboard data
|
||||
final HomeDashboardData data = await _getDashboardDataUseCase();
|
||||
final List<ReorderItem> reorderItems = await _getRecentReordersUseCase();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHomeStatus.success,
|
||||
dashboardData: data,
|
||||
reorderItems: reorderItems,
|
||||
businessName: sessionData.businessName,
|
||||
photoUrl: sessionData.photoUrl,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHomeStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHomeStatus.success,
|
||||
dashboardData: data,
|
||||
reorderItems: reorderItems,
|
||||
businessName: sessionData.businessName,
|
||||
photoUrl: sessionData.photoUrl,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClientHomeStatus.error,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEditModeToggled(
|
||||
@@ -73,7 +74,8 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
ClientHomeWidgetVisibilityToggled event,
|
||||
Emitter<ClientHomeState> emit,
|
||||
) {
|
||||
final Map<String, bool> newVisibility = Map<String, bool>.from(state.widgetVisibility);
|
||||
final Map<String, bool> newVisibility =
|
||||
Map<String, bool>.from(state.widgetVisibility);
|
||||
newVisibility[event.widgetId] = !(newVisibility[event.widgetId] ?? true);
|
||||
emit(state.copyWith(widgetVisibility: newVisibility));
|
||||
}
|
||||
@@ -119,3 +121,4 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../domain/usecases/sign_out_usecase.dart';
|
||||
|
||||
part 'client_settings_event.dart';
|
||||
part 'client_settings_state.dart';
|
||||
|
||||
/// BLoC to manage client settings and profile state.
|
||||
class ClientSettingsBloc
|
||||
extends Bloc<ClientSettingsEvent, ClientSettingsState> {
|
||||
class ClientSettingsBloc extends Bloc<ClientSettingsEvent, ClientSettingsState>
|
||||
with BlocErrorHandler<ClientSettingsState> {
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
ClientSettingsBloc({required SignOutUseCase signOutUseCase})
|
||||
@@ -21,11 +22,14 @@ class ClientSettingsBloc
|
||||
Emitter<ClientSettingsState> emit,
|
||||
) async {
|
||||
emit(const ClientSettingsLoading());
|
||||
try {
|
||||
await _signOutUseCase();
|
||||
emit(const ClientSettingsSignOutSuccess());
|
||||
} catch (e) {
|
||||
emit(ClientSettingsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _signOutUseCase();
|
||||
emit(const ClientSettingsSignOutSuccess());
|
||||
},
|
||||
onError: (String errorKey) => ClientSettingsError(errorKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/orders_day_arguments.dart';
|
||||
import '../../domain/arguments/orders_range_arguments.dart';
|
||||
@@ -10,14 +11,14 @@ import 'view_orders_state.dart';
|
||||
/// Cubit for managing the state of the View Orders feature.
|
||||
///
|
||||
/// This Cubit handles loading orders, date selection, and tab filtering.
|
||||
class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
class ViewOrdersCubit extends Cubit<ViewOrdersState>
|
||||
with BlocErrorHandler<ViewOrdersState> {
|
||||
ViewOrdersCubit({
|
||||
required GetOrdersUseCase getOrdersUseCase,
|
||||
required GetAcceptedApplicationsForDayUseCase getAcceptedAppsUseCase,
|
||||
})
|
||||
: _getOrdersUseCase = getOrdersUseCase,
|
||||
_getAcceptedAppsUseCase = getAcceptedAppsUseCase,
|
||||
super(ViewOrdersState(selectedDate: DateTime.now())) {
|
||||
}) : _getOrdersUseCase = getOrdersUseCase,
|
||||
_getAcceptedAppsUseCase = getAcceptedAppsUseCase,
|
||||
super(ViewOrdersState(selectedDate: DateTime.now())) {
|
||||
_init();
|
||||
}
|
||||
|
||||
@@ -36,30 +37,33 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
}) async {
|
||||
final int requestId = ++_requestId;
|
||||
emit(state.copyWith(status: ViewOrdersStatus.loading));
|
||||
try {
|
||||
final List<OrderItem> orders = await _getOrdersUseCase(
|
||||
OrdersRangeArguments(start: rangeStart, end: rangeEnd),
|
||||
);
|
||||
final Map<String, List<Map<String, dynamic>>> apps = await _getAcceptedAppsUseCase(
|
||||
OrdersDayArguments(day: dayForApps),
|
||||
);
|
||||
final List<OrderItem> updatedOrders = _applyApplications(orders, apps);
|
||||
if (requestId != _requestId) {
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ViewOrdersStatus.success,
|
||||
orders: updatedOrders,
|
||||
),
|
||||
);
|
||||
_updateDerivedState();
|
||||
} catch (_) {
|
||||
if (requestId != _requestId) {
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(status: ViewOrdersStatus.failure));
|
||||
}
|
||||
|
||||
await handleError(
|
||||
emit: (ViewOrdersState s) {
|
||||
if (requestId == _requestId) emit(s);
|
||||
},
|
||||
action: () async {
|
||||
final List<OrderItem> orders = await _getOrdersUseCase(
|
||||
OrdersRangeArguments(start: rangeStart, end: rangeEnd),
|
||||
);
|
||||
final Map<String, List<Map<String, dynamic>>> apps =
|
||||
await _getAcceptedAppsUseCase(OrdersDayArguments(day: dayForApps));
|
||||
|
||||
if (requestId != _requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<OrderItem> updatedOrders = _applyApplications(orders, apps);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ViewOrdersStatus.success,
|
||||
orders: updatedOrders,
|
||||
),
|
||||
);
|
||||
_updateDerivedState();
|
||||
},
|
||||
onError: (String _) => state.copyWith(status: ViewOrdersStatus.failure),
|
||||
);
|
||||
}
|
||||
|
||||
void selectDate(DateTime date) {
|
||||
@@ -78,7 +82,9 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
final DateTime? selectedDate = state.selectedDate;
|
||||
final DateTime updatedSelectedDate =
|
||||
selectedDate != null &&
|
||||
calendarDays.any((DateTime day) => _isSameDay(day, selectedDate))
|
||||
calendarDays.any(
|
||||
(DateTime day) => _isSameDay(day, selectedDate),
|
||||
)
|
||||
? selectedDate
|
||||
: calendarDays.first;
|
||||
emit(
|
||||
@@ -135,17 +141,21 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
}
|
||||
|
||||
Future<void> _refreshAcceptedApplications(DateTime day) async {
|
||||
try {
|
||||
final Map<String, List<Map<String, dynamic>>> apps = await _getAcceptedAppsUseCase(
|
||||
OrdersDayArguments(day: day),
|
||||
);
|
||||
final List<OrderItem> updatedOrders =
|
||||
_applyApplications(state.orders, apps);
|
||||
emit(state.copyWith(orders: updatedOrders));
|
||||
_updateDerivedState();
|
||||
} catch (_) {
|
||||
// Keep existing data on failure.
|
||||
}
|
||||
await handleErrorWithResult(
|
||||
action: () async {
|
||||
final Map<String, List<Map<String, dynamic>>> apps =
|
||||
await _getAcceptedAppsUseCase(OrdersDayArguments(day: day));
|
||||
final List<OrderItem> updatedOrders = _applyApplications(
|
||||
state.orders,
|
||||
apps,
|
||||
);
|
||||
emit(state.copyWith(orders: updatedOrders));
|
||||
_updateDerivedState();
|
||||
},
|
||||
onError: (_) {
|
||||
// Keep existing data on failure, just log error via handleErrorWithResult
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<OrderItem> _applyApplications(
|
||||
@@ -153,7 +163,8 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
Map<String, List<Map<String, dynamic>>> apps,
|
||||
) {
|
||||
return orders.map((OrderItem order) {
|
||||
final List<Map<String, dynamic>> confirmed = apps[order.id] ?? const <Map<String, dynamic>>[];
|
||||
final List<Map<String, dynamic>> confirmed =
|
||||
apps[order.id] ?? const <Map<String, dynamic>>[];
|
||||
if (confirmed.isEmpty) {
|
||||
return order;
|
||||
}
|
||||
@@ -209,9 +220,10 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
).format(state.selectedDate!);
|
||||
|
||||
// Filter by date
|
||||
final List<OrderItem> ordersOnDate = state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr)
|
||||
.toList();
|
||||
final List<OrderItem> ordersOnDate =
|
||||
state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr)
|
||||
.toList();
|
||||
|
||||
// Sort by start time
|
||||
ordersOnDate.sort(
|
||||
@@ -219,30 +231,38 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
);
|
||||
|
||||
if (state.filterTab == 'all') {
|
||||
final List<OrderItem> filtered = ordersOnDate
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
// TODO(orders): move PENDING to its own tab once available.
|
||||
<String>['OPEN', 'FILLED', 'CONFIRMED', 'PENDING', 'ASSIGNED']
|
||||
.contains(s.status),
|
||||
)
|
||||
.toList();
|
||||
final List<OrderItem> filtered =
|
||||
ordersOnDate
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
// TODO(orders): move PENDING to its own tab once available.
|
||||
<String>[
|
||||
'OPEN',
|
||||
'FILLED',
|
||||
'CONFIRMED',
|
||||
'PENDING',
|
||||
'ASSIGNED',
|
||||
].contains(s.status),
|
||||
)
|
||||
.toList();
|
||||
print(
|
||||
'ViewOrders tab=all statuses=${ordersOnDate.map((OrderItem s) => s.status).toList()} filtered=${filtered.length}',
|
||||
);
|
||||
return filtered;
|
||||
} else if (state.filterTab == 'active') {
|
||||
final List<OrderItem> filtered = ordersOnDate
|
||||
.where((OrderItem s) => s.status == 'IN_PROGRESS')
|
||||
.toList();
|
||||
final List<OrderItem> filtered =
|
||||
ordersOnDate
|
||||
.where((OrderItem s) => s.status == 'IN_PROGRESS')
|
||||
.toList();
|
||||
print(
|
||||
'ViewOrders tab=active statuses=${ordersOnDate.map((OrderItem s) => s.status).toList()} filtered=${filtered.length}',
|
||||
);
|
||||
return filtered;
|
||||
} else if (state.filterTab == 'completed') {
|
||||
final List<OrderItem> filtered = ordersOnDate
|
||||
.where((OrderItem s) => s.status == 'COMPLETED')
|
||||
.toList();
|
||||
final List<OrderItem> filtered =
|
||||
ordersOnDate
|
||||
.where((OrderItem s) => s.status == 'COMPLETED')
|
||||
.toList();
|
||||
print(
|
||||
'ViewOrders tab=completed statuses=${ordersOnDate.map((OrderItem s) => s.status).toList()} filtered=${filtered.length}',
|
||||
);
|
||||
@@ -260,11 +280,17 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
|
||||
if (category == 'active') {
|
||||
return state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr && s.status == 'IN_PROGRESS')
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
s.date == selectedDateStr && s.status == 'IN_PROGRESS',
|
||||
)
|
||||
.length;
|
||||
} else if (category == 'completed') {
|
||||
return state.orders
|
||||
.where((OrderItem s) => s.date == selectedDateStr && s.status == 'COMPLETED')
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
s.date == selectedDateStr && s.status == 'COMPLETED',
|
||||
)
|
||||
.length;
|
||||
}
|
||||
return 0;
|
||||
@@ -281,9 +307,15 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
.where(
|
||||
(OrderItem s) =>
|
||||
s.date == selectedDateStr &&
|
||||
<String>['OPEN', 'FILLED', 'CONFIRMED', 'PENDING', 'ASSIGNED']
|
||||
.contains(s.status),
|
||||
<String>[
|
||||
'OPEN',
|
||||
'FILLED',
|
||||
'CONFIRMED',
|
||||
'PENDING',
|
||||
'ASSIGNED',
|
||||
].contains(s.status),
|
||||
)
|
||||
.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user