diff --git a/src/components/Project/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx index 633be7d7..3839d4b0 100644 --- a/src/components/Project/ProjectNav.jsx +++ b/src/components/Project/ProjectNav.jsx @@ -1,84 +1,57 @@ import React from "react"; import { hasUserPermission } from "../../utils/authUtils"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { DIRECTORY_ADMIN, DIRECTORY_MANAGER, DIRECTORY_USER, VIEW_PROJECT_INFRA } from "../../utils/constants"; +import { + DIRECTORY_ADMIN, + DIRECTORY_MANAGER, + DIRECTORY_USER, + VIEW_PROJECT_INFRA, +} from "../../utils/constants"; const ProjectNav = ({ onPillClick, activePill }) => { - const HasViewInfraStructure = useHasUserPermission( VIEW_PROJECT_INFRA ); + const HasViewInfraStructure = useHasUserPermission(VIEW_PROJECT_INFRA); const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN); - const DireManager = useHasUserPermission(DIRECTORY_MANAGER) - const DirUser = useHasUserPermission(DIRECTORY_USER) + const DireManager = useHasUserPermission(DIRECTORY_MANAGER); + const DirUser = useHasUserPermission(DIRECTORY_USER); + const ProjectTab = [ + { key: "profile", icon: "bx bx-user", label: "Profile" }, + { key: "teams", icon: "bx bx-group", label: "Teams" }, + { + key: "infra", + icon: "bx bx-grid-alt", + label: "Infrastructure", + hidden: !HasViewInfraStructure, + }, + { + key: "directory", + icon: "bx bxs-contact", + label: "Directory", + hidden: !(DirAdmin || DireManager || DirUser), + }, + { key: "documents", icon: "bx bx-folder-open", label: "Documents" }, + { key: "setting", icon: "bx bxs-cog", label: "Setting" }, + ]; return (
-
); diff --git a/src/components/Project/ProjectPermission.jsx b/src/components/Project/ProjectPermission.jsx new file mode 100644 index 00000000..2db3f48e --- /dev/null +++ b/src/components/Project/ProjectPermission.jsx @@ -0,0 +1,180 @@ +import React, { useEffect } from "react"; +import { + useProjectLevelEmployeePermission, + useProjectLevelModules, + useUpdateProjectLevelEmployeePermission, +} from "../../hooks/useProjects"; +import { useSelectedproject } from "../../slices/apiDataManager"; +import { useEmployeesByProject } from "../../hooks/useEmployees"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import showToast from "../../services/toastService"; + +export const ProjectPermissionSchema = z.object({ + employeeId: z.string(), + selectedPermissions: z.array(z.string()).optional(), +}); + +const ProjectPermission = () => { + const selectedProject = useSelectedproject(); + + const { data: ProjectModules } = useProjectLevelModules(); + const { employees = [], isLoading: isEmployeeLoading } = + useEmployeesByProject(selectedProject); + + const { + register, + watch, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(ProjectPermissionSchema), + defaultValues: { + employeeId: "", + selectedPermissions: [], + }, + }); + + const selectedEmployee = watch("employeeId"); + + // Fetch permissions for the selected employee + const { data: selectedEmpPermissions } = useProjectLevelEmployeePermission( + selectedEmployee || "", + selectedProject + ); + + // Update form state when employee permissions change + useEffect(() => { + const enabledPerms = + selectedEmpPermissions?.permissions + ?.filter((perm) => perm.isEnabled) + ?.map((perm) => perm.id) || []; + + reset({ + employeeId: selectedEmployee || employees[0]?.id || "", + selectedPermissions: enabledPerms, + }); + }, [selectedEmpPermissions, reset, selectedEmployee, employees]); + + const { mutate: UpdatePermission, isPending } = + useUpdateProjectLevelEmployeePermission(); + const onSubmit = (formData) => { + // Guard 1: Ensure employee selected + if (!formData.employeeId) { + console.warn("No employee selected"); + return; + } + + const existingPermissions = selectedEmpPermissions?.permissions || []; + + // Build payload + const payloadPermissions = + existingPermissions.length > 0 + ? existingPermissions.map((perm) => ({ + id: perm.id, + isEnabled: formData.selectedPermissions?.includes(perm.id) || false, + })) + : (formData.selectedPermissions || []).map((id) => ({ + id, + isEnabled: true, + })); + + // Guard 2: Prevent API call if no permissions at all + if (payloadPermissions.length === 0) { + showToast("No permissions selected", "warn"); + return; + } + + // Guard 3 (optional): Prevent API call if nothing changed + const hasChanges = existingPermissions.some( + (perm) => + perm.isEnabled !== + (formData.selectedPermissions?.includes(perm.id) || false) + ); + if (!hasChanges && existingPermissions.length > 0) { + showToast("No changes detected", "warn"); + return; + } + + const payload = { + employeeId: formData.employeeId, + projectId: selectedProject, + permission: payloadPermissions, + }; + + UpdatePermission(payload); + }; + + return ( +
+ {isEmployeeLoading ? ( + <>Loading... + ) : ( +
+
+
+ + + {errors.employeeId && ( +
+ {errors.employeeId.message} +
+ )} +
+ +
+ + {ProjectModules?.map((feature) => ( +
+
+ {feature.name} +
+
+
+ {feature.featurePermissions?.map((perm) => ( +
+ +
+ ))} +
+
+
+
+ ))} +
+ )} +
+ ); +}; + +export default ProjectPermission; diff --git a/src/components/Project/ProjectSetting.jsx b/src/components/Project/ProjectSetting.jsx new file mode 100644 index 00000000..eef8bc1e --- /dev/null +++ b/src/components/Project/ProjectSetting.jsx @@ -0,0 +1,74 @@ +import React, { useState } from "react"; +import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage"; +import ProjectPermission from "./ProjectPermission"; + +const ProjectSetting = () => { + const [activePill, setActivePill] = useState(() => { + return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions"; + }); + const projectSettingTab = [ + { key: "Permissions", label: "Permissions" }, + { key: "Notification", label: "Notification" }, + { key: "SeparatedLink", label: "Separated link", isButton: true }, + ]; + const handlePillClick = (pillKey) => { + setActivePill(pillKey); + localStorage.setItem("lastActiveProjectSettingTab", pillKey); + }; + + const renderContent = () => { + switch (activePill) { + case "Permissions": + return ; + + case "Notification": + return ; + + default: + return ; + } + }; + + return ( +
+
+
+
+ + +
    + {projectSettingTab.map((item) => + item.isButton ? ( +
  • + +
  • + ) : ( +
  • + +
  • + ) + )} +
+
+
+ +
{renderContent()}
+
+
+ ); +}; + +export default ProjectSetting; diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 3a887d60..a9815fc2 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -14,7 +14,6 @@ import { } from "@tanstack/react-query"; import showToast from "../services/toastService"; - // ------------------------------Query------------------- export const useProjects = () => { @@ -153,7 +152,7 @@ export const useProjectName = () => { isLoading, error, refetch, - isError + isError, } = useQuery({ queryKey: ["basicProjectNameList"], queryFn: async () => { @@ -164,7 +163,13 @@ export const useProjectName = () => { showToast(error.message || "Error while Fetching project Name", "error"); }, }); - return { projectNames: data, loading: isLoading, Error: error, refetch,isError }; + return { + projectNames: data, + loading: isLoading, + Error: error, + refetch, + isError, + }; }; export const useProjectInfra = (projectId) => { @@ -175,7 +180,7 @@ export const useProjectInfra = (projectId) => { } = useQuery({ queryKey: ["ProjectInfra", projectId], queryFn: async () => { - if(!projectId) return null; + if (!projectId) return null; const res = await ProjectRepository.getProjectInfraByproject(projectId); return res.data; }, @@ -207,12 +212,7 @@ export const useProjectTasks = (workAreaId, IsExpandedArea = false) => { return { ProjectTaskList, isLoading, error }; }; -export const useProjectTasksByEmployee = ( - employeeId, - fromDate, - toDate, -) => { - +export const useProjectTasksByEmployee = (employeeId, fromDate, toDate) => { return useQuery({ queryKey: ["TasksByEmployee", employeeId], queryFn: async () => { @@ -230,6 +230,43 @@ export const useProjectTasksByEmployee = ( }); }; +export const useProjectLevelEmployeeList = (ProjectId) => { + return useQuery({ + queryKey: ["ProjectLevelEmployeeList", ProjectId], + queryFn: async () => { + const resp = await ProjectRepository.getProjectLevelEmployeeList( + ProjectId + ); + return resp.data; + }, + enabled: !!ProjectId, + }); +}; + +export const useProjectLevelModules = () => { + return useQuery({ + queryKey: ["ProjectLevelModules"], + queryFn: async () => { + const resp = await ProjectRepository.getProjectLevelModules(); + return resp.data; + }, + }); +}; + +export const useProjectLevelEmployeePermission = (employeeId, projectId) => { + return useQuery({ + queryKey: ["ProjectLevelEmployeePermission", employeeId, projectId], + queryFn: async () => { + const resp = await ProjectRepository.getProjectLevelEmployeePermissions( + employeeId, + projectId + ); + return resp.data; + }, + enabled: !!employeeId && !!projectId, + }); +}; + // -- -------------Mutation------------------------------- export const useCreateProject = ({ onSuccessCallback }) => { @@ -558,3 +595,26 @@ export const useDeleteProjectTask = (onSuccessCallback) => { }, }); }; + +export const useUpdateProjectLevelEmployeePermission = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => + await ProjectRepository.updateProjectLevelEmployeePermission(payload), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ + queryKey: ["ProjectLevelEmployeePermission"], + }); + showToast("Permission Updated successfully", "success"); + }, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to delete task", + "error" + ); + if (onSuccessCallback) onSuccessCallback(); + }, + }); +}; diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index 78b2b662..a517eac2 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -26,6 +26,7 @@ import AttendanceOverview from "../../components/Dashboard/AttendanceChart"; import { setProjectId } from "../../slices/localVariablesSlice"; import ProjectDocument from "../../components/Project/ProjectDocuments"; import ProjectDocuments from "../../components/Project/ProjectDocuments"; +import ProjectSetting from "../../components/Project/ProjectSetting"; const ProjectDetails = () => { const projectId = useSelectedproject(); @@ -126,6 +127,12 @@ const ProjectDetails = () => { ); + case "setting": + return ( +
+ +
+ ); default: return ; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index b2f0f7ff..6543301a 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -37,6 +37,14 @@ const ProjectRepository = { api.get( `/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}` ), + + + // Permission Managment for Employee at Project Level + + getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`), + getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`), + getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), + updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data) }; export const TasksRepository = {