diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index 57187bd..3f36f1f 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -1,83 +1,106 @@ +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: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 + _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 id, + int employeeId, + int projectId, { + String comment = "Marked via mobile app", + required int action, + }) 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( + id, employeeId, image, position.latitude, @@ -85,26 +108,97 @@ class AttendanceController extends GetxController { imageName: imageName, projectId: projectId, comment: comment, + action: action, ); } catch (e) { - print("Error capturing or uploading: $e"); + print("Error capturing or uploading attendance: $e"); return false; } } - List attendanceLogs = []; - Future fetchAttendanceLogs(String? projectId) async { + 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; - var response = await ApiService.getAttendanceLogs(int.parse(projectId)); + final response = await ApiService.getAttendanceLogs( + int.parse(projectId), + dateFrom: dateFrom, + dateTo: dateTo, + ); if (response != null) { - attendanceLogs = response - .map((json) => AttendanceLogModel.fromJson(json)) + 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 logs for project $projectId."); + 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."); } } } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 3d27902..b23a487 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -4,195 +4,175 @@ 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 + // ===== Common Helpers ===== + + static Future _getToken() async { + final token = LocalStorage.getJwtToken(); + if (token == null) { + print("No JWT token found. Please log in."); + } + return token; + } + + static Map _headers(String token) => { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }; + + static Future _getRequest(String endpoint, + {Map? queryParams}) async { + final token = await _getToken(); + if (token == null) return null; + + 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("HTTP GET Exception: $e"); + return null; + } + } + + 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 { - 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/project/list"), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', // Add Authorization header - }, - ); - - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - print("Response body: ${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}"); - print("Response body: ${response.body}"); - } - } catch (e) { - print("Exception while fetching projects: $e"); - } - return null; + final response = await _getRequest("/project/list"); + return response != null + ? _parseResponse(response, label: 'Projects') + : null; } - // 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); - print("Response body: ${response.body}"); - if (json['success'] == true) { - return json['data']; // Return employee data - } else { - print("Error: ${json['message']}"); - } - } else { - print("Error fetching employees: ${response.statusCode}"); - print("Response body: ${response.body}"); - } - } catch (e) { - print("Exception while fetching employees: $e"); - } - return null; + final response = await _getRequest("/attendance/project/team", + queryParams: {"projectId": "$projectId"}); + return response != null + ? _parseResponse(response, label: 'Employees') + : 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?> getAttendanceLogs( + int projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { + 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 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 { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return false; - } + int id, + int employeeId, + XFile imageFile, + double latitude, + double longitude, { + required String imageName, + required int projectId, + String comment = "", + required int action, // action passed here + }) 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, - "Base64Data": base64Image, - "ContentType": contentType, - "FileSize": fileSize, - "Description": "Employee attendance photo" + "fileName": '$imageName', + "contentType": '$contentType', + "fileSize": fileSize, + "description": "Employee attendance photo", + "base64Data": '$base64Image', }; final now = DateTime.now(); - // You can now include the attendance record directly in the main body + final body = { + "id": id, + "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("Attendance Image Upload Body: $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 - }), + Uri.parse("$baseUrl/attendance/record-image"), + headers: _headers(token), + body: jsonEncode(body), ); - print('body: ${jsonEncode({ - "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('uploadAttendanceImage: $baseUrl/attendance/record'); - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - return json['success'] == true; + print("Attendance Image Upload Response: ${response.body}"); + final json = jsonDecode(response.body); + if (response.statusCode == 200 && json['success'] == true) { + return true; } else { - print("Error uploading image: ${response.statusCode}"); - print("Response: ${response.body}"); + print("Failed to upload image. API Error: ${json['message']}"); } } catch (e) { print("Exception during image upload: $e"); } + return false; } - static Future?> getAttendanceLogs(int projectId) async { - try { - String? jwtToken = LocalStorage.getJwtToken(); - if (jwtToken == null) { - print("No JWT token found. Please log in."); - return null; - } + // ===== Utilities ===== - final response = await http.get( - Uri.parse( - "$baseUrl/attendance/project/team?projectId=$projectId"), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $jwtToken', - }, - ); - - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - if (json['success'] == true) { - return json['data']; - } else { - print("Error: ${json['message']}"); - } - } else { - print("Error fetching logs: ${response.statusCode}"); - } - } catch (e) { - print("Exception while fetching logs: $e"); - } - return null; + 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/employee_model.dart b/lib/model/employee_model.dart index f73a24c..7602c06 100644 --- a/lib/model/employee_model.dart +++ b/lib/model/employee_model.dart @@ -1,28 +1,34 @@ class EmployeeModel { final int id; + final int employeeId; final String name; final String designation; final String checkIn; final String checkOut; - final int actions; + final int activity; + int action; EmployeeModel({ required this.id, + required this.employeeId, required this.name, required this.designation, required this.checkIn, required this.checkOut, - required this.actions, + required this.action, + required this.activity, }); factory EmployeeModel.fromJson(Map json) { return EmployeeModel( - id: json['employeeId'] ?? 0, + id: json['id'] ?? 0, + employeeId: json['employeeId'] ?? 0, name: '${json['firstName']} ${json['lastName']}', designation: json['jobRoleName'] ?? '', checkIn: json['checkIn'] ?? '-', // Make sure your API returns this field checkOut: json['checkOut'] ?? '-', - actions: json['actions'] ?? 0, // Make sure your API returns this field + action: json['action'] ?? 0, // Make sure your API returns this field + activity: json['activity'] ?? 0, ); } } 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 8cc8815..08562a3 100644 --- a/lib/view/dashboard/attendanceScreen.dart +++ b/lib/view/dashboard/attendanceScreen.dart @@ -4,7 +4,6 @@ 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/utils/utils.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'; @@ -60,12 +59,84 @@ class _AttendanceScreenState extends State with UIMixin { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + MySpacing.height(flexSpacing), + // Move project selection here + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: MyContainer.bordered( + padding: MySpacing.xy(8, 8), + child: PopupMenuButton( + onSelected: (value) { + setState(() { + attendanceController.selectedProjectId = + value; + attendanceController + .fetchEmployeesByProject(value); + attendanceController + .fetchAttendanceLogs(value); + attendanceController + .fetchAttendanceLogs(value); + }); + }, + itemBuilder: (BuildContext context) { + if (attendanceController.projects.isEmpty) { + return [ + PopupMenuItem( + value: '', + child: MyText.bodySmall('No Data', + fontWeight: 600), + ) + ]; + } + return attendanceController.projects + .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.toString() == + attendanceController + .selectedProjectId) + ?.name ?? + 'Select a Project' + : 'Select a Project', + color: theme.colorScheme.onSurface, + ), + Icon(LucideIcons.chevron_down, + size: 20, + color: theme.colorScheme.onSurface), + ], + ), + ), + ), + ), + ], + ), MySpacing.height(flexSpacing), MyFlex( children: [ MyFlexItem( child: DefaultTabController( - length: 2, + length: 3, child: MyCard.bordered( borderRadiusAll: 4, border: @@ -85,6 +156,7 @@ class _AttendanceScreenState extends State with UIMixin { tabs: const [ Tab(text: 'Employee List'), Tab(text: 'Logs'), + Tab(text: 'Regularization'), ], ), MySpacing.height(16), @@ -93,7 +165,8 @@ class _AttendanceScreenState extends State with UIMixin { child: TabBarView( children: [ employeeListTab(), - reportsTab(), + reportsTab(context), + regularizationTab(context), ], ), ), @@ -115,135 +188,10 @@ class _AttendanceScreenState extends State with UIMixin { } Widget employeeListTab() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: MyContainer.bordered( - padding: MySpacing.xy(4, 8), - child: PopupMenuButton( - onSelected: (value) { - setState(() { - attendanceController.selectedProjectId = value; - attendanceController.fetchEmployeesByProject(value); - attendanceController.fetchAttendanceLogs(value); - }); - }, - itemBuilder: (BuildContext context) { - return attendanceController.projects.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.toString() == - attendanceController.selectedProjectId) - ?.name ?? - 'Select a Project' - : 'Select a Project', - color: theme.colorScheme.onSurface, - ), - Icon(LucideIcons.chevron_down, - size: 16, color: theme.colorScheme.onSurface), - ], - ), - ), - ), - ), - ], - ), - MySpacing.height(24), - attendanceController.employees.isEmpty - ? const Center(child: CircularProgressIndicator()) - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: DataTable( - sortAscending: true, - columnSpacing: 15, - onSelectAll: (_) => {}, - 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('Designation', - color: contentTheme.primary)), - DataColumn( - label: MyText.labelLarge('Actions', - color: contentTheme.primary)), - ], - rows: attendanceController.employees - .mapIndexed((index, employee) => DataRow(cells: [ - DataCell(MyText.bodyMedium(employee.name, - fontWeight: 600)), - DataCell(MyText.bodyMedium(employee.designation, - fontWeight: 600)), - DataCell( - ElevatedButton( - onPressed: () async { - final success = await attendanceController - .captureAndUploadAttendance( - employee.id, - int.parse(attendanceController - .selectedProjectId ?? - "0"), - comment: "Checked in via app", - ); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - success - ? 'Image uploaded successfully!' - : 'Image upload failed.', - ), - ), - ); - }, - child: const Text('Check In'), - ), - ), - ])) - .toList(), - ), - ), - ], - ); - } - - Widget reportsTab() { - if (attendanceController.attendanceLogs.isEmpty) { - attendanceController - .fetchAttendanceLogs(attendanceController.selectedProjectId); - return const Center(child: CircularProgressIndicator()); + if (attendanceController.employees.isEmpty) { + return Center( + child: MyText.bodySmall("No Employees Found", fontWeight: 600), + ); } return SingleChildScrollView( @@ -266,42 +214,348 @@ class _AttendanceScreenState extends State with UIMixin { DataColumn( label: MyText.labelLarge('Name', color: contentTheme.primary)), DataColumn( - label: MyText.labelLarge('Role', color: contentTheme.primary)), + label: MyText.labelLarge('Designation', + 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)), + label: MyText.labelLarge('Actions', 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(IconButton( - icon: Icon(Icons.info_outline, color: contentTheme.primary), - onPressed: () { - // Action logic here - }, - )), - ])) - .toList(), + rows: attendanceController.employees.mapIndexed((index, employee) { + // Set actionText directly from employee's action + String actionText = ""; + int? activity = + employee.activity; // Assuming employee has an 'action' field + + // Set action text based on employee's activity value + if (activity == 1) { + actionText = "Check In"; + } else if (activity == 0) { + actionText = "Check Out"; + } else if (activity == 4) { + // Activity 4 logic + actionText = "Check In"; + } + + return DataRow(cells: [ + DataCell(MyText.bodyMedium(employee.name, fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.designation, fontWeight: 600)), + DataCell(ElevatedButton( + onPressed: () async { + if (attendanceController.selectedProjectId == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please select a project first")), + ); + return; + } + + // Determine the updated action based on current activity + int updatedAction; + String actionText; + + if (activity == 0 || activity == 4) { + // The user is currently checked in (activity == 0), so they need to check out + updatedAction = 0; + actionText = "Check In"; + } else { + // The user is currently checked out (activity == 1), so they need to check in + updatedAction = 1; + actionText = "Check Out"; + } + + // Call the method to capture attendance with the updated action + final success = + await attendanceController.captureAndUploadAttendance( + employee.id, // Pass the employee's ID + employee.employeeId, + + int.parse(attendanceController + .selectedProjectId!), // Pass the selected project ID + comment: actionText, // Action text (Check In / Check Out) + action: updatedAction, + ); + + // Show success or failure message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + success + ? 'Attendance marked successfully!' + : 'Image upload failed.', + ), + ), + ); + if (success) { + // Fetch the updated list of employees and logs after the attendance upload + attendanceController.fetchEmployeesByProject( + attendanceController.selectedProjectId!); + attendanceController.fetchAttendanceLogs( + attendanceController.selectedProjectId!); + // You can add more fetch calls if necessary, such as regularization logs. + } + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 4, horizontal: 6), // Adjust padding + minimumSize: Size(60, 20), // Adjust minimum size for the button + textStyle: TextStyle(fontSize: 12), // Smaller font size + ), + child: Text( + activity == 0 || activity == 4 ? 'Check In' : 'Check Out'), + )), + ]); + }).toList(), ), ); } + + Widget reportsTab(BuildContext context) { + final attendanceController = Get.find(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextButton.icon( + icon: Icon(Icons.date_range), + label: Text("Select Date Range for Attendance"), + onPressed: () => attendanceController.selectDateRangeForAttendance( + context, attendanceController), + ), + ), + if (attendanceController.attendanceLogs.isEmpty) + Expanded( + child: Center( + child: MyText.bodySmall("No Attendance 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.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( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: 6), // Adjust padding + minimumSize: + Size(60, 20), // Adjust minimum size + textStyle: TextStyle( + fontSize: 12), // Smaller font size + ), + 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)), + 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(IconButton( + icon: Icon(Icons.info_outline, + color: contentTheme.primary), + onPressed: () { + // Add action logic + }, + )), + ])) + .toList(), + ), + ), + ), + ], + ); + } }