Merge pull request 'Handled Global and Project level permissions' (#403) from hotchanges_projectPermission into Refactor_Directory

Reviewed-on: #403
Merged
This commit is contained in:
pramod.mahajan 2025-09-16 10:43:30 +00:00
commit 7ef10e3e5b
6 changed files with 147 additions and 159 deletions

View File

@ -7,6 +7,10 @@ import {
DIRECTORY_USER, DIRECTORY_USER,
MANAGE_PROJECT_INFRA, MANAGE_PROJECT_INFRA,
MANAGE_TASK, MANAGE_TASK,
MANAGE_TEAM,
MODIFY_DOCUMENT,
UPLOAD_DOCUMENT,
VIEW_DOCUMENT,
VIEW_PROJECT_INFRA, VIEW_PROJECT_INFRA,
} from "../../utils/constants"; } from "../../utils/constants";
@ -17,6 +21,10 @@ const ProjectNav = ({ onPillClick, activePill }) => {
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN); const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
const DireManager = useHasUserPermission(DIRECTORY_MANAGER); const DireManager = useHasUserPermission(DIRECTORY_MANAGER);
const DirUser = useHasUserPermission(DIRECTORY_USER); const DirUser = useHasUserPermission(DIRECTORY_USER);
const isManageTeam = useHasUserPermission(MANAGE_TEAM)
const isViewDocuments = hasUserPermission(VIEW_DOCUMENT);
const isUploadDocument = useHasUserPermission(UPLOAD_DOCUMENT)
const isModifyDocument = useHasUserPermission(MODIFY_DOCUMENT)
const ProjectTab = [ const ProjectTab = [
{ key: "profile", icon: "bx bx-user", label: "Profile" }, { key: "profile", icon: "bx bx-user", label: "Profile" },
@ -33,8 +41,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
label: "Directory", label: "Directory",
hidden: !(DirAdmin || DireManager || DirUser), hidden: !(DirAdmin || DireManager || DirUser),
}, },
{ key: "documents", icon: "bx bx-folder-open", label: "Documents" }, { key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
{ key: "setting", icon: "bx bxs-cog", label: "Setting" }, { key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
]; ];
return ( return (
<div className="nav-align-top"> <div className="nav-align-top">

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { import {
useProjectLevelEmployeePermission, useProjectLevelEmployeePermission,
useProjectLevelModules, useProjectLevelModules,
@ -27,7 +27,7 @@ const ProjectPermission = () => {
watch, watch,
handleSubmit, handleSubmit,
reset, reset,
formState: { errors }, formState: { errors, isDirty },
} = useForm({ } = useForm({
resolver: zodResolver(ProjectPermissionSchema), resolver: zodResolver(ProjectPermissionSchema),
defaultValues: { defaultValues: {
@ -43,82 +43,82 @@ const ProjectPermission = () => {
selectedProject selectedProject
); );
useEffect(() => { useEffect(() => {
if (!selectedEmployee) return; if (!selectedEmployee) return;
const enabledPerms = const enabledPerms =
selectedEmpPermissions?.permissions selectedEmpPermissions?.permissions
?.filter((perm) => perm.isEnabled) ?.filter((perm) => perm.isEnabled)
?.map((perm) => perm.id) || []; ?.map((perm) => perm.id) || [];
reset((prev) => ({
...prev,
selectedPermissions: enabledPerms,
}));
}, [selectedEmpPermissions, reset, selectedEmployee]);
reset((prev) => ({
...prev,
selectedPermissions: enabledPerms,
}));
}, [selectedEmpPermissions, reset, selectedEmployee]);
const { mutate: updatePermission, isPending } = const { mutate: updatePermission, isPending } =
useUpdateProjectLevelEmployeePermission(); useUpdateProjectLevelEmployeePermission();
const onSubmit = (formData) => { const onSubmit = (formData) => {
if (!formData.employeeId) { if (!formData.employeeId) {
showToast("Please select an employee", "warn"); showToast("Please select an employee", "warn");
return; return;
} }
const existingPermissions = selectedEmpPermissions?.permissions || []; const existingPermissions = selectedEmpPermissions?.permissions || [];
const existingEnabledIds = existingPermissions const existingEnabledIds = existingPermissions
.filter((p) => p.isEnabled) .filter((p) => p.isEnabled)
.map((p) => p.id); .map((p) => p.id);
const newSelectedIds = formData.selectedPermissions || []; const newSelectedIds = formData.selectedPermissions || [];
const removed = existingEnabledIds const removed = existingEnabledIds
.filter((id) => !newSelectedIds.includes(id)) .filter((id) => !newSelectedIds.includes(id))
.map((id) => ({ id, isEnabled: false })); .map((id) => ({ id, isEnabled: false }));
const added = newSelectedIds const added = newSelectedIds
.filter((id) => !existingEnabledIds.includes(id)) .filter((id) => !existingEnabledIds.includes(id))
.map((id) => ({ id, isEnabled: true })); .map((id) => ({ id, isEnabled: true }));
const payloadPermissions = [...removed, ...added]; const payloadPermissions = [...removed, ...added];
if (payloadPermissions.length === 0) { if (payloadPermissions.length === 0) {
showToast("No changes detected", "info"); showToast("No changes detected", "info");
return; return;
} }
const payload = { const payload = {
employeeId: formData.employeeId, employeeId: formData.employeeId,
projectId: selectedProject, projectId: selectedProject,
permission: payloadPermissions, permission: payloadPermissions,
};
updatePermission(payload);
}; };
updatePermission(payload); const useOnClick = useCallback((event) => {}, []);
};
return ( return (
<div className="row px-2 py-1"> <div className="w-100 p py-1 ">
<form className="row" onSubmit={handleSubmit(onSubmit)}> <form className="row" onSubmit={handleSubmit(onSubmit)}>
{/* Employee Dropdown */} <div className="d-flex justify-content-between align-items-end gap-2 mb-3">
<div className="d-flex align-items-end gap-2"> <div className="text-start d-flex align-items-center gap-2">
<div className="text-start"> <div className="d-block"><label className="form-label">Select Employee</label></div>
<label className="form-label">Select Employee</label> <div className="d-block"> <select
<select
className="form-select form-select-sm" className="form-select form-select-sm"
{...register("employeeId")} {...register("employeeId")}
disabled={isPending} disabled={isPending}
dd
> >
{loading ? ( {loading ? (
<option value="">Loading...</option> <option value="">Loading...</option>
) : ( ) : (
<> <>
<option value="">-- Select Employee --</option> <option value="">-- Select Employee --</option>
{[...employees]?.sort((a, b) => {[...employees]
`${a.firstName} ${a.firstName}`?.localeCompare( ?.sort((a, b) =>
`${b.firstName} ${b.lastName}` `${a?.firstName} ${a?.firstName}`?.localeCompare(
`${b?.firstName} ${b?.lastName}`
) )
) )
?.map((emp) => ( ?.map((emp) => (
@ -128,7 +128,7 @@ useEffect(() => {
))} ))}
</> </>
)} )}
</select> </select></div>
{errors.employeeId && ( {errors.employeeId && (
<div className="text-danger small"> <div className="text-danger small">
@ -136,42 +136,48 @@ useEffect(() => {
</div> </div>
)} )}
</div> </div>
<button {isDirty && (
className="btn btn-sm btn-primary" <div className="mt-3 text-end">
disabled={isPending || loading} <button
> type="submit"
{isPending ? "Please Wait..." : "Update Permission"} className="btn btn-sm btn-primary"
</button> disabled={isPending || loading}
>
{isPending ? "Please Wait..." : "Save Permission"}
</button>
</div>
)}
</div> </div>
{/* Permissions */}
{ProjectModules.map((feature) => ( {ProjectModules.map((feature) => (
<div key={feature.id} className="row my-2 px-3 "> <div
<div className="col-12 text-start fw-semibold mb-2"> key={feature.id}
{feature.name} className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
</div> >
<div className="col-12"> <div className="card text-start border-1 p-1">
<div className="row"> <p className="card-title fs-6 fw-semibold">{feature.name}</p>
{feature.featurePermissions?.map((perm) => ( <div className="px-2">
<div className="col-12 col-sm-6 col-md-4 mb-2" key={perm.id}> <ul class="list-unstyled">
<label {feature.featurePermissions?.map((perm) => (
className="form-check-label d-flex align-items-center" <div className="d-flex my-2" key={perm.id}>
htmlFor={perm.id} <label
> className="form-check-label d-flex align-items-center"
<input htmlFor={perm.id}
type="checkbox" >
className="form-check-input me-2" <input
id={perm.id} type="checkbox"
value={perm.id} className="form-check-input me-2"
{...register("selectedPermissions")} id={perm.id}
/> value={perm.id}
{perm.name} {...register("selectedPermissions")}
</label> />
</div> {perm.name}
))} </label>
</div>
))}
</ul>
</div> </div>
</div> </div>
<hr className="my-2" />
</div> </div>
))} ))}
</form> </form>

View File

@ -1,11 +1,16 @@
import { useProfile } from "./useProfile" import { useSelectedProject } from "../slices/apiDataManager";
import { useAllProjectLevelPermissions, useProfile } from "./useProfile";
export const useHasUserPermission = (permission) => { export const useHasUserPermission = (permission) => {
const selectedProject = useSelectedProject();
const { profile } = useProfile(); const { profile } = useProfile();
const { data: projectPermissions = [], isLoading, isError } = useAllProjectLevelPermissions(selectedProject);
if (profile && permission && typeof permission === "string") { if (isLoading || !permission) return false;
return profile?.featurePermissions.includes(permission);
} const globalPerms = profile?.featurePermissions ?? [];
const projectPerms = projectPermissions ?? [];
return false;
return globalPerms.includes(permission) || projectPerms.includes(permission);
}; };

View File

@ -1,67 +1,20 @@
import {useState,useEffect, useCallback} from "react"; import { useState, useEffect, useCallback } from "react";
import AuthRepository from "../repositories/AuthRepository"; import AuthRepository from "../repositories/AuthRepository";
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager"; import {
import {useSelector} from "react-redux"; cacheData,
cacheProfileData,
getCachedData,
getCachedProfileData,
useSelectedProject,
} from "../slices/apiDataManager";
import { useSelector } from "react-redux";
import eventBus from "../services/eventBus"; import eventBus from "../services/eventBus";
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import ProjectRepository from "../repositories/ProjectRepository";
let hasFetched = false; let hasFetched = false;
let hasReceived = false; let hasReceived = false;
// export const useProfile = () => {
// const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
// const [profile, setProfile] = useState(null);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// const fetchData = async () => {
// try {
// setLoading(true);
// let response = await AuthRepository.profile();
// setProfile(response.data);
// cacheProfileData(response.data);
// } catch (error) {
// setError("Failed to fetch data.");
// } finally {
// setLoading(false);
// }
// };
// const validation = () => {
// if (!hasFetched) {
// hasFetched = true;
// if (!loggedUser) {
// fetchData();
// } else {
// setProfile(loggedUser);
// }
// }
// setProfile(loggedUser);
// }
// useEffect(() => {
// validation();
// }, [loggedUser]);
// const handler = useCallback(
// (data) => {
// if(!getCachedData("hasReceived")){
// cacheData("hasReceived", true);
// hasFetched = false;
// validation();
// }
// },[]
// );
// useEffect(() => {
// eventBus.on("assign_project_one", handler);
// return () => eventBus.off("assign_project_one", handler);
// }, [handler]);
// return { profile, loading, error };
// };
export const useProfile = () => { export const useProfile = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser); const loggedUser = useSelector((store) => store.globalVariables.loginUser);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -100,12 +53,26 @@ export const useProfile = () => {
}; };
}; };
export const useSidBarMenu = () => {
export const useSidBarMenu = ()=>{ const userLogged = useSelector((store) => store.globalVariables.loginUser);
const userLogged = useSelector((store)=>store.globalVariables.loginUser);
return useQuery({ return useQuery({
queryKey:["AppMenu"], queryKey: ["AppMenu"],
queryFn:async()=> await AuthRepository.appmenu(), queryFn: async () => await AuthRepository.appmenu(),
enabled: !!userLogged enabled: !!userLogged,
}) });
} };
export const useAllProjectLevelPermissions = (projectId) => {
return useQuery({
queryKey: ["AllProjectLevelPermission", projectId],
queryFn: async () => {
const resp = await ProjectRepository.getAllProjectLevelPermission(
projectId
);
return resp.data;
},
enabled: !!projectId,
});
};

View File

@ -44,7 +44,8 @@ const ProjectRepository = {
getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`), getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`),
getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`), getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`),
getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), 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) updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data),
getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`)
}; };
export const TasksRepository = { export const TasksRepository = {

View File

@ -17,6 +17,7 @@ export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b"
export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f" export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f"
export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b"
export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373" export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"