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...>
+ ) : (
+
+ )}
+
+ );
+};
+
+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 = {