refactor: migrate shifts BLoC state management to a single state class with a status enum.

This commit is contained in:
Achintha Isuru
2026-02-22 10:24:01 -05:00
parent a9ead783e4
commit b593647800
6 changed files with 325 additions and 325 deletions

View File

@@ -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);
},
);
}

View File

@@ -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,
];
}

View File

@@ -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());
}
},

View File

@@ -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';
}
}
}

View File

@@ -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 ...[

View File

@@ -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,
);