Add submitting state to shift approval UI

Add a submitting state to the shift approval flow: include new "submitting" strings in English and Spanish localization files; thread submittingShiftId from ShiftsPage into MyShiftsTab and HistoryShiftsTab and compute per-shift isSubmitting; add isSubmitting prop to ShiftCard and ShiftCardApprovalFooter. When a shift is being submitted the footer shows "SUBMITTING..." and a small CircularProgressIndicator instead of the submit button.
This commit is contained in:
Achintha Isuru
2026-03-18 15:26:53 -04:00
parent b9d64bd53b
commit 77f8b8511c
7 changed files with 44 additions and 6 deletions

View File

@@ -1360,7 +1360,8 @@
"timesheet_submitted": "Timesheet submitted for client approval", "timesheet_submitted": "Timesheet submitted for client approval",
"checked_in": "Checked in", "checked_in": "Checked in",
"submitted": "SUBMITTED", "submitted": "SUBMITTED",
"ready_to_submit": "READY TO SUBMIT" "ready_to_submit": "READY TO SUBMIT",
"submitting": "SUBMITTING..."
}, },
"shift_location": { "shift_location": {
"could_not_open_maps": "Could not open maps" "could_not_open_maps": "Could not open maps"

View File

@@ -1355,7 +1355,8 @@
"timesheet_submitted": "Hoja de tiempo enviada para aprobación del cliente", "timesheet_submitted": "Hoja de tiempo enviada para aprobación del cliente",
"checked_in": "Registrado", "checked_in": "Registrado",
"submitted": "ENVIADO", "submitted": "ENVIADO",
"ready_to_submit": "LISTO PARA ENVIAR" "ready_to_submit": "LISTO PARA ENVIAR",
"submitting": "ENVIANDO..."
}, },
"shift_location": { "shift_location": {
"could_not_open_maps": "No se pudo abrir mapas" "could_not_open_maps": "No se pudo abrir mapas"

View File

@@ -266,6 +266,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
cancelledShifts: cancelledShifts, cancelledShifts: cancelledShifts,
initialDate: _selectedDate, initialDate: _selectedDate,
submittedShiftIds: state.submittedShiftIds, submittedShiftIds: state.submittedShiftIds,
submittingShiftId: state.submittingShiftId,
); );
case ShiftTabType.find: case ShiftTabType.find:
if (availableLoading) { if (availableLoading) {
@@ -282,6 +283,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
return HistoryShiftsTab( return HistoryShiftsTab(
historyShifts: historyShifts, historyShifts: historyShifts,
submittedShiftIds: state.submittedShiftIds, submittedShiftIds: state.submittedShiftIds,
submittingShiftId: state.submittingShiftId,
); );
} }
} }

View File

@@ -21,6 +21,7 @@ class ShiftCard extends StatelessWidget {
this.onSubmitForApproval, this.onSubmitForApproval,
this.showApprovalAction = false, this.showApprovalAction = false,
this.isSubmitted = false, this.isSubmitted = false,
this.isSubmitting = false,
this.onAccept, this.onAccept,
this.onDecline, this.onDecline,
this.isAccepting = false, this.isAccepting = false,
@@ -41,6 +42,9 @@ class ShiftCard extends StatelessWidget {
/// Whether the timesheet has already been submitted. /// Whether the timesheet has already been submitted.
final bool isSubmitted; final bool isSubmitted;
/// Whether the timesheet submission is currently in progress.
final bool isSubmitting;
/// Callback when the accept action is pressed (pending assignments only). /// Callback when the accept action is pressed (pending assignments only).
final VoidCallback? onAccept; final VoidCallback? onAccept;
@@ -91,6 +95,7 @@ class ShiftCard extends StatelessWidget {
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
ShiftCardApprovalFooter( ShiftCardApprovalFooter(
isSubmitted: isSubmitted, isSubmitted: isSubmitted,
isSubmitting: isSubmitting,
onSubmit: onSubmitForApproval, onSubmit: onSubmitForApproval,
), ),
], ],

View File

@@ -8,12 +8,16 @@ class ShiftCardApprovalFooter extends StatelessWidget {
const ShiftCardApprovalFooter({ const ShiftCardApprovalFooter({
super.key, super.key,
required this.isSubmitted, required this.isSubmitted,
this.isSubmitting = false,
this.onSubmit, this.onSubmit,
}); });
/// Whether the timesheet has already been submitted. /// Whether the timesheet has already been submitted.
final bool isSubmitted; final bool isSubmitted;
/// Whether the submission is currently in progress.
final bool isSubmitting;
/// Callback when the submit button is pressed. /// Callback when the submit button is pressed.
final VoidCallback? onSubmit; final VoidCallback? onSubmit;
@@ -23,14 +27,25 @@ class ShiftCardApprovalFooter extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
isSubmitted isSubmitting
? context.t.staff_shifts.my_shift_card.submitting
: isSubmitted
? context.t.staff_shifts.my_shift_card.submitted ? context.t.staff_shifts.my_shift_card.submitted
: context.t.staff_shifts.my_shift_card.ready_to_submit, : context.t.staff_shifts.my_shift_card.ready_to_submit,
style: UiTypography.footnote2b.copyWith( style: UiTypography.footnote2b.copyWith(
color: isSubmitted ? UiColors.textSuccess : UiColors.textSecondary, color: isSubmitted ? UiColors.textSuccess : UiColors.textSecondary,
), ),
), ),
if (!isSubmitted) if (isSubmitting)
const SizedBox(
height: UiConstants.space4,
width: UiConstants.space4,
child: CircularProgressIndicator(
strokeWidth: 2,
color: UiColors.primary,
),
)
else if (!isSubmitted)
UiButton.secondary( UiButton.secondary(
text: context.t.staff_shifts.my_shift_card.submit_for_approval, text: context.t.staff_shifts.my_shift_card.submit_for_approval,
size: UiButtonSize.small, size: UiButtonSize.small,

View File

@@ -17,6 +17,7 @@ class HistoryShiftsTab extends StatelessWidget {
super.key, super.key,
required this.historyShifts, required this.historyShifts,
this.submittedShiftIds = const <String>{}, this.submittedShiftIds = const <String>{},
this.submittingShiftId,
}); });
/// Completed shifts. /// Completed shifts.
@@ -25,6 +26,9 @@ class HistoryShiftsTab extends StatelessWidget {
/// Set of shift IDs that have been successfully submitted for approval. /// Set of shift IDs that have been successfully submitted for approval.
final Set<String> submittedShiftIds; final Set<String> submittedShiftIds;
/// The shift ID currently being submitted (null when idle).
final String? submittingShiftId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (historyShifts.isEmpty) { if (historyShifts.isEmpty) {
@@ -44,6 +48,8 @@ class HistoryShiftsTab extends StatelessWidget {
(CompletedShift shift) { (CompletedShift shift) {
final bool isSubmitted = final bool isSubmitted =
submittedShiftIds.contains(shift.shiftId); submittedShiftIds.contains(shift.shiftId);
final bool isSubmitting =
submittingShiftId == shift.shiftId;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3), padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: ShiftCard( child: ShiftCard(
@@ -52,6 +58,7 @@ class HistoryShiftsTab extends StatelessWidget {
Modular.to.toShiftDetailsById(shift.shiftId), Modular.to.toShiftDetailsById(shift.shiftId),
showApprovalAction: !isSubmitted, showApprovalAction: !isSubmitted,
isSubmitted: isSubmitted, isSubmitted: isSubmitted,
isSubmitting: isSubmitting,
onSubmitForApproval: () { onSubmitForApproval: () {
ReadContext(context).read<ShiftsBloc>().add( ReadContext(context).read<ShiftsBloc>().add(
SubmitForApprovalEvent(shiftId: shift.shiftId), SubmitForApprovalEvent(shiftId: shift.shiftId),

View File

@@ -22,6 +22,7 @@ class MyShiftsTab extends StatefulWidget {
required this.cancelledShifts, required this.cancelledShifts,
this.initialDate, this.initialDate,
this.submittedShiftIds = const <String>{}, this.submittedShiftIds = const <String>{},
this.submittingShiftId,
}); });
/// Assigned shifts for the current week. /// Assigned shifts for the current week.
@@ -39,6 +40,9 @@ class MyShiftsTab extends StatefulWidget {
/// Set of shift IDs that have been successfully submitted for approval. /// Set of shift IDs that have been successfully submitted for approval.
final Set<String> submittedShiftIds; final Set<String> submittedShiftIds;
/// The shift ID currently being submitted (null when idle).
final String? submittingShiftId;
@override @override
State<MyShiftsTab> createState() => _MyShiftsTabState(); State<MyShiftsTab> createState() => _MyShiftsTabState();
} }
@@ -392,6 +396,8 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
shift.status == AssignmentStatus.completed; shift.status == AssignmentStatus.completed;
final bool isSubmitted = final bool isSubmitted =
widget.submittedShiftIds.contains(shift.shiftId); widget.submittedShiftIds.contains(shift.shiftId);
final bool isSubmitting =
widget.submittingShiftId == shift.shiftId;
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -403,6 +409,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
.toShiftDetailsById(shift.shiftId), .toShiftDetailsById(shift.shiftId),
showApprovalAction: isCompleted, showApprovalAction: isCompleted,
isSubmitted: isSubmitted, isSubmitted: isSubmitted,
isSubmitting: isSubmitting,
onSubmitForApproval: () { onSubmitForApproval: () {
ReadContext(context).read<ShiftsBloc>().add( ReadContext(context).read<ShiftsBloc>().add(
SubmitForApprovalEvent( SubmitForApprovalEvent(