feat: enhance shift booking flow with date selection and navigation updates
This commit is contained in:
@@ -28,10 +28,7 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
emit(ShiftDetailsLoading());
|
||||
try {
|
||||
final shift = await getShiftDetails(
|
||||
GetShiftDetailsArguments(
|
||||
shiftId: event.shiftId,
|
||||
roleId: event.roleId,
|
||||
),
|
||||
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
|
||||
);
|
||||
if (shift != null) {
|
||||
emit(ShiftDetailsLoaded(shift));
|
||||
@@ -53,7 +50,9 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
isInstantBook: true,
|
||||
roleId: event.roleId,
|
||||
);
|
||||
emit(const ShiftActionSuccess("Shift successfully booked!"));
|
||||
emit(
|
||||
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ShiftDetailsError(e.toString()));
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ class LoadShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
class BookShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
final String shiftId;
|
||||
final String? roleId;
|
||||
const BookShiftDetailsEvent(this.shiftId, {this.roleId});
|
||||
final DateTime? date;
|
||||
const BookShiftDetailsEvent(this.shiftId, {this.roleId, this.date});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shiftId, roleId];
|
||||
List<Object?> get props => [shiftId, roleId, date];
|
||||
}
|
||||
|
||||
class DeclineShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
|
||||
@@ -30,8 +30,9 @@ class ShiftDetailsError extends ShiftDetailsState {
|
||||
|
||||
class ShiftActionSuccess extends ShiftDetailsState {
|
||||
final String message;
|
||||
const ShiftActionSuccess(this.message);
|
||||
final DateTime? shiftDate;
|
||||
const ShiftActionSuccess(this.message, {this.shiftDate});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => [message, shiftDate];
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
extension ShiftsNavigator on IModularNavigator {
|
||||
void navigateToShiftsHome() {
|
||||
navigate('/worker-main/shifts/');
|
||||
void navigateToShiftsHome({DateTime? selectedDate}) {
|
||||
navigate('/worker-main/shifts/', arguments: {'selectedDate': selectedDate});
|
||||
}
|
||||
|
||||
void pushShiftDetails(Shift shift) {
|
||||
|
||||
@@ -134,7 +134,7 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
backgroundColor: const Color(0xFF10B981),
|
||||
),
|
||||
);
|
||||
Modular.to.navigateToShiftsHome();
|
||||
Modular.to.navigateToShiftsHome(selectedDate: state.shiftDate);
|
||||
} else if (state is ShiftDetailsError) {
|
||||
if (shift == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -473,6 +473,7 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
context,
|
||||
displayShift!.id,
|
||||
displayShift!.roleId,
|
||||
DateTime.tryParse(displayShift!.date),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
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(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
@@ -517,7 +523,7 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
onPressed: () {
|
||||
BlocProvider.of<ShiftDetailsBloc>(
|
||||
context,
|
||||
).add(BookShiftDetailsEvent(id, roleId: roleId));
|
||||
).add(BookShiftDetailsEvent(id, roleId: roleId, date: date));
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF10B981),
|
||||
|
||||
@@ -11,7 +11,8 @@ import '../styles/shifts_styles.dart';
|
||||
|
||||
class ShiftsPage extends StatefulWidget {
|
||||
final String? initialTab;
|
||||
const ShiftsPage({super.key, this.initialTab});
|
||||
final DateTime? selectedDate;
|
||||
const ShiftsPage({super.key, this.initialTab, this.selectedDate});
|
||||
|
||||
@override
|
||||
State<ShiftsPage> createState() => _ShiftsPageState();
|
||||
@@ -19,12 +20,14 @@ class ShiftsPage extends StatefulWidget {
|
||||
|
||||
class _ShiftsPageState extends State<ShiftsPage> {
|
||||
late String _activeTab;
|
||||
DateTime? _selectedDate;
|
||||
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_activeTab = widget.initialTab ?? 'myshifts';
|
||||
_selectedDate = widget.selectedDate;
|
||||
_bloc.add(LoadShiftsEvent());
|
||||
}
|
||||
|
||||
@@ -36,6 +39,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
_activeTab = widget.initialTab!;
|
||||
});
|
||||
}
|
||||
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
|
||||
setState(() {
|
||||
_selectedDate = widget.selectedDate;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -103,7 +111,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
"find",
|
||||
"Find Shifts",
|
||||
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),
|
||||
_buildTab(
|
||||
@@ -130,8 +139,6 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
historyShifts,
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -153,15 +160,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
myShifts: myShifts,
|
||||
pendingAssignments: pendingAssignments,
|
||||
cancelledShifts: cancelledShifts,
|
||||
initialDate: _selectedDate,
|
||||
);
|
||||
case 'find':
|
||||
return FindShiftsTab(
|
||||
availableJobs: availableJobs,
|
||||
);
|
||||
return FindShiftsTab(availableJobs: availableJobs);
|
||||
case 'history':
|
||||
return HistoryShiftsTab(
|
||||
historyShifts: historyShifts,
|
||||
);
|
||||
return HistoryShiftsTab(historyShifts: historyShifts);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ class MyShiftsTab extends StatefulWidget {
|
||||
final List<Shift> myShifts;
|
||||
final List<Shift> pendingAssignments;
|
||||
final List<Shift> cancelledShifts;
|
||||
final DateTime? initialDate;
|
||||
|
||||
const MyShiftsTab({
|
||||
super.key,
|
||||
required this.myShifts,
|
||||
required this.pendingAssignments,
|
||||
required this.cancelledShifts,
|
||||
this.initialDate,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -30,6 +32,45 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
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() {
|
||||
final now = DateTime.now();
|
||||
int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
|
||||
@@ -50,9 +91,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Accept Shift'),
|
||||
content: const Text(
|
||||
'Are you sure you want to accept this shift?',
|
||||
),
|
||||
content: const Text('Are you sure you want to accept this shift?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
@@ -158,10 +197,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
// Calendar Selector
|
||||
Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
@@ -223,7 +259,9 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
width: 44,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.krowBlue : Colors.white,
|
||||
color: isSelected
|
||||
? AppColors.krowBlue
|
||||
: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
@@ -304,10 +342,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
],
|
||||
|
||||
if (visibleCancelledShifts.isNotEmpty) ...[
|
||||
_buildSectionHeader(
|
||||
"Cancelled Shifts",
|
||||
AppColors.krowMuted,
|
||||
),
|
||||
_buildSectionHeader("Cancelled Shifts", AppColors.krowMuted),
|
||||
...visibleCancelledShifts.map(
|
||||
(shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
@@ -329,10 +364,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
|
||||
// Confirmed Shifts
|
||||
if (visibleMyShifts.isNotEmpty) ...[
|
||||
_buildSectionHeader(
|
||||
"Confirmed Shifts",
|
||||
AppColors.krowMuted,
|
||||
),
|
||||
_buildSectionHeader("Confirmed Shifts", AppColors.krowMuted),
|
||||
...visibleMyShifts.map(
|
||||
(shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
@@ -349,8 +381,8 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
title: "No shifts this week",
|
||||
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({
|
||||
required String title,
|
||||
required String client,
|
||||
|
||||
@@ -39,6 +39,15 @@ class StaffShiftsModule extends Module {
|
||||
|
||||
@override
|
||||
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'],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user