Merge pull request 'Refactor attendance button UI and improve feedback messages' (#21) from Vaibhav_Bug-#220 into main

Reviewed-on: #21
This commit is contained in:
vaibhav.surve 2025-05-13 06:36:36 +00:00
commit 42935471cf

View File

@ -279,75 +279,86 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
.uploadingStates[employee.employeeId]?.value ??
false;
final controller = attendanceController;
return SizedBox(
width: 90,
height: 25,
child: ElevatedButton(
onPressed: isUploading
? null
: () async {
controller.uploadingStates[employee.employeeId] =
RxBool(true);
if (controller.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a project first")),
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100, maxWidth: 140),
child: SizedBox(
height: 30,
child: ElevatedButton(
onPressed: isUploading
? null
: () async {
controller.uploadingStates[employee.employeeId] =
RxBool(true);
if (controller.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
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] =
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.'),
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),
padding: const EdgeInsets.symmetric(horizontal: 12),
),
child: isUploading
? 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,
);
return SizedBox(
width: 90,
height: 25,
child: ElevatedButton(
onPressed: isButtonDisabled
? null
: () async {
attendanceController.uploadingStates[uniqueLogKey] =
RxBool(true);
if (attendanceController.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100, maxWidth: 150),
child: SizedBox(
height: 30,
child: ElevatedButton(
onPressed: isButtonDisabled
? null
: () async {
attendanceController.uploadingStates[uniqueLogKey] =
RxBool(false);
return;
}
RxBool(true);
int updatedAction;
String actionText;
bool imageCapture = true;
if (attendanceController.selectedProjectId ==
null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first"),
),
);
attendanceController
.uploadingStates[uniqueLogKey] =
RxBool(false);
return;
}
switch (log.activity) {
case 0:
updatedAction = 0;
actionText = ButtonActions.checkIn;
break;
case 1:
if (log.checkOut == null &&
AttendanceButtonHelper.isOlderThanDays(
log.checkIn, 2)) {
int updatedAction;
String actionText;
bool imageCapture = true;
switch (log.activity) {
case 0:
updatedAction = 0;
actionText = ButtonActions.checkIn;
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;
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 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
.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;
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);
} else {
success = await attendanceController
.captureAndUploadAttendance(
log.id,
@ -724,82 +758,70 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
comment: actionText,
action: updatedAction,
imageCapture: imageCapture,
markTime: formattedSelectedTime,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? '${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] =
RxBool(false);
attendanceController.uploadingStates[uniqueLogKey] =
RxBool(false);
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
attendanceController.update();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AttendanceButtonHelper.getButtonColor(
isYesterday: isYesterday,
isTodayApproved: isTodayApproved,
activity: log.activity,
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController
.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
attendanceController.update();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AttendanceButtonHelper.getButtonColor(
isYesterday: isYesterday,
isTodayApproved: isTodayApproved,
activity: log.activity,
),
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
textStyle: const TextStyle(fontSize: 12),
),
padding:
const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
textStyle: const TextStyle(fontSize: 12),
child: isUploading
? const SizedBox(
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(
children: [
// Approve Button
ElevatedButton(
onPressed: isUploading // Disable button if uploading
? null
: () async {
if (attendanceController.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a project first")),
);
return;
}
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 70, maxWidth: 120),
child: SizedBox(
height: 30,
child: ElevatedButton(
onPressed: isUploading
? null
: () async {
if (attendanceController.selectedProjectId ==
null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
return;
}
attendanceController.uploadingStates[uniqueLogKey]
?.value = true; // Start loading
final success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: "Accepted",
action: 4, // Approve action
imageCapture: false,
);
attendanceController
.uploadingStates[uniqueLogKey]?.value = true;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Approval marked successfully!'
: 'Failed to mark approval.')),
);
final success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: "Accepted",
action: 4,
imageCapture: false,
);
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Approval marked successfully!'
: 'Failed to mark approval.'),
),
);
attendanceController.uploadingStates[uniqueLogKey]
?.value = false; // End loading
},
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),
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController
.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
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),
// Reject Button
ElevatedButton(
onPressed: isUploading // Disable button if uploading
? null
: () async {
if (attendanceController.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a project first")),
);
return;
}
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 70, maxWidth: 120),
child: SizedBox(
height: 30,
child: ElevatedButton(
onPressed: isUploading
? null
: () async {
if (attendanceController.selectedProjectId ==
null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Please select a project first")),
);
return;
}
attendanceController.uploadingStates[uniqueLogKey]
?.value = true; // Start loading
attendanceController
.uploadingStates[uniqueLogKey]?.value = true;
final success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: "Rejected",
action: 5, // Reject action
imageCapture: false,
);
final success = await attendanceController
.captureAndUploadAttendance(
log.id,
log.employeeId,
attendanceController.selectedProjectId!,
comment: "Rejected",
action: 5,
imageCapture: false,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Attendance marked as Rejected!'
: 'Failed to mark attendance.')),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Attendance marked as Rejected!'
: 'Failed to mark attendance.'),
),
);
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
}
if (success) {
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
await attendanceController
.fetchRegularizationLogs(
attendanceController.selectedProjectId!);
await attendanceController.fetchProjectData(
attendanceController.selectedProjectId!);
}
attendanceController.uploadingStates[uniqueLogKey]
?.value = false; // End loading
},
style: ElevatedButton.styleFrom(
backgroundColor:
AttendanceActionColors.colors[ButtonActions.reject],
padding:
const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
minimumSize: const Size(60, 20),
textStyle: const TextStyle(fontSize: 12),
attendanceController
.uploadingStates[uniqueLogKey]?.value = false;
},
style: ElevatedButton.styleFrom(
backgroundColor:
AttendanceActionColors.colors[ButtonActions.reject],
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(
"Reject",
overflow: TextOverflow.ellipsis,
),
),
),
),
child: isUploading
? const CircularProgressIndicator(
strokeWidth:
2) // Show loading indicator while uploading
: const Text("Reject"),
),
],
),