adding validations for apply

This commit is contained in:
José Salazar
2026-02-02 07:14:54 +09:00
parent 136993caec
commit 761830b380
8 changed files with 19483 additions and 18413 deletions

View File

@@ -207,6 +207,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
final List<Shift> mappedShifts = [];
for (final sr in allShiftRoles) {
final DateTime? shiftDate = _toDateTime(sr.shift.date);
final startDt = _toDateTime(sr.startTime);
final endDt = _toDateTime(sr.endTime);
final createdDt = _toDateTime(sr.createdAt);
@@ -220,7 +221,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
hourlyRate: sr.role.costPerHour,
location: sr.shift.location ?? '',
locationAddress: sr.shift.locationAddress ?? '',
date: startDt?.toIso8601String() ?? '',
date: shiftDate?.toIso8601String() ?? '',
startTime: startDt != null
? DateFormat('HH:mm').format(startDt)
: '',
@@ -371,17 +372,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
String targetRoleId = roleId ?? '';
if (targetRoleId.isEmpty) {
final rolesResult = await _dataConnect
.listShiftRolesByShiftId(shiftId: shiftId)
.execute();
if (rolesResult.data.shiftRoles.isEmpty) {
throw Exception('No open roles for this shift');
}
final sr = rolesResult.data.shiftRoles.firstWhere(
(r) => (r.assigned ?? 0) < r.count,
orElse: () => rolesResult.data.shiftRoles.first,
);
targetRoleId = sr.roleId;
throw Exception('Missing role id.');
}
final roleResult = await _dataConnect
@@ -391,6 +382,41 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
if (role == null) {
throw Exception('Shift role not found');
}
final shiftResult = await _dataConnect.getShiftById(id: shiftId).execute();
final shift = shiftResult.data.shift;
if (shift == null) {
throw Exception('Shift not found');
}
final DateTime? shiftDate = _toDateTime(shift.date);
if (shiftDate != null) {
final DateTime dayStartUtc = DateTime.utc(
shiftDate.year,
shiftDate.month,
shiftDate.day,
);
final DateTime dayEndUtc = DateTime.utc(
shiftDate.year,
shiftDate.month,
shiftDate.day,
23,
59,
59,
999,
999,
);
print(
'Staff applyForShift: dayStartUtc=${_toTimestamp(dayStartUtc).toJson()} '
'dayEndUtc=${_toTimestamp(dayEndUtc).toJson()}',
);
final dayApplications = await _dataConnect
.vaidateDayStaffApplication(staffId: staffId)
.dayStart(_toTimestamp(dayStartUtc))
.dayEnd(_toTimestamp(dayEndUtc))
.execute();
if (dayApplications.data.applications.isNotEmpty) {
throw Exception('The user already has a shift that day.');
}
}
final existingApplicationResult = await _dataConnect
.getApplicationByStaffShiftAndRole(
staffId: staffId,
@@ -406,11 +432,6 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
throw Exception('This shift is full.');
}
final shiftResult = await _dataConnect.getShiftById(id: shiftId).execute();
final shift = shiftResult.data.shift;
if (shift == null) {
throw Exception('Shift not found');
}
final int filled = shift.filled ?? 0;
String? appId;

View File

@@ -9,12 +9,20 @@ import '../blocs/shift_details/shift_details_bloc.dart';
import '../blocs/shift_details/shift_details_event.dart';
import '../blocs/shift_details/shift_details_state.dart';
class ShiftDetailsPage extends StatelessWidget {
class ShiftDetailsPage extends StatefulWidget {
final String shiftId;
final Shift? shift;
const ShiftDetailsPage({super.key, required this.shiftId, this.shift});
@override
State<ShiftDetailsPage> createState() => _ShiftDetailsPageState();
}
class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
bool _actionDialogOpen = false;
bool _isApplying = false;
String _formatTime(String time) {
if (time.isEmpty) return '';
try {
@@ -124,10 +132,19 @@ class ShiftDetailsPage extends StatelessWidget {
return BlocProvider<ShiftDetailsBloc>(
create: (_) =>
Modular.get<ShiftDetailsBloc>()
..add(LoadShiftDetailsEvent(shiftId, roleId: shift?.roleId)),
..add(
LoadShiftDetailsEvent(
widget.shiftId,
roleId: widget.shift?.roleId,
),
),
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
listener: (context, state) {
if (state is ShiftActionSuccess || state is ShiftDetailsError) {
_closeActionDialog(context);
}
if (state is ShiftActionSuccess) {
_isApplying = false;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
@@ -136,7 +153,7 @@ class ShiftDetailsPage extends StatelessWidget {
);
Modular.to.navigateToShiftsHome(selectedDate: state.shiftDate);
} else if (state is ShiftDetailsError) {
if (shift == null) {
if (_isApplying || widget.shift == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
@@ -144,6 +161,7 @@ class ShiftDetailsPage extends StatelessWidget {
),
);
}
_isApplying = false;
}
},
child: BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>(
@@ -158,7 +176,7 @@ class ShiftDetailsPage extends StatelessWidget {
if (state is ShiftDetailsLoaded) {
displayShift = state.shift;
} else {
displayShift = shift;
displayShift = widget.shift;
}
if (displayShift == null) {
@@ -474,9 +492,7 @@ class ShiftDetailsPage extends StatelessWidget {
child: ElevatedButton(
onPressed: () => _bookShift(
context,
displayShift!.id,
displayShift!.roleId,
DateTime.tryParse(displayShift!.date),
displayShift!,
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(
@@ -510,9 +526,7 @@ class ShiftDetailsPage extends StatelessWidget {
void _bookShift(
BuildContext context,
String id,
String? roleId,
DateTime? date,
Shift shift,
) {
showDialog(
context: context,
@@ -526,9 +540,15 @@ class ShiftDetailsPage extends StatelessWidget {
),
TextButton(
onPressed: () {
BlocProvider.of<ShiftDetailsBloc>(
context,
).add(BookShiftDetailsEvent(id, roleId: roleId, date: date));
Modular.to.pop();
_showApplyingDialog(context, shift);
BlocProvider.of<ShiftDetailsBloc>(context).add(
BookShiftDetailsEvent(
shift.id,
roleId: shift.roleId,
date: DateTime.tryParse(shift.date),
),
);
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF10B981),
@@ -568,4 +588,62 @@ class ShiftDetailsPage extends StatelessWidget {
),
);
}
void _showApplyingDialog(BuildContext context, Shift shift) {
if (_actionDialogOpen) return;
_actionDialogOpen = true;
_isApplying = true;
showDialog(
context: context,
useRootNavigator: true,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: const Text('Applying'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 36,
width: 36,
child: CircularProgressIndicator(),
),
const SizedBox(height: 16),
Text(
shift.title,
style: UiTypography.body2b.copyWith(
color: UiColors.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
Text(
'${_formatDate(shift.date)}${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}',
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
textAlign: TextAlign.center,
),
if (shift.clientName.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
shift.clientName,
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
textAlign: TextAlign.center,
),
],
],
),
),
).then((_) {
_actionDialogOpen = false;
});
}
void _closeActionDialog(BuildContext context) {
if (!_actionDialogOpen) return;
Navigator.of(context, rootNavigator: true).pop();
_actionDialogOpen = false;
}
}