refactor: migrate shifts BLoC state management to a single state class with a status enum.
This commit is contained in:
@@ -32,7 +32,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
required this.getCancelledShifts,
|
||||
required this.getHistoryShifts,
|
||||
required this.getProfileCompletion,
|
||||
}) : super(ShiftsInitial()) {
|
||||
}) : super(const ShiftsState()) {
|
||||
on<LoadShiftsEvent>(_onLoadShifts);
|
||||
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
|
||||
on<LoadAvailableShiftsEvent>(_onLoadAvailableShifts);
|
||||
@@ -46,8 +46,8 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
LoadShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
if (state is! ShiftsLoaded) {
|
||||
emit(ShiftsLoading());
|
||||
if (state.status != ShiftsStatus.loaded) {
|
||||
emit(state.copyWith(status: ShiftsStatus.loading));
|
||||
}
|
||||
|
||||
await handleError(
|
||||
@@ -58,22 +58,26 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
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',
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ShiftsStatus.loaded,
|
||||
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),
|
||||
onError: (String errorKey) =>
|
||||
state.copyWith(status: ShiftsStatus.error, errorMessage: errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,27 +85,29 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
LoadHistoryShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ShiftsLoaded) return;
|
||||
if (currentState.historyLoading || currentState.historyLoaded) return;
|
||||
if (state.status != ShiftsStatus.loaded) return;
|
||||
if (state.historyLoading || state.historyLoaded) return;
|
||||
|
||||
emit(currentState.copyWith(historyLoading: true));
|
||||
emit(state.copyWith(historyLoading: true));
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final historyResult = await getHistoryShifts();
|
||||
emit(currentState.copyWith(
|
||||
myShiftsLoaded: true,
|
||||
historyShifts: historyResult,
|
||||
historyLoading: false,
|
||||
historyLoaded: true,
|
||||
));
|
||||
emit(
|
||||
state.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);
|
||||
return state.copyWith(
|
||||
historyLoading: false,
|
||||
status: ShiftsStatus.error,
|
||||
errorMessage: errorKey,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -110,33 +116,32 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
LoadAvailableShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ShiftsLoaded) return;
|
||||
if (!event.force &&
|
||||
(currentState.availableLoading || currentState.availableLoaded)) {
|
||||
if (state.status != ShiftsStatus.loaded) return;
|
||||
if (!event.force && (state.availableLoading || state.availableLoaded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
availableLoading: true,
|
||||
availableLoaded: false,
|
||||
));
|
||||
emit(state.copyWith(availableLoading: true, availableLoaded: false));
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
));
|
||||
final availableResult = await getAvailableShifts(
|
||||
const GetAvailableShiftsArguments(),
|
||||
);
|
||||
emit(
|
||||
state.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);
|
||||
return state.copyWith(
|
||||
availableLoading: false,
|
||||
status: ShiftsStatus.error,
|
||||
errorMessage: errorKey,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -145,62 +150,51 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
LoadFindFirstEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
if (state is! ShiftsLoaded) {
|
||||
emit(const ShiftsLoaded(
|
||||
myShifts: [],
|
||||
pendingShifts: [],
|
||||
cancelledShifts: [],
|
||||
availableShifts: [],
|
||||
historyShifts: [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
if (state.status != ShiftsStatus.loaded) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ShiftsStatus.loading,
|
||||
myShifts: const [],
|
||||
pendingShifts: const [],
|
||||
cancelledShifts: const [],
|
||||
availableShifts: const [],
|
||||
historyShifts: const [],
|
||||
availableLoading: false,
|
||||
availableLoaded: false,
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
myShiftsLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final currentState = state is ShiftsLoaded ? state as ShiftsLoaded : null;
|
||||
if (currentState != null && currentState.availableLoaded) return;
|
||||
if (state.availableLoaded) return;
|
||||
|
||||
if (currentState != null) {
|
||||
emit(currentState.copyWith(availableLoading: true));
|
||||
}
|
||||
emit(state.copyWith(availableLoading: true));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
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,
|
||||
));
|
||||
final availableResult = await getAvailableShifts(
|
||||
const GetAvailableShiftsArguments(),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ShiftsStatus.loaded,
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
availableLoading: false,
|
||||
availableLoaded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (state is ShiftsLoaded) {
|
||||
return (state as ShiftsLoaded).copyWith(availableLoading: false);
|
||||
}
|
||||
return ShiftsError(errorKey);
|
||||
return state.copyWith(
|
||||
availableLoading: false,
|
||||
status: ShiftsStatus.error,
|
||||
errorMessage: errorKey,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -216,31 +210,16 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
GetMyShiftsArguments(start: event.start, end: event.end),
|
||||
);
|
||||
|
||||
if (state is ShiftsLoaded) {
|
||||
final currentState = state as ShiftsLoaded;
|
||||
emit(currentState.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ShiftsStatus.loaded,
|
||||
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',
|
||||
));
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => ShiftsError(errorKey),
|
||||
onError: (String errorKey) =>
|
||||
state.copyWith(status: ShiftsStatus.error, errorMessage: errorKey),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,9 +227,8 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
FilterAvailableShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is ShiftsLoaded) {
|
||||
if (!currentState.availableLoaded && !currentState.availableLoading) {
|
||||
if (state.status == ShiftsStatus.loaded) {
|
||||
if (!state.availableLoaded && !state.availableLoading) {
|
||||
add(LoadAvailableShiftsEvent());
|
||||
return;
|
||||
}
|
||||
@@ -258,21 +236,26 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final result = await getAvailableShifts(GetAvailableShiftsArguments(
|
||||
query: event.query ?? currentState.searchQuery,
|
||||
type: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
final result = await getAvailableShifts(
|
||||
GetAvailableShiftsArguments(
|
||||
query: event.query ?? state.searchQuery,
|
||||
type: event.jobType ?? state.jobType,
|
||||
),
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: _filterPastShifts(result),
|
||||
searchQuery: event.query ?? currentState.searchQuery,
|
||||
jobType: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
emit(
|
||||
state.copyWith(
|
||||
availableShifts: _filterPastShifts(result),
|
||||
searchQuery: event.query ?? state.searchQuery,
|
||||
jobType: event.jobType ?? state.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;
|
||||
return state.copyWith(
|
||||
status: ShiftsStatus.error,
|
||||
errorMessage: errorKey,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -282,17 +265,14 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
||||
CheckProfileCompletionEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ShiftsLoaded) return;
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final bool isComplete = await getProfileCompletion();
|
||||
emit(currentState.copyWith(profileComplete: isComplete));
|
||||
emit(state.copyWith(profileComplete: isComplete));
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
return currentState.copyWith(profileComplete: false);
|
||||
return state.copyWith(profileComplete: false);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
part of 'shifts_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class ShiftsState extends Equatable {
|
||||
const ShiftsState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
enum ShiftsStatus { initial, loading, loaded, error }
|
||||
|
||||
class ShiftsInitial extends ShiftsState {}
|
||||
|
||||
class ShiftsLoading extends ShiftsState {}
|
||||
|
||||
class ShiftsLoaded extends ShiftsState {
|
||||
class ShiftsState extends Equatable {
|
||||
final ShiftsStatus status;
|
||||
final List<Shift> myShifts;
|
||||
final List<Shift> pendingShifts;
|
||||
final List<Shift> cancelledShifts;
|
||||
@@ -26,24 +17,28 @@ class ShiftsLoaded extends ShiftsState {
|
||||
final String searchQuery;
|
||||
final String jobType;
|
||||
final bool? profileComplete;
|
||||
final String? errorMessage;
|
||||
|
||||
const ShiftsLoaded({
|
||||
required this.myShifts,
|
||||
required this.pendingShifts,
|
||||
required this.cancelledShifts,
|
||||
required this.availableShifts,
|
||||
required this.historyShifts,
|
||||
required this.availableLoading,
|
||||
required this.availableLoaded,
|
||||
required this.historyLoading,
|
||||
required this.historyLoaded,
|
||||
required this.myShiftsLoaded,
|
||||
required this.searchQuery,
|
||||
required this.jobType,
|
||||
const ShiftsState({
|
||||
this.status = ShiftsStatus.initial,
|
||||
this.myShifts = const [],
|
||||
this.pendingShifts = const [],
|
||||
this.cancelledShifts = const [],
|
||||
this.availableShifts = const [],
|
||||
this.historyShifts = const [],
|
||||
this.availableLoading = false,
|
||||
this.availableLoaded = false,
|
||||
this.historyLoading = false,
|
||||
this.historyLoaded = false,
|
||||
this.myShiftsLoaded = false,
|
||||
this.searchQuery = '',
|
||||
this.jobType = 'all',
|
||||
this.profileComplete,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
ShiftsLoaded copyWith({
|
||||
ShiftsState copyWith({
|
||||
ShiftsStatus? status,
|
||||
List<Shift>? myShifts,
|
||||
List<Shift>? pendingShifts,
|
||||
List<Shift>? cancelledShifts,
|
||||
@@ -57,8 +52,10 @@ class ShiftsLoaded extends ShiftsState {
|
||||
String? searchQuery,
|
||||
String? jobType,
|
||||
bool? profileComplete,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return ShiftsLoaded(
|
||||
return ShiftsState(
|
||||
status: status ?? this.status,
|
||||
myShifts: myShifts ?? this.myShifts,
|
||||
pendingShifts: pendingShifts ?? this.pendingShifts,
|
||||
cancelledShifts: cancelledShifts ?? this.cancelledShifts,
|
||||
@@ -72,32 +69,26 @@ class ShiftsLoaded extends ShiftsState {
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
jobType: jobType ?? this.jobType,
|
||||
profileComplete: profileComplete ?? this.profileComplete,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
myShifts,
|
||||
pendingShifts,
|
||||
cancelledShifts,
|
||||
availableShifts,
|
||||
historyShifts,
|
||||
availableLoading,
|
||||
availableLoaded,
|
||||
historyLoading,
|
||||
historyLoaded,
|
||||
myShiftsLoaded,
|
||||
searchQuery,
|
||||
jobType,
|
||||
profileComplete ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
class ShiftsError extends ShiftsState {
|
||||
final String message;
|
||||
|
||||
const ShiftsError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
myShifts,
|
||||
pendingShifts,
|
||||
cancelledShifts,
|
||||
availableShifts,
|
||||
historyShifts,
|
||||
availableLoading,
|
||||
availableLoaded,
|
||||
historyLoading,
|
||||
historyLoaded,
|
||||
myShiftsLoaded,
|
||||
searchQuery,
|
||||
jobType,
|
||||
profileComplete,
|
||||
errorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../blocs/shifts/shifts_bloc.dart';
|
||||
import '../utils/shift_tab_type.dart';
|
||||
import '../widgets/tabs/my_shifts_tab.dart';
|
||||
import '../widgets/tabs/find_shifts_tab.dart';
|
||||
import '../widgets/tabs/history_shifts_tab.dart';
|
||||
|
||||
class ShiftsPage extends StatefulWidget {
|
||||
final String? initialTab;
|
||||
final ShiftTabType? initialTab;
|
||||
final DateTime? selectedDate;
|
||||
final bool refreshAvailable;
|
||||
const ShiftsPage({
|
||||
@@ -25,7 +26,7 @@ class ShiftsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ShiftsPageState extends State<ShiftsPage> {
|
||||
late String _activeTab;
|
||||
late ShiftTabType _activeTab;
|
||||
DateTime? _selectedDate;
|
||||
bool _prioritizeFind = false;
|
||||
bool _refreshAvailable = false;
|
||||
@@ -35,9 +36,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_activeTab = widget.initialTab ?? 'myshifts';
|
||||
_activeTab = widget.initialTab ?? ShiftTabType.find;
|
||||
_selectedDate = widget.selectedDate;
|
||||
_prioritizeFind = widget.initialTab == 'find';
|
||||
_prioritizeFind = _activeTab == ShiftTabType.find;
|
||||
_refreshAvailable = widget.refreshAvailable;
|
||||
_pendingAvailableRefresh = widget.refreshAvailable;
|
||||
if (_prioritizeFind) {
|
||||
@@ -45,16 +46,15 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
} else {
|
||||
_bloc.add(LoadShiftsEvent());
|
||||
}
|
||||
if (_activeTab == 'history') {
|
||||
if (_activeTab == ShiftTabType.history) {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
if (_activeTab == 'find') {
|
||||
if (_activeTab == ShiftTabType.find) {
|
||||
if (!_prioritizeFind) {
|
||||
_bloc.add(
|
||||
LoadAvailableShiftsEvent(force: _refreshAvailable),
|
||||
);
|
||||
_bloc.add(LoadAvailableShiftsEvent(force: _refreshAvailable));
|
||||
}
|
||||
}
|
||||
|
||||
// Check profile completion
|
||||
_bloc.add(const CheckProfileCompletionEvent());
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
if (widget.initialTab != null && widget.initialTab != _activeTab) {
|
||||
setState(() {
|
||||
_activeTab = widget.initialTab!;
|
||||
_prioritizeFind = widget.initialTab == 'find';
|
||||
_prioritizeFind = _activeTab == ShiftTabType.find;
|
||||
});
|
||||
}
|
||||
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
|
||||
@@ -86,50 +86,31 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
value: _bloc,
|
||||
child: BlocConsumer<ShiftsBloc, ShiftsState>(
|
||||
listener: (context, state) {
|
||||
if (state is ShiftsError) {
|
||||
if (state.status == ShiftsStatus.error &&
|
||||
state.errorMessage != null) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: translateErrorKey(state.message),
|
||||
message: translateErrorKey(state.errorMessage!),
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (_pendingAvailableRefresh && state is ShiftsLoaded) {
|
||||
if (_pendingAvailableRefresh && state.status == ShiftsStatus.loaded) {
|
||||
_pendingAvailableRefresh = false;
|
||||
_bloc.add(const LoadAvailableShiftsEvent(force: true));
|
||||
}
|
||||
final bool baseLoaded = state is ShiftsLoaded;
|
||||
final List<Shift> myShifts = (state is ShiftsLoaded)
|
||||
? state.myShifts
|
||||
: [];
|
||||
final List<Shift> availableJobs = (state is ShiftsLoaded)
|
||||
? state.availableShifts
|
||||
: [];
|
||||
final bool availableLoading = (state is ShiftsLoaded)
|
||||
? state.availableLoading
|
||||
: false;
|
||||
final bool availableLoaded = (state is ShiftsLoaded)
|
||||
? state.availableLoaded
|
||||
: false;
|
||||
final List<Shift> pendingAssignments = (state is ShiftsLoaded)
|
||||
? state.pendingShifts
|
||||
: [];
|
||||
final List<Shift> cancelledShifts = (state is ShiftsLoaded)
|
||||
? state.cancelledShifts
|
||||
: [];
|
||||
final List<Shift> historyShifts = (state is ShiftsLoaded)
|
||||
? state.historyShifts
|
||||
: [];
|
||||
final bool historyLoading = (state is ShiftsLoaded)
|
||||
? state.historyLoading
|
||||
: false;
|
||||
final bool historyLoaded = (state is ShiftsLoaded)
|
||||
? state.historyLoaded
|
||||
: false;
|
||||
final bool myShiftsLoaded = (state is ShiftsLoaded)
|
||||
? state.myShiftsLoaded
|
||||
: false;
|
||||
final bool baseLoaded = state.status == ShiftsStatus.loaded;
|
||||
final List<Shift> myShifts = state.myShifts;
|
||||
final List<Shift> availableJobs = state.availableShifts;
|
||||
final bool availableLoading = state.availableLoading;
|
||||
final bool availableLoaded = state.availableLoaded;
|
||||
final List<Shift> pendingAssignments = state.pendingShifts;
|
||||
final List<Shift> cancelledShifts = state.cancelledShifts;
|
||||
final List<Shift> historyShifts = state.historyShifts;
|
||||
final bool historyLoading = state.historyLoading;
|
||||
final bool historyLoaded = state.historyLoaded;
|
||||
final bool myShiftsLoaded = state.myShiftsLoaded;
|
||||
final bool blockTabsForFind = _prioritizeFind && !availableLoaded;
|
||||
|
||||
// Note: "filteredJobs" logic moved to FindShiftsTab
|
||||
@@ -160,44 +141,47 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
// Tabs
|
||||
Row(
|
||||
children: [
|
||||
if (state is ShiftsLoaded && state.profileComplete != false)
|
||||
if (state.profileComplete != false)
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
"myshifts",
|
||||
ShiftTabType.myShifts,
|
||||
t.staff_shifts.tabs.my_shifts,
|
||||
UiIcons.calendar,
|
||||
myShifts.length,
|
||||
showCount: myShiftsLoaded,
|
||||
enabled: !blockTabsForFind && (state.profileComplete ?? false),
|
||||
enabled:
|
||||
!blockTabsForFind &&
|
||||
(state.profileComplete ?? false),
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
if (state is ShiftsLoaded && state.profileComplete != false)
|
||||
if (state.profileComplete != false)
|
||||
const SizedBox(width: UiConstants.space2)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
_buildTab(
|
||||
"find",
|
||||
ShiftTabType.find,
|
||||
t.staff_shifts.tabs.find_work,
|
||||
UiIcons.search,
|
||||
availableJobs.length,
|
||||
showCount: availableLoaded,
|
||||
enabled: baseLoaded,
|
||||
),
|
||||
if (state is ShiftsLoaded && state.profileComplete != false)
|
||||
if (state.profileComplete != false)
|
||||
const SizedBox(width: UiConstants.space2)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
if (state is ShiftsLoaded && state.profileComplete != false)
|
||||
if (state.profileComplete != false)
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
"history",
|
||||
ShiftTabType.history,
|
||||
t.staff_shifts.tabs.history,
|
||||
UiIcons.clock,
|
||||
historyShifts.length,
|
||||
showCount: historyLoaded,
|
||||
enabled: !blockTabsForFind &&
|
||||
enabled:
|
||||
!blockTabsForFind &&
|
||||
baseLoaded &&
|
||||
(state.profileComplete ?? false),
|
||||
),
|
||||
@@ -212,9 +196,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
|
||||
// Body Content
|
||||
Expanded(
|
||||
child: state is ShiftsLoading
|
||||
child: state.status == ShiftsStatus.loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: state is ShiftsError
|
||||
: state.status == ShiftsStatus.error
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
@@ -222,7 +206,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
translateErrorKey(state.message),
|
||||
translateErrorKey(state.errorMessage ?? ''),
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -258,47 +242,45 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
bool historyLoading,
|
||||
) {
|
||||
switch (_activeTab) {
|
||||
case 'myshifts':
|
||||
case ShiftTabType.myShifts:
|
||||
return MyShiftsTab(
|
||||
myShifts: myShifts,
|
||||
pendingAssignments: pendingAssignments,
|
||||
cancelledShifts: cancelledShifts,
|
||||
initialDate: _selectedDate,
|
||||
);
|
||||
case 'find':
|
||||
case ShiftTabType.find:
|
||||
if (availableLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return FindShiftsTab(availableJobs: availableJobs);
|
||||
case 'history':
|
||||
case ShiftTabType.history:
|
||||
if (historyLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return HistoryShiftsTab(historyShifts: historyShifts);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTab(
|
||||
String id,
|
||||
ShiftTabType type,
|
||||
String label,
|
||||
IconData icon,
|
||||
int count, {
|
||||
bool showCount = true,
|
||||
bool enabled = true,
|
||||
}) {
|
||||
final isActive = _activeTab == id;
|
||||
final isActive = _activeTab == type;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: !enabled
|
||||
? null
|
||||
: () {
|
||||
setState(() => _activeTab = id);
|
||||
if (id == 'history') {
|
||||
setState(() => _activeTab = type);
|
||||
if (type == ShiftTabType.history) {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
if (id == 'find') {
|
||||
if (type == ShiftTabType.find) {
|
||||
_bloc.add(LoadAvailableShiftsEvent());
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
enum ShiftTabType {
|
||||
myShifts,
|
||||
find,
|
||||
history;
|
||||
|
||||
static ShiftTabType fromString(String? value) {
|
||||
if (value == null) return ShiftTabType.find;
|
||||
switch (value.toLowerCase()) {
|
||||
case 'myshifts':
|
||||
return ShiftTabType.myShifts;
|
||||
case 'find':
|
||||
return ShiftTabType.find;
|
||||
case 'history':
|
||||
return ShiftTabType.history;
|
||||
default:
|
||||
return ShiftTabType.find;
|
||||
}
|
||||
}
|
||||
|
||||
String get id {
|
||||
switch (this) {
|
||||
case ShiftTabType.myShifts:
|
||||
return 'myshifts';
|
||||
case ShiftTabType.find:
|
||||
return 'find';
|
||||
case ShiftTabType.history:
|
||||
return 'history';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ class MyShiftCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyShiftCardState extends State<MyShiftCard> {
|
||||
|
||||
String _formatTime(String time) {
|
||||
if (time.isEmpty) return '';
|
||||
try {
|
||||
@@ -77,22 +76,23 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
String _getShiftType() {
|
||||
// Handling potential localization key availability
|
||||
try {
|
||||
final String orderType = (widget.shift.orderType ?? '').toUpperCase();
|
||||
if (orderType == 'PERMANENT') {
|
||||
return t.staff_shifts.filter.long_term;
|
||||
}
|
||||
if (orderType == 'RECURRING') {
|
||||
return t.staff_shifts.filter.multi_day;
|
||||
}
|
||||
if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) {
|
||||
return t.staff_shifts.filter.long_term;
|
||||
}
|
||||
if (widget.shift.durationDays != null && widget.shift.durationDays! > 1) {
|
||||
return t.staff_shifts.filter.multi_day;
|
||||
}
|
||||
return t.staff_shifts.filter.one_day;
|
||||
final String orderType = (widget.shift.orderType ?? '').toUpperCase();
|
||||
if (orderType == 'PERMANENT') {
|
||||
return t.staff_shifts.filter.long_term;
|
||||
}
|
||||
if (orderType == 'RECURRING') {
|
||||
return t.staff_shifts.filter.multi_day;
|
||||
}
|
||||
if (widget.shift.durationDays != null &&
|
||||
widget.shift.durationDays! > 30) {
|
||||
return t.staff_shifts.filter.long_term;
|
||||
}
|
||||
if (widget.shift.durationDays != null && widget.shift.durationDays! > 1) {
|
||||
return t.staff_shifts.filter.multi_day;
|
||||
}
|
||||
return t.staff_shifts.filter.one_day;
|
||||
} catch (_) {
|
||||
return "One Day";
|
||||
return "One Day";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,34 +110,34 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
|
||||
// Fallback localization if keys missing
|
||||
try {
|
||||
if (status == 'confirmed') {
|
||||
statusText = t.staff_shifts.status.confirmed;
|
||||
statusColor = UiColors.textLink;
|
||||
statusBg = UiColors.primary;
|
||||
} else if (status == 'checked_in') {
|
||||
statusText = 'Checked in';
|
||||
statusColor = UiColors.textSuccess;
|
||||
statusBg = UiColors.iconSuccess;
|
||||
} else if (status == 'pending' || status == 'open') {
|
||||
statusText = t.staff_shifts.status.act_now;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
} else if (status == 'swap') {
|
||||
statusText = t.staff_shifts.status.swap_requested;
|
||||
statusColor = UiColors.textWarning;
|
||||
statusBg = UiColors.textWarning;
|
||||
statusIcon = UiIcons.swap;
|
||||
} else if (status == 'completed') {
|
||||
statusText = t.staff_shifts.status.completed;
|
||||
statusColor = UiColors.textSuccess;
|
||||
statusBg = UiColors.iconSuccess;
|
||||
} else if (status == 'no_show') {
|
||||
statusText = t.staff_shifts.status.no_show;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
}
|
||||
if (status == 'confirmed') {
|
||||
statusText = t.staff_shifts.status.confirmed;
|
||||
statusColor = UiColors.textLink;
|
||||
statusBg = UiColors.primary;
|
||||
} else if (status == 'checked_in') {
|
||||
statusText = 'Checked in';
|
||||
statusColor = UiColors.textSuccess;
|
||||
statusBg = UiColors.iconSuccess;
|
||||
} else if (status == 'pending' || status == 'open') {
|
||||
statusText = t.staff_shifts.status.act_now;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
} else if (status == 'swap') {
|
||||
statusText = t.staff_shifts.status.swap_requested;
|
||||
statusColor = UiColors.textWarning;
|
||||
statusBg = UiColors.textWarning;
|
||||
statusIcon = UiIcons.swap;
|
||||
} else if (status == 'completed') {
|
||||
statusText = t.staff_shifts.status.completed;
|
||||
statusColor = UiColors.textSuccess;
|
||||
statusBg = UiColors.iconSuccess;
|
||||
} else if (status == 'no_show') {
|
||||
statusText = t.staff_shifts.status.no_show;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
}
|
||||
} catch (_) {
|
||||
statusText = status?.toUpperCase() ?? "";
|
||||
statusText = status?.toUpperCase() ?? "";
|
||||
}
|
||||
|
||||
final schedules = widget.shift.schedules ?? <ShiftSchedule>[];
|
||||
@@ -145,8 +145,9 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
final List<ShiftSchedule> visibleSchedules = schedules.length <= 5
|
||||
? schedules
|
||||
: schedules.take(3).toList();
|
||||
final int remainingSchedules =
|
||||
schedules.length <= 5 ? 0 : schedules.length - 3;
|
||||
final int remainingSchedules = schedules.length <= 5
|
||||
? 0
|
||||
: schedules.length - 3;
|
||||
final String scheduleRange = hasSchedules
|
||||
? () {
|
||||
final first = schedules.first.date;
|
||||
@@ -192,7 +193,9 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
children: [
|
||||
if (statusIcon != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: UiConstants.space2),
|
||||
padding: const EdgeInsets.only(
|
||||
right: UiConstants.space2,
|
||||
),
|
||||
child: Icon(
|
||||
statusIcon,
|
||||
size: UiConstants.iconXs,
|
||||
@@ -203,7 +206,9 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: const EdgeInsets.only(right: UiConstants.space2),
|
||||
margin: const EdgeInsets.only(
|
||||
right: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: statusBg,
|
||||
shape: BoxShape.circle,
|
||||
@@ -257,14 +262,18 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
border: Border.all(
|
||||
color: UiColors.primary.withValues(alpha: 0.09),
|
||||
),
|
||||
),
|
||||
child: widget.shift.logoUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
child: Image.network(
|
||||
widget.shift.logoUrl!,
|
||||
fit: BoxFit.contain,
|
||||
@@ -290,8 +299,7 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.shift.title,
|
||||
@@ -347,11 +355,13 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(
|
||||
scheduleRange,
|
||||
style: UiTypography.footnote2r.copyWith(color: UiColors.primary),
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(
|
||||
scheduleRange,
|
||||
style: UiTypography.footnote2r.copyWith(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
...visibleSchedules.map(
|
||||
(schedule) => Padding(
|
||||
@@ -368,7 +378,9 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
Text(
|
||||
'+$remainingSchedules more schedules',
|
||||
style: UiTypography.footnote2r.copyWith(
|
||||
color: UiColors.primary.withOpacity(0.7),
|
||||
color: UiColors.primary.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -410,10 +422,11 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
Text(
|
||||
'... +${widget.shift.durationDays! - 1} more days',
|
||||
style: UiTypography.footnote2r.copyWith(
|
||||
color:
|
||||
UiColors.primary.withOpacity(0.7),
|
||||
color: UiColors.primary.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
] else ...[
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'domain/usecases/apply_for_shift_usecase.dart';
|
||||
import 'domain/usecases/get_shift_details_usecase.dart';
|
||||
import 'presentation/blocs/shifts/shifts_bloc.dart';
|
||||
import 'presentation/blocs/shift_details/shift_details_bloc.dart';
|
||||
import 'presentation/utils/shift_tab_type.dart';
|
||||
import 'presentation/pages/shifts_page.dart';
|
||||
|
||||
class StaffShiftsModule extends Module {
|
||||
@@ -45,14 +46,16 @@ class StaffShiftsModule extends Module {
|
||||
i.add(GetShiftDetailsUseCase.new);
|
||||
|
||||
// Bloc
|
||||
i.add(() => ShiftsBloc(
|
||||
getMyShifts: i.get(),
|
||||
getAvailableShifts: i.get(),
|
||||
getPendingAssignments: i.get(),
|
||||
getCancelledShifts: i.get(),
|
||||
getHistoryShifts: i.get(),
|
||||
getProfileCompletion: i.get(),
|
||||
));
|
||||
i.add(
|
||||
() => ShiftsBloc(
|
||||
getMyShifts: i.get(),
|
||||
getAvailableShifts: i.get(),
|
||||
getPendingAssignments: i.get(),
|
||||
getCancelledShifts: i.get(),
|
||||
getHistoryShifts: i.get(),
|
||||
getProfileCompletion: i.get(),
|
||||
),
|
||||
);
|
||||
i.add(ShiftDetailsBloc.new);
|
||||
}
|
||||
|
||||
@@ -63,8 +66,9 @@ class StaffShiftsModule extends Module {
|
||||
child: (_) {
|
||||
final args = r.args.data as Map?;
|
||||
final queryParams = r.args.queryParams;
|
||||
final initialTabStr = queryParams['tab'] ?? args?['initialTab'];
|
||||
return ShiftsPage(
|
||||
initialTab: queryParams['tab'] ?? args?['initialTab'],
|
||||
initialTab: ShiftTabType.fromString(initialTabStr),
|
||||
selectedDate: args?['selectedDate'],
|
||||
refreshAvailable: args?['refreshAvailable'] == true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user