feat: enhance shift booking flow with date selection and navigation updates

This commit is contained in:
Achintha Isuru
2026-02-01 12:35:42 -05:00
parent 5d561ff825
commit 54f28a85ce
8 changed files with 95 additions and 45 deletions

View File

@@ -28,10 +28,7 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
emit(ShiftDetailsLoading()); emit(ShiftDetailsLoading());
try { try {
final shift = await getShiftDetails( final shift = await getShiftDetails(
GetShiftDetailsArguments( GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
shiftId: event.shiftId,
roleId: event.roleId,
),
); );
if (shift != null) { if (shift != null) {
emit(ShiftDetailsLoaded(shift)); emit(ShiftDetailsLoaded(shift));
@@ -53,7 +50,9 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
isInstantBook: true, isInstantBook: true,
roleId: event.roleId, roleId: event.roleId,
); );
emit(const ShiftActionSuccess("Shift successfully booked!")); emit(
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
);
} catch (e) { } catch (e) {
emit(ShiftDetailsError(e.toString())); emit(ShiftDetailsError(e.toString()));
} }

View File

@@ -19,10 +19,11 @@ class LoadShiftDetailsEvent extends ShiftDetailsEvent {
class BookShiftDetailsEvent extends ShiftDetailsEvent { class BookShiftDetailsEvent extends ShiftDetailsEvent {
final String shiftId; final String shiftId;
final String? roleId; final String? roleId;
const BookShiftDetailsEvent(this.shiftId, {this.roleId}); final DateTime? date;
const BookShiftDetailsEvent(this.shiftId, {this.roleId, this.date});
@override @override
List<Object?> get props => [shiftId, roleId]; List<Object?> get props => [shiftId, roleId, date];
} }
class DeclineShiftDetailsEvent extends ShiftDetailsEvent { class DeclineShiftDetailsEvent extends ShiftDetailsEvent {

View File

@@ -30,8 +30,9 @@ class ShiftDetailsError extends ShiftDetailsState {
class ShiftActionSuccess extends ShiftDetailsState { class ShiftActionSuccess extends ShiftDetailsState {
final String message; final String message;
const ShiftActionSuccess(this.message); final DateTime? shiftDate;
const ShiftActionSuccess(this.message, {this.shiftDate});
@override @override
List<Object?> get props => [message]; List<Object?> get props => [message, shiftDate];
} }

View File

@@ -2,8 +2,8 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
extension ShiftsNavigator on IModularNavigator { extension ShiftsNavigator on IModularNavigator {
void navigateToShiftsHome() { void navigateToShiftsHome({DateTime? selectedDate}) {
navigate('/worker-main/shifts/'); navigate('/worker-main/shifts/', arguments: {'selectedDate': selectedDate});
} }
void pushShiftDetails(Shift shift) { void pushShiftDetails(Shift shift) {

View File

@@ -134,7 +134,7 @@ class ShiftDetailsPage extends StatelessWidget {
backgroundColor: const Color(0xFF10B981), backgroundColor: const Color(0xFF10B981),
), ),
); );
Modular.to.navigateToShiftsHome(); Modular.to.navigateToShiftsHome(selectedDate: state.shiftDate);
} else if (state is ShiftDetailsError) { } else if (state is ShiftDetailsError) {
if (shift == null) { if (shift == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -473,6 +473,7 @@ class ShiftDetailsPage extends StatelessWidget {
context, context,
displayShift!.id, displayShift!.id,
displayShift!.roleId, displayShift!.roleId,
DateTime.tryParse(displayShift!.date),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981), backgroundColor: const Color(0xFF10B981),
@@ -502,7 +503,12 @@ class ShiftDetailsPage extends StatelessWidget {
); );
} }
void _bookShift(BuildContext context, String id, String? roleId) { void _bookShift(
BuildContext context,
String id,
String? roleId,
DateTime? date,
) {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
@@ -517,7 +523,7 @@ class ShiftDetailsPage extends StatelessWidget {
onPressed: () { onPressed: () {
BlocProvider.of<ShiftDetailsBloc>( BlocProvider.of<ShiftDetailsBloc>(
context, context,
).add(BookShiftDetailsEvent(id, roleId: roleId)); ).add(BookShiftDetailsEvent(id, roleId: roleId, date: date));
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: const Color(0xFF10B981), foregroundColor: const Color(0xFF10B981),

View File

@@ -11,7 +11,8 @@ import '../styles/shifts_styles.dart';
class ShiftsPage extends StatefulWidget { class ShiftsPage extends StatefulWidget {
final String? initialTab; final String? initialTab;
const ShiftsPage({super.key, this.initialTab}); final DateTime? selectedDate;
const ShiftsPage({super.key, this.initialTab, this.selectedDate});
@override @override
State<ShiftsPage> createState() => _ShiftsPageState(); State<ShiftsPage> createState() => _ShiftsPageState();
@@ -19,12 +20,14 @@ class ShiftsPage extends StatefulWidget {
class _ShiftsPageState extends State<ShiftsPage> { class _ShiftsPageState extends State<ShiftsPage> {
late String _activeTab; late String _activeTab;
DateTime? _selectedDate;
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>(); final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_activeTab = widget.initialTab ?? 'myshifts'; _activeTab = widget.initialTab ?? 'myshifts';
_selectedDate = widget.selectedDate;
_bloc.add(LoadShiftsEvent()); _bloc.add(LoadShiftsEvent());
} }
@@ -36,6 +39,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
_activeTab = widget.initialTab!; _activeTab = widget.initialTab!;
}); });
} }
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
setState(() {
_selectedDate = widget.selectedDate;
});
}
} }
@override @override
@@ -103,7 +111,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
"find", "find",
"Find Shifts", "Find Shifts",
UiIcons.search, UiIcons.search,
availableJobs.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs. availableJobs
.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs.
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildTab( _buildTab(
@@ -130,8 +139,6 @@ class _ShiftsPageState extends State<ShiftsPage> {
historyShifts, historyShifts,
), ),
), ),
], ],
), ),
); );
@@ -153,15 +160,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
myShifts: myShifts, myShifts: myShifts,
pendingAssignments: pendingAssignments, pendingAssignments: pendingAssignments,
cancelledShifts: cancelledShifts, cancelledShifts: cancelledShifts,
initialDate: _selectedDate,
); );
case 'find': case 'find':
return FindShiftsTab( return FindShiftsTab(availableJobs: availableJobs);
availableJobs: availableJobs,
);
case 'history': case 'history':
return HistoryShiftsTab( return HistoryShiftsTab(historyShifts: historyShifts);
historyShifts: historyShifts,
);
default: default:
return const SizedBox.shrink(); return const SizedBox.shrink();
} }

View File

@@ -14,12 +14,14 @@ class MyShiftsTab extends StatefulWidget {
final List<Shift> myShifts; final List<Shift> myShifts;
final List<Shift> pendingAssignments; final List<Shift> pendingAssignments;
final List<Shift> cancelledShifts; final List<Shift> cancelledShifts;
final DateTime? initialDate;
const MyShiftsTab({ const MyShiftsTab({
super.key, super.key,
required this.myShifts, required this.myShifts,
required this.pendingAssignments, required this.pendingAssignments,
required this.cancelledShifts, required this.cancelledShifts,
this.initialDate,
}); });
@override @override
@@ -30,6 +32,45 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
DateTime _selectedDate = DateTime.now(); DateTime _selectedDate = DateTime.now();
int _weekOffset = 0; int _weekOffset = 0;
@override
void initState() {
super.initState();
if (widget.initialDate != null) {
_applyInitialDate(widget.initialDate!);
}
}
@override
void didUpdateWidget(MyShiftsTab oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialDate != null &&
widget.initialDate != oldWidget.initialDate) {
_applyInitialDate(widget.initialDate!);
}
}
void _applyInitialDate(DateTime date) {
_selectedDate = date;
final now = DateTime.now();
int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
int daysSinceFriday = (reactDayIndex + 2) % 7;
// Base Friday
final baseStart = DateTime(
now.year,
now.month,
now.day,
).subtract(Duration(days: daysSinceFriday));
final target = DateTime(date.year, date.month, date.day);
final diff = target.difference(baseStart).inDays;
setState(() {
_weekOffset = (diff / 7).floor();
});
}
List<DateTime> _getCalendarDays() { List<DateTime> _getCalendarDays() {
final now = DateTime.now(); final now = DateTime.now();
int reactDayIndex = now.weekday == 7 ? 0 : now.weekday; int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
@@ -50,9 +91,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Accept Shift'), title: const Text('Accept Shift'),
content: const Text( content: const Text('Are you sure you want to accept this shift?'),
'Are you sure you want to accept this shift?',
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
@@ -158,10 +197,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
// Calendar Selector // Calendar Selector
Container( Container(
color: Colors.white, color: Colors.white,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
vertical: 16,
horizontal: 16,
),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@@ -223,7 +259,9 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
width: 44, width: 44,
height: 60, height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.krowBlue : Colors.white, color: isSelected
? AppColors.krowBlue
: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
@@ -304,10 +342,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
], ],
if (visibleCancelledShifts.isNotEmpty) ...[ if (visibleCancelledShifts.isNotEmpty) ...[
_buildSectionHeader( _buildSectionHeader("Cancelled Shifts", AppColors.krowMuted),
"Cancelled Shifts",
AppColors.krowMuted,
),
...visibleCancelledShifts.map( ...visibleCancelledShifts.map(
(shift) => Padding( (shift) => Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 16),
@@ -329,10 +364,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
// Confirmed Shifts // Confirmed Shifts
if (visibleMyShifts.isNotEmpty) ...[ if (visibleMyShifts.isNotEmpty) ...[
_buildSectionHeader( _buildSectionHeader("Confirmed Shifts", AppColors.krowMuted),
"Confirmed Shifts",
AppColors.krowMuted,
),
...visibleMyShifts.map( ...visibleMyShifts.map(
(shift) => Padding( (shift) => Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
@@ -349,8 +381,8 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
title: "No shifts this week", title: "No shifts this week",
subtitle: "Try finding new jobs in the Find tab", subtitle: "Try finding new jobs in the Find tab",
), ),
const SizedBox(height: UiConstants.space32), const SizedBox(height: UiConstants.space32),
], ],
), ),
), ),
@@ -385,8 +417,6 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
); );
} }
Widget _buildCancelledCard({ Widget _buildCancelledCard({
required String title, required String title,
required String client, required String client,

View File

@@ -39,6 +39,15 @@ class StaffShiftsModule extends Module {
@override @override
void routes(RouteManager r) { void routes(RouteManager r) {
r.child('/', child: (_) => const ShiftsPage()); r.child(
'/',
child: (_) {
final args = r.args.data as Map?;
return ShiftsPage(
initialTab: args?['initialTab'],
selectedDate: args?['selectedDate'],
);
},
);
} }
} }