From 8408b67aa06bcdf02b5fec59788ddc87d8ecc028 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 21 May 2025 17:57:09 +0530 Subject: [PATCH] Refactor code structure for improved readability and maintainability, Changed the Attendance Screen UI --- .../attendance/attendence_action_button.dart | 293 ++ lib/model/attendance/info_card.dart | 158 ++ .../attendance/regualrize_action_button.dart | 126 + lib/model/attendance_log_model.dart | 9 + lib/model/attendance_log_view_model.dart | 12 +- lib/model/employee_info.dart | 2 +- lib/model/employee_model.dart | 30 +- lib/model/regularization_log_model.dart | 6 + lib/routes.dart | 5 +- .../Attendence/attendance_screen.dart | 748 +++++ .../Attendence/attendence_log_screen.dart | 227 ++ lib/view/dashboard/attendanceScreen.dart | 2443 +++++++++-------- 12 files changed, 2873 insertions(+), 1186 deletions(-) create mode 100644 lib/model/attendance/attendence_action_button.dart create mode 100644 lib/model/attendance/info_card.dart create mode 100644 lib/model/attendance/regualrize_action_button.dart create mode 100644 lib/view/dashboard/Attendence/attendance_screen.dart create mode 100644 lib/view/dashboard/Attendence/attendence_log_screen.dart diff --git a/lib/model/attendance/attendence_action_button.dart b/lib/model/attendance/attendence_action_button.dart new file mode 100644 index 0000000..9816237 --- /dev/null +++ b/lib/model/attendance/attendence_action_button.dart @@ -0,0 +1,293 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; + +import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; +import 'package:marco/helpers/utils/attendance_actions.dart'; + +class AttendanceActionButton extends StatefulWidget { + final dynamic employee; + final AttendanceController attendanceController; + + const AttendanceActionButton({ + Key? key, + required this.employee, + required this.attendanceController, + }) : super(key: key); + + @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); + + // Defer the Rx initialization after first frame to avoid setState during build + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!widget.attendanceController.uploadingStates + .containsKey(uniqueLogKey)) { + widget.attendanceController.uploadingStates[uniqueLogKey] = false.obs; + } + }); + } + + Future showTimePickerForRegularization({ + required BuildContext context, + required DateTime checkInTime, + }) async { + final pickedTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(DateTime.now()), + ); + + if (pickedTime != null) { + final selectedDateTime = DateTime( + checkInTime.year, + checkInTime.month, + checkInTime.day, + pickedTime.hour, + pickedTime.minute, + ); + + if (selectedDateTime.isAfter(checkInTime)) { + return selectedDateTime; + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select a time after check-in time.")), + ); + return null; + } + } + return null; + } + + void _handleButtonPressed(BuildContext context) async { + // Set uploading state true safely + widget.attendanceController.uploadingStates[uniqueLogKey]?.value = true; + + if (widget.attendanceController.selectedProjectId == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select a project first"), + ), + ); + widget.attendanceController.uploadingStates[uniqueLogKey]?.value = false; + return; + } + + int updatedAction; + String actionText; + bool imageCapture = true; + + switch (widget.employee.activity) { + case 0: + updatedAction = 0; + actionText = ButtonActions.checkIn; + break; + case 1: + if (widget.employee.checkOut == null && + AttendanceButtonHelper.isOlderThanDays( + widget.employee.checkIn, 2)) { + updatedAction = 2; + actionText = ButtonActions.requestRegularize; + imageCapture = false; + } else if (widget.employee.checkOut != null && + AttendanceButtonHelper.isOlderThanDays( + widget.employee.checkOut, 2)) { + updatedAction = 2; + actionText = ButtonActions.requestRegularize; + } else { + updatedAction = 1; + actionText = ButtonActions.checkOut; + } + break; + case 2: + updatedAction = 2; + actionText = ButtonActions.requestRegularize; + break; + case 4: + updatedAction = 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: widget.employee.checkIn!, + ); + if (selectedTime != null) { + final formattedSelectedTime = + DateFormat("hh:mm a").format(selectedTime); + success = await widget.attendanceController.captureAndUploadAttendance( + widget.employee.id, + widget.employee.employeeId, + widget.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()}'), + ), + ); + } + } else { + success = await widget.attendanceController.captureAndUploadAttendance( + widget.employee.id, + widget.employee.employeeId, + widget.attendanceController.selectedProjectId!, + comment: actionText, + action: updatedAction, + imageCapture: imageCapture, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(success + ? '${actionText.toLowerCase()} marked successfully!' + : 'Failed to ${actionText.toLowerCase()}'), + ), + ); + } + + widget.attendanceController.uploadingStates[uniqueLogKey]?.value = false; + + if (success) { + widget.attendanceController.fetchEmployeesByProject( + widget.attendanceController.selectedProjectId!); + widget.attendanceController + .fetchAttendanceLogs(widget.attendanceController.selectedProjectId!); + await widget.attendanceController.fetchRegularizationLogs( + widget.attendanceController.selectedProjectId!); + await widget.attendanceController + .fetchProjectData(widget.attendanceController.selectedProjectId!); + widget.attendanceController.update(); + } + } + + @override + Widget build(BuildContext context) { + return Obx(() { + final isUploading = + widget.attendanceController.uploadingStates[uniqueLogKey]?.value ?? + false; + + final isYesterday = AttendanceButtonHelper.isLogFromYesterday( + widget.employee.checkIn, widget.employee.checkOut); + final isTodayApproved = AttendanceButtonHelper.isTodayApproved( + widget.employee.activity, widget.employee.checkIn); + final isApprovedButNotToday = + AttendanceButtonHelper.isApprovedButNotToday( + widget.employee.activity, isTodayApproved); + + final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( + isUploading: isUploading, + isYesterday: isYesterday, + activity: widget.employee.activity, + isApprovedButNotToday: isApprovedButNotToday, + ); + + final buttonText = AttendanceButtonHelper.getButtonText( + activity: widget.employee.activity, + checkIn: widget.employee.checkIn, + checkOut: widget.employee.checkOut, + isTodayApproved: isTodayApproved, + ); + + final buttonColor = AttendanceButtonHelper.getButtonColor( + isYesterday: isYesterday, + isTodayApproved: isTodayApproved, + activity: widget.employee.activity, + ); + + return AttendanceActionButtonUI( + isUploading: isUploading, + isButtonDisabled: isButtonDisabled, + buttonText: buttonText, + buttonColor: buttonColor, + onPressed: + isButtonDisabled ? null : () => _handleButtonPressed(context), + ); + }); + } +} + +class AttendanceActionButtonUI extends StatelessWidget { + final bool isUploading; + final bool isButtonDisabled; + final String buttonText; + final Color buttonColor; + final VoidCallback? onPressed; + + const AttendanceActionButtonUI({ + Key? key, + required this.isUploading, + required this.isButtonDisabled, + required this.buttonText, + required this.buttonColor, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 30, + child: ElevatedButton( + onPressed: isButtonDisabled ? null : 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), + const SizedBox(width: 4), + ] else if (buttonText.toLowerCase() == 'rejected') ...[ + const Icon(Icons.close, size: 16, color: Colors.red), + const SizedBox(width: 4), + ] else if (buttonText.toLowerCase() == 'requested') ...[ + const Icon(Icons.hourglass_top, + size: 16, color: Colors.orange), + const SizedBox(width: 4), + ], + Flexible( + child: Text( + buttonText, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/model/attendance/info_card.dart b/lib/model/attendance/info_card.dart new file mode 100644 index 0000000..b1cf4ac --- /dev/null +++ b/lib/model/attendance/info_card.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; + +typedef ItemWidgetBuilder = Widget Function(BuildContext context, T item, int index); +typedef ButtonActionCallback = Future Function(T item); + +class ReusableListCard extends StatelessWidget { + final List items; + final Widget emptyWidget; + final ItemWidgetBuilder itemBuilder; + final ButtonActionCallback onActionPressed; + final ButtonActionCallback onViewPressed; + final String Function(T item) getPrimaryText; + final String Function(T item) getSecondaryText; + final String Function(T item)? getInTimeText; + final String Function(T item)? getOutTimeText; + final bool Function(T item)? isUploading; + final String Function(T item)? getButtonText; + final Color Function(String buttonText)? getButtonColor; + + const ReusableListCard({ + Key? key, + required this.items, + required this.emptyWidget, + required this.itemBuilder, + required this.onActionPressed, + required this.onViewPressed, + required this.getPrimaryText, + required this.getSecondaryText, + this.getInTimeText, + this.getOutTimeText, + this.isUploading, + this.getButtonText, + this.getButtonColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + elevation: 1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: items.isEmpty + ? emptyWidget + : Column( + children: List.generate(items.length, (index) { + final item = items[index]; + final buttonText = getButtonText?.call(item) ?? 'Action'; + final uploading = isUploading?.call(item) ?? false; + final buttonColor = + getButtonColor?.call(buttonText) ?? Theme.of(context).primaryColor; + + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Theme.of(context).cardColor, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Custom item widget builder for avatar or image + itemBuilder(context, item, index), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + getPrimaryText(item), + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + getSecondaryText(item), + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + if (getInTimeText != null && getOutTimeText != null) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + const Text("In: ", style: TextStyle(fontWeight: FontWeight.w600)), + Text(getInTimeText!(item), style: const TextStyle(fontWeight: FontWeight.w600)), + const SizedBox(width: 16), + const Text("Out: ", style: TextStyle(fontWeight: FontWeight.w600)), + Text(getOutTimeText!(item), style: const TextStyle(fontWeight: FontWeight.w600)), + ], + ), + ), + const SizedBox(height: 12), + Row( + children: [ + SizedBox( + height: 30, + child: ElevatedButton( + onPressed: () => onViewPressed(item), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueGrey, + padding: const EdgeInsets.symmetric(horizontal: 12), + textStyle: const TextStyle(fontSize: 12), + ), + child: const Text('View', style: TextStyle(fontSize: 12)), + ), + ), + const SizedBox(width: 8), + SizedBox( + height: 30, + child: ElevatedButton( + onPressed: uploading + ? null + : () => onActionPressed(item), + style: ElevatedButton.styleFrom( + backgroundColor: buttonColor, + padding: const EdgeInsets.symmetric(horizontal: 12), + textStyle: const TextStyle(fontSize: 12), + ), + child: uploading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Text(buttonText, style: const TextStyle(fontSize: 12)), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + if (index != items.length - 1) + Divider(color: Colors.grey.withOpacity(0.3), thickness: 1, height: 1), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/model/attendance/regualrize_action_button.dart b/lib/model/attendance/regualrize_action_button.dart new file mode 100644 index 0000000..b77b605 --- /dev/null +++ b/lib/model/attendance/regualrize_action_button.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:marco/helpers/utils/attendance_actions.dart'; + +enum ButtonActions { approve, reject } + +class RegularizeActionButton extends StatefulWidget { + final dynamic attendanceController; // Replace dynamic with your controller's type + final dynamic log; // Replace dynamic with your log model type + final String uniqueLogKey; + final ButtonActions action; + + const RegularizeActionButton({ + Key? key, + required this.attendanceController, + required this.log, + required this.uniqueLogKey, + required this.action, + }) : super(key: key); + + @override + State createState() => _RegularizeActionButtonState(); +} + +class _RegularizeActionButtonState extends State { + bool isUploading = false; + + static const Map _buttonTexts = { + ButtonActions.approve: "Approve", + ButtonActions.reject: "Reject", + }; + + static const Map _buttonComments = { + ButtonActions.approve: "Accepted", + ButtonActions.reject: "Rejected", + }; + + static const Map _buttonActionCodes = { + ButtonActions.approve: 4, + ButtonActions.reject: 5, + }; + + Color get backgroundColor { + // Use string keys for correct color lookup + return AttendanceActionColors.colors[_buttonTexts[widget.action]!] ?? Colors.grey; + } + + Future _handlePress() async { + if (widget.attendanceController.selectedProjectId == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Please select a project first")), + ); + return; + } + + setState(() { + isUploading = true; + }); + + widget.attendanceController.uploadingStates[widget.uniqueLogKey]?.value = true; + + final success = await widget.attendanceController.captureAndUploadAttendance( + widget.log.id, + widget.log.employeeId, + widget.attendanceController.selectedProjectId!, + comment: _buttonComments[widget.action]!, + action: _buttonActionCodes[widget.action]!, + imageCapture: false, + ); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(success + ? '${_buttonTexts[widget.action]} marked successfully!' + : 'Failed to mark ${_buttonTexts[widget.action]}.'), + ), + ); + + if (success) { + widget.attendanceController.fetchEmployeesByProject(widget.attendanceController.selectedProjectId!); + widget.attendanceController.fetchAttendanceLogs(widget.attendanceController.selectedProjectId!); + await widget.attendanceController.fetchRegularizationLogs(widget.attendanceController.selectedProjectId!); + await widget.attendanceController.fetchProjectData(widget.attendanceController.selectedProjectId!); + } + + widget.attendanceController.uploadingStates[widget.uniqueLogKey]?.value = false; + + setState(() { + isUploading = false; + }); + } + + @override + Widget build(BuildContext context) { + final buttonText = _buttonTexts[widget.action]!; + + return ConstrainedBox( + constraints: const BoxConstraints(minWidth: 70, maxWidth: 120), + child: SizedBox( + height: 30, + child: ElevatedButton( + onPressed: isUploading ? null : _handlePress, + style: ElevatedButton.styleFrom( + backgroundColor: backgroundColor, + foregroundColor: Colors.white, // Ensures visibility on all backgrounds + 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: Text( + buttonText, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ); + } +} diff --git a/lib/model/attendance_log_model.dart b/lib/model/attendance_log_model.dart index c4889e4..d595657 100644 --- a/lib/model/attendance_log_model.dart +++ b/lib/model/attendance_log_model.dart @@ -6,6 +6,9 @@ class AttendanceLogModel { final DateTime? checkIn; final DateTime? checkOut; final int activity; + final String firstName; + final String lastName; + final String designation; AttendanceLogModel({ required this.id, @@ -15,6 +18,9 @@ class AttendanceLogModel { this.checkIn, this.checkOut, required this.activity, + required this.firstName, + required this.lastName, + required this.designation, }); factory AttendanceLogModel.fromJson(Map json) { @@ -30,6 +36,9 @@ class AttendanceLogModel { ? DateTime.tryParse(json['checkOutTime']) : null, activity: json['activity'] ?? 0, + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', + designation: json['jobRoleName'] ?? '', ); } diff --git a/lib/model/attendance_log_view_model.dart b/lib/model/attendance_log_view_model.dart index 6db06d6..1c8ebc1 100644 --- a/lib/model/attendance_log_view_model.dart +++ b/lib/model/attendance_log_view_model.dart @@ -1,4 +1,5 @@ import 'package:intl/intl.dart'; + class AttendanceLogViewModel { final DateTime? activityTime; final String? imageUrl; @@ -7,6 +8,7 @@ class AttendanceLogViewModel { final String? preSignedUrl; final String? longitude; final String? latitude; + final int? activity; AttendanceLogViewModel({ this.activityTime, @@ -16,6 +18,7 @@ class AttendanceLogViewModel { this.preSignedUrl, this.longitude, this.latitude, + required this.activity, }); factory AttendanceLogViewModel.fromJson(Map json) { @@ -29,6 +32,7 @@ class AttendanceLogViewModel { preSignedUrl: json['preSignedUrl']?.toString(), longitude: json['longitude']?.toString(), latitude: json['latitude']?.toString(), + activity: json['activity'] ?? 0, ); } @@ -41,12 +45,14 @@ class AttendanceLogViewModel { 'preSignedUrl': preSignedUrl, 'longitude': longitude, 'latitude': latitude, + 'activity': activity, }; } - String? get formattedDate => - activityTime != null ? DateFormat('yyyy-MM-dd').format(activityTime!) : null; + String? get formattedDate => activityTime != null + ? DateFormat('yyyy-MM-dd').format(activityTime!) + : null; String? get formattedTime => activityTime != null ? DateFormat('hh:mm a').format(activityTime!) : null; -} \ No newline at end of file +} diff --git a/lib/model/employee_info.dart b/lib/model/employee_info.dart index 8ea355c..87505a9 100644 --- a/lib/model/employee_info.dart +++ b/lib/model/employee_info.dart @@ -4,7 +4,7 @@ class EmployeeInfo { final String lastName; final String gender; final String birthDate; - final String joiningDate; + final String joiningDate; final String currentAddress; final String phoneNumber; final String emergencyPhoneNumber; diff --git a/lib/model/employee_model.dart b/lib/model/employee_model.dart index 4a9dc19..156ffaf 100644 --- a/lib/model/employee_model.dart +++ b/lib/model/employee_model.dart @@ -3,8 +3,10 @@ class EmployeeModel { final String employeeId; final String name; final String designation; - final String checkIn; - final String checkOut; + final DateTime? checkIn; + final DateTime? checkOut; + final String firstName; + final String lastName; final int activity; int action; final String jobRole; @@ -16,8 +18,10 @@ class EmployeeModel { required this.employeeId, required this.name, required this.designation, - required this.checkIn, - required this.checkOut, + this.checkIn, + this.checkOut, + required this.firstName, + required this.lastName, required this.activity, required this.action, required this.jobRole, @@ -31,13 +35,19 @@ class EmployeeModel { employeeId: json['employeeId']?.toString() ?? '', name: '${json['firstName'] ?? ''} ${json['lastName'] ?? ''}'.trim(), designation: json['jobRoleName'] ?? '', - checkIn: json['checkIn']?.toString() ?? '-', - checkOut: json['checkOut']?.toString() ?? '-', + checkIn: json['checkInTime'] != null + ? DateTime.tryParse(json['checkInTime']) + : null, + checkOut: json['checkOutTime'] != null + ? DateTime.tryParse(json['checkOutTime']) + : null, action: json['action'] ?? 0, activity: json['activity'] ?? 0, jobRole: json['jobRole']?.toString() ?? '-', - email: json['email']?.toString() ?? '-', - phoneNumber: json['phoneNumber']?.toString() ?? '-', + email: json['email']?.toString() ?? '-', + phoneNumber: json['phoneNumber']?.toString() ?? '-', + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', ); } @@ -48,8 +58,8 @@ class EmployeeModel { 'firstName': name.split(' ').first, 'lastName': name.split(' ').length > 1 ? name.split(' ').last : '', 'jobRoleName': designation, - 'checkIn': checkIn, - 'checkOut': checkOut, + 'checkInTime': checkIn?.toIso8601String(), + 'checkOutTime': checkOut?.toIso8601String(), 'action': action, 'activity': activity, 'jobRole': jobRole.isEmpty ? '-' : jobRole, diff --git a/lib/model/regularization_log_model.dart b/lib/model/regularization_log_model.dart index 95c8650..68a0ab1 100644 --- a/lib/model/regularization_log_model.dart +++ b/lib/model/regularization_log_model.dart @@ -6,6 +6,8 @@ class RegularizationLogModel { final DateTime? checkIn; final DateTime? checkOut; final int activity; + final String firstName; + final String lastName; RegularizationLogModel({ required this.id, @@ -15,6 +17,8 @@ class RegularizationLogModel { this.checkIn, this.checkOut, required this.activity, + required this.firstName, + required this.lastName, }); factory RegularizationLogModel.fromJson(Map json) { @@ -30,6 +34,8 @@ class RegularizationLogModel { ? DateTime.tryParse(json['checkOutTime'].toString()) : null, activity: json['activity'] ?? 0, + firstName: json['firstName'] ?? '', + lastName: json['lastName'] ?? '', ); } diff --git a/lib/routes.dart b/lib/routes.dart index ce59f67..c5a5264 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -10,13 +10,14 @@ import 'package:marco/view/error_pages/coming_soon_screen.dart'; import 'package:marco/view/error_pages/error_404_screen.dart'; import 'package:marco/view/error_pages/error_500_screen.dart'; // import 'package:marco/view/dashboard/attendance_screen.dart'; -import 'package:marco/view/dashboard/attendanceScreen.dart'; +// import 'package:marco/view/dashboard/attendanceScreen.dart'; import 'package:marco/view/dashboard/dashboard_screen.dart'; import 'package:marco/view/dashboard/add_employee_screen.dart'; import 'package:marco/view/dashboard/employee_screen.dart'; import 'package:marco/view/dashboard/daily_task_screen.dart'; import 'package:marco/view/taskPlaning/report_task_screen.dart'; import 'package:marco/view/taskPlaning/comment_task_screen.dart'; +import 'package:marco/view/dashboard/Attendence/attendance_screen.dart'; class AuthMiddleware extends GetMiddleware { @override @@ -29,7 +30,7 @@ getPageRoute() { var routes = [ GetPage( name: '/', - page: () => const AttendanceScreen(), + page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]), // Dashboard diff --git a/lib/view/dashboard/Attendence/attendance_screen.dart b/lib/view/dashboard/Attendence/attendance_screen.dart new file mode 100644 index 0000000..6e22113 --- /dev/null +++ b/lib/view/dashboard/Attendence/attendance_screen.dart @@ -0,0 +1,748 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:marco/helpers/theme/app_theme.dart'; +import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; +import 'package:marco/helpers/utils/my_shadow.dart'; +import 'package:marco/helpers/widgets/my_breadcrumb.dart'; +import 'package:marco/helpers/widgets/my_breadcrumb_item.dart'; +import 'package:marco/helpers/widgets/my_card.dart'; +import 'package:marco/helpers/widgets/my_container.dart'; +import 'package:marco/helpers/widgets/my_flex.dart'; +import 'package:marco/helpers/widgets/my_flex_item.dart'; +import 'package:marco/helpers/widgets/my_spacing.dart'; +import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/view/layouts/layout.dart'; +import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; +import 'package:marco/controller/permission_controller.dart'; +import 'package:intl/intl.dart'; +import 'package:marco/helpers/widgets/avatar.dart'; +import 'package:marco/model/attendance/log_details_view.dart'; +import 'package:marco/model/attendance/attendence_action_button.dart'; +import 'package:marco/model/attendance/regualrize_action_button.dart'; +import 'package:marco/model/attendance/attendence_filter_sheet.dart'; + +class AttendanceScreen extends StatefulWidget { + AttendanceScreen({super.key}); + + @override + State createState() => _AttendanceScreenState(); +} + +class _AttendanceScreenState extends State with UIMixin { + final AttendanceController attendanceController = + Get.put(AttendanceController()); + final PermissionController permissionController = + Get.put(PermissionController()); + + String selectedTab = 'todaysAttendance'; + + @override + Widget build(BuildContext context) { + return Layout( + child: GetBuilder( + init: attendanceController, + tag: 'attendance_dashboard_controller', + builder: (controller) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: MySpacing.x(flexSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.titleMedium("Attendance", + fontSize: 18, fontWeight: 600), + MyBreadcrumb( + children: [ + MyBreadcrumbItem(name: 'Dashboard'), + MyBreadcrumbItem(name: 'Attendance', active: true), + ], + ), + ], + ), + ), + MySpacing.height(flexSpacing), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + MyText.bodyMedium( + "Filter", + fontWeight: 600, + ), + // Wrap with Tooltip and InkWell for interactive feedback + Tooltip( + message: 'Filter Project', + child: InkWell( + borderRadius: BorderRadius.circular(24), + onTap: () async { + final result = + await showModalBottomSheet>( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical(top: Radius.circular(12)), + ), + builder: (context) => AttendanceFilterBottomSheet( + controller: attendanceController, + permissionController: permissionController, + selectedTab: selectedTab, + ), + ); + + if (result != null) { + final selectedProjectId = + result['projectId'] as String?; + final selectedView = result['selectedTab'] as String?; + + if (selectedProjectId != null && + selectedProjectId != + attendanceController.selectedProjectId) { + attendanceController.selectedProjectId = + selectedProjectId; + try { + await attendanceController + .fetchEmployeesByProject(selectedProjectId); + await attendanceController + .fetchAttendanceLogs(selectedProjectId); + await attendanceController + .fetchRegularizationLogs(selectedProjectId); + await attendanceController + .fetchProjectData(selectedProjectId); + } catch (_) {} + attendanceController + .update(['attendance_dashboard_controller']); + } + + if (selectedView != null && + selectedView != selectedTab) { + setState(() { + selectedTab = selectedView; + }); + } + } + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.filter_list_alt, + color: Colors.blueAccent, + size: 28, + ), + ), + ), + ), + ), + const SizedBox(width: 4), + MyText.bodyMedium( + "Refresh", + fontWeight: 600, + ), + Tooltip( + message: 'Refresh Data', + child: InkWell( + borderRadius: BorderRadius.circular(24), + onTap: () async { + final projectId = + attendanceController.selectedProjectId; + if (projectId != null && projectId.isNotEmpty) { + try { + await attendanceController + .fetchEmployeesByProject(projectId); + await attendanceController + .fetchAttendanceLogs(projectId); + await attendanceController + .fetchRegularizationLogs(projectId); + await attendanceController + .fetchProjectData(projectId); + attendanceController + .update(['attendance_dashboard_controller']); + } catch (e) { + debugPrint("Error refreshing data: $e"); + } + } + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.refresh, + color: Colors.green, + size: 28, + ), + ), + ), + ), + ), + ], + ), + MySpacing.height(flexSpacing), + Padding( + padding: MySpacing.x(flexSpacing / 2), + child: MyFlex(children: [ + MyFlexItem( + sizes: 'lg-12 md-12 sm-12', + child: selectedTab == 'todaysAttendance' + ? employeeListTab() + : selectedTab == 'attendanceLogs' + ? employeeLog() + : regularizationScreen(), + ), + ]), + ), + ], + ); + }, + ), + ); + } + + Widget employeeListTab() { + return Obx(() { + final isLoading = attendanceController.isLoadingEmployees.value; + final employees = attendanceController.employees; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: [ + Expanded( + child: MyText.titleMedium( + "Today's Attendance", + fontWeight: 600, + ), + ), + ], + ), + ), + MyCard.bordered( + borderRadiusAll: 4, + border: Border.all(color: Colors.grey.withOpacity(0.2)), + shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), + paddingAll: 8, + child: isLoading + ? Center(child: CircularProgressIndicator()) + : employees.isEmpty + ? Center( + child: MyText.bodySmall( + "No Employees Assigned to This Project", + fontWeight: 600, + ), + ) + : Column( + children: List.generate(employees.length, (index) { + final employee = employees[index]; + return Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8), + child: MyContainer( + paddingAll: 5, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Avatar( + firstName: employee.firstName, + lastName: employee.lastName, + size: 31, + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + MyText.bodyMedium( + employee.name, + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + maxLines: 1, + ), + MySpacing.width(6), + MyText.bodySmall( + '(${employee.designation})', + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + maxLines: 1, + color: Colors.grey[ + 700], // optional styling + ), + ], + ), + MySpacing.height(8), + (employee.checkIn != null || + employee.checkOut != null) + ? Row( + children: [ + if (employee.checkIn != + null) ...[ + Icon( + Icons + .arrow_circle_right, + size: 16, + color: + Colors.green), + MySpacing.width(4), + Expanded( + child: + MyText.bodySmall( + DateFormat( + 'hh:mm a') + .format(employee + .checkIn!), + fontWeight: 600, + overflow: + TextOverflow + .ellipsis, + ), + ), + MySpacing.width(16), + ], + if (employee.checkOut != + null) ...[ + Icon( + Icons + .arrow_circle_left, + size: 16, + color: Colors.red), + MySpacing.width(4), + Expanded( + child: + MyText.bodySmall( + DateFormat( + 'hh:mm a') + .format(employee + .checkOut!), + fontWeight: 600, + overflow: + TextOverflow + .ellipsis, + ), + ), + ], + ], + ) + : SizedBox.shrink(), + MySpacing.height(12), + Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + AttendanceActionButton( + employee: employee, + attendanceController: + attendanceController, + ), + if (employee.checkIn != + null) ...[ + MySpacing.width(8), + AttendanceLogViewButton( + employee: employee, + attendanceController: + attendanceController, + ), + ], + ], + ), + ], + ), + ), + ], + ), + ), + ), + if (index != employees.length - 1) + Divider( + color: Colors.grey.withOpacity(0.3), + thickness: 1, + height: 1, + ), + ], + ); + }), + ), + ), + ], + ); + }); + } + + Widget employeeLog() { + return Obx(() { + final logs = List.of(attendanceController.attendanceLogs); + logs.sort((a, b) { + final aDate = a.checkIn ?? DateTime(0); + final bDate = b.checkIn ?? DateTime(0); + return bDate.compareTo(aDate); + }); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: [ + Expanded( + child: MyText.titleMedium( + "Attendance Logs", + fontWeight: 600, + ), + ), + ], + ), + ), + MyCard.bordered( + borderRadiusAll: 4, + border: Border.all(color: Colors.grey.withOpacity(0.2)), + shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), + paddingAll: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (attendanceController.isLoadingAttendanceLogs.value) + const Padding( + padding: EdgeInsets.symmetric(vertical: 32), + child: Center(child: CircularProgressIndicator()), + ) + else if (logs.isEmpty) + MyText.bodySmall( + "No Attendance Logs Found for this Project", + fontWeight: 600, + ) + else + Column( + children: List.generate(logs.length, (index) { + final employee = logs[index]; + final currentDate = employee.checkIn != null + ? DateFormat('dd MMM yyyy').format(employee.checkIn!) + : ''; + final previousDate = + index > 0 && logs[index - 1].checkIn != null + ? DateFormat('dd MMM yyyy') + .format(logs[index - 1].checkIn!) + : ''; + + final showDateHeader = + index == 0 || currentDate != previousDate; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showDateHeader) + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: MyText.bodyMedium( + currentDate, + fontWeight: 700, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 8), + child: MyContainer( + paddingAll: 8, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Avatar( + firstName: employee.firstName, + lastName: employee.lastName, + size: 31, + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: MyText.bodyMedium( + employee.name, + fontWeight: 600, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + MySpacing.width(6), + Flexible( + child: MyText.bodySmall( + '(${employee.designation})', + fontWeight: 600, + overflow: TextOverflow.ellipsis, + maxLines: 1, + color: Colors.grey[700], + ), + ), + ], + ), + MySpacing.height(8), + (employee.checkIn != null || + employee.checkOut != null) + ? Row( + children: [ + if (employee.checkIn != + null) ...[ + Icon( + Icons + .arrow_circle_right, + size: 16, + color: Colors.green), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + DateFormat('hh:mm a') + .format(employee + .checkIn!), + fontWeight: 600, + overflow: TextOverflow + .ellipsis, + ), + ), + MySpacing.width(16), + ], + if (employee.checkOut != + null) ...[ + Icon( + Icons.arrow_circle_left, + size: 16, + color: Colors.red), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + DateFormat('hh:mm a') + .format(employee + .checkOut!), + fontWeight: 600, + overflow: TextOverflow + .ellipsis, + ), + ), + ], + ], + ) + : SizedBox.shrink(), + MySpacing.height(12), + Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Flexible( + child: AttendanceActionButton( + employee: employee, + attendanceController: + attendanceController, + ), + ), + MySpacing.width(8), + Flexible( + child: AttendanceLogViewButton( + employee: employee, + attendanceController: + attendanceController, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + if (index != logs.length - 1) + Divider( + color: Colors.grey.withOpacity(0.3), + thickness: 1, + height: 1, + ), + ], + ); + }), + ), + ], + ), + ), + ], + ); + }); + } + + Widget regularizationScreen() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 4.0), + child: MyText.titleMedium( + "Regularization Requests", + fontWeight: 600, + ), + ), + Obx(() { + final employees = attendanceController.regularizationLogs; + + return MyCard.bordered( + borderRadiusAll: 4, + border: Border.all(color: Colors.grey.withOpacity(0.2)), + shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), + paddingAll: 8, + child: attendanceController.isLoadingRegularizationLogs.value + ? const Padding( + padding: EdgeInsets.symmetric(vertical: 32.0), + child: Center(child: CircularProgressIndicator()), + ) + : employees.isEmpty + ? MyText.bodySmall( + "No Regularization Requests Found for this Project", + fontWeight: 600, + ) + : Column( + children: List.generate(employees.length, (index) { + final employee = employees[index]; + return Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8), + child: MyContainer( + paddingAll: 8, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Avatar( + firstName: employee.firstName, + lastName: employee.lastName, + size: 31, + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: MyText.bodyMedium( + employee.name, + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + maxLines: 1, + ), + ), + MySpacing.width(6), + Flexible( + child: MyText.bodySmall( + '(${employee.role})', + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + maxLines: 1, + color: Colors.grey[700], + ), + ), + ], + ), + MySpacing.height(8), + Row( + children: [ + if (employee.checkIn != + null) ...[ + Icon(Icons.arrow_circle_right, + size: 16, + color: Colors.green), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + DateFormat('hh:mm a') + .format(employee + .checkIn!), + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + ), + ), + MySpacing.width(16), + ], + if (employee.checkOut != + null) ...[ + Icon(Icons.arrow_circle_left, + size: 16, + color: Colors.red), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + DateFormat('hh:mm a') + .format(employee + .checkOut!), + fontWeight: 600, + overflow: + TextOverflow.ellipsis, + ), + ), + ], + ], + ), + MySpacing.height(12), + Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + RegularizeActionButton( + attendanceController: + attendanceController, + log: employee, + uniqueLogKey: + employee.employeeId, + action: ButtonActions.approve, + ), + const SizedBox(width: 8), + RegularizeActionButton( + attendanceController: + attendanceController, + log: employee, + uniqueLogKey: + employee.employeeId, + action: ButtonActions.reject, + ), + ], + ) + ], + ), + ), + ], + ), + ), + ), + if (index != employees.length - 1) + Divider( + color: Colors.grey.withOpacity(0.3), + thickness: 1, + height: 1, + ), + ], + ); + }), + ), + ); + }), + ], + ); + } +} diff --git a/lib/view/dashboard/Attendence/attendence_log_screen.dart b/lib/view/dashboard/Attendence/attendence_log_screen.dart new file mode 100644 index 0000000..0b586d1 --- /dev/null +++ b/lib/view/dashboard/Attendence/attendence_log_screen.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:marco/helpers/theme/app_theme.dart'; +import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; +import 'package:marco/helpers/utils/my_shadow.dart'; +import 'package:marco/helpers/widgets/my_breadcrumb.dart'; +import 'package:marco/helpers/widgets/my_breadcrumb_item.dart'; +import 'package:marco/helpers/widgets/my_card.dart'; +import 'package:marco/helpers/widgets/my_container.dart'; +import 'package:marco/helpers/widgets/my_flex.dart'; +import 'package:marco/helpers/widgets/my_flex_item.dart'; +import 'package:marco/helpers/widgets/my_spacing.dart'; +import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/view/layouts/layout.dart'; +import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; +import 'package:marco/controller/permission_controller.dart'; +import 'package:intl/intl.dart'; +import 'package:marco/model/attendance/attendence_action_button.dart'; +import 'package:marco/helpers/widgets/avatar.dart'; +import 'package:marco/model/attendance/log_details_view.dart'; + +class AttendenceLogScreen extends StatefulWidget { + const AttendenceLogScreen({super.key}); + + @override + State createState() => _AttendenceLogScreenState(); +} + +class _AttendenceLogScreenState extends State + with UIMixin { + final AttendanceController attendanceController = + Get.put(AttendanceController()); + final PermissionController permissionController = + Get.put(PermissionController()); + @override + Widget build(BuildContext context) { + return Layout( + child: GetBuilder( + init: attendanceController, + tag: 'attendence_controller', + builder: (controller) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: MySpacing.x(flexSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.titleMedium("Attendence", + fontSize: 18, fontWeight: 600), + MyBreadcrumb( + children: [ + MyBreadcrumbItem(name: 'Dashboard'), + MyBreadcrumbItem(name: 'Attendence', active: true), + ], + ), + ], + ), + ), + MySpacing.height(flexSpacing), + Padding( + padding: MySpacing.x(flexSpacing / 2), + child: MyFlex(children: [ + MyFlexItem(sizes: 'lg-12 md-12 sm-12', child: employeeLog()), + ]), + ) + ], + ); + }, + ), + ); + } + + Widget employeeLog() { + return MyCard.bordered( + borderRadiusAll: 4, + border: Border.all(color: Colors.grey.withOpacity(0.2)), + shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), + paddingAll: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + attendanceController.employees.isEmpty + ? MyText.bodySmall("No Employees Assigned to This Project", + fontWeight: 600) + : Column( + children: List.generate(attendanceController.employees.length, + (index) { + final employee = attendanceController.employees[index]; + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: MyContainer( + paddingAll: 12, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Avatar( + firstName: employee.firstName, + lastName: employee.lastName, + size: 31, + ) + ], + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: MyText.bodyMedium( + '${employee.name} ' + ' (${employee.designation})', + fontWeight: 600, + maxLines: null, + overflow: TextOverflow.visible, + softWrap: true, + ), + ), + ], + ), + MySpacing.height(8), + Row( + children: [ + MyText.bodySmall("In: ", + fontWeight: 600), + MyText.bodySmall( + employee.checkIn != null + ? DateFormat('hh:mm a') + .format(employee.checkIn!) + : '--', + fontWeight: 600, + ), + MySpacing.width(16), + MyText.bodySmall("Out: ", + fontWeight: 600), + MyText.bodySmall( + employee.checkOut != null + ? DateFormat('hh:mm a') + .format(employee.checkOut!) + : '--', + fontWeight: 600, + ), + ], + ), + MySpacing.height(12), + Row( + children: [ + AttendanceLogViewButton( + employee: employee, + attendanceController: + attendanceController, + ), + MySpacing.width(8), + AttendanceActionButton( + employee: employee, + attendanceController: + attendanceController, + + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + if (index != attendanceController.employees.length - 1) + Padding( + padding: const EdgeInsets.symmetric(), + child: Divider( + color: Colors.grey.withOpacity(0.3), + thickness: 1, + height: 1, + ), + ), + ], + ); + }), + ), + ], + ), + ); + } + + Future showTimePickerForRegularization({ + required BuildContext context, + required DateTime checkInTime, + }) async { + final pickedTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(DateTime.now()), + ); + + if (pickedTime != null) { + final selectedDateTime = DateTime( + checkInTime.year, + checkInTime.month, + checkInTime.day, + pickedTime.hour, + pickedTime.minute, + ); + + // Ensure selected time is after check-in time + if (selectedDateTime.isAfter(checkInTime)) { + return selectedDateTime; + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select a time after check-in time.")), + ); + return null; + } + } + return null; + } +} diff --git a/lib/view/dashboard/attendanceScreen.dart b/lib/view/dashboard/attendanceScreen.dart index 7551fb5..382ddcd 100644 --- a/lib/view/dashboard/attendanceScreen.dart +++ b/lib/view/dashboard/attendanceScreen.dart @@ -1,1219 +1,1322 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_lucide/flutter_lucide.dart'; -import 'package:get/get.dart'; -import 'package:marco/helpers/theme/app_theme.dart'; -import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; -import 'package:marco/helpers/utils/permission_constants.dart'; -import 'package:marco/helpers/utils/attendance_actions.dart'; -import 'package:marco/helpers/widgets/my_breadcrumb.dart'; -import 'package:marco/helpers/widgets/my_breadcrumb_item.dart'; -import 'package:marco/helpers/widgets/my_card.dart'; -import 'package:marco/helpers/widgets/my_container.dart'; -import 'package:marco/helpers/widgets/my_flex.dart'; -import 'package:marco/helpers/widgets/my_flex_item.dart'; -import 'package:marco/helpers/widgets/my_list_extension.dart'; -import 'package:marco/helpers/widgets/my_loading_component.dart'; -import 'package:marco/helpers/widgets/my_refresh_wrapper.dart'; -import 'package:marco/helpers/widgets/my_spacing.dart'; -import 'package:marco/helpers/widgets/my_text.dart'; -import 'package:marco/helpers/utils/my_shadow.dart'; -import 'package:marco/model/my_paginated_table.dart'; -import 'package:marco/view/layouts/layout.dart'; -import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; -import 'package:intl/intl.dart'; -import 'package:marco/controller/permission_controller.dart'; -import 'package:url_launcher/url_launcher.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_lucide/flutter_lucide.dart'; +// import 'package:get/get.dart'; +// import 'package:marco/helpers/theme/app_theme.dart'; +// import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; +// import 'package:marco/helpers/utils/permission_constants.dart'; +// import 'package:marco/helpers/utils/attendance_actions.dart'; +// import 'package:marco/helpers/widgets/my_breadcrumb.dart'; +// import 'package:marco/helpers/widgets/my_breadcrumb_item.dart'; +// import 'package:marco/helpers/widgets/my_card.dart'; +// import 'package:marco/helpers/widgets/my_container.dart'; +// import 'package:marco/helpers/widgets/my_flex.dart'; +// import 'package:marco/helpers/widgets/my_flex_item.dart'; +// import 'package:marco/helpers/widgets/my_list_extension.dart'; +// import 'package:marco/helpers/widgets/my_loading_component.dart'; +// import 'package:marco/helpers/widgets/my_refresh_wrapper.dart'; +// import 'package:marco/helpers/widgets/my_spacing.dart'; +// import 'package:marco/helpers/widgets/my_text.dart'; +// import 'package:marco/helpers/utils/my_shadow.dart'; +// import 'package:marco/model/my_paginated_table.dart'; +// import 'package:marco/view/layouts/layout.dart'; +// import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; +// import 'package:intl/intl.dart'; +// import 'package:marco/controller/permission_controller.dart'; +// import 'package:url_launcher/url_launcher.dart'; +// import 'package:marco/images.dart'; -class AttendanceScreen extends StatefulWidget { - const AttendanceScreen({super.key}); +// class AttendanceScreen extends StatefulWidget { +// const AttendanceScreen({super.key}); - @override - State createState() => _AttendanceScreenState(); -} +// @override +// State createState() => _AttendanceScreenState(); +// } -class _AttendanceScreenState extends State with UIMixin { - final AttendanceController attendanceController = - Get.put(AttendanceController()); - final PermissionController permissionController = - Get.put(PermissionController()); +// class _AttendanceScreenState extends State with UIMixin { +// final AttendanceController attendanceController = +// Get.put(AttendanceController()); +// final PermissionController permissionController = +// Get.put(PermissionController()); - @override - Widget build(BuildContext context) { - return Layout( - child: GetBuilder( - init: attendanceController, - tag: 'attendance_dashboard_controller', - builder: (controller) { - return LoadingComponent( - isLoading: controller.isLoading.value, - loadingText: 'Loading Attendance...', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: MySpacing.x(flexSpacing), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyText.titleMedium("Attendance", - fontSize: 18, fontWeight: 600), - MyBreadcrumb( - children: [ - MyBreadcrumbItem(name: 'Dashboard'), - MyBreadcrumbItem(name: 'Attendance', active: true), - ], - ), - ], - ), - ), - MySpacing.height(flexSpacing), - Padding( - padding: MySpacing.x(flexSpacing / 2), - child: MyFlex( - children: [ - // Project Selection Dropdown - MyFlexItem( - sizes: 'lg-12', - child: MyContainer.bordered( - padding: MySpacing.xy(8, 8), - child: PopupMenuButton( - onSelected: (value) async { - attendanceController.selectedProjectId = value; - await attendanceController - .fetchEmployeesByProject(value); - await attendanceController - .fetchAttendanceLogs(value); - await attendanceController - .fetchRegularizationLogs(value); - await attendanceController - .fetchProjectData(value); - attendanceController.update(); - }, - itemBuilder: (BuildContext context) { - if (attendanceController.projects.isEmpty) { - return [ - PopupMenuItem( - value: '', - child: MyText.bodySmall('No Data', - fontWeight: 600), - ) - ]; - } - // Filter projects based on permissions - final accessibleProjects = attendanceController - .projects - .where((project) => permissionController - .isUserAssignedToProject( - project.id.toString())) - .toList(); +// @override +// Widget build(BuildContext context) { +// return Layout( +// child: GetBuilder( +// init: attendanceController, +// tag: 'attendance_dashboard_controller', +// builder: (controller) { +// return LoadingComponent( +// isLoading: controller.isLoading.value, +// loadingText: 'Loading Attendance...', +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Padding( +// padding: MySpacing.x(flexSpacing), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// MyText.titleMedium("Attendance", +// fontSize: 18, fontWeight: 600), +// MyBreadcrumb( +// children: [ +// MyBreadcrumbItem(name: 'Dashboard'), +// MyBreadcrumbItem(name: 'Attendance', active: true), +// ], +// ), +// ], +// ), +// ), +// MySpacing.height(flexSpacing), +// Padding( +// padding: MySpacing.x(flexSpacing / 2), +// child: MyFlex( +// children: [ +// // Project Selection Dropdown +// MyFlexItem( +// sizes: 'lg-12', +// child: MyContainer.bordered( +// padding: MySpacing.xy(8, 8), +// child: PopupMenuButton( +// onSelected: (value) async { +// attendanceController.selectedProjectId = value; +// await attendanceController +// .fetchEmployeesByProject(value); +// await attendanceController +// .fetchAttendanceLogs(value); +// await attendanceController +// .fetchRegularizationLogs(value); +// await attendanceController +// .fetchProjectData(value); +// attendanceController.update(); +// }, +// itemBuilder: (BuildContext context) { +// if (attendanceController.projects.isEmpty) { +// return [ +// PopupMenuItem( +// value: '', +// child: MyText.bodySmall('No Data', +// fontWeight: 600), +// ) +// ]; +// } +// // Filter projects based on permissions +// final accessibleProjects = attendanceController +// .projects +// .where((project) => permissionController +// .isUserAssignedToProject( +// project.id.toString())) +// .toList(); - if (accessibleProjects.isEmpty) { - return [ - PopupMenuItem( - value: '', - child: MyText.bodySmall( - 'No Projects Assigned', - fontWeight: 600), - ) - ]; - } +// if (accessibleProjects.isEmpty) { +// return [ +// PopupMenuItem( +// value: '', +// child: MyText.bodySmall( +// 'No Projects Assigned', +// fontWeight: 600), +// ) +// ]; +// } - return accessibleProjects.map((project) { - return PopupMenuItem( - value: project.id.toString(), - height: 32, - child: MyText.bodySmall( - project.name, - color: theme.colorScheme.onSurface, - fontWeight: 600, - ), - ); - }).toList(); - }, - color: theme.cardTheme.color, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyText.labelSmall( - attendanceController.selectedProjectId != null - ? attendanceController.projects - .firstWhereOrNull((proj) => - proj.id == - attendanceController - .selectedProjectId) - ?.name ?? - 'Select a Project' - : 'Select a Project', - color: theme.colorScheme.onSurface, - ), - Icon(LucideIcons.chevron_down, - size: 20, - color: theme.colorScheme.onSurface), - ], - ), - ), - ), - ), +// return accessibleProjects.map((project) { +// return PopupMenuItem( +// value: project.id.toString(), +// height: 32, +// child: MyText.bodySmall( +// project.name, +// color: theme.colorScheme.onSurface, +// fontWeight: 600, +// ), +// ); +// }).toList(); +// }, +// color: theme.cardTheme.color, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// MyText.labelSmall( +// attendanceController.selectedProjectId != null +// ? attendanceController.projects +// .firstWhereOrNull((proj) => +// proj.id == +// attendanceController +// .selectedProjectId) +// ?.name ?? +// 'Select a Project' +// : 'Select a Project', +// color: theme.colorScheme.onSurface, +// ), +// Icon(LucideIcons.chevron_down, +// size: 20, +// color: theme.colorScheme.onSurface), +// ], +// ), +// ), +// ), +// ), - // Tab Section - MyFlexItem( - sizes: 'lg-12', - child: Obx(() { - bool hasRegularizationPermission = - permissionController.hasPermission( - Permissions.regularizeAttendance); +// // Tab Section +// MyFlexItem( +// sizes: 'lg-12', +// child: Obx(() { +// bool hasRegularizationPermission = +// permissionController.hasPermission( +// Permissions.regularizeAttendance); - final tabs = [ - const Tab(text: 'Today`s'), - const Tab(text: 'Logs'), - if (hasRegularizationPermission) - const Tab(text: 'Regularization'), - ]; +// final tabs = [ +// const Tab(text: 'Today`s'), +// const Tab(text: 'Logs'), +// if (hasRegularizationPermission) +// const Tab(text: 'Regularization'), +// ]; - final views = [ - employeeListTab(), - reportsTab(context), - if (hasRegularizationPermission) - regularizationTab(context), - ]; +// final views = [ +// employeeListTab(), +// reportsTab(context), +// if (hasRegularizationPermission) +// regularizationTab(context), +// ]; - return DefaultTabController( - length: tabs.length, - child: MyCard.bordered( - borderRadiusAll: 4, - border: - Border.all(color: Colors.grey.withAlpha(50)), - shadow: MyShadow( - elevation: 1, - position: MyShadowPosition.bottom), - paddingAll: 10, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TabBar( - labelColor: theme.colorScheme.primary, - unselectedLabelColor: theme - .colorScheme.onSurface - .withAlpha(150), - tabs: tabs, - ), - MySpacing.height(16), - SizedBox( - height: 630, - child: TabBarView(children: views), - ), - ], - ), - ), - ); - }), - ), - ], - ), - ), - ], - ), - ); - }, - ), - ); - } +// return DefaultTabController( +// length: tabs.length, +// child: MyCard.bordered( +// borderRadiusAll: 4, +// border: +// Border.all(color: Colors.grey.withAlpha(50)), +// shadow: MyShadow( +// elevation: 1, +// position: MyShadowPosition.bottom), +// paddingAll: 10, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// TabBar( +// labelColor: theme.colorScheme.primary, +// unselectedLabelColor: theme +// .colorScheme.onSurface +// .withAlpha(150), +// tabs: tabs, +// ), +// MySpacing.height(16), +// SizedBox( +// height: 630, +// child: TabBarView(children: views), +// ), +// ], +// ), +// ), +// ); +// }), +// ), +// ], +// ), +// ), +// ], +// ), +// ); +// }, +// ), +// ); +// } - Widget employeeListTab() { - if (attendanceController.employees.isEmpty) { - return Center( - child: MyText.bodySmall("No Employees Assigned to This Project", - fontWeight: 600), - ); - } +// Widget employeeListTab() { +// return MyCard.bordered( +// borderRadiusAll: 4, +// border: Border.all(color: Colors.grey.withOpacity(0.2)), +// shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), +// paddingAll: 24, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// attendanceController.employees.isEmpty +// ? MyText.bodySmall("No Employees Assigned to This Project", +// fontWeight: 600) +// : SizedBox( +// height: 600, +// child: SingleChildScrollView( +// child: Column( +// children: attendanceController.employees.map((employee) { +// int? activity = employee.activity; +// String buttonText = (activity == 0 || activity == 4) +// ? ButtonActions.checkIn +// : ButtonActions.checkOut; - final columns = [ - DataColumn( - label: MyText.labelLarge('Name', color: contentTheme.primary), - ), - DataColumn( - label: MyText.labelLarge('Actions', color: contentTheme.primary), - ), - ]; +// return Column( +// children: [ +// MyContainer.bordered( +// paddingAll: 12, +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// // LEFT COLUMN: Avatar centered vertically +// Column( +// mainAxisAlignment: MainAxisAlignment.center, // <-- center vertically +// children: [ +// MyContainer.rounded( +// paddingAll: 0, +// height: 44, +// width: 44, +// clipBehavior: Clip.antiAliasWithSaveLayer, +// child: Image.asset( +// Images.avatars[1], +// fit: BoxFit.cover, +// ), +// ), +// MySpacing.height(12), +// ], +// ), +// MySpacing.width(16), +// // RIGHT COLUMN: Name and role on the same line + other info +// Expanded( +// flex: 1, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// // Name and designation inline row +// Row( +// children: [ +// Expanded( +// child: MyText.bodyMedium( +// employee.name, +// fontWeight: 600, +// overflow: TextOverflow.ellipsis, +// ), +// ), +// MySpacing.width(8), +// Flexible( +// child: MyText.labelMedium( +// employee.designation, +// fontWeight: 600, +// xMuted: true, +// overflow: TextOverflow.ellipsis, +// ), +// ), +// ], +// ), +// MySpacing.height(8), +// Row( +// children: [ +// MyText.bodySmall("In: ", fontWeight: 600), +// MyText.bodySmall( +// employee.checkIn != null +// ? DateFormat('hh:mm a') +// .format(employee.checkIn!) +// : '--', +// fontWeight: 600, +// ), +// MySpacing.width(16), +// MyText.bodySmall("Out: ", fontWeight: 600), +// MyText.bodySmall( +// employee.checkOut != null +// ? DateFormat('hh:mm a') +// .format(employee.checkOut!) +// : '--', +// fontWeight: 600, +// ), +// ], +// ), +// MySpacing.height(12), +// Row( +// children: [ +// IconButton( +// icon: const Icon(Icons.visibility, size: 20), +// onPressed: () async { +// await attendanceController +// .fetchLogsView(employee.id.toString()); +// showModalBottomSheet( +// context: context, +// isScrollControlled: true, +// shape: const RoundedRectangleBorder( +// borderRadius: BorderRadius.vertical( +// top: Radius.circular(16), +// ), +// ), +// backgroundColor: Theme.of(context).cardColor, +// builder: (context) { +// return Padding( +// padding: EdgeInsets.only( +// left: 16, +// right: 16, +// top: 16, +// bottom: MediaQuery.of(context) +// .viewInsets +// .bottom + +// 16, +// ), +// // Your modal content here +// ); +// }, +// ); +// }, +// ), +// MySpacing.width(8), +// Obx(() { +// final isUploading = +// attendanceController +// .uploadingStates[ +// employee.employeeId] +// ?.value ?? +// false; +// final controller = attendanceController; - final rows = attendanceController.employees.mapIndexed((index, employee) { - int? activity = employee.activity; - String buttonText = (activity == 0 || activity == 4) - ? ButtonActions.checkIn - : ButtonActions.checkOut; +// 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.'), +// ), +// ); - return DataRow(cells: [ - DataCell( - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyText.bodyMedium(employee.name, fontWeight: 600), - SizedBox(height: 2), - MyText.bodySmall(employee.designation, color: Colors.grey), - ], - ), - ), - DataCell( - Obx(() { - final isUploading = attendanceController - .uploadingStates[employee.employeeId]?.value ?? - false; - final controller = attendanceController; - 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); - 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), +// 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), +// ), +// ), +// ), +// ), +// ); +// }), +// ], +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// MySpacing.height(16), +// ], +// ); +// }).toList(), +// ), +// ), +// ), +// ], +// ), +// ); +// } - 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(Colors.white), - ), - ) - : FittedBox( - fit: BoxFit.scaleDown, - child: Text( - buttonText, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12), - ), - ), - ), - ), - ); - }), - ), - ]); - }).toList(); - return Padding( - padding: const EdgeInsets.all(0.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Spacer(), - MyText.bodySmall( - '${DateFormat('dd-MM-yyyy').format(DateTime.now())}', - fontWeight: 600, - color: Colors.grey[700], - ), - const SizedBox(width: 16), - ], - ), - const SizedBox(height: 8), - MyRefreshableContent( - onRefresh: () async { - if (attendanceController.selectedProjectId != null) { - await attendanceController.fetchEmployeesByProject( - attendanceController.selectedProjectId!); - await attendanceController.fetchProjectData( - attendanceController.selectedProjectId!); - attendanceController.update(); - } else { - await attendanceController.fetchProjects(); - } - }, - child: MyPaginatedTable( - columns: columns, - rows: rows, - ), - ), - ], - ), - ) - ); - } - Widget reportsTab(BuildContext context) { - final attendanceController = Get.find(); - final groupedLogs = attendanceController.groupLogsByCheckInDate(); +// Widget reportsTab(BuildContext context) { +// final attendanceController = Get.find(); +// final groupedLogs = attendanceController.groupLogsByCheckInDate(); - final columns = [ - DataColumn(label: MyText.labelLarge('Name', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Check-In', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Check-Out', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('View', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Action', color: contentTheme.primary)), - ]; +// final columns = [ +// DataColumn(label: MyText.labelLarge('Name', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Check-In', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Check-Out', color: contentTheme.primary)), +// DataColumn(label: MyText.labelLarge('View', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Action', color: contentTheme.primary)), +// ]; - List rows = []; +// List rows = []; - // Iterate over grouped logs - groupedLogs.forEach((checkInDate, logs) { - // Add a row for the check-in date as a header - rows.add( - DataRow(color: WidgetStateProperty.all(Colors.grey[200]), cells: [ - DataCell(MyText.bodyMedium(checkInDate, fontWeight: 600)), - DataCell(MyText.bodyMedium('')), - DataCell(MyText.bodyMedium('')), - DataCell(MyText.bodyMedium('')), - DataCell(MyText.bodyMedium('')), - ])); - // Add rows for each log in this group - for (var log in logs) { - rows.add(DataRow(cells: [ - DataCell( - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyText.bodyMedium(log.name, fontWeight: 600), - SizedBox(height: 2), - MyText.bodySmall(log.role, color: Colors.grey), - ], - ), - ), - DataCell( - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium( - log.checkIn != null - ? DateFormat('hh:mm a').format(log.checkIn!) - : '', - fontWeight: 600, - ), - ], - ), - ), - DataCell( - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium( - log.checkOut != null - ? DateFormat('hh:mm a').format(log.checkOut!) - : '', - fontWeight: 600, - ), - ], - ), - ), - DataCell( - IconButton( - icon: const Icon(Icons.visibility, size: 18), - onPressed: () async { - await attendanceController.fetchLogsView(log.id.toString()); - showModalBottomSheet( - context: context, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical(top: Radius.circular(16)), - ), - backgroundColor: Theme.of(context).cardColor, - builder: (context) { - return Padding( - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: MediaQuery.of(context).viewInsets.bottom + 16, - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyText.titleMedium( - "Attendance Log Details", - fontWeight: 700, - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - const SizedBox(height: 16), - if (attendanceController - .attendenceLogsView.isNotEmpty) ...[ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: MyText.bodyMedium("Date", - fontWeight: 600)), - Expanded( - child: MyText.bodyMedium("Time", - fontWeight: 600)), - Expanded( - child: MyText.bodyMedium("Description", - fontWeight: 600)), - Expanded( - child: MyText.bodyMedium("Image", - fontWeight: 600)), - ], - ), - const Divider(thickness: 1, height: 24), - ], - if (attendanceController - .attendenceLogsView.isNotEmpty) - ...attendanceController.attendenceLogsView - .map((log) => Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: MyText.bodyMedium( - log.formattedDate ?? '-', - fontWeight: 600)), - Expanded( - child: MyText.bodyMedium( - log.formattedTime ?? '-', - fontWeight: 600)), - Expanded( - child: Row( - children: [ - if (log.latitude != null && - log.longitude != null) - GestureDetector( - onTap: () async { - final url = - 'https://www.google.com/maps/search/?api=1&query=${log.latitude},${log.longitude}'; - if (await canLaunchUrl( - Uri.parse(url))) { - await launchUrl( - Uri.parse(url), - mode: LaunchMode - .externalApplication); - } else { - ScaffoldMessenger.of( - context) - .showSnackBar( - const SnackBar( - content: Text( - 'Could not open Google Maps')), - ); - } - }, - child: const Padding( - padding: - EdgeInsets.only( - right: 4.0), - child: Icon( - Icons.location_on, - size: 18, - color: Colors.blue), - ), - ), - Expanded( - child: MyText.bodyMedium( - log.comment ?? '-', - fontWeight: 600, - ), - ), - ], - ), - ), - Expanded( - child: GestureDetector( - onTap: () { - if (log.preSignedUrl != - null) { - showDialog( - context: context, - builder: (_) => Dialog( - child: Image.network( - log.preSignedUrl!, - fit: BoxFit.cover, - height: 400, - errorBuilder: - (context, error, - stackTrace) { - return const Icon( - Icons - .broken_image, - size: 50, - color: Colors - .grey); - }, - ), - ), - ); - } - }, - child: log.thumbPreSignedUrl != - null - ? Image.network( - log.thumbPreSignedUrl!, - height: 40, - width: 40, - fit: BoxFit.cover, - errorBuilder: (context, - error, stackTrace) { - return const Icon( - Icons - .broken_image, - size: 40, - color: - Colors.grey); - }, - ) - : const Icon( - Icons.broken_image, - size: 40, - color: Colors.grey), - ), - ), - ], - ), - )), - ], - ), - ), - ); - }, - ); - }, - ), - ), - DataCell( - Obx(() { - final uniqueLogKey = - AttendanceButtonHelper.getUniqueKey(log.employeeId, log.id); - final isUploading = - attendanceController.uploadingStates[uniqueLogKey]?.value ?? - false; +// // Iterate over grouped logs +// groupedLogs.forEach((checkInDate, logs) { +// // Add a row for the check-in date as a header +// rows.add( +// DataRow(color: WidgetStateProperty.all(Colors.grey[200]), cells: [ +// DataCell(MyText.bodyMedium(checkInDate, fontWeight: 600)), +// DataCell(MyText.bodyMedium('')), +// DataCell(MyText.bodyMedium('')), +// DataCell(MyText.bodyMedium('')), +// DataCell(MyText.bodyMedium('')), +// ])); +// // Add rows for each log in this group +// for (var log in logs) { +// rows.add(DataRow(cells: [ +// DataCell( +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// MyText.bodyMedium(log.name, fontWeight: 600), +// SizedBox(height: 2), +// MyText.bodySmall(log.role, color: Colors.grey), +// ], +// ), +// ), +// DataCell( +// Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// MyText.bodyMedium( +// log.checkIn != null +// ? DateFormat('hh:mm a').format(log.checkIn!) +// : '--', +// fontWeight: 600, +// ), +// ], +// ), +// ), +// DataCell( +// Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// MyText.bodyMedium( +// log.checkOut != null +// ? DateFormat('hh:mm a').format(log.checkOut!) +// : '--', +// fontWeight: 600, +// ), +// ], +// ), +// ), +// DataCell( +// IconButton( +// icon: const Icon(Icons.visibility, size: 18), +// onPressed: () async { +// await attendanceController.fetchLogsView(log.id.toString()); +// showModalBottomSheet( +// context: context, +// isScrollControlled: true, +// shape: const RoundedRectangleBorder( +// borderRadius: +// BorderRadius.vertical(top: Radius.circular(16)), +// ), +// backgroundColor: Theme.of(context).cardColor, +// builder: (context) { +// return Padding( +// padding: EdgeInsets.only( +// left: 16, +// right: 16, +// top: 16, +// bottom: MediaQuery.of(context).viewInsets.bottom + 16, +// ), +// child: SingleChildScrollView( +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// MyText.titleMedium( +// "Attendance Log Details", +// fontWeight: 700, +// ), +// IconButton( +// icon: const Icon(Icons.close), +// onPressed: () => Navigator.pop(context), +// ), +// ], +// ), +// const SizedBox(height: 16), +// if (attendanceController +// .attendenceLogsView.isNotEmpty) ...[ +// Row( +// mainAxisAlignment: +// MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: MyText.bodyMedium("Date", +// fontWeight: 600)), +// Expanded( +// child: MyText.bodyMedium("Time", +// fontWeight: 600)), +// Expanded( +// child: MyText.bodyMedium("Description", +// fontWeight: 600)), +// Expanded( +// child: MyText.bodyMedium("Image", +// fontWeight: 600)), +// ], +// ), +// const Divider(thickness: 1, height: 24), +// ], +// if (attendanceController +// .attendenceLogsView.isNotEmpty) +// ...attendanceController.attendenceLogsView +// .map((log) => Padding( +// padding: const EdgeInsets.symmetric( +// vertical: 8.0), +// child: Row( +// mainAxisAlignment: +// MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: MyText.bodyMedium( +// log.formattedDate ?? '-', +// fontWeight: 600)), +// Expanded( +// child: MyText.bodyMedium( +// log.formattedTime ?? '-', +// fontWeight: 600)), +// Expanded( +// child: Row( +// children: [ +// if (log.latitude != null && +// log.longitude != null) +// GestureDetector( +// onTap: () async { +// final url = +// 'https://www.google.com/maps/search/?api=1&query=${log.latitude},${log.longitude}'; +// if (await canLaunchUrl( +// Uri.parse(url))) { +// await launchUrl( +// Uri.parse(url), +// mode: LaunchMode +// .externalApplication); +// } else { +// ScaffoldMessenger.of( +// context) +// .showSnackBar( +// const SnackBar( +// content: Text( +// 'Could not open Google Maps')), +// ); +// } +// }, +// child: const Padding( +// padding: +// EdgeInsets.only( +// right: 4.0), +// child: Icon( +// Icons.location_on, +// size: 18, +// color: Colors.blue), +// ), +// ), +// Expanded( +// child: MyText.bodyMedium( +// log.comment ?? '-', +// fontWeight: 600, +// ), +// ), +// ], +// ), +// ), +// Expanded( +// child: GestureDetector( +// onTap: () { +// if (log.preSignedUrl != +// null) { +// showDialog( +// context: context, +// builder: (_) => Dialog( +// child: Image.network( +// log.preSignedUrl!, +// fit: BoxFit.cover, +// height: 400, +// errorBuilder: +// (context, error, +// stackTrace) { +// return const Icon( +// Icons +// .broken_image, +// size: 50, +// color: Colors +// .grey); +// }, +// ), +// ), +// ); +// } +// }, +// child: log.thumbPreSignedUrl != +// null +// ? Image.network( +// log.thumbPreSignedUrl!, +// height: 40, +// width: 40, +// fit: BoxFit.cover, +// errorBuilder: (context, +// error, stackTrace) { +// return const Icon( +// Icons +// .broken_image, +// size: 40, +// color: +// Colors.grey); +// }, +// ) +// : const Icon( +// Icons.broken_image, +// size: 40, +// color: Colors.grey), +// ), +// ), +// ], +// ), +// )), +// ], +// ), +// ), +// ); +// }, +// ); +// }, +// ), +// ), +// DataCell( +// Obx(() { +// final uniqueLogKey = +// AttendanceButtonHelper.getUniqueKey(log.employeeId, log.id); +// final isUploading = +// attendanceController.uploadingStates[uniqueLogKey]?.value ?? +// false; - final isYesterday = AttendanceButtonHelper.isLogFromYesterday( - log.checkIn, log.checkOut); - final isTodayApproved = AttendanceButtonHelper.isTodayApproved( - log.activity, log.checkIn); - final isApprovedButNotToday = - AttendanceButtonHelper.isApprovedButNotToday( - log.activity, isTodayApproved); +// final isYesterday = AttendanceButtonHelper.isLogFromYesterday( +// log.checkIn, log.checkOut); +// final isTodayApproved = AttendanceButtonHelper.isTodayApproved( +// log.activity, log.checkIn); +// final isApprovedButNotToday = +// AttendanceButtonHelper.isApprovedButNotToday( +// log.activity, isTodayApproved); - final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( - isUploading: isUploading, - isYesterday: isYesterday, - activity: log.activity, - isApprovedButNotToday: isApprovedButNotToday, - ); +// final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( +// isUploading: isUploading, +// isYesterday: isYesterday, +// activity: log.activity, +// isApprovedButNotToday: isApprovedButNotToday, +// ); - return ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100, maxWidth: 150), - child: SizedBox( - height: 30, - child: ElevatedButton( - onPressed: isButtonDisabled - ? null - : () async { - attendanceController.uploadingStates[uniqueLogKey] = - RxBool(true); +// return ConstrainedBox( +// constraints: const BoxConstraints(minWidth: 100, maxWidth: 150), +// child: SizedBox( +// height: 30, +// 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"), - ), - ); - attendanceController - .uploadingStates[uniqueLogKey] = - RxBool(false); - return; - } +// if (attendanceController.selectedProjectId == +// null) { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: +// Text("Please select a project first"), +// ), +// ); +// attendanceController +// .uploadingStates[uniqueLogKey] = +// RxBool(false); +// return; +// } - int updatedAction; - String actionText; - bool imageCapture = true; +// 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; - break; - case 4: - updatedAction = isTodayApproved ? 0 : 0; - actionText = ButtonActions.checkIn; - break; - default: - updatedAction = 0; - actionText = "Unknown Action"; - break; - } +// 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; +// 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()}'), - ), - ); - } - } 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()}'), - ), - ); - } +// 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()}'), +// ), +// ); +// } +// } 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, - ), - 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(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), - ), - ), - ), - ), - ); - }), - ), - ])); - } - }); +// 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), +// ), +// child: isUploading +// ? const SizedBox( +// width: 16, +// height: 16, +// child: CircularProgressIndicator( +// strokeWidth: 2, +// valueColor: +// AlwaysStoppedAnimation(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), +// ), +// ), +// ), +// ), +// ); +// }), +// ), +// ])); +// } +// }); - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: GetBuilder( - id: 'attendance_dashboard_controller', - builder: (controller) { - String labelText; - if (controller.startDateAttendance != null && - controller.endDateAttendance != null) { - final start = DateFormat('dd MM yyyy') - .format(controller.startDateAttendance!); - final end = DateFormat('dd MM yyyy') - .format(controller.endDateAttendance!); - labelText = "$start - $end"; - } else { - labelText = "Select Date Range for Attendance"; - } +// return SingleChildScrollView( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Padding( +// padding: const EdgeInsets.all(8.0), +// child: GetBuilder( +// id: 'attendance_dashboard_controller', +// builder: (controller) { +// String labelText; +// if (controller.startDateAttendance != null && +// controller.endDateAttendance != null) { +// final start = DateFormat('dd MM yyyy') +// .format(controller.startDateAttendance!); +// final end = DateFormat('dd MM yyyy') +// .format(controller.endDateAttendance!); +// labelText = "$start - $end"; +// } else { +// labelText = "Select Date Range for Attendance"; +// } - return TextButton.icon( - icon: const Icon(Icons.date_range), - label: Text(labelText, overflow: TextOverflow.ellipsis), - onPressed: () => controller.selectDateRangeForAttendance( - Get.context!, controller), - ); - }, - ), - ), - if (attendanceController.attendanceLogs.isEmpty) - Align( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 40), - child: MyText.bodySmall( - "No Attendance Records Found", - fontWeight: 600, - ), - ), - ) - else - MyRefreshableContent( - onRefresh: () async { - if (attendanceController.selectedProjectId != null) { - await attendanceController.fetchAttendanceLogs( - attendanceController.selectedProjectId!); - await attendanceController.fetchProjectData( - attendanceController.selectedProjectId!); - attendanceController.update(); - } else { - await attendanceController.fetchProjects(); - } - }, - child: SingleChildScrollView( - child: MyPaginatedTable( - columns: columns, - rows: rows, - ), - ), - ) - ], - ), - ); - } +// return TextButton.icon( +// icon: const Icon(Icons.date_range), +// label: Text(labelText, overflow: TextOverflow.ellipsis), +// onPressed: () => controller.selectDateRangeForAttendance( +// Get.context!, controller), +// ); +// }, +// ), +// ), +// if (attendanceController.attendanceLogs.isEmpty) +// Align( +// alignment: Alignment.center, +// child: Padding( +// padding: const EdgeInsets.symmetric(vertical: 40), +// child: MyText.bodySmall( +// "No Attendance Records Found", +// fontWeight: 600, +// ), +// ), +// ) +// else +// MyRefreshableContent( +// onRefresh: () async { +// if (attendanceController.selectedProjectId != null) { +// await attendanceController.fetchAttendanceLogs( +// attendanceController.selectedProjectId!); +// await attendanceController.fetchProjectData( +// attendanceController.selectedProjectId!); +// attendanceController.update(); +// } else { +// await attendanceController.fetchProjects(); +// } +// }, +// child: SingleChildScrollView( +// child: MyPaginatedTable( +// columns: columns, +// rows: rows, +// ), +// ), +// ) +// ], +// ), +// ); +// } - Widget regularizationTab(BuildContext context) { - final attendanceController = Get.find(); +// Widget regularizationTab(BuildContext context) { +// final attendanceController = Get.find(); - final columns = [ - DataColumn(label: MyText.labelLarge('Name', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Check-In', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Check-Out', color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Action', color: contentTheme.primary)), - ]; +// final columns = [ +// DataColumn(label: MyText.labelLarge('Name', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Check-In', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Check-Out', color: contentTheme.primary)), +// DataColumn( +// label: MyText.labelLarge('Action', color: contentTheme.primary)), +// ]; - final rows = - attendanceController.regularizationLogs.mapIndexed((index, log) { - final uniqueLogKey = - '${log.id}-${log.employeeId}'; // Unique key for each log - final isUploading = - attendanceController.uploadingStates[uniqueLogKey]?.value ?? - false; // Check the upload state +// final rows = +// attendanceController.regularizationLogs.mapIndexed((index, log) { +// final uniqueLogKey = +// '${log.id}-${log.employeeId}'; // Unique key for each log +// final isUploading = +// attendanceController.uploadingStates[uniqueLogKey]?.value ?? +// false; // Check the upload state - return DataRow(cells: [ - DataCell( - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyText.bodyMedium(log.name, fontWeight: 600), - SizedBox(height: 2), - MyText.bodySmall(log.role, color: Colors.grey), - ], - ), - ), - DataCell( - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium( - log.checkIn != null - ? DateFormat('dd/MMM/yyyy').format(log.checkIn!) - : '-', - fontWeight: 600, - ), - MyText.bodyMedium( - log.checkIn != null - ? DateFormat('hh:mm a').format(log.checkIn!) - : '', - fontWeight: 600, - ), - ], - ), - ), - DataCell( - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium( - log.checkOut != null - ? DateFormat('dd MMM yyyy').format(log.checkOut!) - : '-', - fontWeight: 600, - ), - MyText.bodyMedium( - log.checkOut != null - ? DateFormat('hh:mm a').format(log.checkOut!) - : '', - fontWeight: 600, - ), - ], - ), - ), - DataCell( - Row( - children: [ - // Approve Button - 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; - } +// return DataRow(cells: [ +// DataCell( +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// MyText.bodyMedium(log.name, fontWeight: 600), +// SizedBox(height: 2), +// MyText.bodySmall(log.role, color: Colors.grey), +// ], +// ), +// ), +// DataCell( +// Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// MyText.bodyMedium( +// log.checkIn != null +// ? DateFormat('dd/MMM/yyyy').format(log.checkIn!) +// : '-', +// fontWeight: 600, +// ), +// MyText.bodyMedium( +// log.checkIn != null +// ? DateFormat('hh:mm a').format(log.checkIn!) +// : '', +// fontWeight: 600, +// ), +// ], +// ), +// ), +// DataCell( +// Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// MyText.bodyMedium( +// log.checkOut != null +// ? DateFormat('dd MMM yyyy').format(log.checkOut!) +// : '-', +// fontWeight: 600, +// ), +// MyText.bodyMedium( +// log.checkOut != null +// ? DateFormat('hh:mm a').format(log.checkOut!) +// : '', +// fontWeight: 600, +// ), +// ], +// ), +// ), +// DataCell( +// Row( +// children: [ +// // Approve Button +// 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; +// attendanceController +// .uploadingStates[uniqueLogKey]?.value = true; - final success = await attendanceController - .captureAndUploadAttendance( - log.id, - log.employeeId, - attendanceController.selectedProjectId!, - comment: "Accepted", - action: 4, - imageCapture: false, - ); +// final success = await attendanceController +// .captureAndUploadAttendance( +// log.id, +// log.employeeId, +// attendanceController.selectedProjectId!, +// comment: "Accepted", +// action: 4, +// imageCapture: false, +// ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(success - ? 'Approval marked successfully!' - : 'Failed to mark approval.'), - ), - ); +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// content: Text(success +// ? 'Approval marked successfully!' +// : 'Failed to mark approval.'), +// ), +// ); - 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; - }, - 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, - ), - ), - ), - ), - ), +// 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, +// ), +// ), +// ), +// ), +// ), +// const SizedBox(width: 8), +// // Reject Button +// 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; +// } - const SizedBox(width: 8), +// attendanceController +// .uploadingStates[uniqueLogKey]?.value = true; - // Reject Button - 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; - } +// final success = await attendanceController +// .captureAndUploadAttendance( +// log.id, +// log.employeeId, +// attendanceController.selectedProjectId!, +// comment: "Rejected", +// action: 5, +// imageCapture: false, +// ); - attendanceController - .uploadingStates[uniqueLogKey]?.value = true; +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// content: Text(success +// ? 'Attendance marked as Rejected!' +// : 'Failed to mark attendance.'), +// ), +// ); - final success = await attendanceController - .captureAndUploadAttendance( - log.id, - log.employeeId, - attendanceController.selectedProjectId!, - comment: "Rejected", - action: 5, - 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 - ? 'Attendance marked as Rejected!' - : 'Failed to mark attendance.'), - ), - ); +// 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, +// ), +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ]); +// }).toList(); - if (success) { - attendanceController.fetchEmployeesByProject( - attendanceController.selectedProjectId!); - attendanceController.fetchAttendanceLogs( - attendanceController.selectedProjectId!); - await attendanceController - .fetchRegularizationLogs( - attendanceController.selectedProjectId!); - await attendanceController.fetchProjectData( - attendanceController.selectedProjectId!); - } +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Padding( +// padding: const EdgeInsets.all(8.0), +// ), +// if (attendanceController.regularizationLogs.isEmpty) +// Expanded( +// child: Center( +// child: MyText.bodySmall("No Regularization Records Found", +// fontWeight: 600), +// ), +// ) +// else +// Expanded( +// child: MyRefreshableContent( +// onRefresh: () async { +// if (attendanceController.selectedProjectId != null) { +// await attendanceController.fetchProjectData( +// attendanceController.selectedProjectId!); +// await attendanceController.fetchRegularizationLogs( +// attendanceController.selectedProjectId!); +// attendanceController.update(); +// } else { +// await attendanceController.fetchProjects(); +// } +// }, +// child: SingleChildScrollView( +// child: MyPaginatedTable( +// columns: columns, +// rows: rows, +// columnSpacing: 15.0, +// ), +// ), +// ), +// ) +// ], +// ); +// } - 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, - ), - ), - ), - ), - ), - ], - ), - ), - ]); - }).toList(); +// Future showTimePickerForRegularization({ +// required BuildContext context, +// required DateTime checkInTime, +// }) async { +// final pickedTime = await showTimePicker( +// context: context, +// initialTime: TimeOfDay.fromDateTime(DateTime.now()), +// ); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - ), - if (attendanceController.regularizationLogs.isEmpty) - Expanded( - child: Center( - child: MyText.bodySmall("No Regularization Records Found", - fontWeight: 600), - ), - ) - else - Expanded( - child: MyRefreshableContent( - onRefresh: () async { - if (attendanceController.selectedProjectId != null) { - await attendanceController.fetchProjectData( - attendanceController.selectedProjectId!); - await attendanceController.fetchRegularizationLogs( - attendanceController.selectedProjectId!); - attendanceController.update(); - } else { - await attendanceController.fetchProjects(); - } - }, - child: SingleChildScrollView( - child: MyPaginatedTable( - columns: columns, - rows: rows, - columnSpacing: 15.0, - ), - ), - ), - ) - ], - ); - } +// if (pickedTime != null) { +// final selectedDateTime = DateTime( +// checkInTime.year, +// checkInTime.month, +// checkInTime.day, +// pickedTime.hour, +// pickedTime.minute, +// ); - Future showTimePickerForRegularization({ - required BuildContext context, - required DateTime checkInTime, - }) async { - final pickedTime = await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(DateTime.now()), - ); - - if (pickedTime != null) { - final selectedDateTime = DateTime( - checkInTime.year, - checkInTime.month, - checkInTime.day, - pickedTime.hour, - pickedTime.minute, - ); - - // Ensure selected time is after check-in time - if (selectedDateTime.isAfter(checkInTime)) { - return selectedDateTime; - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Please select a time after check-in time.")), - ); - return null; - } - } - return null; - } -} +// // Ensure selected time is after check-in time +// if (selectedDateTime.isAfter(checkInTime)) { +// return selectedDateTime; +// } else { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text("Please select a time after check-in time.")), +// ); +// return null; +// } +// } +// return null; +// } +// }