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:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user