import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:marco/helpers/theme/app_theme.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/my_shadow.dart'; import 'package:marco/helpers/widgets/my_breadcrumb.dart'; import 'package:marco/helpers/widgets/my_breadcrumb_item.dart'; import 'package:marco/helpers/widgets/my_card.dart'; import 'package:marco/helpers/widgets/my_container.dart'; import 'package:marco/helpers/widgets/my_flex.dart'; import 'package:marco/helpers/widgets/my_flex_item.dart'; import 'package:marco/helpers/widgets/my_list_extension.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/view/layouts/layout.dart'; import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; import 'package:intl/intl.dart'; import 'package:marco/controller/permission_controller.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; // Make sure this import is correct import 'package:marco/helpers/utils/attendance_actions.dart'; import 'package:marco/helpers/widgets/my_refresh_wrapper.dart'; class AttendanceScreen extends StatefulWidget { const AttendanceScreen({super.key}); @override State createState() => _AttendanceScreenState(); } class _AttendanceScreenState extends State with UIMixin { AttendanceController attendanceController = Get.put(AttendanceController()); PermissionController permissionController = Get.find(); @override Widget build(BuildContext context) { return Layout( child: MyRefreshWrapper( onRefresh: () async { // Call your controller's refresh logic if (attendanceController.selectedProjectId != null) { await attendanceController.fetchEmployeesByProject( attendanceController.selectedProjectId!); await attendanceController .fetchAttendanceLogs(attendanceController.selectedProjectId!); } else { await attendanceController.fetchProjects(); } }, child: GetBuilder( init: attendanceController, tag: 'attendance_dashboard_controller', builder: (controller) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: MySpacing.x(flexSpacing), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MyText.titleMedium("Attendance", fontSize: 18, fontWeight: 600), MyBreadcrumb( children: [ MyBreadcrumbItem(name: 'Dashboard'), MyBreadcrumbItem(name: 'Attendance', active: true), ], ), ], ), ), MySpacing.height(flexSpacing), Padding( padding: MySpacing.x(flexSpacing / 2), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(flexSpacing), // Project selection dropdown 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); }); }, 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: Obx(() { bool hasRegularizationPermission = permissionController.hasPermission( Permissions.regularizeAttendance); final tabs = [ const Tab(text: 'Employee List'), const Tab(text: 'Logs'), if (hasRegularizationPermission) const Tab(text: 'Regularization'), ]; final views = [ employeeListTab(), reportsTab(context), if (hasRegularizationPermission) regularizationTab(context), ]; return DefaultTabController( length: tabs.length, child: MyCard.bordered( borderRadiusAll: 4, border: Border.all( color: Colors.grey.withAlpha(50)), shadow: MyShadow( elevation: 1, position: MyShadowPosition.bottom), paddingAll: 10, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TabBar( labelColor: theme.colorScheme.primary, unselectedLabelColor: theme .colorScheme.onSurface .withAlpha(150), tabs: tabs, ), MySpacing.height(16), SizedBox( height: 500, child: TabBarView(children: views), ), ], ), ), ); }), ), ], ), ], ), ), ], ); }, ), ), ); } Widget employeeListTab() { if (attendanceController.employees.isEmpty) { return Center( child: MyText.bodySmall("No Employees Found", fontWeight: 600), ); } return 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('Designation', color: contentTheme.primary)), DataColumn( label: MyText.labelLarge('Actions', color: contentTheme.primary)), ], 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 || activity == 4) { actionText = "Check Out"; } else if (activity == 2) { actionText = "Request Regularize"; } 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) { updatedAction = 0; actionText = ButtonActions.checkIn; } else { updatedAction = 1; actionText = ButtonActions.checkOut; } final success = await attendanceController.captureAndUploadAttendance( employee.id, employee.employeeId, int.parse(attendanceController.selectedProjectId!), comment: actionText, action: updatedAction, ); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( success ? 'Attendance marked successfully!' : 'Image upload failed.', ), ), ); if (success) { attendanceController.fetchEmployeesByProject( attendanceController.selectedProjectId!); attendanceController.fetchAttendanceLogs( attendanceController.selectedProjectId!); } }, style: ElevatedButton.styleFrom( backgroundColor: AttendanceActionColors.colors[ (activity == 0 || activity == 4) ? ButtonActions.checkIn : ButtonActions.checkOut], padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), minimumSize: const Size(60, 20), textStyle: const TextStyle(fontSize: 12), ), child: Text((activity == 0 || activity == 4) ? ButtonActions.checkIn : ButtonActions.checkOut), )) ]); }).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('View', 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( Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium( log.checkIn != null ? DateFormat('dd MMM yyyy') .format(log.checkIn!) : '-', fontWeight: 600, ), MyText.bodyMedium( log.checkIn != null ? DateFormat('hh:mm a') .format(log.checkIn!) : '', fontWeight: 600, ), ], ), ), DataCell( Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium( log.checkOut != null ? DateFormat('dd MMM yyyy') .format(log.checkOut!) : '-', fontWeight: 600, ), MyText.bodyMedium( log.checkOut != null ? DateFormat('hh:mm a') .format(log.checkOut!) : '', fontWeight: 600, ), ], ), ), DataCell( IconButton( icon: const Icon(Icons.visibility, size: 18), onPressed: () async { await attendanceController .fetchLogsView(log.id.toString()); 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), if (attendanceController .attendenceLogsView.isNotEmpty) Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Expanded( child: MyText.bodyMedium( "Date", fontWeight: 600)), Expanded( child: MyText.bodyMedium( "Time", fontWeight: 600)), Expanded( child: MyText.bodyMedium( "Description", fontWeight: 600)), Expanded( child: MyText.bodyMedium( "Image", fontWeight: 600)), ], ), const Divider( thickness: 1, height: 24), if (attendanceController .attendenceLogsView.isNotEmpty) ...attendanceController .attendenceLogsView .map((log) { return Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Expanded( child: MyText.bodyMedium( log.formattedDate ?? '-', fontWeight: 600)), Expanded( child: MyText.bodyMedium( log.formattedTime ?? '-', fontWeight: 600)), Expanded( child: MyText.bodyMedium( log.comment ?? '-', fontWeight: 600)), Expanded( child: GestureDetector( onTap: () { if (log.preSignedUrl != null) { showDialog( context: context, builder: (_) => Dialog( child: Image.network( log.preSignedUrl!, fit: BoxFit .cover, height: 400, errorBuilder: (context, error, stackTrace) { return Icon( Icons .broken_image, size: 50, color: Colors .grey); }, ), ), ); } }, child: log.thumbPreSignedUrl != null ? Image.network( log.thumbPreSignedUrl!, fit: BoxFit.cover, height: 40, width: 40, errorBuilder: (context, error, stackTrace) { return Icon( Icons .broken_image, size: 40, color: Colors .grey); }, ) : Icon( Icons .broken_image, size: 40, color: Colors.grey), ), ), ], ); }), Align( alignment: Alignment.centerRight, child: ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text("Close"), ), ), ], ), ); }, ); }, ), ), // The action button 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 the employee's activity // Determine the updated action based on current activity int updatedAction; String actionText; if (log.activity == 0 || log.activity == 4) { // Needs to Check In updatedAction = 0; actionText = "Check In"; } else if (log.activity == 1) { // Needs to Check Out updatedAction = 1; actionText = "Check Out"; } else if (log.activity == 2) { // Needs to Request Regularize updatedAction = 2; actionText = "Request Regularize"; } else { // Default case (fallback) updatedAction = 0; actionText = "Unknown Action"; } // Call the method to capture and upload attendance final success = await attendanceController .captureAndUploadAttendance( log.id, log.employeeId, int.parse(attendanceController .selectedProjectId!), // Pass the selected project ID comment: actionText, // Action text (Check In/Check Out) action: updatedAction, // Updated action ); // Show success or failure message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( success ? 'Attendance marked successfully!' : 'Image upload failed.', ), ), ); if (success) { // Fetch updated logs and employees attendanceController.fetchEmployeesByProject( attendanceController.selectedProjectId!); attendanceController.fetchAttendanceLogs( attendanceController.selectedProjectId!); } }, style: ElevatedButton.styleFrom( backgroundColor: AttendanceActionColors.colors[ (log.activity == 0 || log.activity == 4) ? ButtonActions.checkIn : ButtonActions.checkOut], padding: EdgeInsets.symmetric( vertical: 4, horizontal: 6), minimumSize: Size(60, 20), textStyle: TextStyle(fontSize: 12), ), child: Text( (log.activity == 0 || log.activity == 4) ? ButtonActions.checkIn : (log.activity == 2) ? ButtonActions.requestRegularize : ButtonActions.checkOut, ), )), ])) .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( Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium( log.checkIn != null ? DateFormat('dd/MMM/yyyy') .format(log.checkIn!) : '-', fontWeight: 600, ), MyText.bodyMedium( log.checkIn != null ? DateFormat('hh:mm a') .format(log.checkIn!) : '', fontWeight: 600, ), ], ), ), DataCell( Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium( log.checkOut != null ? DateFormat('dd MMM yyyy') .format(log.checkOut!) : '-', fontWeight: 600, ), MyText.bodyMedium( log.checkOut != null ? DateFormat('hh:mm a') .format(log.checkOut!) : '', fontWeight: 600, ), ], ), ), DataCell(IconButton( icon: Icon(Icons.info_outline, color: contentTheme.primary), onPressed: () { // Add action logic }, )), ])) .toList(), ), ), ), ], ); } }