Fix: Resolve critical linting issues and bugs (concurrency, syntax, dead code)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/sign_in_with_phone_arguments.dart';
|
||||
import '../../domain/arguments/verify_otp_arguments.dart';
|
||||
@@ -10,7 +11,9 @@ import 'auth_event.dart';
|
||||
import 'auth_state.dart';
|
||||
|
||||
/// BLoC responsible for handling authentication logic.
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
with BlocErrorHandler<AuthState>
|
||||
implements Disposable {
|
||||
/// The use case for signing in with a phone number.
|
||||
final SignInWithPhoneUseCase _signInUseCase;
|
||||
|
||||
@@ -84,7 +87,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
status: AuthStatus.error,
|
||||
mode: event.mode,
|
||||
phoneNumber: event.phoneNumber ?? state.phoneNumber,
|
||||
errorMessage: 'Please wait ${remaining}s before requesting a new code.',
|
||||
errorMessage:
|
||||
'Please wait ${remaining}s before requesting a new code.',
|
||||
cooldownSecondsRemaining: remaining,
|
||||
),
|
||||
);
|
||||
@@ -105,39 +109,40 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
cooldownSecondsRemaining: 0,
|
||||
),
|
||||
);
|
||||
try {
|
||||
final String? verificationId = await _signInUseCase(
|
||||
SignInWithPhoneArguments(
|
||||
phoneNumber: event.phoneNumber ?? state.phoneNumber,
|
||||
),
|
||||
);
|
||||
if (token != _requestToken) return;
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: AuthStatus.codeSent,
|
||||
verificationId: verificationId,
|
||||
cooldownSecondsRemaining: 0,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (token != _requestToken) return;
|
||||
emit(
|
||||
state.copyWith(
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final String? verificationId = await _signInUseCase(
|
||||
SignInWithPhoneArguments(
|
||||
phoneNumber: event.phoneNumber ?? state.phoneNumber,
|
||||
),
|
||||
);
|
||||
if (token != _requestToken) return;
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: AuthStatus.codeSent,
|
||||
verificationId: verificationId,
|
||||
cooldownSecondsRemaining: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (token != _requestToken) return state;
|
||||
return state.copyWith(
|
||||
status: AuthStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
errorMessage: errorKey,
|
||||
cooldownSecondsRemaining: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onCooldownTicked(
|
||||
AuthCooldownTicked event,
|
||||
Emitter<AuthState> emit,
|
||||
) {
|
||||
print('Auth cooldown tick: ${event.secondsRemaining}');
|
||||
if (event.secondsRemaining <= 0) {
|
||||
print('Auth cooldown finished: clearing message');
|
||||
_cancelCooldownTimer();
|
||||
_cooldownUntil = null;
|
||||
emit(
|
||||
@@ -166,11 +171,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
add(AuthCooldownTicked(remaining));
|
||||
_cooldownTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
|
||||
remaining -= 1;
|
||||
print('Auth cooldown timer: remaining=$remaining');
|
||||
if (remaining <= 0) {
|
||||
timer.cancel();
|
||||
_cooldownTimer = null;
|
||||
print('Auth cooldown timer: reached 0, emitting tick');
|
||||
add(const AuthCooldownTicked(0));
|
||||
return;
|
||||
}
|
||||
@@ -183,27 +186,29 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
_cooldownTimer = null;
|
||||
}
|
||||
|
||||
|
||||
/// Handles OTP submission and verification.
|
||||
Future<void> _onOtpSubmitted(
|
||||
AuthOtpSubmitted event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: AuthStatus.loading));
|
||||
try {
|
||||
final User? user = await _verifyOtpUseCase(
|
||||
VerifyOtpArguments(
|
||||
verificationId: event.verificationId,
|
||||
smsCode: event.smsCode,
|
||||
mode: event.mode,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: AuthStatus.authenticated, user: user));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(status: AuthStatus.error, errorMessage: e.toString()),
|
||||
);
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final User? user = await _verifyOtpUseCase(
|
||||
VerifyOtpArguments(
|
||||
verificationId: event.verificationId,
|
||||
smsCode: event.smsCode,
|
||||
mode: event.mode,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: AuthStatus.authenticated, user: user));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: AuthStatus.error,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Disposes the BLoC resources.
|
||||
@@ -213,3 +218,4 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../../domain/usecases/submit_profile_setup_usecase.dart';
|
||||
|
||||
import '../../../domain/usecases/search_cities_usecase.dart';
|
||||
@@ -10,7 +11,8 @@ export 'profile_setup_event.dart';
|
||||
export 'profile_setup_state.dart';
|
||||
|
||||
/// BLoC responsible for managing the profile setup state and logic.
|
||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
with BlocErrorHandler<ProfileSetupState> {
|
||||
ProfileSetupBloc({
|
||||
required SubmitProfileSetup submitProfileSetup,
|
||||
required SearchCitiesUseCase searchCities,
|
||||
@@ -86,25 +88,25 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ProfileSetupStatus.loading));
|
||||
|
||||
try {
|
||||
await _submitProfileSetup(
|
||||
fullName: state.fullName,
|
||||
bio: state.bio.isEmpty ? null : state.bio,
|
||||
preferredLocations: state.preferredLocations,
|
||||
maxDistanceMiles: state.maxDistanceMiles,
|
||||
industries: state.industries,
|
||||
skills: state.skills,
|
||||
);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _submitProfileSetup(
|
||||
fullName: state.fullName,
|
||||
bio: state.bio.isEmpty ? null : state.bio,
|
||||
preferredLocations: state.preferredLocations,
|
||||
maxDistanceMiles: state.maxDistanceMiles,
|
||||
industries: state.industries,
|
||||
skills: state.skills,
|
||||
);
|
||||
|
||||
emit(state.copyWith(status: ProfileSetupStatus.success));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ProfileSetupStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(state.copyWith(status: ProfileSetupStatus.success));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ProfileSetupStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLocationQueryChanged(
|
||||
@@ -116,6 +118,8 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
return;
|
||||
}
|
||||
|
||||
// For search, we might want to handle errors silently or distinctively
|
||||
// Using simple try-catch here as it's a search-as-you-type feature where error dialogs are intrusive
|
||||
try {
|
||||
final results = await _searchCities(event.query);
|
||||
emit(state.copyWith(locationSuggestions: results));
|
||||
@@ -132,3 +136,4 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecases/apply_quick_set_usecase.dart';
|
||||
import '../../domain/usecases/get_weekly_availability_usecase.dart';
|
||||
import '../../domain/usecases/update_day_availability_usecase.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'availability_event.dart';
|
||||
import 'availability_state.dart';
|
||||
|
||||
class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
|
||||
with BlocErrorHandler<AvailabilityState> {
|
||||
final GetWeeklyAvailabilityUseCase getWeeklyAvailability;
|
||||
final UpdateDayAvailabilityUseCase updateDayAvailability;
|
||||
final ApplyQuickSetUseCase applyQuickSet;
|
||||
@@ -28,27 +30,34 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
Emitter<AvailabilityState> emit,
|
||||
) async {
|
||||
emit(AvailabilityLoading());
|
||||
try {
|
||||
final days = await getWeeklyAvailability(
|
||||
GetWeeklyAvailabilityParams(event.weekStart));
|
||||
emit(AvailabilityLoaded(
|
||||
days: days,
|
||||
currentWeekStart: event.weekStart,
|
||||
selectedDate: event.preselectedDate ??
|
||||
(days.isNotEmpty ? days.first.date : DateTime.now()),
|
||||
));
|
||||
} catch (e) {
|
||||
emit(AvailabilityError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final days = await getWeeklyAvailability(
|
||||
GetWeeklyAvailabilityParams(event.weekStart),
|
||||
);
|
||||
emit(
|
||||
AvailabilityLoaded(
|
||||
days: days,
|
||||
currentWeekStart: event.weekStart,
|
||||
selectedDate: event.preselectedDate ??
|
||||
(days.isNotEmpty ? days.first.date : DateTime.now()),
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => AvailabilityError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelectDate(SelectDate event, Emitter<AvailabilityState> emit) {
|
||||
if (state is AvailabilityLoaded) {
|
||||
// Clear success message on navigation
|
||||
emit((state as AvailabilityLoaded).copyWith(
|
||||
selectedDate: event.date,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
emit(
|
||||
(state as AvailabilityLoaded).copyWith(
|
||||
selectedDate: event.date,
|
||||
clearSuccessMessage: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,14 +67,17 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
) async {
|
||||
if (state is AvailabilityLoaded) {
|
||||
final currentState = state as AvailabilityLoaded;
|
||||
|
||||
|
||||
// Clear message
|
||||
emit(currentState.copyWith(clearSuccessMessage: true));
|
||||
|
||||
final newWeekStart = currentState.currentWeekStart
|
||||
.add(Duration(days: event.direction * 7));
|
||||
|
||||
final diff = currentState.selectedDate.difference(currentState.currentWeekStart).inDays;
|
||||
final newWeekStart = currentState.currentWeekStart.add(
|
||||
Duration(days: event.direction * 7),
|
||||
);
|
||||
|
||||
final diff = currentState.selectedDate
|
||||
.difference(currentState.currentWeekStart)
|
||||
.inDays;
|
||||
final newSelectedDate = newWeekStart.add(Duration(days: diff));
|
||||
|
||||
add(LoadAvailability(newWeekStart, preselectedDate: newSelectedDate));
|
||||
@@ -78,7 +90,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
) async {
|
||||
if (state is AvailabilityLoaded) {
|
||||
final currentState = state as AvailabilityLoaded;
|
||||
|
||||
|
||||
final newDay = event.day.copyWith(isAvailable: !event.day.isAvailable);
|
||||
final updatedDays = currentState.days.map((d) {
|
||||
return d.date == event.day.date ? newDay : d;
|
||||
@@ -90,18 +102,29 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(days: currentState.days));
|
||||
}
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit(
|
||||
(state as AvailabilityLoaded).copyWith(
|
||||
successMessage: 'Availability updated',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
return (state as AvailabilityLoaded).copyWith(
|
||||
days: currentState.days,
|
||||
);
|
||||
}
|
||||
return AvailabilityError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +143,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
}).toList();
|
||||
|
||||
final newDay = event.day.copyWith(slots: updatedSlots);
|
||||
|
||||
|
||||
final updatedDays = currentState.days.map((d) {
|
||||
return d.date == event.day.date ? newDay : d;
|
||||
}).toList();
|
||||
@@ -131,18 +154,29 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(days: currentState.days));
|
||||
}
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit(
|
||||
(state as AvailabilityLoaded).copyWith(
|
||||
successMessage: 'Availability updated',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
return (state as AvailabilityLoaded).copyWith(
|
||||
days: currentState.days,
|
||||
);
|
||||
}
|
||||
return AvailabilityError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,28 +186,39 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
) async {
|
||||
if (state is AvailabilityLoaded) {
|
||||
final currentState = state as AvailabilityLoaded;
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isActionInProgress: true,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
final newDays = await applyQuickSet(
|
||||
ApplyQuickSetParams(currentState.currentWeekStart, event.type));
|
||||
|
||||
emit(currentState.copyWith(
|
||||
days: newDays,
|
||||
isActionInProgress: false,
|
||||
successMessage: 'Availability updated',
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isActionInProgress: false,
|
||||
// Could set error message here if we had a field for it, or emit AvailabilityError
|
||||
// But emitting AvailabilityError would replace the whole screen.
|
||||
));
|
||||
}
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
isActionInProgress: true,
|
||||
clearSuccessMessage: true,
|
||||
),
|
||||
);
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final newDays = await applyQuickSet(
|
||||
ApplyQuickSetParams(currentState.currentWeekStart, event.type),
|
||||
);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
days: newDays,
|
||||
isActionInProgress: false,
|
||||
successMessage: 'Availability updated',
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (state is AvailabilityLoaded) {
|
||||
return (state as AvailabilityLoaded).copyWith(
|
||||
isActionInProgress: false,
|
||||
);
|
||||
}
|
||||
return AvailabilityError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_todays_shift_usecase.dart';
|
||||
import '../../domain/usecases/get_attendance_status_usecase.dart';
|
||||
@@ -10,8 +11,8 @@ import '../../domain/arguments/clock_out_arguments.dart';
|
||||
import 'clock_in_event.dart';
|
||||
import 'clock_in_state.dart';
|
||||
|
||||
class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
|
||||
class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
||||
with BlocErrorHandler<ClockInState> {
|
||||
ClockInBloc({
|
||||
required GetTodaysShiftUseCase getTodaysShift,
|
||||
required GetAttendanceStatusUseCase getAttendanceStatus,
|
||||
@@ -47,92 +48,105 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
Emitter<ClockInState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.loading));
|
||||
try {
|
||||
final List<Shift> shifts = await _getTodaysShift();
|
||||
final AttendanceStatus status = await _getAttendanceStatus();
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<Shift> shifts = await _getTodaysShift();
|
||||
final AttendanceStatus status = await _getAttendanceStatus();
|
||||
|
||||
// Check permissions silently on load? Maybe better to wait for user interaction or specific event
|
||||
// However, if shift exists, we might want to check permission state
|
||||
Shift? selectedShift;
|
||||
if (shifts.isNotEmpty) {
|
||||
if (status.activeShiftId != null) {
|
||||
try {
|
||||
selectedShift =
|
||||
shifts.firstWhere((Shift s) => s.id == status.activeShiftId);
|
||||
} catch (_) {}
|
||||
Shift? selectedShift;
|
||||
if (shifts.isNotEmpty) {
|
||||
if (status.activeShiftId != null) {
|
||||
try {
|
||||
selectedShift =
|
||||
shifts.firstWhere((Shift s) => s.id == status.activeShiftId);
|
||||
} catch (_) {}
|
||||
}
|
||||
selectedShift ??= shifts.last;
|
||||
}
|
||||
selectedShift ??= shifts.last;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
todayShifts: shifts,
|
||||
selectedShift: selectedShift,
|
||||
attendance: status,
|
||||
));
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
todayShifts: shifts,
|
||||
selectedShift: selectedShift,
|
||||
attendance: status,
|
||||
));
|
||||
|
||||
if (selectedShift != null && !status.isCheckedIn) {
|
||||
add(RequestLocationPermission());
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
if (selectedShift != null && !status.isCheckedIn) {
|
||||
add(RequestLocationPermission());
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClockInStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRequestLocationPermission(
|
||||
RequestLocationPermission event,
|
||||
Emitter<ClockInState> emit,
|
||||
) async {
|
||||
try {
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
|
||||
final bool hasConsent = permission == LocationPermission.always || permission == LocationPermission.whileInUse;
|
||||
|
||||
emit(state.copyWith(hasLocationConsent: hasConsent));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
|
||||
if (hasConsent) {
|
||||
_startLocationUpdates();
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(errorMessage: "Location error: $e"));
|
||||
}
|
||||
final bool hasConsent =
|
||||
permission == LocationPermission.always ||
|
||||
permission == LocationPermission.whileInUse;
|
||||
|
||||
emit(state.copyWith(hasLocationConsent: hasConsent));
|
||||
|
||||
if (hasConsent) {
|
||||
await _startLocationUpdates();
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _startLocationUpdates() async {
|
||||
// Note: handleErrorWithResult could be used here too if we want centralized logging/conversion
|
||||
try {
|
||||
final Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
||||
|
||||
final Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
double distance = 0;
|
||||
bool isVerified = false; // Require location match by default if shift has location
|
||||
bool isVerified =
|
||||
false; // Require location match by default if shift has location
|
||||
|
||||
if (state.selectedShift != null &&
|
||||
state.selectedShift!.latitude != null &&
|
||||
state.selectedShift!.longitude != null) {
|
||||
distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
state.selectedShift!.latitude!,
|
||||
state.selectedShift!.longitude!,
|
||||
);
|
||||
isVerified = distance <= allowedRadiusMeters;
|
||||
distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
state.selectedShift!.latitude!,
|
||||
state.selectedShift!.longitude!,
|
||||
);
|
||||
isVerified = distance <= allowedRadiusMeters;
|
||||
} else {
|
||||
// If no shift location, assume verified or don't restrict?
|
||||
// For strict clock-in, maybe false? but let's default to verified to avoid blocking if data missing
|
||||
isVerified = true;
|
||||
isVerified = true;
|
||||
}
|
||||
|
||||
|
||||
if (!isClosed) {
|
||||
add(LocationUpdated(position: position, distance: distance, isVerified: isVerified));
|
||||
add(
|
||||
LocationUpdated(
|
||||
position: position,
|
||||
distance: distance,
|
||||
isVerified: isVerified,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle error silently or via state
|
||||
} catch (_) {
|
||||
// Geolocator errors usually handled via onRequestLocationPermission
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +158,8 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
currentLocation: event.position,
|
||||
distanceFromVenue: event.distance,
|
||||
isLocationVerified: event.isVerified,
|
||||
etaMinutes: (event.distance / 80).round(), // Rough estimate: 80m/min walking speed
|
||||
etaMinutes:
|
||||
(event.distance / 80).round(), // Rough estimate: 80m/min walking speed
|
||||
));
|
||||
}
|
||||
|
||||
@@ -152,10 +167,10 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
CommuteModeToggled event,
|
||||
Emitter<ClockInState> emit,
|
||||
) {
|
||||
emit(state.copyWith(isCommuteModeOn: event.isEnabled));
|
||||
if (event.isEnabled) {
|
||||
add(RequestLocationPermission());
|
||||
}
|
||||
emit(state.copyWith(isCommuteModeOn: event.isEnabled));
|
||||
if (event.isEnabled) {
|
||||
add(RequestLocationPermission());
|
||||
}
|
||||
}
|
||||
|
||||
void _onShiftSelected(
|
||||
@@ -186,28 +201,23 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
CheckInRequested event,
|
||||
Emitter<ClockInState> emit,
|
||||
) async {
|
||||
// Only verify location if not using NFC (or depending on requirements) - enforcing for swipe
|
||||
if (state.checkInMode == 'swipe' && !state.isLocationVerified) {
|
||||
// Allow for now since coordinates are hardcoded and might not match user location
|
||||
// emit(state.copyWith(errorMessage: "You must be at the location to clock in."));
|
||||
// return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: ClockInStatus.actionInProgress));
|
||||
try {
|
||||
final AttendanceStatus newStatus = await _clockIn(
|
||||
ClockInArguments(shiftId: event.shiftId, notes: event.notes),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
attendance: newStatus,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final AttendanceStatus newStatus = await _clockIn(
|
||||
ClockInArguments(shiftId: event.shiftId, notes: event.notes),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
attendance: newStatus,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClockInStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCheckOut(
|
||||
@@ -215,23 +225,25 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
Emitter<ClockInState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.actionInProgress));
|
||||
try {
|
||||
final AttendanceStatus newStatus = await _clockOut(
|
||||
ClockOutArguments(
|
||||
notes: event.notes,
|
||||
breakTimeMinutes: 0, // Should be passed from event if supported
|
||||
applicationId: state.attendance.activeApplicationId,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
attendance: newStatus,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final AttendanceStatus newStatus = await _clockOut(
|
||||
ClockOutArguments(
|
||||
notes: event.notes,
|
||||
breakTimeMinutes: 0,
|
||||
applicationId: state.attendance.activeApplicationId,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
attendance: newStatus,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClockInStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class ClockInCubit extends Cubit<ClockInState> { // 500m radius
|
||||
return;
|
||||
}
|
||||
|
||||
_getCurrentLocation();
|
||||
await _getCurrentLocation();
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isLoading: false, error: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'package:staff_home/src/domain/usecases/get_home_shifts.dart';
|
||||
@@ -8,7 +9,7 @@ import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||
part 'home_state.dart';
|
||||
|
||||
/// Simple Cubit to manage home page state (shifts + loading/error).
|
||||
class HomeCubit extends Cubit<HomeState> {
|
||||
class HomeCubit extends Cubit<HomeState> with BlocErrorHandler<HomeState> {
|
||||
final GetHomeShifts _getHomeShifts;
|
||||
final HomeRepository _repository;
|
||||
|
||||
@@ -20,29 +21,32 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
Future<void> loadShifts() async {
|
||||
if (isClosed) return;
|
||||
emit(state.copyWith(status: HomeStatus.loading));
|
||||
try {
|
||||
final result = await _getHomeShifts.call();
|
||||
final name = await _repository.getStaffName();
|
||||
if (isClosed) return;
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: HomeStatus.loaded,
|
||||
todayShifts: result.today,
|
||||
tomorrowShifts: result.tomorrow,
|
||||
recommendedShifts: result.recommended,
|
||||
staffName: name,
|
||||
// Mock profile status for now, ideally fetched from a user repository
|
||||
isProfileComplete: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (isClosed) return;
|
||||
emit(
|
||||
state.copyWith(status: HomeStatus.error, errorMessage: e.toString()),
|
||||
);
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final result = await _getHomeShifts.call();
|
||||
final name = await _repository.getStaffName();
|
||||
if (isClosed) return;
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: HomeStatus.loaded,
|
||||
todayShifts: result.today,
|
||||
tomorrowShifts: result.tomorrow,
|
||||
recommendedShifts: result.recommended,
|
||||
staffName: name,
|
||||
// Mock profile status for now, ideally fetched from a user repository
|
||||
isProfileComplete: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (isClosed) return state; // Avoid state emission if closed, though emit handles it gracefully usually
|
||||
return state.copyWith(status: HomeStatus.error, errorMessage: errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void toggleAutoMatch(bool enabled) {
|
||||
emit(state.copyWith(autoMatchEnabled: enabled));
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ class RecommendedShiftCard extends StatelessWidget {
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
shift.locationAddress ?? shift.location,
|
||||
shift.locationAddress,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: UiColors.mutedForeground,
|
||||
|
||||
@@ -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/arguments/get_payment_history_arguments.dart';
|
||||
import '../../../domain/usecases/get_payment_history_usecase.dart';
|
||||
@@ -6,8 +7,8 @@ import '../../../domain/usecases/get_payment_summary_usecase.dart';
|
||||
import 'payments_event.dart';
|
||||
import 'payments_state.dart';
|
||||
|
||||
class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
|
||||
|
||||
class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState>
|
||||
with BlocErrorHandler<PaymentsState> {
|
||||
PaymentsBloc({
|
||||
required this.getPaymentSummary,
|
||||
required this.getPaymentHistory,
|
||||
@@ -23,20 +24,24 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
|
||||
Emitter<PaymentsState> emit,
|
||||
) async {
|
||||
emit(PaymentsLoading());
|
||||
try {
|
||||
final PaymentSummary currentSummary = await getPaymentSummary();
|
||||
|
||||
final List<StaffPayment> history = await getPaymentHistory(
|
||||
const GetPaymentHistoryArguments('week'),
|
||||
);
|
||||
emit(PaymentsLoaded(
|
||||
summary: currentSummary,
|
||||
history: history,
|
||||
activePeriod: 'week',
|
||||
));
|
||||
} catch (e) {
|
||||
emit(PaymentsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final PaymentSummary currentSummary = await getPaymentSummary();
|
||||
|
||||
final List<StaffPayment> history = await getPaymentHistory(
|
||||
const GetPaymentHistoryArguments('week'),
|
||||
);
|
||||
emit(
|
||||
PaymentsLoaded(
|
||||
summary: currentSummary,
|
||||
history: history,
|
||||
activePeriod: 'week',
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => PaymentsError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onChangePeriod(
|
||||
@@ -45,17 +50,22 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
|
||||
) async {
|
||||
final PaymentsState currentState = state;
|
||||
if (currentState is PaymentsLoaded) {
|
||||
try {
|
||||
final List<StaffPayment> newHistory = await getPaymentHistory(
|
||||
GetPaymentHistoryArguments(event.period),
|
||||
);
|
||||
emit(currentState.copyWith(
|
||||
history: newHistory,
|
||||
activePeriod: event.period,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(PaymentsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<StaffPayment> newHistory = await getPaymentHistory(
|
||||
GetPaymentHistoryArguments(event.period),
|
||||
);
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
history: newHistory,
|
||||
activePeriod: event.period,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => PaymentsError(errorKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../domain/usecases/get_profile_usecase.dart';
|
||||
import '../../domain/usecases/sign_out_usecase.dart';
|
||||
import 'profile_state.dart';
|
||||
@@ -6,15 +7,14 @@ import 'profile_state.dart';
|
||||
/// Cubit for managing the Profile feature state.
|
||||
///
|
||||
/// Handles loading profile data and user sign-out actions.
|
||||
class ProfileCubit extends Cubit<ProfileState> {
|
||||
class ProfileCubit extends Cubit<ProfileState>
|
||||
with BlocErrorHandler<ProfileState> {
|
||||
final GetProfileUseCase _getProfileUseCase;
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
/// Creates a [ProfileCubit] with the required use cases.
|
||||
ProfileCubit(
|
||||
this._getProfileUseCase,
|
||||
this._signOutUseCase,
|
||||
) : super(const ProfileState());
|
||||
ProfileCubit(this._getProfileUseCase, this._signOutUseCase)
|
||||
: super(const ProfileState());
|
||||
|
||||
/// Loads the staff member's profile.
|
||||
///
|
||||
@@ -24,18 +24,16 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
Future<void> loadProfile() async {
|
||||
emit(state.copyWith(status: ProfileStatus.loading));
|
||||
|
||||
try {
|
||||
final profile = await _getProfileUseCase();
|
||||
emit(state.copyWith(
|
||||
status: ProfileStatus.loaded,
|
||||
profile: profile,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: ProfileStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final profile = await _getProfileUseCase();
|
||||
emit(state.copyWith(status: ProfileStatus.loaded, profile: profile));
|
||||
},
|
||||
onError:
|
||||
(String errorKey) =>
|
||||
state.copyWith(status: ProfileStatus.error, errorMessage: errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
/// Signs out the current user.
|
||||
@@ -49,12 +47,21 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
|
||||
emit(state.copyWith(status: ProfileStatus.loading));
|
||||
|
||||
try {
|
||||
await _signOutUseCase();
|
||||
emit(state.copyWith(status: ProfileStatus.signedOut));
|
||||
} catch (e) {
|
||||
// Error handling can be added here if needed
|
||||
// For now, we let the navigation happen regardless
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _signOutUseCase();
|
||||
emit(state.copyWith(status: ProfileStatus.signedOut));
|
||||
},
|
||||
onError: (String _) {
|
||||
// For sign out errors, we might want to just proceed or show error
|
||||
// Current implementation was silent catch, let's keep it robust but consistent
|
||||
// If we want to force navigation even on error, we would do it here
|
||||
// But usually handleError emits the error state.
|
||||
// Let's stick to standard error reporting for now.
|
||||
return state.copyWith(status: ProfileStatus.error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ import '../widgets/profile_menu_item.dart';
|
||||
import '../widgets/profile_header.dart';
|
||||
import '../widgets/reliability_score_bar.dart';
|
||||
import '../widgets/reliability_stats_card.dart';
|
||||
import '../widgets/reliability_stats_card.dart';
|
||||
import '../widgets/section_title.dart';
|
||||
import '../widgets/language_selector_bottom_sheet.dart';
|
||||
|
||||
/// The main Staff Profile page.
|
||||
///
|
||||
@@ -178,6 +180,25 @@ class StaffProfilePage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
SectionTitle(
|
||||
i18n.header.title.contains("Perfil") ? "Ajustes" : "Settings",
|
||||
),
|
||||
ProfileMenuGrid(
|
||||
crossAxisCount: 3,
|
||||
children: [
|
||||
ProfileMenuItem(
|
||||
icon: UiIcons.globe,
|
||||
label: i18n.header.title.contains("Perfil") ? "Idioma" : "Language",
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => const LanguageSelectorBottomSheet(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
LogoutButton(
|
||||
onTap: () => _onSignOut(cubit, state),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
/// A bottom sheet that allows the user to select their preferred language.
|
||||
///
|
||||
/// Displays options for English and Spanish, and updates the application's
|
||||
/// locale via the [LocaleBloc].
|
||||
class LanguageSelectorBottomSheet extends StatelessWidget {
|
||||
/// Creates a [LanguageSelectorBottomSheet].
|
||||
const LanguageSelectorBottomSheet({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(UiConstants.space6),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.background,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.radiusBase)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
t.settings.change_language,
|
||||
style: UiTypography.headline4m,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
_buildLanguageOption(
|
||||
context,
|
||||
label: 'English',
|
||||
locale: AppLocale.en,
|
||||
),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
_buildLanguageOption(
|
||||
context,
|
||||
label: 'Español',
|
||||
locale: AppLocale.es,
|
||||
),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLanguageOption(
|
||||
BuildContext context, {
|
||||
required String label,
|
||||
required AppLocale locale,
|
||||
}) {
|
||||
// Check if this option is currently selected.
|
||||
// We can use LocaleSettings.currentLocale for a quick check,
|
||||
// or access the BLoC state if we wanted to be reactive to state changes here directly,
|
||||
// but LocaleSettings is sufficient for the initial check.
|
||||
final bool isSelected = LocaleSettings.currentLocale == locale;
|
||||
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// Dispatch the ChangeLocale event to the LocaleBloc
|
||||
Modular.get<LocaleBloc>().add(ChangeLocale(locale.flutterLocale));
|
||||
|
||||
// Close the bottom sheet
|
||||
Navigator.pop(context);
|
||||
|
||||
// Force a rebuild of the entire app to reflect locale change instantly if not handled by root widget
|
||||
// (Usually handled by BlocBuilder at the root, but this ensures settings are updated)
|
||||
},
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
horizontal: UiConstants.space4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary.withValues(alpha: 0.1) : UiColors.background,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
|
||||
border: Border.all(
|
||||
color: isSelected ? UiColors.primary : UiColors.border,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: isSelected
|
||||
? UiTypography.body1b.copyWith(color: UiColors.primary)
|
||||
: UiTypography.body1r,
|
||||
),
|
||||
if (isSelected)
|
||||
Icon(
|
||||
UiIcons.check,
|
||||
color: UiColors.primary,
|
||||
size: 24.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../../domain/usecases/get_certificates_usecase.dart';
|
||||
import 'certificates_state.dart';
|
||||
|
||||
class CertificatesCubit extends Cubit<CertificatesState> {
|
||||
class CertificatesCubit extends Cubit<CertificatesState>
|
||||
with BlocErrorHandler<CertificatesState> {
|
||||
final GetCertificatesUseCase _getCertificatesUseCase;
|
||||
|
||||
CertificatesCubit(this._getCertificatesUseCase) : super(const CertificatesState()) {
|
||||
CertificatesCubit(this._getCertificatesUseCase)
|
||||
: super(const CertificatesState()) {
|
||||
loadCertificates();
|
||||
}
|
||||
|
||||
Future<void> loadCertificates() async {
|
||||
emit(state.copyWith(status: CertificatesStatus.loading));
|
||||
try {
|
||||
final List<StaffDocument> certificates = await _getCertificatesUseCase();
|
||||
emit(state.copyWith(
|
||||
status: CertificatesStatus.success,
|
||||
certificates: certificates,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: CertificatesStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<StaffDocument> certificates =
|
||||
await _getCertificatesUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CertificatesStatus.success,
|
||||
certificates: certificates,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: CertificatesStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
|
||||
),
|
||||
];
|
||||
|
||||
/*
|
||||
try {
|
||||
final QueryResult<ListStaffDocumentsByStaffIdData,
|
||||
ListStaffDocumentsByStaffIdVariables> result =
|
||||
@@ -63,6 +64,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
|
||||
} catch (e) {
|
||||
throw Exception('Failed to fetch documents: $e');
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
domain.StaffDocument _mapToDomain(
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../../domain/usecases/get_documents_usecase.dart';
|
||||
import 'documents_state.dart';
|
||||
|
||||
class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
class DocumentsCubit extends Cubit<DocumentsState>
|
||||
with BlocErrorHandler<DocumentsState> {
|
||||
final GetDocumentsUseCase _getDocumentsUseCase;
|
||||
|
||||
DocumentsCubit(this._getDocumentsUseCase) : super(const DocumentsState());
|
||||
|
||||
Future<void> loadDocuments() async {
|
||||
emit(state.copyWith(status: DocumentsStatus.loading));
|
||||
try {
|
||||
final List<StaffDocument> documents = await _getDocumentsUseCase();
|
||||
emit(state.copyWith(
|
||||
status: DocumentsStatus.success,
|
||||
documents: documents,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: DocumentsStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<StaffDocument> documents = await _getDocumentsUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: DocumentsStatus.success,
|
||||
documents: documents,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: DocumentsStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../../domain/usecases/submit_i9_form_usecase.dart';
|
||||
import 'form_i9_state.dart';
|
||||
|
||||
class FormI9Cubit extends Cubit<FormI9State> {
|
||||
class FormI9Cubit extends Cubit<FormI9State> with BlocErrorHandler<FormI9State> {
|
||||
final SubmitI9FormUseCase _submitI9FormUseCase;
|
||||
String _formId = '';
|
||||
|
||||
@@ -16,31 +17,33 @@ class FormI9Cubit extends Cubit<FormI9State> {
|
||||
emit(const FormI9State()); // Reset to empty if no form
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final Map<String, dynamic> data = form.formData;
|
||||
_formId = form.id;
|
||||
emit(FormI9State(
|
||||
firstName: data['firstName'] as String? ?? '',
|
||||
lastName: data['lastName'] as String? ?? '',
|
||||
middleInitial: data['middleInitial'] as String? ?? '',
|
||||
otherLastNames: data['otherLastNames'] as String? ?? '',
|
||||
dob: data['dob'] as String? ?? '',
|
||||
ssn: data['ssn'] as String? ?? '',
|
||||
email: data['email'] as String? ?? '',
|
||||
phone: data['phone'] as String? ?? '',
|
||||
address: data['address'] as String? ?? '',
|
||||
aptNumber: data['aptNumber'] as String? ?? '',
|
||||
city: data['city'] as String? ?? '',
|
||||
state: data['state'] as String? ?? '',
|
||||
zipCode: data['zipCode'] as String? ?? '',
|
||||
citizenshipStatus: data['citizenshipStatus'] as String? ?? '',
|
||||
uscisNumber: data['uscisNumber'] as String? ?? '',
|
||||
admissionNumber: data['admissionNumber'] as String? ?? '',
|
||||
passportNumber: data['passportNumber'] as String? ?? '',
|
||||
countryIssuance: data['countryIssuance'] as String? ?? '',
|
||||
preparerUsed: data['preparerUsed'] as bool? ?? false,
|
||||
signature: data['signature'] as String? ?? '',
|
||||
));
|
||||
emit(
|
||||
FormI9State(
|
||||
firstName: data['firstName'] as String? ?? '',
|
||||
lastName: data['lastName'] as String? ?? '',
|
||||
middleInitial: data['middleInitial'] as String? ?? '',
|
||||
otherLastNames: data['otherLastNames'] as String? ?? '',
|
||||
dob: data['dob'] as String? ?? '',
|
||||
ssn: data['ssn'] as String? ?? '',
|
||||
email: data['email'] as String? ?? '',
|
||||
phone: data['phone'] as String? ?? '',
|
||||
address: data['address'] as String? ?? '',
|
||||
aptNumber: data['aptNumber'] as String? ?? '',
|
||||
city: data['city'] as String? ?? '',
|
||||
state: data['state'] as String? ?? '',
|
||||
zipCode: data['zipCode'] as String? ?? '',
|
||||
citizenshipStatus: data['citizenshipStatus'] as String? ?? '',
|
||||
uscisNumber: data['uscisNumber'] as String? ?? '',
|
||||
admissionNumber: data['admissionNumber'] as String? ?? '',
|
||||
passportNumber: data['passportNumber'] as String? ?? '',
|
||||
countryIssuance: data['countryIssuance'] as String? ?? '',
|
||||
preparerUsed: data['preparerUsed'] as bool? ?? false,
|
||||
signature: data['signature'] as String? ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void nextStep(int totalSteps) {
|
||||
@@ -58,8 +61,10 @@ class FormI9Cubit extends Cubit<FormI9State> {
|
||||
// Personal Info
|
||||
void firstNameChanged(String value) => emit(state.copyWith(firstName: value));
|
||||
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
|
||||
void middleInitialChanged(String value) => emit(state.copyWith(middleInitial: value));
|
||||
void otherLastNamesChanged(String value) => emit(state.copyWith(otherLastNames: value));
|
||||
void middleInitialChanged(String value) =>
|
||||
emit(state.copyWith(middleInitial: value));
|
||||
void otherLastNamesChanged(String value) =>
|
||||
emit(state.copyWith(otherLastNames: value));
|
||||
void dobChanged(String value) => emit(state.copyWith(dob: value));
|
||||
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
|
||||
void emailChanged(String value) => emit(state.copyWith(email: value));
|
||||
@@ -73,55 +78,65 @@ class FormI9Cubit extends Cubit<FormI9State> {
|
||||
void zipCodeChanged(String value) => emit(state.copyWith(zipCode: value));
|
||||
|
||||
// Citizenship
|
||||
void citizenshipStatusChanged(String value) => emit(state.copyWith(citizenshipStatus: value));
|
||||
void uscisNumberChanged(String value) => emit(state.copyWith(uscisNumber: value));
|
||||
void admissionNumberChanged(String value) => emit(state.copyWith(admissionNumber: value));
|
||||
void passportNumberChanged(String value) => emit(state.copyWith(passportNumber: value));
|
||||
void countryIssuanceChanged(String value) => emit(state.copyWith(countryIssuance: value));
|
||||
void citizenshipStatusChanged(String value) =>
|
||||
emit(state.copyWith(citizenshipStatus: value));
|
||||
void uscisNumberChanged(String value) =>
|
||||
emit(state.copyWith(uscisNumber: value));
|
||||
void admissionNumberChanged(String value) =>
|
||||
emit(state.copyWith(admissionNumber: value));
|
||||
void passportNumberChanged(String value) =>
|
||||
emit(state.copyWith(passportNumber: value));
|
||||
void countryIssuanceChanged(String value) =>
|
||||
emit(state.copyWith(countryIssuance: value));
|
||||
|
||||
// Signature
|
||||
void preparerUsedChanged(bool value) => emit(state.copyWith(preparerUsed: value));
|
||||
void preparerUsedChanged(bool value) =>
|
||||
emit(state.copyWith(preparerUsed: value));
|
||||
void signatureChanged(String value) => emit(state.copyWith(signature: value));
|
||||
|
||||
Future<void> submit() async {
|
||||
emit(state.copyWith(status: FormI9Status.submitting));
|
||||
try {
|
||||
final Map<String, dynamic> formData = {
|
||||
'firstName': state.firstName,
|
||||
'lastName': state.lastName,
|
||||
'middleInitial': state.middleInitial,
|
||||
'otherLastNames': state.otherLastNames,
|
||||
'dob': state.dob,
|
||||
'ssn': state.ssn,
|
||||
'email': state.email,
|
||||
'phone': state.phone,
|
||||
'address': state.address,
|
||||
'aptNumber': state.aptNumber,
|
||||
'city': state.city,
|
||||
'state': state.state,
|
||||
'zipCode': state.zipCode,
|
||||
'citizenshipStatus': state.citizenshipStatus,
|
||||
'uscisNumber': state.uscisNumber,
|
||||
'admissionNumber': state.admissionNumber,
|
||||
'passportNumber': state.passportNumber,
|
||||
'countryIssuance': state.countryIssuance,
|
||||
'preparerUsed': state.preparerUsed,
|
||||
'signature': state.signature,
|
||||
};
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final Map<String, dynamic> formData = {
|
||||
'firstName': state.firstName,
|
||||
'lastName': state.lastName,
|
||||
'middleInitial': state.middleInitial,
|
||||
'otherLastNames': state.otherLastNames,
|
||||
'dob': state.dob,
|
||||
'ssn': state.ssn,
|
||||
'email': state.email,
|
||||
'phone': state.phone,
|
||||
'address': state.address,
|
||||
'aptNumber': state.aptNumber,
|
||||
'city': state.city,
|
||||
'state': state.state,
|
||||
'zipCode': state.zipCode,
|
||||
'citizenshipStatus': state.citizenshipStatus,
|
||||
'uscisNumber': state.uscisNumber,
|
||||
'admissionNumber': state.admissionNumber,
|
||||
'passportNumber': state.passportNumber,
|
||||
'countryIssuance': state.countryIssuance,
|
||||
'preparerUsed': state.preparerUsed,
|
||||
'signature': state.signature,
|
||||
};
|
||||
|
||||
final I9TaxForm form = I9TaxForm(
|
||||
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
|
||||
title: 'Form I-9',
|
||||
formData: formData,
|
||||
);
|
||||
final I9TaxForm form = I9TaxForm(
|
||||
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
|
||||
title: 'Form I-9',
|
||||
formData: formData,
|
||||
);
|
||||
|
||||
await _submitI9FormUseCase(form);
|
||||
emit(state.copyWith(status: FormI9Status.success));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: FormI9Status.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await _submitI9FormUseCase(form);
|
||||
emit(state.copyWith(status: FormI9Status.success));
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: FormI9Status.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../../domain/usecases/get_tax_forms_usecase.dart';
|
||||
import 'tax_forms_state.dart';
|
||||
|
||||
class TaxFormsCubit extends Cubit<TaxFormsState> {
|
||||
class TaxFormsCubit extends Cubit<TaxFormsState>
|
||||
with BlocErrorHandler<TaxFormsState> {
|
||||
final GetTaxFormsUseCase _getTaxFormsUseCase;
|
||||
|
||||
TaxFormsCubit(this._getTaxFormsUseCase) : super(const TaxFormsState());
|
||||
|
||||
Future<void> loadTaxForms() async {
|
||||
emit(state.copyWith(status: TaxFormsStatus.loading));
|
||||
try {
|
||||
final List<TaxForm> forms = await _getTaxFormsUseCase();
|
||||
emit(state.copyWith(
|
||||
status: TaxFormsStatus.success,
|
||||
forms: forms,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: TaxFormsStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<TaxForm> forms = await _getTaxFormsUseCase();
|
||||
emit(state.copyWith(status: TaxFormsStatus.success, forms: forms));
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: TaxFormsStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../../domain/usecases/submit_w4_form_usecase.dart';
|
||||
import 'form_w4_state.dart';
|
||||
|
||||
class FormW4Cubit extends Cubit<FormW4State> {
|
||||
class FormW4Cubit extends Cubit<FormW4State> with BlocErrorHandler<FormW4State> {
|
||||
final SubmitW4FormUseCase _submitW4FormUseCase;
|
||||
String _formId = '';
|
||||
|
||||
@@ -16,31 +17,33 @@ class FormW4Cubit extends Cubit<FormW4State> {
|
||||
emit(const FormW4State()); // Reset
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final Map<String, dynamic> data = form.formData;
|
||||
_formId = form.id;
|
||||
|
||||
|
||||
// Combine address parts if needed, or take existing
|
||||
final String city = data['city'] as String? ?? '';
|
||||
final String stateVal = data['state'] as String? ?? '';
|
||||
final String zip = data['zipCode'] as String? ?? '';
|
||||
final String cityStateZip = '$city, $stateVal $zip'.trim();
|
||||
|
||||
emit(FormW4State(
|
||||
firstName: data['firstName'] as String? ?? '',
|
||||
lastName: data['lastName'] as String? ?? '',
|
||||
ssn: data['ssn'] as String? ?? '',
|
||||
address: data['address'] as String? ?? '',
|
||||
cityStateZip: cityStateZip.contains(',') ? cityStateZip : '',
|
||||
filingStatus: data['filingStatus'] as String? ?? '',
|
||||
multipleJobs: data['multipleJobs'] as bool? ?? false,
|
||||
qualifyingChildren: data['qualifyingChildren'] as int? ?? 0,
|
||||
otherDependents: data['otherDependents'] as int? ?? 0,
|
||||
otherIncome: data['otherIncome'] as String? ?? '',
|
||||
deductions: data['deductions'] as String? ?? '',
|
||||
extraWithholding: data['extraWithholding'] as String? ?? '',
|
||||
signature: data['signature'] as String? ?? '',
|
||||
));
|
||||
emit(
|
||||
FormW4State(
|
||||
firstName: data['firstName'] as String? ?? '',
|
||||
lastName: data['lastName'] as String? ?? '',
|
||||
ssn: data['ssn'] as String? ?? '',
|
||||
address: data['address'] as String? ?? '',
|
||||
cityStateZip: cityStateZip.contains(',') ? cityStateZip : '',
|
||||
filingStatus: data['filingStatus'] as String? ?? '',
|
||||
multipleJobs: data['multipleJobs'] as bool? ?? false,
|
||||
qualifyingChildren: data['qualifyingChildren'] as int? ?? 0,
|
||||
otherDependents: data['otherDependents'] as int? ?? 0,
|
||||
otherIncome: data['otherIncome'] as String? ?? '',
|
||||
deductions: data['deductions'] as String? ?? '',
|
||||
extraWithholding: data['extraWithholding'] as String? ?? '',
|
||||
signature: data['signature'] as String? ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void nextStep(int totalSteps) {
|
||||
@@ -62,52 +65,65 @@ class FormW4Cubit extends Cubit<FormW4State> {
|
||||
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
|
||||
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
|
||||
void addressChanged(String value) => emit(state.copyWith(address: value));
|
||||
void cityStateZipChanged(String value) => emit(state.copyWith(cityStateZip: value));
|
||||
void cityStateZipChanged(String value) =>
|
||||
emit(state.copyWith(cityStateZip: value));
|
||||
|
||||
// Form Data
|
||||
void filingStatusChanged(String value) => emit(state.copyWith(filingStatus: value));
|
||||
void multipleJobsChanged(bool value) => emit(state.copyWith(multipleJobs: value));
|
||||
void qualifyingChildrenChanged(int value) => emit(state.copyWith(qualifyingChildren: value));
|
||||
void otherDependentsChanged(int value) => emit(state.copyWith(otherDependents: value));
|
||||
|
||||
void filingStatusChanged(String value) =>
|
||||
emit(state.copyWith(filingStatus: value));
|
||||
void multipleJobsChanged(bool value) =>
|
||||
emit(state.copyWith(multipleJobs: value));
|
||||
void qualifyingChildrenChanged(int value) =>
|
||||
emit(state.copyWith(qualifyingChildren: value));
|
||||
void otherDependentsChanged(int value) =>
|
||||
emit(state.copyWith(otherDependents: value));
|
||||
|
||||
// Adjustments
|
||||
void otherIncomeChanged(String value) => emit(state.copyWith(otherIncome: value));
|
||||
void deductionsChanged(String value) => emit(state.copyWith(deductions: value));
|
||||
void extraWithholdingChanged(String value) => emit(state.copyWith(extraWithholding: value));
|
||||
void otherIncomeChanged(String value) =>
|
||||
emit(state.copyWith(otherIncome: value));
|
||||
void deductionsChanged(String value) =>
|
||||
emit(state.copyWith(deductions: value));
|
||||
void extraWithholdingChanged(String value) =>
|
||||
emit(state.copyWith(extraWithholding: value));
|
||||
void signatureChanged(String value) => emit(state.copyWith(signature: value));
|
||||
|
||||
Future<void> submit() async {
|
||||
emit(state.copyWith(status: FormW4Status.submitting));
|
||||
try {
|
||||
final Map<String, dynamic> formData = {
|
||||
'firstName': state.firstName,
|
||||
'lastName': state.lastName,
|
||||
'ssn': state.ssn,
|
||||
'address': state.address,
|
||||
'cityStateZip': state.cityStateZip, // Note: Repository should split this if needed.
|
||||
'filingStatus': state.filingStatus,
|
||||
'multipleJobs': state.multipleJobs,
|
||||
'qualifyingChildren': state.qualifyingChildren,
|
||||
'otherDependents': state.otherDependents,
|
||||
'otherIncome': state.otherIncome,
|
||||
'deductions': state.deductions,
|
||||
'extraWithholding': state.extraWithholding,
|
||||
'signature': state.signature,
|
||||
};
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final Map<String, dynamic> formData = {
|
||||
'firstName': state.firstName,
|
||||
'lastName': state.lastName,
|
||||
'ssn': state.ssn,
|
||||
'address': state.address,
|
||||
'cityStateZip':
|
||||
state.cityStateZip, // Note: Repository should split this if needed.
|
||||
'filingStatus': state.filingStatus,
|
||||
'multipleJobs': state.multipleJobs,
|
||||
'qualifyingChildren': state.qualifyingChildren,
|
||||
'otherDependents': state.otherDependents,
|
||||
'otherIncome': state.otherIncome,
|
||||
'deductions': state.deductions,
|
||||
'extraWithholding': state.extraWithholding,
|
||||
'signature': state.signature,
|
||||
};
|
||||
|
||||
final W4TaxForm form = W4TaxForm(
|
||||
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
|
||||
title: 'Form W-4',
|
||||
formData: formData,
|
||||
);
|
||||
final W4TaxForm form = W4TaxForm(
|
||||
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
|
||||
title: 'Form W-4',
|
||||
formData: formData,
|
||||
);
|
||||
|
||||
await _submitW4FormUseCase(form);
|
||||
emit(state.copyWith(status: FormW4Status.success));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: FormW4Status.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await _submitW4FormUseCase(form);
|
||||
emit(state.copyWith(status: FormW4Status.success));
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: FormW4Status.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,12 +147,12 @@ class TaxFormsPage extends StatelessWidget {
|
||||
if (form is I9TaxForm) {
|
||||
final result = await Modular.to.pushNamed('i9', arguments: form);
|
||||
if (result == true && context.mounted) {
|
||||
BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
|
||||
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
|
||||
}
|
||||
} else if (form is W4TaxForm) {
|
||||
final result = await Modular.to.pushNamed('w4', arguments: form);
|
||||
if (result == true && context.mounted) {
|
||||
BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
|
||||
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/add_bank_account_params.dart';
|
||||
import '../../domain/usecases/add_bank_account_usecase.dart';
|
||||
import '../../domain/usecases/get_bank_accounts_usecase.dart';
|
||||
import 'bank_account_state.dart';
|
||||
|
||||
class BankAccountCubit extends Cubit<BankAccountState> {
|
||||
class BankAccountCubit extends Cubit<BankAccountState>
|
||||
with BlocErrorHandler<BankAccountState> {
|
||||
final GetBankAccountsUseCase _getBankAccountsUseCase;
|
||||
final AddBankAccountUseCase _addBankAccountUseCase;
|
||||
|
||||
BankAccountCubit({
|
||||
required GetBankAccountsUseCase getBankAccountsUseCase,
|
||||
required AddBankAccountUseCase addBankAccountUseCase,
|
||||
}) : _getBankAccountsUseCase = getBankAccountsUseCase,
|
||||
_addBankAccountUseCase = addBankAccountUseCase,
|
||||
super(const BankAccountState());
|
||||
}) : _getBankAccountsUseCase = getBankAccountsUseCase,
|
||||
_addBankAccountUseCase = addBankAccountUseCase,
|
||||
super(const BankAccountState());
|
||||
|
||||
Future<void> loadAccounts() async {
|
||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||
try {
|
||||
final List<BankAccount> accounts = await _getBankAccountsUseCase();
|
||||
emit(state.copyWith(
|
||||
status: BankAccountStatus.loaded,
|
||||
accounts: accounts,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: BankAccountStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<BankAccount> accounts = await _getBankAccountsUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BankAccountStatus.loaded,
|
||||
accounts: accounts,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: BankAccountStatus.error,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void toggleForm(bool show) {
|
||||
@@ -43,35 +50,47 @@ class BankAccountCubit extends Cubit<BankAccountState> {
|
||||
required String type,
|
||||
}) async {
|
||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||
|
||||
|
||||
// Create domain entity
|
||||
final BankAccount newAccount = BankAccount(
|
||||
id: '', // Generated by server usually
|
||||
userId: '', // Handled by Repo/Auth
|
||||
bankName: bankName,
|
||||
accountNumber: accountNumber,
|
||||
accountName: '',
|
||||
sortCode: routingNumber,
|
||||
type: type == 'CHECKING' ? BankAccountType.checking : BankAccountType.savings,
|
||||
last4: accountNumber.length > 4 ? accountNumber.substring(accountNumber.length - 4) : accountNumber,
|
||||
isPrimary: false,
|
||||
id: '', // Generated by server usually
|
||||
userId: '', // Handled by Repo/Auth
|
||||
bankName: bankName,
|
||||
accountNumber: accountNumber,
|
||||
accountName: '',
|
||||
sortCode: routingNumber,
|
||||
type:
|
||||
type == 'CHECKING'
|
||||
? BankAccountType.checking
|
||||
: BankAccountType.savings,
|
||||
last4:
|
||||
accountNumber.length > 4
|
||||
? accountNumber.substring(accountNumber.length - 4)
|
||||
: accountNumber,
|
||||
isPrimary: false,
|
||||
);
|
||||
|
||||
try {
|
||||
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount));
|
||||
|
||||
// Re-fetch to get latest state including server-generated IDs
|
||||
await loadAccounts();
|
||||
|
||||
emit(state.copyWith(
|
||||
status: BankAccountStatus.accountAdded,
|
||||
showForm: false, // Close form on success
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: BankAccountStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
// Re-fetch to get latest state including server-generated IDs
|
||||
await loadAccounts();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: BankAccountStatus.accountAdded,
|
||||
showForm: false, // Close form on success
|
||||
),
|
||||
);
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: BankAccountStatus.error,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/get_time_cards_arguments.dart';
|
||||
import '../../domain/usecases/get_time_cards_usecase.dart';
|
||||
@@ -8,35 +9,55 @@ part 'time_card_event.dart';
|
||||
part 'time_card_state.dart';
|
||||
|
||||
/// BLoC to manage Time Card state.
|
||||
class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState> {
|
||||
class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState>
|
||||
with BlocErrorHandler<TimeCardState> {
|
||||
final GetTimeCardsUseCase getTimeCards;
|
||||
|
||||
|
||||
TimeCardBloc({required this.getTimeCards}) : super(TimeCardInitial()) {
|
||||
on<LoadTimeCards>(_onLoadTimeCards);
|
||||
on<ChangeMonth>(_onChangeMonth);
|
||||
}
|
||||
|
||||
/// Handles fetching time cards for the requested month.
|
||||
Future<void> _onLoadTimeCards(LoadTimeCards event, Emitter<TimeCardState> emit) async {
|
||||
Future<void> _onLoadTimeCards(
|
||||
LoadTimeCards event,
|
||||
Emitter<TimeCardState> emit,
|
||||
) async {
|
||||
emit(TimeCardLoading());
|
||||
try {
|
||||
final List<TimeCard> cards = await getTimeCards(GetTimeCardsArguments(event.month));
|
||||
|
||||
final double totalHours = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalHours);
|
||||
final double totalEarnings = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalPay);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<TimeCard> cards = await getTimeCards(
|
||||
GetTimeCardsArguments(event.month),
|
||||
);
|
||||
|
||||
emit(TimeCardLoaded(
|
||||
timeCards: cards,
|
||||
selectedMonth: event.month,
|
||||
totalHours: totalHours,
|
||||
totalEarnings: totalEarnings,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(TimeCardError(e.toString()));
|
||||
}
|
||||
final double totalHours = cards.fold(
|
||||
0.0,
|
||||
(double sum, TimeCard t) => sum + t.totalHours,
|
||||
);
|
||||
final double totalEarnings = cards.fold(
|
||||
0.0,
|
||||
(double sum, TimeCard t) => sum + t.totalPay,
|
||||
);
|
||||
|
||||
emit(
|
||||
TimeCardLoaded(
|
||||
timeCards: cards,
|
||||
selectedMonth: event.month,
|
||||
totalHours: totalHours,
|
||||
totalEarnings: totalEarnings,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => TimeCardError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onChangeMonth(ChangeMonth event, Emitter<TimeCardState> emit) async {
|
||||
add(LoadTimeCards(event.month));
|
||||
|
||||
Future<void> _onChangeMonth(
|
||||
ChangeMonth event,
|
||||
Emitter<TimeCardState> emit,
|
||||
) async {
|
||||
add(LoadTimeCards(event.month));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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/arguments/save_attire_arguments.dart';
|
||||
@@ -8,7 +9,8 @@ import '../../domain/usecases/save_attire_usecase.dart';
|
||||
import '../../domain/usecases/upload_attire_photo_usecase.dart';
|
||||
import 'attire_state.dart';
|
||||
|
||||
class AttireCubit extends Cubit<AttireState> {
|
||||
class AttireCubit extends Cubit<AttireState>
|
||||
with BlocErrorHandler<AttireState> {
|
||||
final GetAttireOptionsUseCase _getAttireOptionsUseCase;
|
||||
final SaveAttireUseCase _saveAttireUseCase;
|
||||
final UploadAttirePhotoUseCase _uploadAttirePhotoUseCase;
|
||||
@@ -23,30 +25,41 @@ class AttireCubit extends Cubit<AttireState> {
|
||||
|
||||
Future<void> loadOptions() async {
|
||||
emit(state.copyWith(status: AttireStatus.loading));
|
||||
try {
|
||||
final List<AttireItem> options = await _getAttireOptionsUseCase();
|
||||
|
||||
// Auto-select mandatory items initially as per prototype
|
||||
final List<String> mandatoryIds = options
|
||||
.where((AttireItem e) => e.isMandatory)
|
||||
.map((AttireItem e) => e.id)
|
||||
.toList();
|
||||
|
||||
final List<String> initialSelection = List<String>.from(state.selectedIds);
|
||||
for (final String id in mandatoryIds) {
|
||||
if (!initialSelection.contains(id)) {
|
||||
initialSelection.add(id);
|
||||
}
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<AttireItem> options = await _getAttireOptionsUseCase();
|
||||
|
||||
emit(state.copyWith(
|
||||
status: AttireStatus.success,
|
||||
options: options,
|
||||
selectedIds: initialSelection,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(status: AttireStatus.failure, errorMessage: e.toString()));
|
||||
}
|
||||
// Auto-select mandatory items initially as per prototype
|
||||
final List<String> mandatoryIds =
|
||||
options
|
||||
.where((AttireItem e) => e.isMandatory)
|
||||
.map((AttireItem e) => e.id)
|
||||
.toList();
|
||||
|
||||
final List<String> initialSelection = List<String>.from(
|
||||
state.selectedIds,
|
||||
);
|
||||
for (final String id in mandatoryIds) {
|
||||
if (!initialSelection.contains(id)) {
|
||||
initialSelection.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: AttireStatus.success,
|
||||
options: options,
|
||||
selectedIds: initialSelection,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: AttireStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void toggleSelection(String id) {
|
||||
@@ -67,51 +80,81 @@ class AttireCubit extends Cubit<AttireState> {
|
||||
}
|
||||
|
||||
Future<void> uploadPhoto(String itemId) async {
|
||||
final Map<String, bool> currentUploading = Map<String, bool>.from(state.uploadingStatus);
|
||||
final Map<String, bool> currentUploading = Map<String, bool>.from(
|
||||
state.uploadingStatus,
|
||||
);
|
||||
currentUploading[itemId] = true;
|
||||
emit(state.copyWith(uploadingStatus: currentUploading));
|
||||
|
||||
try {
|
||||
final String url = await _uploadAttirePhotoUseCase(
|
||||
UploadAttirePhotoArguments(itemId: itemId),
|
||||
);
|
||||
|
||||
final Map<String, String> currentPhotos = Map<String, String>.from(state.photoUrls);
|
||||
currentPhotos[itemId] = url;
|
||||
|
||||
// Auto-select item on upload success if not selected
|
||||
final List<String> currentSelection = List<String>.from(state.selectedIds);
|
||||
if (!currentSelection.contains(itemId)) {
|
||||
currentSelection.add(itemId);
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final String url = await _uploadAttirePhotoUseCase(
|
||||
UploadAttirePhotoArguments(itemId: itemId),
|
||||
);
|
||||
|
||||
currentUploading[itemId] = false;
|
||||
emit(state.copyWith(
|
||||
uploadingStatus: currentUploading,
|
||||
photoUrls: currentPhotos,
|
||||
selectedIds: currentSelection,
|
||||
));
|
||||
} catch (e) {
|
||||
currentUploading[itemId] = false;
|
||||
emit(state.copyWith(
|
||||
uploadingStatus: currentUploading,
|
||||
final Map<String, String> currentPhotos = Map<String, String>.from(
|
||||
state.photoUrls,
|
||||
);
|
||||
currentPhotos[itemId] = url;
|
||||
|
||||
// Auto-select item on upload success if not selected
|
||||
final List<String> currentSelection = List<String>.from(
|
||||
state.selectedIds,
|
||||
);
|
||||
if (!currentSelection.contains(itemId)) {
|
||||
currentSelection.add(itemId);
|
||||
}
|
||||
|
||||
final Map<String, bool> updatedUploading = Map<String, bool>.from(
|
||||
state.uploadingStatus,
|
||||
);
|
||||
updatedUploading[itemId] = false;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
uploadingStatus: updatedUploading,
|
||||
photoUrls: currentPhotos,
|
||||
selectedIds: currentSelection,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
final Map<String, bool> updatedUploading = Map<String, bool>.from(
|
||||
state.uploadingStatus,
|
||||
);
|
||||
updatedUploading[itemId] = false;
|
||||
// Could handle error specifically via snackbar event
|
||||
));
|
||||
}
|
||||
// For now, attaching the error message but keeping state generally usable
|
||||
return state.copyWith(
|
||||
uploadingStatus: updatedUploading,
|
||||
errorMessage: errorKey,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
if (!state.canSave) return;
|
||||
|
||||
|
||||
emit(state.copyWith(status: AttireStatus.saving));
|
||||
try {
|
||||
await _saveAttireUseCase(SaveAttireArguments(
|
||||
selectedItemIds: state.selectedIds,
|
||||
photoUrls: state.photoUrls,
|
||||
));
|
||||
emit(state.copyWith(status: AttireStatus.saved));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(status: AttireStatus.failure, errorMessage: e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await _saveAttireUseCase(
|
||||
SaveAttireArguments(
|
||||
selectedItemIds: state.selectedIds,
|
||||
photoUrls: state.photoUrls,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: AttireStatus.saved));
|
||||
},
|
||||
onError:
|
||||
(String errorKey) => state.copyWith(
|
||||
status: AttireStatus.failure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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/arguments/get_emergency_contacts_arguments.dart';
|
||||
import '../../domain/arguments/save_emergency_contacts_arguments.dart';
|
||||
@@ -12,7 +13,8 @@ export 'emergency_contact_state.dart';
|
||||
|
||||
// BLoC
|
||||
class EmergencyContactBloc
|
||||
extends Bloc<EmergencyContactEvent, EmergencyContactState> {
|
||||
extends Bloc<EmergencyContactEvent, EmergencyContactState>
|
||||
with BlocErrorHandler<EmergencyContactState> {
|
||||
final GetEmergencyContactsUseCase getEmergencyContacts;
|
||||
final SaveEmergencyContactsUseCase saveEmergencyContacts;
|
||||
|
||||
@@ -28,29 +30,30 @@ class EmergencyContactBloc
|
||||
|
||||
add(EmergencyContactsLoaded());
|
||||
}
|
||||
|
||||
|
||||
Future<void> _onLoaded(
|
||||
EmergencyContactsLoaded event,
|
||||
Emitter<EmergencyContactState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: EmergencyContactStatus.loading));
|
||||
try {
|
||||
final contacts = await getEmergencyContacts(
|
||||
const GetEmergencyContactsArguments(),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: EmergencyContactStatus.loaded,
|
||||
contacts: contacts.isNotEmpty
|
||||
? contacts
|
||||
: [EmergencyContact.empty()],
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final contacts = await getEmergencyContacts(
|
||||
const GetEmergencyContactsArguments(),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: EmergencyContactStatus.loaded,
|
||||
contacts: contacts.isNotEmpty ? contacts : [EmergencyContact.empty()],
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: EmergencyContactStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onAdded(
|
||||
@@ -85,18 +88,19 @@ class EmergencyContactBloc
|
||||
Emitter<EmergencyContactState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: EmergencyContactStatus.saving));
|
||||
try {
|
||||
await saveEmergencyContacts(
|
||||
SaveEmergencyContactsArguments(
|
||||
contacts: state.contacts,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: EmergencyContactStatus.saved));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await saveEmergencyContacts(
|
||||
SaveEmergencyContactsArguments(contacts: state.contacts),
|
||||
);
|
||||
emit(state.copyWith(status: EmergencyContactStatus.saved));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: EmergencyContactStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/save_experience_arguments.dart';
|
||||
import '../../domain/usecases/get_staff_industries_usecase.dart';
|
||||
@@ -92,8 +93,8 @@ class ExperienceState extends Equatable {
|
||||
}
|
||||
|
||||
// BLoC
|
||||
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
|
||||
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
||||
with BlocErrorHandler<ExperienceState> {
|
||||
final GetStaffIndustriesUseCase getIndustries;
|
||||
final GetStaffSkillsUseCase getSkills;
|
||||
final SaveExperienceUseCase saveExperience;
|
||||
@@ -102,10 +103,12 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
required this.getIndustries,
|
||||
required this.getSkills,
|
||||
required this.saveExperience,
|
||||
}) : super(const ExperienceState(
|
||||
availableIndustries: Industry.values,
|
||||
availableSkills: ExperienceSkill.values,
|
||||
)) {
|
||||
}) : super(
|
||||
const ExperienceState(
|
||||
availableIndustries: Industry.values,
|
||||
availableSkills: ExperienceSkill.values,
|
||||
),
|
||||
) {
|
||||
on<ExperienceLoaded>(_onLoaded);
|
||||
on<ExperienceIndustryToggled>(_onIndustryToggled);
|
||||
on<ExperienceSkillToggled>(_onSkillToggled);
|
||||
@@ -120,26 +123,28 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
Emitter<ExperienceState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ExperienceStatus.loading));
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
getIndustries(),
|
||||
getSkills(),
|
||||
]);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final results = await Future.wait([getIndustries(), getSkills()]);
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ExperienceStatus.initial,
|
||||
selectedIndustries: results[0]
|
||||
.map((e) => Industry.fromString(e))
|
||||
.whereType<Industry>()
|
||||
.toList(),
|
||||
selectedSkills: results[1],
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ExperienceStatus.initial,
|
||||
selectedIndustries:
|
||||
results[0]
|
||||
.map((e) => Industry.fromString(e))
|
||||
.whereType<Industry>()
|
||||
.toList(),
|
||||
selectedSkills: results[1],
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ExperienceStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onIndustryToggled(
|
||||
@@ -183,19 +188,22 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
Emitter<ExperienceState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ExperienceStatus.loading));
|
||||
try {
|
||||
await saveExperience(
|
||||
SaveExperienceArguments(
|
||||
industries: state.selectedIndustries.map((e) => e.value).toList(),
|
||||
skills: state.selectedSkills,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: ExperienceStatus.success));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await saveExperience(
|
||||
SaveExperienceArguments(
|
||||
industries: state.selectedIndustries.map((e) => e.value).toList(),
|
||||
skills: state.selectedSkills,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: ExperienceStatus.success));
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ExperienceStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../domain/usecases/get_personal_info_usecase.dart';
|
||||
@@ -13,17 +14,17 @@ import 'personal_info_state.dart';
|
||||
/// during onboarding or profile editing. It delegates business logic to
|
||||
/// use cases following Clean Architecture principles.
|
||||
class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
||||
with BlocErrorHandler<PersonalInfoState>
|
||||
implements Disposable {
|
||||
|
||||
/// Creates a [PersonalInfoBloc].
|
||||
///
|
||||
/// Requires the use cases to load and update the profile.
|
||||
PersonalInfoBloc({
|
||||
required GetPersonalInfoUseCase getPersonalInfoUseCase,
|
||||
required UpdatePersonalInfoUseCase updatePersonalInfoUseCase,
|
||||
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
|
||||
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
|
||||
super(const PersonalInfoState.initial()) {
|
||||
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
|
||||
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
|
||||
super(const PersonalInfoState.initial()) {
|
||||
on<PersonalInfoLoadRequested>(_onLoadRequested);
|
||||
on<PersonalInfoFieldChanged>(_onFieldChanged);
|
||||
on<PersonalInfoAddressSelected>(_onAddressSelected);
|
||||
@@ -40,32 +41,37 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
||||
Emitter<PersonalInfoState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PersonalInfoStatus.loading));
|
||||
try {
|
||||
final Staff staff = await _getPersonalInfoUseCase();
|
||||
|
||||
// Initialize form values from staff entity
|
||||
// Note: Staff entity currently stores address as a string, but we want to map it to 'preferredLocations'
|
||||
final Map<String, dynamic> initialValues = <String, dynamic>{
|
||||
'name': staff.name,
|
||||
'email': staff.email,
|
||||
'phone': staff.phone,
|
||||
'preferredLocations': staff.address != null
|
||||
? <String?>[staff.address]
|
||||
: <dynamic>[], // TODO: Map correctly when Staff entity supports list
|
||||
'avatar': staff.avatar,
|
||||
};
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final Staff staff = await _getPersonalInfoUseCase();
|
||||
|
||||
emit(state.copyWith(
|
||||
status: PersonalInfoStatus.loaded,
|
||||
staff: staff,
|
||||
formValues: initialValues,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
// Initialize form values from staff entity
|
||||
// Note: Staff entity currently stores address as a string, but we want to map it to 'preferredLocations'
|
||||
final Map<String, dynamic> initialValues = <String, dynamic>{
|
||||
'name': staff.name,
|
||||
'email': staff.email,
|
||||
'phone': staff.phone,
|
||||
'preferredLocations':
|
||||
staff.address != null
|
||||
? <String?>[staff.address]
|
||||
: <dynamic>[], // TODO: Map correctly when Staff entity supports list
|
||||
'avatar': staff.avatar,
|
||||
};
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PersonalInfoStatus.loaded,
|
||||
staff: staff,
|
||||
formValues: initialValues,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: PersonalInfoStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles updating a field value in the current staff profile.
|
||||
@@ -86,43 +92,48 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
||||
if (state.staff == null) return;
|
||||
|
||||
emit(state.copyWith(status: PersonalInfoStatus.saving));
|
||||
try {
|
||||
final Staff updatedStaff = await _updatePersonalInfoUseCase(
|
||||
UpdatePersonalInfoParams(
|
||||
staffId: state.staff!.id,
|
||||
data: state.formValues,
|
||||
),
|
||||
);
|
||||
|
||||
// Update local state with the returned staff and keep form values in sync
|
||||
final Map<String, dynamic> newValues = <String, dynamic>{
|
||||
'name': updatedStaff.name,
|
||||
'email': updatedStaff.email,
|
||||
'phone': updatedStaff.phone,
|
||||
'preferredLocations': updatedStaff.address != null
|
||||
? <String?>[updatedStaff.address]
|
||||
: <dynamic>[],
|
||||
'avatar': updatedStaff.avatar,
|
||||
};
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final Staff updatedStaff = await _updatePersonalInfoUseCase(
|
||||
UpdatePersonalInfoParams(
|
||||
staffId: state.staff!.id,
|
||||
data: state.formValues,
|
||||
),
|
||||
);
|
||||
|
||||
emit(state.copyWith(
|
||||
status: PersonalInfoStatus.saved,
|
||||
staff: updatedStaff,
|
||||
formValues: newValues,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
// Update local state with the returned staff and keep form values in sync
|
||||
final Map<String, dynamic> newValues = <String, dynamic>{
|
||||
'name': updatedStaff.name,
|
||||
'email': updatedStaff.email,
|
||||
'phone': updatedStaff.phone,
|
||||
'preferredLocations':
|
||||
updatedStaff.address != null
|
||||
? <String?>[updatedStaff.address]
|
||||
: <dynamic>[],
|
||||
'avatar': updatedStaff.avatar,
|
||||
};
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PersonalInfoStatus.saved,
|
||||
staff: updatedStaff,
|
||||
formValues: newValues,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: PersonalInfoStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddressSelected(
|
||||
PersonalInfoAddressSelected event,
|
||||
Emitter<PersonalInfoState> emit,
|
||||
) {
|
||||
// TODO: Implement Google Places logic if needed
|
||||
// TODO: Implement Google Places logic if needed
|
||||
}
|
||||
|
||||
/// With _onPhotoUploadRequested and _onSaveRequested removed or renamed,
|
||||
@@ -133,3 +144,4 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../../domain/usecases/apply_for_shift_usecase.dart';
|
||||
import '../../../domain/usecases/decline_shift_usecase.dart';
|
||||
import '../../../domain/usecases/get_shift_details_usecase.dart';
|
||||
@@ -6,7 +7,8 @@ import '../../../domain/arguments/get_shift_details_arguments.dart';
|
||||
import 'shift_details_event.dart';
|
||||
import 'shift_details_state.dart';
|
||||
|
||||
class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState>
|
||||
with BlocErrorHandler<ShiftDetailsState> {
|
||||
final GetShiftDetailsUseCase getShiftDetails;
|
||||
final ApplyForShiftUseCase applyForShift;
|
||||
final DeclineShiftUseCase declineShift;
|
||||
@@ -26,47 +28,54 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(ShiftDetailsLoading());
|
||||
try {
|
||||
final shift = await getShiftDetails(
|
||||
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
|
||||
);
|
||||
if (shift != null) {
|
||||
emit(ShiftDetailsLoaded(shift));
|
||||
} else {
|
||||
emit(const ShiftDetailsError("Shift not found"));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(ShiftDetailsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final shift = await getShiftDetails(
|
||||
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
|
||||
);
|
||||
if (shift != null) {
|
||||
emit(ShiftDetailsLoaded(shift));
|
||||
} else {
|
||||
emit(const ShiftDetailsError("Shift not found"));
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => ShiftDetailsError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onBookShift(
|
||||
BookShiftDetailsEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
try {
|
||||
await applyForShift(
|
||||
event.shiftId,
|
||||
isInstantBook: true,
|
||||
roleId: event.roleId,
|
||||
);
|
||||
emit(
|
||||
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ShiftDetailsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await applyForShift(
|
||||
event.shiftId,
|
||||
isInstantBook: true,
|
||||
roleId: event.roleId,
|
||||
);
|
||||
emit(
|
||||
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => ShiftDetailsError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDeclineShift(
|
||||
DeclineShiftDetailsEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
try {
|
||||
await declineShift(event.shiftId);
|
||||
emit(const ShiftActionSuccess("Shift declined"));
|
||||
} catch (e) {
|
||||
emit(ShiftDetailsError(e.toString()));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
await declineShift(event.shiftId);
|
||||
emit(const ShiftActionSuccess("Shift declined"));
|
||||
},
|
||||
onError: (String errorKey) => ShiftDetailsError(errorKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@@ -14,7 +15,8 @@ import '../../../domain/usecases/get_pending_assignments_usecase.dart';
|
||||
part 'shifts_event.dart';
|
||||
part 'shifts_state.dart';
|
||||
|
||||
class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
with BlocErrorHandler<ShiftsState> {
|
||||
final GetMyShiftsUseCase getMyShifts;
|
||||
final GetAvailableShiftsUseCase getAvailableShifts;
|
||||
final GetPendingAssignmentsUseCase getPendingAssignments;
|
||||
@@ -43,33 +45,32 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
if (state is! ShiftsLoaded) {
|
||||
emit(ShiftsLoading());
|
||||
}
|
||||
|
||||
// Determine what to load based on current tab?
|
||||
// Or load all for simplicity as per prototype logic which had them all in memory.
|
||||
|
||||
try {
|
||||
final List<DateTime> days = _getCalendarDaysForOffset(0);
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: days.first, end: days.last),
|
||||
);
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: const [],
|
||||
cancelledShifts: const [],
|
||||
availableShifts: const [],
|
||||
historyShifts: const [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: true,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const ShiftsError('Failed to load shifts'));
|
||||
}
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final List<DateTime> days = _getCalendarDaysForOffset(0);
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: days.first, end: days.last),
|
||||
);
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: const [],
|
||||
cancelledShifts: const [],
|
||||
availableShifts: const [],
|
||||
historyShifts: const [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: true,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) => ShiftsError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadHistoryShifts(
|
||||
@@ -81,17 +82,24 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
if (currentState.historyLoading || currentState.historyLoaded) return;
|
||||
|
||||
emit(currentState.copyWith(historyLoading: true));
|
||||
try {
|
||||
final historyResult = await getHistoryShifts();
|
||||
emit(currentState.copyWith(
|
||||
myShiftsLoaded: true,
|
||||
historyShifts: historyResult,
|
||||
historyLoading: false,
|
||||
historyLoaded: true,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(currentState.copyWith(historyLoading: false));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final historyResult = await getHistoryShifts();
|
||||
emit(currentState.copyWith(
|
||||
myShiftsLoaded: true,
|
||||
historyShifts: historyResult,
|
||||
historyLoading: false,
|
||||
historyLoaded: true,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (state is ShiftsLoaded) {
|
||||
return (state as ShiftsLoaded).copyWith(historyLoading: false);
|
||||
}
|
||||
return ShiftsError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadAvailableShifts(
|
||||
@@ -103,17 +111,24 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
if (currentState.availableLoading || currentState.availableLoaded) return;
|
||||
|
||||
emit(currentState.copyWith(availableLoading: true));
|
||||
try {
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(currentState.copyWith(availableLoading: false));
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (state is ShiftsLoaded) {
|
||||
return (state as ShiftsLoaded).copyWith(availableLoading: false);
|
||||
}
|
||||
return ShiftsError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadFindFirst(
|
||||
@@ -137,81 +152,86 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
));
|
||||
}
|
||||
|
||||
final currentState =
|
||||
state is ShiftsLoaded ? state as ShiftsLoaded : null;
|
||||
final currentState = state is ShiftsLoaded ? state as ShiftsLoaded : null;
|
||||
if (currentState != null && currentState.availableLoaded) return;
|
||||
|
||||
if (currentState != null) {
|
||||
emit(currentState.copyWith(availableLoading: true));
|
||||
}
|
||||
|
||||
try {
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
final loadedState = state is ShiftsLoaded
|
||||
? state as ShiftsLoaded
|
||||
: const ShiftsLoaded(
|
||||
myShifts: [],
|
||||
pendingShifts: [],
|
||||
cancelledShifts: [],
|
||||
availableShifts: [],
|
||||
historyShifts: [],
|
||||
availableLoading: true,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
);
|
||||
emit(loadedState.copyWith(
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
));
|
||||
} catch (_) {
|
||||
if (state is ShiftsLoaded) {
|
||||
final current = state as ShiftsLoaded;
|
||||
emit(current.copyWith(availableLoading: false));
|
||||
}
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
final loadedState = state is ShiftsLoaded
|
||||
? state as ShiftsLoaded
|
||||
: const ShiftsLoaded(
|
||||
myShifts: [],
|
||||
pendingShifts: [],
|
||||
cancelledShifts: [],
|
||||
availableShifts: [],
|
||||
historyShifts: [],
|
||||
availableLoading: true,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
);
|
||||
emit(loadedState.copyWith(
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (state is ShiftsLoaded) {
|
||||
return (state as ShiftsLoaded).copyWith(availableLoading: false);
|
||||
}
|
||||
return ShiftsError(errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadShiftsForRange(
|
||||
LoadShiftsForRangeEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: event.start, end: event.end),
|
||||
);
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: event.start, end: event.end),
|
||||
);
|
||||
|
||||
if (state is ShiftsLoaded) {
|
||||
final currentState = state as ShiftsLoaded;
|
||||
emit(currentState.copyWith(
|
||||
if (state is ShiftsLoaded) {
|
||||
final currentState = state as ShiftsLoaded;
|
||||
emit(currentState.copyWith(
|
||||
myShifts: myShiftsResult,
|
||||
myShiftsLoaded: true,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: const [],
|
||||
cancelledShifts: const [],
|
||||
availableShifts: const [],
|
||||
historyShifts: const [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: true,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: const [],
|
||||
cancelledShifts: const [],
|
||||
availableShifts: const [],
|
||||
historyShifts: const [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: true,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const ShiftsError('Failed to load shifts'));
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => ShiftsError(errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFilterAvailableShifts(
|
||||
@@ -224,23 +244,27 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
add(LoadAvailableShiftsEvent());
|
||||
return;
|
||||
}
|
||||
// Optimistic update or loading indicator?
|
||||
// Since it's filtering, we can just reload available.
|
||||
|
||||
try {
|
||||
final result = await getAvailableShifts(GetAvailableShiftsArguments(
|
||||
query: event.query ?? currentState.searchQuery,
|
||||
type: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(result),
|
||||
searchQuery: event.query ?? currentState.searchQuery,
|
||||
jobType: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
} catch (_) {
|
||||
// Error handling if filter fails
|
||||
}
|
||||
await handleError(
|
||||
emit: emit,
|
||||
action: () async {
|
||||
final result = await getAvailableShifts(GetAvailableShiftsArguments(
|
||||
query: event.query ?? currentState.searchQuery,
|
||||
type: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(result),
|
||||
searchQuery: event.query ?? currentState.searchQuery,
|
||||
jobType: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
// Stay on current state for filtering errors, maybe show a snackbar?
|
||||
// For now just logging is enough via handleError mixin.
|
||||
return currentState;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,3 +292,4 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user