From fd14243f5a74c40638b8c32047832a7436fb640b Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 23 Apr 2025 16:18:44 +0530 Subject: [PATCH 1/3] Implement date range selection for attendance logs and refactor attendance screen layout --- .../attendance_screen_controller.dart | 60 ++- lib/helpers/services/api_service.dart | 37 +- lib/view/dashboard/attendanceScreen.dart | 362 ++++++++++-------- 3 files changed, 266 insertions(+), 193 deletions(-) diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index 57187bd..f06a1ad 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -6,6 +6,7 @@ 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'; class AttendanceController extends GetxController { List attendances = []; @@ -17,6 +18,8 @@ class AttendanceController extends GetxController { void onInit() { super.onInit(); fetchProjects(); // Fetch projects when initializing + // fetchAttendanceLogs(selectedProjectId); + // fetchAttendanceLogs(selectedProjectId); } // Fetch projects from API @@ -92,19 +95,52 @@ class AttendanceController extends GetxController { } } - List attendanceLogs = []; - Future fetchAttendanceLogs(String? projectId) async { - if (projectId == null) return; +DateTime? startDate; +DateTime? endDate; - var response = await ApiService.getAttendanceLogs(int.parse(projectId)); +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 (response != null) { - attendanceLogs = response - .map((json) => AttendanceLogModel.fromJson(json)) - .toList(); - update(); - } else { - print("Failed to fetch logs for project $projectId."); - } + if (picked != null) { + startDate = picked.start; + endDate = picked.end; + + await controller.fetchAttendanceLogs( + controller.selectedProjectId, + dateFrom: startDate, + dateTo: endDate, + ); } +} + + + 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 3d27902..c820a2c 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -26,7 +26,6 @@ class ApiService { 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 { @@ -34,7 +33,6 @@ class ApiService { } } else { print("Error fetching projects: ${response.statusCode}"); - print("Response body: ${response.body}"); } } catch (e) { print("Exception while fetching projects: $e"); @@ -62,7 +60,6 @@ class ApiService { 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 { @@ -70,7 +67,6 @@ class ApiService { } } else { print("Error fetching employees: ${response.statusCode}"); - print("Response body: ${response.body}"); } } catch (e) { print("Exception while fetching employees: $e"); @@ -138,24 +134,12 @@ class ApiService { "image": [imageObject], // Directly included in the 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; } else { print("Error uploading image: ${response.statusCode}"); - print("Response: ${response.body}"); } } catch (e) { print("Exception during image upload: $e"); @@ -163,7 +147,11 @@ class ApiService { return false; } - static Future?> getAttendanceLogs(int projectId) async { + static Future?> getAttendanceLogs( + int projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { try { String? jwtToken = LocalStorage.getJwtToken(); if (jwtToken == null) { @@ -171,9 +159,18 @@ class ApiService { return null; } + 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 uri = Uri.parse("$baseUrl/attendance/project/team") + .replace(queryParameters: queryParameters); + print('uri: $uri'); final response = await http.get( - Uri.parse( - "$baseUrl/attendance/project/team?projectId=$projectId"), + uri, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $jwtToken', @@ -182,8 +179,10 @@ class ApiService { 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']}"); } diff --git a/lib/view/dashboard/attendanceScreen.dart b/lib/view/dashboard/attendanceScreen.dart index 8cc8815..dcd44e7 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,6 +59,76 @@ 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(4, 8), + child: PopupMenuButton( + onSelected: (value) { + setState(() { + attendanceController.selectedProjectId = + value; + attendanceController + .fetchEmployeesByProject(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: 16, + color: theme.colorScheme.onSurface), + ], + ), + ), + ), + ), + ], + ), MySpacing.height(flexSpacing), MyFlex( children: [ @@ -93,7 +162,7 @@ class _AttendanceScreenState extends State with UIMixin { child: TabBarView( children: [ employeeListTab(), - reportsTab(), + reportsTab(context), ], ), ), @@ -115,135 +184,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 +210,136 @@ 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 - }, - )), + 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(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"), + onPressed: () => attendanceController.selectDateRange(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(IconButton( + icon: Icon(Icons.info_outline, + color: contentTheme.primary), + onPressed: () { + // Add action logic + }, + )), + ])) + .toList(), + ), + ), + ), + ], + ); + } } From 7936072b74a9681e042f242705d9e805b0d5dfae Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Thu, 24 Apr 2025 16:49:33 +0530 Subject: [PATCH 2/3] 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)), From 8da8ee8371b862814639544ad732b409d60ae4a9 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Fri, 25 Apr 2025 12:13:41 +0530 Subject: [PATCH 3/3] made chnages for check in and check out --- .../attendance_screen_controller.dart | 4 + lib/helpers/services/api_service.dart | 20 +-- lib/model/employee_model.dart | 14 +- lib/view/dashboard/attendanceScreen.dart | 122 +++++++++++++----- 4 files changed, 118 insertions(+), 42 deletions(-) diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index 69800f7..3f36f1f 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -79,9 +79,11 @@ class AttendanceController extends GetxController { } Future captureAndUploadAttendance( + int id, int employeeId, int projectId, { String comment = "Marked via mobile app", + required int action, }) async { try { final image = await ImagePicker().pickImage( @@ -98,6 +100,7 @@ class AttendanceController extends GetxController { ApiService.generateImageName(employeeId, employees.length + 1); return await ApiService.uploadAttendanceImage( + id, employeeId, image, position.latitude, @@ -105,6 +108,7 @@ class AttendanceController extends GetxController { imageName: imageName, projectId: projectId, comment: comment, + action: action, ); } catch (e) { print("Error capturing or uploading attendance: $e"); diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index b4c58d5..b23a487 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -106,6 +106,7 @@ class ApiService { // ===== Upload Image ===== static Future uploadAttendanceImage( + int id, int employeeId, XFile imageFile, double latitude, @@ -113,7 +114,7 @@ class ApiService { required String imageName, required int projectId, String comment = "", - int action = 0, + required int action, // action passed here }) async { final token = await _getToken(); if (token == null) return false; @@ -135,7 +136,7 @@ class ApiService { final now = DateTime.now(); final body = { - "id": null, + "id": id, "employeeId": employeeId, "projectId": projectId, "markTime": DateFormat('hh:mm a').format(now), @@ -146,21 +147,24 @@ class ApiService { "longitude": '$longitude', "image": imageObject }; - print("Upload body Image: $body"); + print("Attendance Image Upload Body: $body"); final response = await http.post( Uri.parse("$baseUrl/attendance/record-image"), headers: _headers(token), body: jsonEncode(body), ); - - print('Upload request: $baseUrl/attendance/record-image'); + print("Attendance Image Upload Response: ${response.body}"); final json = jsonDecode(response.body); - print("Upload Image response: ${response.body}"); - return response.statusCode == 200 && json['success'] == true; + if (response.statusCode == 200 && json['success'] == true) { + return true; + } else { + print("Failed to upload image. API Error: ${json['message']}"); + } } catch (e) { print("Exception during image upload: $e"); - return false; } + + return false; } // ===== Utilities ===== 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/view/dashboard/attendanceScreen.dart b/lib/view/dashboard/attendanceScreen.dart index 18d0d1f..08562a3 100644 --- a/lib/view/dashboard/attendanceScreen.dart +++ b/lib/view/dashboard/attendanceScreen.dart @@ -66,7 +66,7 @@ class _AttendanceScreenState extends State with UIMixin { children: [ Expanded( child: MyContainer.bordered( - padding: MySpacing.xy(4, 8), + padding: MySpacing.xy(8, 8), child: PopupMenuButton( onSelected: (value) { setState(() { @@ -122,7 +122,7 @@ class _AttendanceScreenState extends State with UIMixin { color: theme.colorScheme.onSurface, ), Icon(LucideIcons.chevron_down, - size: 16, + size: 20, color: theme.colorScheme.onSurface), ], ), @@ -219,37 +219,90 @@ class _AttendanceScreenState extends State with UIMixin { 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", - ); + 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 - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - success - ? 'Attendence Marked successfully!' - : 'Image upload failed.', - ), - ), - ); - }, - child: const Text('Check In'), + // 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.', ), ), - ])) - .toList(), + ); + 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(), ), ); } @@ -333,6 +386,15 @@ class _AttendanceScreenState extends State with UIMixin { )), 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