From 910bb5e6b4d6f94cf11d2d94bbadf60eeea9d731 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Sat, 8 Nov 2025 11:53:34 +0530 Subject: [PATCH] removed employees by project --- .../employee/employees_screen_controller.dart | 132 +++------ .../employees/add_employee_bottom_sheet.dart | 7 +- .../employees_screen_filter_sheet.dart | 171 ------------ lib/view/employees/employees_screen.dart | 253 +++--------------- 4 files changed, 78 insertions(+), 485 deletions(-) delete mode 100644 lib/model/employees/employees_screen_filter_sheet.dart diff --git a/lib/controller/employee/employees_screen_controller.dart b/lib/controller/employee/employees_screen_controller.dart index 3841a76..7d95421 100644 --- a/lib/controller/employee/employees_screen_controller.dart +++ b/lib/controller/employee/employees_screen_controller.dart @@ -1,91 +1,56 @@ import 'package:get/get.dart'; import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; -import 'package:marco/model/attendance/attendance_model.dart'; -import 'package:marco/model/project_model.dart'; import 'package:marco/model/employees/employee_model.dart'; import 'package:marco/model/employees/employee_details_model.dart'; -import 'package:marco/controller/project_controller.dart'; class EmployeesScreenController extends GetxController { - List attendances = []; - List projects = []; - String? selectedProjectId; - List employeeDetails = []; - RxBool isAllEmployeeSelected = false.obs; + /// ✅ Data lists RxList employees = [].obs; - - RxBool isLoading = false.obs; - RxMap uploadingStates = {}.obs; Rxn selectedEmployeeDetails = Rxn(); + + /// ✅ Loading states + RxBool isLoading = false.obs; RxBool isLoadingEmployeeDetails = false.obs; + /// ✅ Selection state + RxBool isAllEmployeeSelected = false.obs; + RxSet selectedEmployeeIds = {}.obs; + + /// ✅ Upload state tracking (if needed later) + RxMap uploadingStates = {}.obs; + @override void onInit() { super.onInit(); - isLoading.value = true; - fetchAllProjects().then((_) { - final projectId = Get.find().selectedProject?.id; - if (projectId != null) { - selectedProjectId = projectId; - fetchEmployeesByProject(projectId); - } else if (isAllEmployeeSelected.value) { - fetchAllEmployees(); - } else { - clearEmployees(); - } - }); - } - - Future fetchAllProjects() async { - isLoading.value = true; - - await _handleApiCall( - ApiService.getProjects, - onSuccess: (data) { - projects = data.map((json) => ProjectModel.fromJson(json)).toList(); - logSafe( - "Projects fetched: ${projects.length} projects loaded.", - level: LogLevel.info, - ); - }, - onEmpty: () { - logSafe("No project data found or API call failed.", - level: LogLevel.warning); - }, - ); - - isLoading.value = false; - update(); - } - - void clearEmployees() { - employees.clear(); - logSafe("Employees cleared", level: LogLevel.info); - update(['employee_screen_controller']); + fetchAllEmployees(); } + /// 🔹 Fetch all employees (no project filter) Future fetchAllEmployees({String? organizationId}) async { isLoading.value = true; update(['employee_screen_controller']); await _handleApiCall( - () => ApiService.getAllEmployees( - organizationId: organizationId), // pass orgId to API + () => ApiService.getAllEmployees(organizationId: organizationId), onSuccess: (data) { employees.assignAll(data.map((json) => EmployeeModel.fromJson(json))); logSafe( "All Employees fetched: ${employees.length} employees loaded.", level: LogLevel.info, ); + + // Reset selection states when new data arrives + selectedEmployeeIds.clear(); + isAllEmployeeSelected.value = false; }, onEmpty: () { employees.clear(); - logSafe( - "No Employee data found or API call failed", - level: LogLevel.warning, - ); + selectedEmployeeIds.clear(); + isAllEmployeeSelected.value = false; + logSafe("No Employee data found or API call failed", + level: LogLevel.warning); }, ); @@ -93,28 +58,7 @@ class EmployeesScreenController extends GetxController { update(['employee_screen_controller']); } - Future fetchEmployeesByProject(String projectId, - {String? organizationId}) async { - if (projectId.isEmpty) return; - - isLoading.value = true; - - await _handleApiCall( - () => ApiService.getAllEmployeesByProject(projectId, - organizationId: organizationId), - onSuccess: (data) { - employees.assignAll(data.map((json) => EmployeeModel.fromJson(json))); - for (var emp in employees) { - uploadingStates[emp.id] = false.obs; - } - }, - onEmpty: () => employees.clear(), - ); - - isLoading.value = false; - update(['employee_screen_controller']); - } - + /// 🔹 Fetch details for a specific employee Future fetchEmployeeDetails(String? employeeId) async { if (employeeId == null || employeeId.isEmpty) return; @@ -124,31 +68,34 @@ class EmployeesScreenController extends GetxController { () => ApiService.getEmployeeDetails(employeeId), onSuccess: (data) { selectedEmployeeDetails.value = EmployeeDetailsModel.fromJson(data); - logSafe( - "Employee details loaded for $employeeId", - level: LogLevel.info, - ); + logSafe("Employee details loaded for $employeeId", + level: LogLevel.info); }, onEmpty: () { selectedEmployeeDetails.value = null; - logSafe( - "No employee details found for $employeeId", - level: LogLevel.warning, - ); + logSafe("No employee details found for $employeeId", + level: LogLevel.warning); }, onError: (e) { selectedEmployeeDetails.value = null; - logSafe( - "Error fetching employee details for $employeeId", - level: LogLevel.error, - error: e, - ); + logSafe("Error fetching employee details for $employeeId", + level: LogLevel.error, error: e); }, ); isLoadingEmployeeDetails.value = false; } + /// 🔹 Clear all employee data + void clearEmployees() { + employees.clear(); + selectedEmployeeIds.clear(); + isAllEmployeeSelected.value = false; + logSafe("Employees cleared", level: LogLevel.info); + update(['employee_screen_controller']); + } + + /// 🔹 Generic handler for list API responses Future _handleApiCall( Future?> Function() apiCall, { required Function(List) onSuccess, @@ -171,6 +118,7 @@ class EmployeesScreenController extends GetxController { } } + /// 🔹 Generic handler for single-object API responses Future _handleSingleApiCall( Future?> Function() apiCall, { required Function(Map) onSuccess, diff --git a/lib/model/employees/add_employee_bottom_sheet.dart b/lib/model/employees/add_employee_bottom_sheet.dart index 3cd4a4e..59dba23 100644 --- a/lib/model/employees/add_employee_bottom_sheet.dart +++ b/lib/model/employees/add_employee_bottom_sheet.dart @@ -537,13 +537,8 @@ class _AddEmployeeBottomSheetState extends State if (result != null && result['success'] == true) { final employeeController = Get.find(); - final projectId = employeeController.selectedProjectId; - if (projectId == null) { - await employeeController.fetchAllEmployees(); - } else { - await employeeController.fetchEmployeesByProject(projectId); - } + await employeeController.fetchAllEmployees(); employeeController.update(['employee_screen_controller']); if (mounted) Navigator.pop(context, result['data']); diff --git a/lib/model/employees/employees_screen_filter_sheet.dart b/lib/model/employees/employees_screen_filter_sheet.dart deleted file mode 100644 index 24f672c..0000000 --- a/lib/model/employees/employees_screen_filter_sheet.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:marco/controller/permission_controller.dart'; -import 'package:marco/controller/employee/employees_screen_controller.dart'; -import 'package:marco/helpers/widgets/my_text.dart'; - -class EmployeesScreenFilterSheet extends StatelessWidget { - final EmployeesScreenController controller; - final PermissionController permissionController; - - const EmployeesScreenFilterSheet({ - super.key, - required this.controller, - required this.permissionController, - }); - - @override - Widget build(BuildContext context) { - String? tempSelectedProjectId; - bool showProjectList = false; - - final accessibleProjects = controller.projects - .where((project) => - permissionController.isUserAssignedToProject(project.id.toString())) - .toList(); - - return StatefulBuilder(builder: (context, setState) { - List filterWidgets; - - if (showProjectList) { - filterWidgets = []; - - // Add "All Employees" option at the top - filterWidgets.add( - ListTile( - dense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - title: MyText.titleSmall('All Employees'), - trailing: - tempSelectedProjectId == null ? const Icon(Icons.check) : null, - onTap: () { - setState(() { - tempSelectedProjectId = null; - showProjectList = false; - }); - }, - ), - ); - - // Add all accessible projects below - if (accessibleProjects.isEmpty) { - filterWidgets.add( - Padding( - padding: const EdgeInsets.all(12.0), - child: Center( - child: MyText.titleSmall( - 'No Projects Assigned', - fontWeight: 600, - ), - ), - ), - ); - } else { - filterWidgets.addAll(accessibleProjects.map((project) { - return ListTile( - dense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - title: MyText.titleSmall(project.name), - trailing: tempSelectedProjectId == project.id.toString() - ? const Icon(Icons.check) - : null, - onTap: () { - setState(() { - tempSelectedProjectId = project.id.toString(); - showProjectList = false; - }); - }, - ); - }).toList()); - } - } else { - String selectedProjectName = 'All Employees'; - if (tempSelectedProjectId != null) { - final selectedProject = accessibleProjects.isNotEmpty - ? accessibleProjects.firstWhere( - (p) => p.id.toString() == tempSelectedProjectId, - orElse: () => accessibleProjects[0], - ) - : null; - - if (selectedProject != null) { - selectedProjectName = selectedProject.name; - } - } - - filterWidgets = [ - Padding( - padding: EdgeInsets.fromLTRB(16, 12, 16, 4), - child: Align( - alignment: Alignment.centerLeft, - child: MyText.titleSmall( - 'Select Project', - fontWeight: 600, - ), - ), - ), - ListTile( - dense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - title: MyText.titleSmall(selectedProjectName), - trailing: const Icon(Icons.arrow_drop_down), - onTap: () => setState(() => showProjectList = true), - ), - ]; - } - - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(top: 12, bottom: 8), - child: Center( - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(4), - ), - ), - ), - ), - ...filterWidgets, - const Divider(), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 95, 132, 255), - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: MyText.titleSmall( - 'Apply Filter', - fontWeight: 600, - color: Colors.white, - ), - onPressed: () { - Navigator.pop(context, { - 'projectId': tempSelectedProjectId, - }); - }, - ), - ), - ), - ], - ), - ), - ); - }); - } -} diff --git a/lib/view/employees/employees_screen.dart b/lib/view/employees/employees_screen.dart index 12bb2ea..dea9f7b 100644 --- a/lib/view/employees/employees_screen.dart +++ b/lib/view/employees/employees_screen.dart @@ -16,8 +16,6 @@ import 'package:marco/controller/permission_controller.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/view/employees/employee_profile_screen.dart'; -import 'package:marco/controller/tenant/organization_selection_controller.dart'; -import 'package:marco/helpers/widgets/tenant/organization_selector.dart'; class EmployeesScreen extends StatefulWidget { const EmployeesScreen({super.key}); @@ -27,64 +25,32 @@ class EmployeesScreen extends StatefulWidget { } class _EmployeesScreenState extends State with UIMixin { - final EmployeesScreenController _employeeController = - Get.put(EmployeesScreenController()); - final PermissionController permissionController = - Get.put(PermissionController()); + late final EmployeesScreenController _employeeController; + late final PermissionController _permissionController; final TextEditingController _searchController = TextEditingController(); final RxList _filteredEmployees = [].obs; - final OrganizationController _organizationController = - Get.put(OrganizationController()); @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _initEmployees(); - _searchController - .addListener(() => _filterEmployees(_searchController.text)); + _employeeController = Get.put(EmployeesScreenController()); + _permissionController = Get.put(PermissionController()); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await _initEmployees(); + _searchController.addListener(() { + _filterEmployees(_searchController.text); + }); }); } Future _initEmployees() async { - final projectId = Get.find().selectedProject?.id; - final orgId = _organizationController.selectedOrganization.value?.id; - - if (projectId != null) { - await _organizationController.fetchOrganizations(projectId); - } - - if (_employeeController.isAllEmployeeSelected.value) { - _employeeController.selectedProjectId = null; - await _employeeController.fetchAllEmployees(organizationId: orgId); - } else if (projectId != null) { - _employeeController.selectedProjectId = projectId; - await _employeeController.fetchEmployeesByProject(projectId, - organizationId: orgId); - } else { - _employeeController.clearEmployees(); - } - + await _employeeController.fetchAllEmployees(); _filterEmployees(_searchController.text); } Future _refreshEmployees() async { try { - final projectId = Get.find().selectedProject?.id; - final orgId = _organizationController.selectedOrganization.value?.id; - final allSelected = _employeeController.isAllEmployeeSelected.value; - - _employeeController.selectedProjectId = allSelected ? null : projectId; - - if (allSelected) { - await _employeeController.fetchAllEmployees(organizationId: orgId); - } else if (projectId != null) { - await _employeeController.fetchEmployeesByProject(projectId, - organizationId: orgId); - } else { - _employeeController.clearEmployees(); - } - + await _employeeController.fetchAllEmployees(); _filterEmployees(_searchController.text); _employeeController.update(['employee_screen_controller']); } catch (e, stackTrace) { @@ -95,27 +61,16 @@ class _EmployeesScreenState extends State with UIMixin { void _filterEmployees(String query) { final employees = _employeeController.employees; - - List filtered; - - if (query.isEmpty) { - filtered = List.from(employees); - } else { - final q = query.toLowerCase(); - filtered = employees - .where( - (e) => - e.name.toLowerCase().contains(q) || - e.email.toLowerCase().contains(q) || - e.phoneNumber.toLowerCase().contains(q) || - e.jobRole.toLowerCase().contains(q), - ) - .toList(); - } - - filtered - .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); - + final searchQuery = query.toLowerCase(); + final filtered = query.isEmpty + ? List.from(employees) + : employees.where((e) => + e.name.toLowerCase().contains(searchQuery) || + e.email.toLowerCase().contains(searchQuery) || + e.phoneNumber.toLowerCase().contains(searchQuery) || + e.jobRole.toLowerCase().contains(searchQuery), + ).toList(); + filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); _filteredEmployees.assignAll(filtered); } @@ -134,7 +89,6 @@ class _EmployeesScreenState extends State with UIMixin { final employeeData = result['data']; final employeeId = employeeData['id'] as String; - final jobRoleId = employeeData['jobRoleId'] as String?; await showModalBottomSheet( context: context, @@ -143,15 +97,16 @@ class _EmployeesScreenState extends State with UIMixin { borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), backgroundColor: Colors.transparent, - builder: (context) => AssignProjectBottomSheet( + builder: (_) => AssignProjectBottomSheet( employeeId: employeeId, - jobRoleId: jobRoleId ?? '', + jobRoleId: '', // no jobRoleId required ), ); await _refreshEmployees(); } + @override Widget build(BuildContext context) { return Scaffold( @@ -164,7 +119,6 @@ class _EmployeesScreenState extends State with UIMixin { tag: 'employee_screen_controller', builder: (_) { _filterEmployees(_searchController.text); - return MyRefreshIndicator( onRefresh: _refreshEmployees, child: SingleChildScrollView( @@ -174,7 +128,10 @@ class _EmployeesScreenState extends State with UIMixin { crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(flexSpacing), - _buildSearchAndActionRow(), + Padding( + padding: MySpacing.x(15), + child: _buildSearchField(), + ), MySpacing.height(flexSpacing), Padding( padding: MySpacing.x(flexSpacing), @@ -203,8 +160,7 @@ class _EmployeesScreenState extends State with UIMixin { child: Row( children: [ IconButton( - icon: const Icon(Icons.arrow_back_ios_new, - color: Colors.black, size: 20), + icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offNamed('/dashboard'), ), MySpacing.width(8), @@ -249,17 +205,9 @@ class _EmployeesScreenState extends State with UIMixin { Widget _buildFloatingActionButton() { return Obx(() { - // Show nothing while permissions are loading - if (permissionController.isLoading.value) { - return const SizedBox.shrink(); - } - - // Show FAB only if user has Manage Employees permission - final hasPermission = - permissionController.hasPermission(Permissions.manageEmployees); - if (!hasPermission) { - return const SizedBox.shrink(); - } + if (_permissionController.isLoading.value) return const SizedBox.shrink(); + final hasPermission = _permissionController.hasPermission(Permissions.manageEmployees); + if (!hasPermission) return const SizedBox.shrink(); return InkWell( onTap: _onAddNewEmployee, @@ -270,11 +218,7 @@ class _EmployeesScreenState extends State with UIMixin { color: contentTheme.primary, borderRadius: BorderRadius.circular(28), boxShadow: const [ - BoxShadow( - color: Colors.black26, - blurRadius: 6, - offset: Offset(0, 3), - ) + BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 3)), ], ), child: const Row( @@ -290,58 +234,6 @@ class _EmployeesScreenState extends State with UIMixin { }); } - Widget _buildSearchAndActionRow() { - return Padding( - padding: MySpacing.x(15), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Search Field Row - Row( - children: [ - Expanded(child: _buildSearchField()), - const SizedBox(width: 8), - _buildPopupMenu(), - ], - ), - - // Organization Selector Row - Row( - children: [ - Expanded( - child: OrganizationSelector( - controller: _organizationController, - height: 36, - onSelectionChanged: (org) async { - // Make sure the selectedOrganization is updated immediately - _organizationController.selectOrganization(org); - - final projectId = - Get.find().selectedProject?.id; - - if (_employeeController.isAllEmployeeSelected.value) { - await _employeeController.fetchAllEmployees( - organizationId: _organizationController - .selectedOrganization.value?.id); - } else if (projectId != null) { - await _employeeController.fetchEmployeesByProject( - projectId, - organizationId: _organizationController - .selectedOrganization.value?.id); - } - - _employeeController.update(['employee_screen_controller']); - }, - ), - ), - ], - ), - MySpacing.height(8), - ], - ), - ); - } - Widget _buildSearchField() { return SizedBox( height: 36, @@ -350,99 +242,30 @@ class _EmployeesScreenState extends State with UIMixin { style: const TextStyle(fontSize: 13, height: 1.2), decoration: InputDecoration( isDense: true, - contentPadding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), prefixIcon: const Icon(Icons.search, size: 18, color: Colors.grey), - prefixIconConstraints: - const BoxConstraints(minWidth: 32, minHeight: 32), - hintText: 'Search contacts...', - hintStyle: const TextStyle(fontSize: 13, color: Colors.grey), + hintText: 'Search employees...', filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300, width: 1), ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.grey.shade300, width: 1), - ), suffixIcon: _searchController.text.isNotEmpty ? GestureDetector( onTap: () { _searchController.clear(); _filterEmployees(''); - setState(() {}); }, child: const Icon(Icons.close, size: 18, color: Colors.grey), ) : null, ), - onChanged: (_) => setState(() {}), + onChanged: (_) => _filterEmployees(_searchController.text), ), ); } - Widget _buildPopupMenu() { - return Obx(() { - if (permissionController.isLoading.value || - !permissionController.hasPermission(Permissions.viewAllEmployees)) { - return const SizedBox.shrink(); - } - - return PopupMenuButton( - icon: Stack( - clipBehavior: Clip.none, - children: [ - const Icon(Icons.tune, color: Colors.black), - Obx(() => _employeeController.isAllEmployeeSelected.value - ? Positioned( - right: -1, - top: -1, - child: Container( - width: 10, - height: 10, - decoration: const BoxDecoration( - color: Colors.red, shape: BoxShape.circle), - ), - ) - : const SizedBox.shrink()), - ], - ), - onSelected: (value) async { - if (value == 'all_employees') { - _employeeController.isAllEmployeeSelected.toggle(); - await _initEmployees(); - _employeeController.update(['employee_screen_controller']); - } - }, - itemBuilder: (_) => [ - PopupMenuItem( - value: 'all_employees', - child: Obx( - () => Row( - children: [ - Checkbox( - value: _employeeController.isAllEmployeeSelected.value, - onChanged: (_) => Navigator.pop(context, 'all_employees'), - checkColor: Colors.white, - activeColor: contentTheme.primary, - side: const BorderSide(color: Colors.black, width: 1.5), - fillColor: MaterialStateProperty.resolveWith( - (states) => states.contains(MaterialState.selected) - ? contentTheme.primary - : Colors.white), - ), - const Text('All Employees'), - ], - ), - ), - ), - ], - ); - }); - } - Widget _buildEmployeeList() { return Obx(() { if (_employeeController.isLoading.value) { @@ -456,13 +279,11 @@ class _EmployeesScreenState extends State with UIMixin { } final employees = _filteredEmployees; - if (employees.isEmpty) { return Padding( padding: const EdgeInsets.only(top: 60), child: Center( - child: MyText.bodySmall("No Employees Found", - fontWeight: 600, color: Colors.grey[700]), + child: MyText.bodySmall("No Employees Found", fontWeight: 600, color: Colors.grey[700]), ), ); }