Refactor attendance button UI and improve feedback messages

This commit is contained in:
Vaibhav Surve 2025-05-13 12:06:00 +05:30
parent 5462f0aa24
commit 932cfe81e7

View File

@ -279,75 +279,86 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
.uploadingStates[employee.employeeId]?.value ?? .uploadingStates[employee.employeeId]?.value ??
false; false;
final controller = attendanceController; final controller = attendanceController;
return SizedBox( return ConstrainedBox(
width: 90, constraints: const BoxConstraints(minWidth: 100, maxWidth: 140),
height: 25, child: SizedBox(
child: ElevatedButton( height: 30,
onPressed: isUploading child: ElevatedButton(
? null onPressed: isUploading
: () async { ? null
controller.uploadingStates[employee.employeeId] = : () async {
RxBool(true); controller.uploadingStates[employee.employeeId] =
if (controller.selectedProjectId == null) { RxBool(true);
ScaffoldMessenger.of(context).showSnackBar( if (controller.selectedProjectId == null) {
const SnackBar( ScaffoldMessenger.of(context).showSnackBar(
content: Text("Please select a project first")), const SnackBar(
content:
Text("Please select a project first")),
);
controller.uploadingStates[employee.employeeId] =
RxBool(false);
return;
}
final updatedAction =
(activity == 0 || activity == 4) ? 0 : 1;
final actionText = (updatedAction == 0)
? ButtonActions.checkIn
: ButtonActions.checkOut;
final success =
await controller.captureAndUploadAttendance(
employee.id,
employee.employeeId,
controller.selectedProjectId!,
comment: actionText,
action: updatedAction,
); );
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Attendance marked successfully!'
: 'Image upload failed.'),
),
);
controller.uploadingStates[employee.employeeId] = controller.uploadingStates[employee.employeeId] =
RxBool(false); RxBool(false);
return;
} if (success) {
final updatedAction = await Future.wait([
(activity == 0 || activity == 4) ? 0 : 1; controller.fetchEmployeesByProject(
final actionText = (updatedAction == 0) controller.selectedProjectId!),
? ButtonActions.checkIn controller.fetchAttendanceLogs(
: ButtonActions.checkOut; controller.selectedProjectId!),
final success = controller.fetchProjectData(
await controller.captureAndUploadAttendance( controller.selectedProjectId!),
employee.id, ]);
employee.employeeId, controller.update();
controller.selectedProjectId!, }
comment: actionText, },
action: updatedAction, style: ElevatedButton.styleFrom(
); backgroundColor: AttendanceActionColors.colors[buttonText],
ScaffoldMessenger.of(context).showSnackBar( textStyle: const TextStyle(fontSize: 12),
SnackBar( padding: const EdgeInsets.symmetric(horizontal: 12),
content: Text(success ),
? 'Attendance marked successfully!' child: isUploading
: 'Image upload failed.'), ? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
buttonText,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
), ),
);
controller.uploadingStates[employee.employeeId] =
RxBool(false);
if (success) {
await Future.wait([
controller.fetchEmployeesByProject(
controller.selectedProjectId!),
controller.fetchAttendanceLogs(
controller.selectedProjectId!),
controller.fetchProjectData(
controller.selectedProjectId!),
]);
controller.update();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AttendanceActionColors.colors[buttonText],
textStyle: const TextStyle(fontSize: 12),
),
child: isUploading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
), ),
) ),
: Text(buttonText),
), ),
); );
}), }),
@ -645,77 +656,100 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
isApprovedButNotToday: isApprovedButNotToday, isApprovedButNotToday: isApprovedButNotToday,
); );
return SizedBox( return ConstrainedBox(
width: 90, constraints: const BoxConstraints(minWidth: 100, maxWidth: 150),
height: 25, child: SizedBox(
child: ElevatedButton( height: 30,
onPressed: isButtonDisabled child: ElevatedButton(
? null onPressed: isButtonDisabled
: () async { ? null
attendanceController.uploadingStates[uniqueLogKey] = : () async {
RxBool(true);
if (attendanceController.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
attendanceController.uploadingStates[uniqueLogKey] = attendanceController.uploadingStates[uniqueLogKey] =
RxBool(false); RxBool(true);
return;
}
int updatedAction; if (attendanceController.selectedProjectId ==
String actionText; null) {
bool imageCapture = true; ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first"),
),
);
attendanceController
.uploadingStates[uniqueLogKey] =
RxBool(false);
return;
}
switch (log.activity) { int updatedAction;
case 0: String actionText;
updatedAction = 0; bool imageCapture = true;
actionText = ButtonActions.checkIn;
break; switch (log.activity) {
case 1: case 0:
if (log.checkOut == null && updatedAction = 0;
AttendanceButtonHelper.isOlderThanDays( actionText = ButtonActions.checkIn;
log.checkIn, 2)) { break;
case 1:
if (log.checkOut == null &&
AttendanceButtonHelper.isOlderThanDays(
log.checkIn, 2)) {
updatedAction = 2;
actionText = ButtonActions.requestRegularize;
imageCapture = false;
} else if (log.checkOut != null &&
AttendanceButtonHelper.isOlderThanDays(
log.checkOut, 2)) {
updatedAction = 2;
actionText = ButtonActions.requestRegularize;
} else {
updatedAction = 1;
actionText = ButtonActions.checkOut;
}
break;
case 2:
updatedAction = 2; updatedAction = 2;
actionText = ButtonActions.requestRegularize; actionText = ButtonActions.requestRegularize;
imageCapture = false; break;
} else if (log.checkOut != null && case 4:
AttendanceButtonHelper.isOlderThanDays( updatedAction = isTodayApproved ? 0 : 0;
log.checkOut, 2)) { actionText = ButtonActions.checkIn;
updatedAction = 2; break;
actionText = ButtonActions.requestRegularize; default:
} else { updatedAction = 0;
updatedAction = 1; actionText = "Unknown Action";
actionText = ButtonActions.checkOut; break;
}
bool success = false;
if (actionText == ButtonActions.requestRegularize) {
final selectedTime =
await showTimePickerForRegularization(
context: context,
checkInTime: log.checkIn!,
);
if (selectedTime != null) {
final formattedSelectedTime =
DateFormat("hh:mm a").format(selectedTime);
success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: actionText,
action: updatedAction,
imageCapture: imageCapture,
markTime: formattedSelectedTime,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? '${actionText.toLowerCase()} marked successfully!'
: 'Failed to ${actionText.toLowerCase()}'),
),
);
} }
break; } else {
case 2:
updatedAction = 2;
actionText = ButtonActions.requestRegularize;
break;
case 4:
updatedAction = isTodayApproved ? 0 : 0;
actionText = ButtonActions.checkIn;
break;
default:
updatedAction = 0;
actionText = "Unknown Action";
break;
}
bool success = false;
if (actionText == ButtonActions.requestRegularize) {
final selectedTime =
await showTimePickerForRegularization(
context: context,
checkInTime: log.checkIn!,
);
if (selectedTime != null) {
final formattedSelectedTime =
DateFormat("hh:mm a").format(selectedTime);
success = await attendanceController success = await attendanceController
.captureAndUploadAttendance( .captureAndUploadAttendance(
log.id, log.id,
@ -724,82 +758,70 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
comment: actionText, comment: actionText,
action: updatedAction, action: updatedAction,
imageCapture: imageCapture, imageCapture: imageCapture,
markTime: formattedSelectedTime,
); );
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(success content: Text(success
? '${actionText.toLowerCase()} marked successfully!' ? '${actionText.toLowerCase()} marked successfully!'
: 'Failed to ${actionText.toLowerCase()}.'), : 'Failed to ${actionText.toLowerCase()}'),
), ),
); );
} }
} else {
success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: actionText,
action: updatedAction,
imageCapture: imageCapture,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? '${actionText.toLowerCase()} marked successfully!'
: 'Failed to ${actionText.toLowerCase()}.'),
),
);
}
attendanceController.uploadingStates[uniqueLogKey] = attendanceController.uploadingStates[uniqueLogKey] =
RxBool(false); RxBool(false);
if (success) { if (success) {
attendanceController.fetchEmployeesByProject( attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!); attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs( attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!); attendanceController.selectedProjectId!);
await attendanceController.fetchRegularizationLogs( await attendanceController
attendanceController.selectedProjectId!); .fetchRegularizationLogs(
await attendanceController.fetchProjectData( attendanceController.selectedProjectId!);
attendanceController.selectedProjectId!); await attendanceController.fetchProjectData(
attendanceController.update(); attendanceController.selectedProjectId!);
} attendanceController.update();
}, }
style: ElevatedButton.styleFrom( },
backgroundColor: AttendanceButtonHelper.getButtonColor( style: ElevatedButton.styleFrom(
isYesterday: isYesterday, backgroundColor: AttendanceButtonHelper.getButtonColor(
isTodayApproved: isTodayApproved, isYesterday: isYesterday,
activity: log.activity, isTodayApproved: isTodayApproved,
activity: log.activity,
),
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
textStyle: const TextStyle(fontSize: 12),
), ),
padding: child: isUploading
const EdgeInsets.symmetric(vertical: 4, horizontal: 6), ? const SizedBox(
textStyle: const TextStyle(fontSize: 12), width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
AttendanceButtonHelper.getButtonText(
activity: log.activity,
checkIn: log.checkIn,
checkOut: log.checkOut,
isTodayApproved: isTodayApproved,
),
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
),
), ),
child: isUploading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
AttendanceButtonHelper.getButtonText(
activity: log.activity,
checkIn: log.checkIn,
checkOut: log.checkOut,
isTodayApproved: isTodayApproved,
),
),
), ),
); );
}), }),
) ),
])); ]));
} }
}); });
@ -917,129 +939,165 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
Row( Row(
children: [ children: [
// Approve Button // Approve Button
ElevatedButton( ConstrainedBox(
onPressed: isUploading // Disable button if uploading constraints: const BoxConstraints(minWidth: 70, maxWidth: 120),
? null child: SizedBox(
: () async { height: 30,
if (attendanceController.selectedProjectId == null) { child: ElevatedButton(
ScaffoldMessenger.of(context).showSnackBar( onPressed: isUploading
const SnackBar( ? null
content: Text("Please select a project first")), : () async {
); if (attendanceController.selectedProjectId ==
return; null) {
} ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
return;
}
attendanceController.uploadingStates[uniqueLogKey] attendanceController
?.value = true; // Start loading .uploadingStates[uniqueLogKey]?.value = true;
final success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: "Accepted",
action: 4, // Approve action
imageCapture: false,
);
ScaffoldMessenger.of(context).showSnackBar( final success = await attendanceController
SnackBar( .captureAndUploadAttendance(
content: Text(success log.id,
? 'Approval marked successfully!' log.employeeId,
: 'Failed to mark approval.')), attendanceController.selectedProjectId!,
); comment: "Accepted",
action: 4,
imageCapture: false,
);
if (success) { ScaffoldMessenger.of(context).showSnackBar(
attendanceController.fetchEmployeesByProject( SnackBar(
attendanceController.selectedProjectId!); content: Text(success
attendanceController.fetchAttendanceLogs( ? 'Approval marked successfully!'
attendanceController.selectedProjectId!); : 'Failed to mark approval.'),
await attendanceController.fetchRegularizationLogs( ),
attendanceController.selectedProjectId!); );
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
}
attendanceController.uploadingStates[uniqueLogKey] if (success) {
?.value = false; // End loading attendanceController.fetchEmployeesByProject(
}, attendanceController.selectedProjectId!);
style: ElevatedButton.styleFrom( attendanceController.fetchAttendanceLogs(
backgroundColor: attendanceController.selectedProjectId!);
AttendanceActionColors.colors[ButtonActions.approve], await attendanceController
padding: .fetchRegularizationLogs(
const EdgeInsets.symmetric(vertical: 4, horizontal: 6), attendanceController.selectedProjectId!);
minimumSize: const Size(60, 20), await attendanceController.fetchProjectData(
textStyle: const TextStyle(fontSize: 12), attendanceController.selectedProjectId!);
}
attendanceController
.uploadingStates[uniqueLogKey]?.value = false;
},
style: ElevatedButton.styleFrom(
backgroundColor:
AttendanceActionColors.colors[ButtonActions.approve],
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
minimumSize: const Size(60, 20),
textStyle: const TextStyle(fontSize: 12),
),
child: isUploading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: FittedBox(
fit: BoxFit.scaleDown,
child: const Text(
"Approve",
overflow: TextOverflow.ellipsis,
),
),
),
), ),
child: isUploading
? const CircularProgressIndicator(
strokeWidth:
2) // Show loading indicator while uploading
: const Text("Approve"),
), ),
// Space between buttons
const SizedBox(width: 8), const SizedBox(width: 8),
// Reject Button // Reject Button
ElevatedButton( ConstrainedBox(
onPressed: isUploading // Disable button if uploading constraints: const BoxConstraints(minWidth: 70, maxWidth: 120),
? null child: SizedBox(
: () async { height: 30,
if (attendanceController.selectedProjectId == null) { child: ElevatedButton(
ScaffoldMessenger.of(context).showSnackBar( onPressed: isUploading
const SnackBar( ? null
content: Text("Please select a project first")), : () async {
); if (attendanceController.selectedProjectId ==
return; null) {
} ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
return;
}
attendanceController.uploadingStates[uniqueLogKey] attendanceController
?.value = true; // Start loading .uploadingStates[uniqueLogKey]?.value = true;
final success = await attendanceController final success = await attendanceController
.captureAndUploadAttendance( .captureAndUploadAttendance(
log.id, log.id,
log.employeeId, log.employeeId,
attendanceController.selectedProjectId!, attendanceController.selectedProjectId!,
comment: "Rejected", comment: "Rejected",
action: 5, // Reject action action: 5,
imageCapture: false, imageCapture: false,
); );
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(success content: Text(success
? 'Attendance marked as Rejected!' ? 'Attendance marked as Rejected!'
: 'Failed to mark attendance.')), : 'Failed to mark attendance.'),
); ),
);
if (success) { if (success) {
attendanceController.fetchEmployeesByProject( attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!); attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs( attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!); attendanceController.selectedProjectId!);
await attendanceController.fetchRegularizationLogs( await attendanceController
attendanceController.selectedProjectId!); .fetchRegularizationLogs(
await attendanceController.fetchProjectData( attendanceController.selectedProjectId!);
attendanceController.selectedProjectId!); await attendanceController.fetchProjectData(
} attendanceController.selectedProjectId!);
}
attendanceController.uploadingStates[uniqueLogKey] attendanceController
?.value = false; // End loading .uploadingStates[uniqueLogKey]?.value = false;
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor:
AttendanceActionColors.colors[ButtonActions.reject], AttendanceActionColors.colors[ButtonActions.reject],
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(vertical: 4, horizontal: 6), vertical: 4, horizontal: 6),
minimumSize: const Size(60, 20), minimumSize: const Size(60, 20),
textStyle: const TextStyle(fontSize: 12), textStyle: const TextStyle(fontSize: 12),
),
child: isUploading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: FittedBox(
fit: BoxFit.scaleDown,
child: const Text(
"Reject",
overflow: TextOverflow.ellipsis,
),
),
),
), ),
child: isUploading
? const CircularProgressIndicator(
strokeWidth:
2) // Show loading indicator while uploading
: const Text("Reject"),
), ),
], ],
), ),