From 7936072b74a9681e042f242705d9e805b0d5dfae Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Thu, 24 Apr 2025 16:49:33 +0530 Subject: [PATCH] Add attendance log and regularization log models; enhance attendance screen with date range selection and regularization tab --- .../attendance_screen_controller.dart | 236 ++++++++------ lib/helpers/services/api_service.dart | 303 ++++++++---------- ...ogModel.dart => attendance_log_model.dart} | 3 + lib/model/attendance_log_view_model.dart | 24 ++ lib/model/regularization_log_model.dart | 25 ++ lib/view/dashboard/attendanceScreen.dart | 164 +++++++++- 6 files changed, 496 insertions(+), 259 deletions(-) rename lib/model/{AttendanceLogModel.dart => attendance_log_model.dart} (89%) create mode 100644 lib/model/attendance_log_view_model.dart create mode 100644 lib/model/regularization_log_model.dart diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index f06a1ad..69800f7 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -1,85 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:get/get.dart'; + import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/model/attendance_model.dart'; -import 'package:marco/model/project_model.dart'; // Assuming you have a ProjectModel for the projects. -import 'package:marco/model/employee_model.dart'; // Assuming you have an EmployeeModel for the employees. -import 'package:marco/model/AttendanceLogModel.dart'; -import 'package:flutter/material.dart'; +import 'package:marco/model/project_model.dart'; +import 'package:marco/model/employee_model.dart'; +import 'package:marco/model/attendance_log_model.dart'; +import 'package:marco/model/regularization_log_model.dart'; +import 'package:marco/model/attendance_log_view_model.dart'; class AttendanceController extends GetxController { List attendances = []; - List projects = []; // List of projects - String? selectedProjectId; // Currently selected project ID - List employees = []; // Employees of the selected project + List projects = []; + String? selectedProjectId; + List employees = []; + + DateTime? startDateAttendance; + DateTime? endDateAttendance; + + List attendanceLogs = []; + List regularizationLogs = []; + List attendenceLogsView = []; @override void onInit() { super.onInit(); - fetchProjects(); // Fetch projects when initializing - // fetchAttendanceLogs(selectedProjectId); - // fetchAttendanceLogs(selectedProjectId); + _initializeDefaults(); + } + + void _initializeDefaults() { + _setDefaultDateRange(); + fetchProjects(); + } + + void _setDefaultDateRange() { + final today = DateTime.now(); + startDateAttendance = today.subtract(const Duration(days: 7)); + endDateAttendance = today; } - // Fetch projects from API Future fetchProjects() async { - var response = await ApiService.getProjects(); // Call the project API + final response = await ApiService.getProjects(); - if (response != null) { - projects = response - .map((json) => ProjectModel.fromJson(json)) - .toList(); - - // Set default to the first project if available - if (projects.isNotEmpty) { - selectedProjectId = projects.first.id.toString(); - await fetchEmployeesByProject( - selectedProjectId); // Fetch employees for the first project - } - - update([ - 'attendance_dashboard_controller' - ]); // Notify GetBuilder with your tag + if (response != null && response.isNotEmpty) { + projects = response.map((json) => ProjectModel.fromJson(json)).toList(); + selectedProjectId = projects.first.id.toString(); + await _fetchProjectData(selectedProjectId); + update(['attendance_dashboard_controller']); } else { print("No projects data found or failed to fetch data."); } } - // Fetch employees by project ID + Future _fetchProjectData(String? projectId) async { + if (projectId == null) return; + + await Future.wait([ + fetchEmployeesByProject(projectId), + fetchAttendanceLogs(projectId, + dateFrom: startDateAttendance, dateTo: endDateAttendance), + fetchRegularizationLogs(projectId), + ]); + } + Future fetchEmployeesByProject(String? projectId) async { if (projectId == null) return; - var response = await ApiService.getEmployeesByProject(int.parse(projectId)); - + final response = + await ApiService.getEmployeesByProject(int.parse(projectId)); if (response != null) { - employees = response - .map((json) => EmployeeModel.fromJson(json)) - .toList(); - update(); // Trigger UI rebuild + employees = response.map((json) => EmployeeModel.fromJson(json)).toList(); + update(); } else { print("Failed to fetch employees for project $projectId."); } } - Future captureAndUploadAttendance(int employeeId, int projectId, - {String comment = "Marked via mobile app"}) async { + Future captureAndUploadAttendance( + int employeeId, + int projectId, { + String comment = "Marked via mobile app", + }) async { try { - final XFile? image = await ImagePicker().pickImage( + final image = await ImagePicker().pickImage( source: ImageSource.camera, imageQuality: 80, ); - if (image == null) return false; final position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high); - - String imageName = ApiService.generateImageName( - employeeId, - employees.length + 1, + desiredAccuracy: LocationAccuracy.high, ); + final imageName = + ApiService.generateImageName(employeeId, employees.length + 1); + return await ApiService.uploadAttendanceImage( employeeId, image, @@ -90,57 +107,94 @@ class AttendanceController extends GetxController { comment: comment, ); } catch (e) { - print("Error capturing or uploading: $e"); + print("Error capturing or uploading attendance: $e"); return false; } } -DateTime? startDate; -DateTime? endDate; - -Future selectDateRange(BuildContext context, AttendanceController controller) async { - final DateTimeRange? picked = await showDateRangePicker( - context: context, - firstDate: DateTime(2022), - lastDate: DateTime.now(), - initialDateRange: DateTimeRange( - start: startDate ?? DateTime.now().subtract(Duration(days: 7)), - end: endDate ?? DateTime.now(), - ), - ); - - if (picked != null) { - startDate = picked.start; - endDate = picked.end; - - await controller.fetchAttendanceLogs( - controller.selectedProjectId, - dateFrom: startDate, - dateTo: endDate, + Future selectDateRangeForAttendance( + BuildContext context, + AttendanceController controller, + ) async { + final picked = await showDateRangePicker( + context: context, + firstDate: DateTime(2022), + lastDate: DateTime.now(), + initialDateRange: DateTimeRange( + start: startDateAttendance ?? + DateTime.now().subtract(const Duration(days: 7)), + end: endDateAttendance ?? DateTime.now(), + ), ); + + if (picked != null) { + startDateAttendance = picked.start; + endDateAttendance = picked.end; + + await controller.fetchAttendanceLogs( + controller.selectedProjectId, + dateFrom: picked.start, + dateTo: picked.end, + ); + } + } + + Future fetchAttendanceLogs( + String? projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { + if (projectId == null) return; + + final response = await ApiService.getAttendanceLogs( + int.parse(projectId), + dateFrom: dateFrom, + dateTo: dateTo, + ); + + if (response != null) { + attendanceLogs = + response.map((json) => AttendanceLogModel.fromJson(json)).toList(); + print("Attendance logs fetched: ${response}"); + update(); + } else { + print("Failed to fetch attendance logs for project $projectId."); + } + } + + Future fetchRegularizationLogs( + String? projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { + if (projectId == null) return; + + final response = + await ApiService.getRegularizationLogs(int.parse(projectId)); + + if (response != null) { + regularizationLogs = response + .map((json) => RegularizationLogModel.fromJson(json)) + .toList(); + update(); + } else { + print("Failed to fetch regularization logs for project $projectId."); + } + } + + Future fetchLogsView(String? id) async { + if (id == null) return; + + final response = + await ApiService.getAttendanceLogView(int.parse(id)); + + if (response != null) { + attendenceLogsView = response + .map((json) => AttendanceLogViewModel.fromJson(json)) + .toList(); + update(); + } else { + print("Failed to fetch regularization logs for project $id."); + } } -} - - - List attendanceLogs = []; -Future fetchAttendanceLogs(String? projectId, - {DateTime? dateFrom, DateTime? dateTo}) async { - if (projectId == null) return; - - var response = await ApiService.getAttendanceLogs( - int.parse(projectId), - dateFrom: dateFrom, - dateTo: dateTo, - ); - - if (response != null) { - attendanceLogs = response - .map((json) => AttendanceLogModel.fromJson(json)) - .toList(); - update(); - } else { - print("Failed to fetch logs for project $projectId."); - } -} - } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index c820a2c..b4c58d5 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -4,147 +4,69 @@ import 'package:image_picker/image_picker.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:intl/intl.dart'; + class ApiService { static const String baseUrl = "https://api.marcoaiot.com/api"; - // Fetch the list of projects - static Future?> getProjects() async { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return null; - } + // ===== Common Helpers ===== - final response = await http.get( - Uri.parse("$baseUrl/project/list"), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', // Add Authorization header - }, - ); - - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - if (json['success'] == true) { - return json['data']; // Return the data if success is true - } else { - print("Error: ${json['message']}"); - } - } else { - print("Error fetching projects: ${response.statusCode}"); - } - } catch (e) { - print("Exception while fetching projects: $e"); + static Future _getToken() async { + final token = LocalStorage.getJwtToken(); + if (token == null) { + print("No JWT token found. Please log in."); } - return null; + return token; } - // Fetch employees by project ID - static Future?> getEmployeesByProject(int projectId) async { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return null; - } - - final response = await http.get( - Uri.parse( - "$baseUrl/attendance/project/team?projectId=$projectId"), // Ensure correct endpoint - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', - }, - ); - - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - if (json['success'] == true) { - return json['data']; // Return employee data - } else { - print("Error: ${json['message']}"); - } - } else { - print("Error fetching employees: ${response.statusCode}"); - } - } catch (e) { - print("Exception while fetching employees: $e"); - } - return null; - } - - static String generateImageName(int employeeId, int count) { - final now = DateTime.now(); - final formattedDate = "${now.year.toString().padLeft(4, '0')}" - "${now.month.toString().padLeft(2, '0')}" - "${now.day.toString().padLeft(2, '0')}_" - "${now.hour.toString().padLeft(2, '0')}" - "${now.minute.toString().padLeft(2, '0')}" - "${now.second.toString().padLeft(2, '0')}"; - final imageNumber = count.toString().padLeft(3, '0'); - return "${employeeId}_${formattedDate}_$imageNumber.jpg"; - } - - static Future uploadAttendanceImage( - int employeeId, XFile imageFile, double latitude, double longitude, - {required String imageName, - required int projectId, - String comment = "", - int action = 0}) async { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return false; - } - - final bytes = await imageFile.readAsBytes(); - final base64Image = base64Encode(bytes); - final fileSize = await imageFile.length(); - final contentType = "image/${imageFile.path.split('.').last}"; - - final imageObject = { - "FileName": imageName, - "Base64Data": base64Image, - "ContentType": contentType, - "FileSize": fileSize, - "Description": "Employee attendance photo" + static Map _headers(String token) => { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', }; - final now = DateTime.now(); + static Future _getRequest(String endpoint, + {Map? queryParams}) async { + final token = await _getToken(); + if (token == null) return null; - // You can now include the attendance record directly in the main body - final response = await http.post( - Uri.parse("$baseUrl/attendance/record"), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', - }, - body: jsonEncode({ - "ID": null, - "employeeId": employeeId, - "projectId": projectId, - "markTime": DateFormat('hh:mm a').format(now), - "comment": comment, - "action": action, - "date": DateFormat('yyyy-MM-dd').format(now), - "latitude": latitude, - "longitude": longitude, - "image": [imageObject], // Directly included in the body - }), - ); - print('uploadAttendanceImage: $baseUrl/attendance/record'); - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - return json['success'] == true; - } else { - print("Error uploading image: ${response.statusCode}"); - } + final uri = + Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams); + print('GET request: $uri'); + + try { + return await http.get(uri, headers: _headers(token)); } catch (e) { - print("Exception during image upload: $e"); + print("HTTP GET Exception: $e"); + return null; } - return false; + } + + static dynamic _parseResponse(http.Response response, {String label = ''}) { + print("$label Response: ${response.body}"); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + if (json['success'] == true) return json['data']; + print("API Error: ${json['message']}"); + } else { + print("HTTP Error [$label]: ${response.statusCode}"); + } + return null; + } + + // ===== API Calls ===== + + static Future?> getProjects() async { + final response = await _getRequest("/project/list"); + return response != null + ? _parseResponse(response, label: 'Projects') + : null; + } + + static Future?> getEmployeesByProject(int projectId) async { + final response = await _getRequest("/attendance/project/team", + queryParams: {"projectId": "$projectId"}); + return response != null + ? _parseResponse(response, label: 'Employees') + : null; } static Future?> getAttendanceLogs( @@ -152,46 +74,101 @@ class ApiService { DateTime? dateFrom, DateTime? dateTo, }) async { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return null; - } + final query = { + "projectId": "$projectId", + if (dateFrom != null) + "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), + if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), + }; - final queryParameters = { - "projectId": projectId.toString(), - if (dateFrom != null) - "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), - if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), + final response = + await _getRequest("/attendance/project/team", queryParams: query); + return response != null + ? _parseResponse(response, label: 'Attendance Logs') + : null; + } + + static Future?> getAttendanceLogView(int id) async { + final response = await _getRequest("/attendance/log/attendance/$id"); + return response != null + ? _parseResponse(response, label: 'Attendance Log Details') + : null; + } + + static Future?> getRegularizationLogs(int projectId) async { + final response = await _getRequest("/attendance/regularize", + queryParams: {"projectId": "$projectId"}); + return response != null + ? _parseResponse(response, label: 'Regularization') + : null; + } + + // ===== Upload Image ===== + + static Future uploadAttendanceImage( + int employeeId, + XFile imageFile, + double latitude, + double longitude, { + required String imageName, + required int projectId, + String comment = "", + int action = 0, + }) async { + final token = await _getToken(); + if (token == null) return false; + + try { + final bytes = await imageFile.readAsBytes(); + final base64Image = base64Encode(bytes); + final fileSize = await imageFile.length(); + final contentType = "image/${imageFile.path.split('.').last}"; + + final imageObject = { + "fileName": '$imageName', + "contentType": '$contentType', + "fileSize": fileSize, + "description": "Employee attendance photo", + "base64Data": '$base64Image', }; - final uri = Uri.parse("$baseUrl/attendance/project/team") - .replace(queryParameters: queryParameters); - print('uri: $uri'); - final response = await http.get( - uri, - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', - }, + final now = DateTime.now(); + + final body = { + "id": null, + "employeeId": employeeId, + "projectId": projectId, + "markTime": DateFormat('hh:mm a').format(now), + "comment": comment, + "action": action, + "date": DateFormat('yyyy-MM-dd').format(now), + "latitude": '$latitude', + "longitude": '$longitude', + "image": imageObject + }; + print("Upload body Image: $body"); + final response = await http.post( + Uri.parse("$baseUrl/attendance/record-image"), + headers: _headers(token), + body: jsonEncode(body), ); - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - print("Response body: ${response.body}"); - if (json['success'] == true) { - return json['data']; - - } else { - print("Error: ${json['message']}"); - } - } else { - print("Error fetching logs: ${response.statusCode}"); - } + print('Upload request: $baseUrl/attendance/record-image'); + final json = jsonDecode(response.body); + print("Upload Image response: ${response.body}"); + return response.statusCode == 200 && json['success'] == true; } catch (e) { - print("Exception while fetching logs: $e"); + print("Exception during image upload: $e"); + return false; } - return null; + } + + // ===== Utilities ===== + + static String generateImageName(int employeeId, int count) { + final now = DateTime.now(); + final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now); + final imageNumber = count.toString().padLeft(3, '0'); + return "${employeeId}_${dateStr}_$imageNumber.jpg"; } } diff --git a/lib/model/AttendanceLogModel.dart b/lib/model/attendance_log_model.dart similarity index 89% rename from lib/model/AttendanceLogModel.dart rename to lib/model/attendance_log_model.dart index bf54b2d..9f965ff 100644 --- a/lib/model/AttendanceLogModel.dart +++ b/lib/model/attendance_log_model.dart @@ -4,6 +4,7 @@ class AttendanceLogModel { final DateTime? checkIn; final DateTime? checkOut; final int activity; + final int id; AttendanceLogModel({ required this.name, @@ -11,6 +12,7 @@ class AttendanceLogModel { this.checkIn, this.checkOut, required this.activity, + required this.id, }); factory AttendanceLogModel.fromJson(Map json) { @@ -20,6 +22,7 @@ class AttendanceLogModel { checkIn: json['checkInTime'] != null ? DateTime.tryParse(json['checkInTime']) : null, checkOut: json['checkOutTime'] != null ? DateTime.tryParse(json['checkOutTime']) : null, activity: json['activity'] ?? 0, + id: json['id'] != null ? json['id'] : null, ); } } diff --git a/lib/model/attendance_log_view_model.dart b/lib/model/attendance_log_view_model.dart new file mode 100644 index 0000000..6380b5c --- /dev/null +++ b/lib/model/attendance_log_view_model.dart @@ -0,0 +1,24 @@ +import 'package:intl/intl.dart'; +class AttendanceLogViewModel { + final DateTime? activityTime; + final String? imageUrl; + final String? description; + + AttendanceLogViewModel({ + this.activityTime, + this.imageUrl, + this.description, + }); + + factory AttendanceLogViewModel.fromJson(Map json) { + return AttendanceLogViewModel( + activityTime: json['activityTime'] != null ? DateTime.tryParse(json['activityTime']) : null, + imageUrl: json['imageUrl'], + description: json['description'], + ); + } + + 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/regularization_log_model.dart b/lib/model/regularization_log_model.dart new file mode 100644 index 0000000..8d26b1b --- /dev/null +++ b/lib/model/regularization_log_model.dart @@ -0,0 +1,25 @@ +class RegularizationLogModel { + final String name; + final String role; + final DateTime? checkIn; + final DateTime? checkOut; + final int activity; + + RegularizationLogModel({ + required this.name, + required this.role, + this.checkIn, + this.checkOut, + required this.activity, + }); + + factory RegularizationLogModel.fromJson(Map json) { + return RegularizationLogModel( + name: "${json['firstName'] ?? ''} ${json['lastName'] ?? ''}".trim(), + role: json['jobRoleName'] ?? '', + checkIn: json['checkInTime'] != null ? DateTime.tryParse(json['checkInTime']) : null, + checkOut: json['checkOutTime'] != null ? DateTime.tryParse(json['checkOutTime']) : null, + activity: json['activity'] ?? 0, + ); + } +} diff --git a/lib/view/dashboard/attendanceScreen.dart b/lib/view/dashboard/attendanceScreen.dart index dcd44e7..18d0d1f 100644 --- a/lib/view/dashboard/attendanceScreen.dart +++ b/lib/view/dashboard/attendanceScreen.dart @@ -76,6 +76,8 @@ class _AttendanceScreenState extends State with UIMixin { .fetchEmployeesByProject(value); attendanceController .fetchAttendanceLogs(value); + attendanceController + .fetchAttendanceLogs(value); }); }, itemBuilder: (BuildContext context) { @@ -134,7 +136,7 @@ class _AttendanceScreenState extends State with UIMixin { children: [ MyFlexItem( child: DefaultTabController( - length: 2, + length: 3, child: MyCard.bordered( borderRadiusAll: 4, border: @@ -154,6 +156,7 @@ class _AttendanceScreenState extends State with UIMixin { tabs: const [ Tab(text: 'Employee List'), Tab(text: 'Logs'), + Tab(text: 'Regularization'), ], ), MySpacing.height(16), @@ -163,6 +166,7 @@ class _AttendanceScreenState extends State with UIMixin { children: [ employeeListTab(), reportsTab(context), + regularizationTab(context), ], ), ), @@ -235,7 +239,7 @@ class _AttendanceScreenState extends State with UIMixin { SnackBar( content: Text( success - ? 'Image uploaded successfully!' + ? 'Attendence Marked successfully!' : 'Image upload failed.', ), ), @@ -260,9 +264,9 @@ class _AttendanceScreenState extends State with UIMixin { padding: const EdgeInsets.all(8.0), child: TextButton.icon( icon: Icon(Icons.date_range), - label: Text("Select Date Range"), - onPressed: () => attendanceController.selectDateRange(context, attendanceController), - + label: Text("Select Date Range for Attendance"), + onPressed: () => attendanceController.selectDateRangeForAttendance( + context, attendanceController), ), ), if (attendanceController.attendanceLogs.isEmpty) @@ -308,6 +312,156 @@ class _AttendanceScreenState extends State with UIMixin { color: contentTheme.primary)), ], rows: attendanceController.attendanceLogs + .mapIndexed((index, log) => DataRow(cells: [ + DataCell( + MyText.bodyMedium(log.name, fontWeight: 600)), + DataCell( + MyText.bodyMedium(log.role, fontWeight: 600)), + DataCell(MyText.bodyMedium( + log.checkIn != null + ? DateFormat('dd MMM yyyy hh:mm a') + .format(log.checkIn!) + : '-', + fontWeight: 600, + )), + DataCell(MyText.bodyMedium( + log.checkOut != null + ? DateFormat('dd MMM yyyy hh:mm a') + .format(log.checkOut!) + : '-', + fontWeight: 600, + )), + DataCell( + ElevatedButton( + onPressed: () async { + // Call fetchLogsView to load the log data + await attendanceController.fetchLogsView(log.id + .toString()); // Assuming `log.id` is available + // Open the bottom sheet to display the log details + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(16)), + ), + backgroundColor: theme.cardTheme.color, + builder: (context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + MyText.titleMedium( + "Attendance Log Details", + fontWeight: 700), + const SizedBox(height: 16), + // Display the log details + if (attendanceController + .attendenceLogsView.isNotEmpty) + ...attendanceController + .attendenceLogsView + .map((log) => Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + MyText.bodyMedium( + "Date: ${log.formattedDate ?? '-'}", + fontWeight: 600, + ), + MyText.bodyMedium( + "Time: ${log.formattedTime ?? '-'}", + fontWeight: 600, + ), + MyText.bodyMedium( + "Description: ${log.description ?? '-'}", + fontWeight: 600, + ), + const Divider( + thickness: 1, + height: 24), + ], + )), + Align( + alignment: Alignment.centerRight, + child: ElevatedButton( + onPressed: () => + Navigator.pop(context), + child: const Text("Close"), + ), + ) + ], + ), + ); + }, + ); + }, + child: const Text('View'), + ), + ) + ])) + .toList(), + ), + ), + ), + ], + ); + } + + Widget regularizationTab(BuildContext context) { + final attendanceController = Get.find(); + + 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: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + sortAscending: true, + columnSpacing: 15, + headingRowColor: + WidgetStatePropertyAll(contentTheme.primary.withAlpha(40)), + dataRowMaxHeight: 60, + showBottomBorder: true, + clipBehavior: Clip.antiAliasWithSaveLayer, + border: TableBorder.all( + borderRadius: BorderRadius.circular(4), + style: BorderStyle.solid, + width: 0.4, + color: Colors.grey, + ), + columns: [ + DataColumn( + label: MyText.labelLarge('Name', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Role', + 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)), + ], + rows: attendanceController.regularizationLogs .mapIndexed((index, log) => DataRow(cells: [ DataCell( MyText.bodyMedium(log.name, fontWeight: 600)),