refactor: Clean up AttendanceActionButton and AttendanceFilterBottomSheet code by removing unused comment bottom sheet function and filter button properties for improved readability and maintainability.

This commit is contained in:
Vaibhav Surve 2025-08-02 16:44:39 +05:30
parent fe66f35be7
commit 9d9afe37b8
2 changed files with 153 additions and 208 deletions

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
import 'package:marco/helpers/utils/attendance_actions.dart'; import 'package:marco/helpers/utils/attendance_actions.dart';
@ -21,76 +22,6 @@ class AttendanceActionButton extends StatefulWidget {
State<AttendanceActionButton> createState() => _AttendanceActionButtonState(); State<AttendanceActionButton> createState() => _AttendanceActionButtonState();
} }
Future<String?> _showCommentBottomSheet(
BuildContext context, String actionText) async {
final TextEditingController commentController = TextEditingController();
String? errorText;
return showModalBottomSheet<String>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return StatefulBuilder(
builder: (context, setModalState) {
void submit() {
final comment = commentController.text.trim();
if (comment.isEmpty) {
setModalState(() => errorText = 'Comment cannot be empty.');
return;
}
Navigator.of(context).pop(comment);
}
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: BaseBottomSheet(
title: 'Add Comment for ${capitalizeFirstLetter(actionText)}',
onCancel: () => Navigator.of(context).pop(),
onSubmit: submit,
isSubmitting: false,
submitText: 'Submit',
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: commentController,
maxLines: 4,
decoration: InputDecoration(
hintText: 'Type your comment here...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey.shade100,
errorText: errorText,
),
onChanged: (_) {
if (errorText != null) {
setModalState(() => errorText = null);
}
},
),
],
),
),
);
},
);
},
);
}
String capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
class _AttendanceActionButtonState extends State<AttendanceActionButton> { class _AttendanceActionButtonState extends State<AttendanceActionButton> {
late final String uniqueLogKey; late final String uniqueLogKey;
@ -110,51 +41,45 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
}); });
} }
Future<DateTime?> showTimePickerForRegularization({ Future<DateTime?> _pickRegularizationTime(DateTime checkInTime) async {
required BuildContext context,
required DateTime checkInTime,
}) async {
final pickedTime = await showTimePicker( final pickedTime = await showTimePicker(
context: context, context: context,
initialTime: TimeOfDay.fromDateTime(DateTime.now()), initialTime: TimeOfDay.fromDateTime(DateTime.now()),
); );
if (pickedTime != null) { if (pickedTime == null) return null;
final selectedDateTime = DateTime(
checkInTime.year, final selected = DateTime(
checkInTime.month, checkInTime.year,
checkInTime.day, checkInTime.month,
pickedTime.hour, checkInTime.day,
pickedTime.minute, pickedTime.hour,
pickedTime.minute,
);
final now = DateTime.now();
if (selected.isBefore(checkInTime)) {
showAppSnackbar(
title: "Invalid Time",
message: "Time must be after check-in.",
type: SnackbarType.warning,
); );
return null;
final now = DateTime.now(); } else if (selected.isAfter(now)) {
showAppSnackbar(
if (selectedDateTime.isBefore(checkInTime)) { title: "Invalid Time",
showAppSnackbar( message: "Future time is not allowed.",
title: "Invalid Time", type: SnackbarType.warning,
message: "Time must be after check-in.", );
type: SnackbarType.warning, return null;
);
return null;
} else if (selectedDateTime.isAfter(now)) {
showAppSnackbar(
title: "Invalid Time",
message: "Future time is not allowed.",
type: SnackbarType.warning,
);
return null;
}
return selectedDateTime;
} }
return null; return selected;
} }
void _handleButtonPressed(BuildContext context) async { Future<void> _handleButtonPressed() async {
widget.attendanceController.uploadingStates[uniqueLogKey]?.value = true; final controller = widget.attendanceController;
final projectController = Get.find<ProjectController>(); final projectController = Get.find<ProjectController>();
final selectedProjectId = projectController.selectedProject?.id; final selectedProjectId = projectController.selectedProject?.id;
@ -164,46 +89,43 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
message: "Please select a project first", message: "Please select a project first",
type: SnackbarType.error, type: SnackbarType.error,
); );
widget.attendanceController.uploadingStates[uniqueLogKey]?.value = false;
return; return;
} }
int updatedAction; controller.uploadingStates[uniqueLogKey]?.value = true;
int action;
String actionText; String actionText;
bool imageCapture = true; bool imageCapture = true;
switch (widget.employee.activity) { switch (widget.employee.activity) {
case 0: case 0:
updatedAction = 0; case 4:
action = 0;
actionText = ButtonActions.checkIn; actionText = ButtonActions.checkIn;
break; break;
case 1: case 1:
if (widget.employee.checkOut == null && final isOld = AttendanceButtonHelper.isOlderThanDays(widget.employee.checkIn, 2);
AttendanceButtonHelper.isOlderThanDays( final isOldCheckout = AttendanceButtonHelper.isOlderThanDays(widget.employee.checkOut, 2);
widget.employee.checkIn, 2)) {
updatedAction = 2; if (widget.employee.checkOut == null && isOld) {
action = 2;
actionText = ButtonActions.requestRegularize; actionText = ButtonActions.requestRegularize;
imageCapture = false; imageCapture = false;
} else if (widget.employee.checkOut != null && } else if (widget.employee.checkOut != null && isOldCheckout) {
AttendanceButtonHelper.isOlderThanDays( action = 2;
widget.employee.checkOut, 2)) {
updatedAction = 2;
actionText = ButtonActions.requestRegularize; actionText = ButtonActions.requestRegularize;
} else { } else {
updatedAction = 1; action = 1;
actionText = ButtonActions.checkOut; actionText = ButtonActions.checkOut;
} }
break; break;
case 2: case 2:
updatedAction = 2; action = 2;
actionText = ButtonActions.requestRegularize; actionText = ButtonActions.requestRegularize;
break; break;
case 4:
updatedAction = 0;
actionText = ButtonActions.checkIn;
break;
default: default:
updatedAction = 0; action = 0;
actionText = "Unknown Action"; actionText = "Unknown Action";
break; break;
} }
@ -219,67 +141,41 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
if (isYesterdayCheckIn && if (isYesterdayCheckIn &&
widget.employee.checkOut == null && widget.employee.checkOut == null &&
actionText == ButtonActions.checkOut) { actionText == ButtonActions.checkOut) {
selectedTime = await showTimePickerForRegularization( selectedTime = await _pickRegularizationTime(widget.employee.checkIn!);
context: context,
checkInTime: widget.employee.checkIn!,
);
if (selectedTime == null) { if (selectedTime == null) {
widget.attendanceController.uploadingStates[uniqueLogKey]?.value = controller.uploadingStates[uniqueLogKey]?.value = false;
false;
return; return;
} }
} }
final userComment = await _showCommentBottomSheet(context, actionText); final comment = await _showCommentBottomSheet(context, actionText);
if (userComment == null || userComment.isEmpty) { if (comment == null || comment.isEmpty) {
widget.attendanceController.uploadingStates[uniqueLogKey]?.value = false; controller.uploadingStates[uniqueLogKey]?.value = false;
return; return;
} }
bool success = false; bool success = false;
String? markTime;
if (actionText == ButtonActions.requestRegularize) { if (actionText == ButtonActions.requestRegularize) {
final regularizeTime = selectedTime ?? selectedTime ??= await _pickRegularizationTime(widget.employee.checkIn!);
await showTimePickerForRegularization( if (selectedTime != null) {
context: context, markTime = DateFormat("hh:mm a").format(selectedTime);
checkInTime: widget.employee.checkIn!,
);
if (regularizeTime != null) {
final formattedTime = DateFormat("hh:mm a").format(regularizeTime);
success = await widget.attendanceController.captureAndUploadAttendance(
widget.employee.id,
widget.employee.employeeId,
selectedProjectId,
comment: userComment,
action: updatedAction,
imageCapture: imageCapture,
markTime: formattedTime,
);
} }
} else if (selectedTime != null) { } else if (selectedTime != null) {
final formattedTime = DateFormat("hh:mm a").format(selectedTime); markTime = DateFormat("hh:mm a").format(selectedTime);
success = await widget.attendanceController.captureAndUploadAttendance(
widget.employee.id,
widget.employee.employeeId,
selectedProjectId,
comment: userComment,
action: updatedAction,
imageCapture: imageCapture,
markTime: formattedTime,
);
} else {
success = await widget.attendanceController.captureAndUploadAttendance(
widget.employee.id,
widget.employee.employeeId,
selectedProjectId,
comment: userComment,
action: updatedAction,
imageCapture: imageCapture,
);
} }
success = await controller.captureAndUploadAttendance(
widget.employee.id,
widget.employee.employeeId,
selectedProjectId,
comment: comment,
action: action,
imageCapture: imageCapture,
markTime: markTime,
);
showAppSnackbar( showAppSnackbar(
title: success ? '${capitalizeFirstLetter(actionText)} Success' : 'Error', title: success ? '${capitalizeFirstLetter(actionText)} Success' : 'Error',
message: success message: success
@ -288,57 +184,47 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
type: success ? SnackbarType.success : SnackbarType.error, type: success ? SnackbarType.success : SnackbarType.error,
); );
widget.attendanceController.uploadingStates[uniqueLogKey]?.value = false; controller.uploadingStates[uniqueLogKey]?.value = false;
if (success) { if (success) {
widget.attendanceController.fetchEmployeesByProject(selectedProjectId); controller.fetchEmployeesByProject(selectedProjectId);
widget.attendanceController.fetchAttendanceLogs(selectedProjectId); controller.fetchAttendanceLogs(selectedProjectId);
await widget.attendanceController.fetchRegularizationLogs(selectedProjectId); await controller.fetchRegularizationLogs(selectedProjectId);
await widget.attendanceController.fetchProjectData(selectedProjectId); await controller.fetchProjectData(selectedProjectId);
widget.attendanceController.update(); controller.update();
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Obx(() {
final isUploading = final controller = widget.attendanceController;
widget.attendanceController.uploadingStates[uniqueLogKey]?.value ??
false;
final isYesterday = AttendanceButtonHelper.isLogFromYesterday( final isUploading = controller.uploadingStates[uniqueLogKey]?.value ?? false;
widget.employee.checkIn, final emp = widget.employee;
widget.employee.checkOut,
);
final isTodayApproved = AttendanceButtonHelper.isTodayApproved( final isYesterday = AttendanceButtonHelper.isLogFromYesterday(emp.checkIn, emp.checkOut);
widget.employee.activity, final isTodayApproved = AttendanceButtonHelper.isTodayApproved(emp.activity, emp.checkIn);
widget.employee.checkIn, final isApprovedButNotToday = AttendanceButtonHelper.isApprovedButNotToday(emp.activity, isTodayApproved);
);
final isApprovedButNotToday = AttendanceButtonHelper.isApprovedButNotToday(
widget.employee.activity,
isTodayApproved,
);
final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled(
isUploading: isUploading, isUploading: isUploading,
isYesterday: isYesterday, isYesterday: isYesterday,
activity: widget.employee.activity, activity: emp.activity,
isApprovedButNotToday: isApprovedButNotToday, isApprovedButNotToday: isApprovedButNotToday,
); );
final buttonText = AttendanceButtonHelper.getButtonText( final buttonText = AttendanceButtonHelper.getButtonText(
activity: widget.employee.activity, activity: emp.activity,
checkIn: widget.employee.checkIn, checkIn: emp.checkIn,
checkOut: widget.employee.checkOut, checkOut: emp.checkOut,
isTodayApproved: isTodayApproved, isTodayApproved: isTodayApproved,
); );
final buttonColor = AttendanceButtonHelper.getButtonColor( final buttonColor = AttendanceButtonHelper.getButtonColor(
isYesterday: isYesterday, isYesterday: isYesterday,
isTodayApproved: isTodayApproved, isTodayApproved: isTodayApproved,
activity: widget.employee.activity, activity: emp.activity,
); );
return AttendanceActionButtonUI( return AttendanceActionButtonUI(
@ -346,7 +232,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
isButtonDisabled: isButtonDisabled, isButtonDisabled: isButtonDisabled,
buttonText: buttonText, buttonText: buttonText,
buttonColor: buttonColor, buttonColor: buttonColor,
onPressed: isButtonDisabled ? null : () => _handleButtonPressed(context), onPressed: isButtonDisabled ? null : _handleButtonPressed,
); );
}); });
} }
@ -391,17 +277,14 @@ class AttendanceActionButtonUI extends StatelessWidget {
: Row( : Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (buttonText.toLowerCase() == 'approved') ...[ if (buttonText.toLowerCase() == 'approved')
const Icon(Icons.check, size: 16, color: Colors.green), const Icon(Icons.check, size: 16, color: Colors.green),
const SizedBox(width: 4), if (buttonText.toLowerCase() == 'rejected')
] else if (buttonText.toLowerCase() == 'rejected') ...[
const Icon(Icons.close, size: 16, color: Colors.red), const Icon(Icons.close, size: 16, color: Colors.red),
if (buttonText.toLowerCase() == 'requested')
const Icon(Icons.hourglass_top, size: 16, color: Colors.orange),
if (['approved', 'rejected', 'requested'].contains(buttonText.toLowerCase()))
const SizedBox(width: 4), const SizedBox(width: 4),
] else if (buttonText.toLowerCase() == 'requested') ...[
const Icon(Icons.hourglass_top,
size: 16, color: Colors.orange),
const SizedBox(width: 4),
],
Flexible( Flexible(
child: Text( child: Text(
buttonText, buttonText,
@ -415,3 +298,68 @@ class AttendanceActionButtonUI extends StatelessWidget {
); );
} }
} }
Future<String?> _showCommentBottomSheet(BuildContext context, String actionText) async {
final commentController = TextEditingController();
String? errorText;
return showModalBottomSheet<String>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return StatefulBuilder(
builder: (context, setModalState) {
void submit() {
final comment = commentController.text.trim();
if (comment.isEmpty) {
setModalState(() => errorText = 'Comment cannot be empty.');
return;
}
Navigator.of(context).pop(comment);
}
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: BaseBottomSheet(
title: 'Add Comment for ${capitalizeFirstLetter(actionText)}',
onCancel: () => Navigator.of(context).pop(),
onSubmit: submit,
isSubmitting: false,
submitText: 'Submit',
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: commentController,
maxLines: 4,
decoration: InputDecoration(
hintText: 'Type your comment here...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey.shade100,
errorText: errorText,
),
onChanged: (_) {
if (errorText != null) {
setModalState(() => errorText = null);
}
},
),
],
),
),
);
},
);
},
);
}
String capitalizeFirstLetter(String text) =>
text.isEmpty ? text : text[0].toUpperCase() + text.substring(1);

View File

@ -137,9 +137,6 @@ class _AttendanceFilterBottomSheetState
onSubmit: () => Navigator.pop(context, { onSubmit: () => Navigator.pop(context, {
'selectedTab': tempSelectedTab, 'selectedTab': tempSelectedTab,
}), }),
submitText: "Apply Filter",
submitIcon: Icons.filter_alt_outlined,
submitColor: const Color.fromARGB(255, 95, 132, 255),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: buildMainFilters(), children: buildMainFilters(),