import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/controller/attendance/attendance_screen_controller.dart'; import 'package:marco/helpers/utils/attendance_actions.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; class AttendanceActionButton extends StatefulWidget { final dynamic employee; final AttendanceController attendanceController; const AttendanceActionButton({ super.key, required this.employee, required this.attendanceController, }); @override State createState() => _AttendanceActionButtonState(); } class _AttendanceActionButtonState extends State { late final String uniqueLogKey; @override void initState() { super.initState(); uniqueLogKey = AttendanceButtonHelper.getUniqueKey( widget.employee.employeeId, widget.employee.id, ); WidgetsBinding.instance.addPostFrameCallback((_) { widget.attendanceController.uploadingStates.putIfAbsent( uniqueLogKey, () => false.obs, ); }); } Future _pickRegularizationTime(DateTime checkInTime) async { final pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(DateTime.now()), ); if (pickedTime == null) return null; final selected = DateTime( checkInTime.year, checkInTime.month, checkInTime.day, 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; } if (selected.isAfter(now)) { showAppSnackbar( title: "Invalid Time", message: "Future time is not allowed.", type: SnackbarType.warning, ); return null; } return selected; } Future _handleButtonPressed() async { final controller = widget.attendanceController; final projectController = Get.find(); final selectedProjectId = projectController.selectedProject?.id; if (selectedProjectId == null) { showAppSnackbar( title: "Project Required", message: "Please select a project first", type: SnackbarType.error, ); return; } controller.uploadingStates[uniqueLogKey]?.value = true; int action; String actionText; bool imageCapture = true; switch (widget.employee.activity) { case 0: case 4: action = 0; actionText = ButtonActions.checkIn; break; case 1: final isOldCheckIn = AttendanceButtonHelper.isOlderThanDays(widget.employee.checkIn, 2); final isOldCheckOut = AttendanceButtonHelper.isOlderThanDays(widget.employee.checkOut, 2); if (widget.employee.checkOut == null && isOldCheckIn) { action = 2; actionText = ButtonActions.requestRegularize; imageCapture = false; } else if (widget.employee.checkOut != null && isOldCheckOut) { action = 2; actionText = ButtonActions.requestRegularize; } else { action = 1; actionText = ButtonActions.checkOut; } break; case 2: action = 2; actionText = ButtonActions.requestRegularize; break; default: action = 0; actionText = "Unknown Action"; break; } DateTime? selectedTime; final isYesterdayCheckIn = widget.employee.checkIn != null && DateUtils.isSameDay( widget.employee.checkIn, DateTime.now().subtract(const Duration(days: 1)), ); if (isYesterdayCheckIn && widget.employee.checkOut == null && actionText == ButtonActions.checkOut) { selectedTime = await _pickRegularizationTime(widget.employee.checkIn!); if (selectedTime == null) { controller.uploadingStates[uniqueLogKey]?.value = false; return; } } final comment = await _showCommentBottomSheet( context, actionText, selectedTime: selectedTime, checkInDate: widget.employee.checkIn, ); if (comment == null || comment.isEmpty) { controller.uploadingStates[uniqueLogKey]?.value = false; return; } String? markTime; if (actionText == ButtonActions.requestRegularize) { selectedTime ??= await _pickRegularizationTime(widget.employee.checkIn!); markTime = selectedTime != null ? DateFormat("hh:mm a").format(selectedTime) : null; } else if (selectedTime != null) { markTime = DateFormat("hh:mm a").format(selectedTime); } final success = await controller.captureAndUploadAttendance( widget.employee.id, widget.employee.employeeId, selectedProjectId, comment: comment, action: action, imageCapture: imageCapture, markTime: markTime, ); showAppSnackbar( title: success ? '${capitalizeFirstLetter(actionText)} Success' : 'Error', message: success ? '${capitalizeFirstLetter(actionText)} marked successfully!' : 'Failed to ${actionText.toLowerCase()}', type: success ? SnackbarType.success : SnackbarType.error, ); controller.uploadingStates[uniqueLogKey]?.value = false; if (success) { await controller.fetchTodaysAttendance(selectedProjectId); await controller.fetchAttendanceLogs(selectedProjectId); await controller.fetchRegularizationLogs(selectedProjectId); await controller.fetchProjectData(selectedProjectId); controller.update(); } } @override Widget build(BuildContext context) { return Obx(() { final controller = widget.attendanceController; final isUploading = controller.uploadingStates[uniqueLogKey]?.value ?? false; final emp = widget.employee; final isYesterday = AttendanceButtonHelper.isLogFromYesterday(emp.checkIn, emp.checkOut); final isTodayApproved = AttendanceButtonHelper.isTodayApproved(emp.activity, emp.checkIn); final isApprovedButNotToday = AttendanceButtonHelper.isApprovedButNotToday(emp.activity, isTodayApproved); final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( isUploading: isUploading, isYesterday: isYesterday, activity: emp.activity, isApprovedButNotToday: isApprovedButNotToday, ); final buttonText = AttendanceButtonHelper.getButtonText( activity: emp.activity, checkIn: emp.checkIn, checkOut: emp.checkOut, isTodayApproved: isTodayApproved, ); final buttonColor = AttendanceButtonHelper.getButtonColor( isYesterday: isYesterday, isTodayApproved: isTodayApproved, activity: emp.activity, ); return AttendanceActionButtonUI( isUploading: isUploading, isButtonDisabled: isButtonDisabled, buttonText: buttonText, buttonColor: buttonColor, onPressed: isButtonDisabled ? null : _handleButtonPressed, ); }); } } class AttendanceActionButtonUI extends StatelessWidget { final bool isUploading; final bool isButtonDisabled; final String buttonText; final Color buttonColor; final VoidCallback? onPressed; const AttendanceActionButtonUI({ super.key, required this.isUploading, required this.isButtonDisabled, required this.buttonText, required this.buttonColor, required this.onPressed, }); @override Widget build(BuildContext context) { return SizedBox( height: 30, child: ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( backgroundColor: buttonColor, padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), textStyle: const TextStyle(fontSize: 12), ), child: isUploading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Row( mainAxisSize: MainAxisSize.min, children: [ if (buttonText.toLowerCase() == 'approved') const Icon(Icons.check, size: 16, color: Colors.green), if (buttonText.toLowerCase() == 'rejected') 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), Flexible( child: Text( buttonText, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12), ), ), ], ), ), ); } } Future _showCommentBottomSheet( BuildContext context, String actionText, { DateTime? selectedTime, DateTime? checkInDate, }) async { final commentController = TextEditingController(); String? errorText; // Prepare title String sheetTitle = "Add Comment for ${capitalizeFirstLetter(actionText)}"; if (selectedTime != null && checkInDate != null) { sheetTitle = "${capitalizeFirstLetter(actionText)} for ${DateFormat('dd MMM yyyy').format(checkInDate)} at ${DateFormat('hh:mm a').format(selectedTime)}"; } return showModalBottomSheet( 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: sheetTitle, // 👈 now showing full sentence as title onCancel: () => Navigator.of(context).pop(), onSubmit: submit, isSubmitting: false, submitText: 'Submit', child: 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);