225 lines
7.3 KiB
JavaScript
225 lines
7.3 KiB
JavaScript
import React, { useCallback, useEffect } from "react";
|
|
import {
|
|
useProjectLevelEmployeePermission,
|
|
useProjectLevelModules,
|
|
useUpdateProjectLevelEmployeePermission,
|
|
} from "../../hooks/useProjects";
|
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
|
import { useEmployeesByProject } from "../../hooks/useEmployees";
|
|
import { useForm, Controller } 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().min(1, "Employee is required"),
|
|
selectedPermissions: z.array(z.string()).optional(),
|
|
});
|
|
|
|
const ProjectPermission = () => {
|
|
const selectedProject = useSelectedProject();
|
|
|
|
const { data: ProjectModules = [] } = useProjectLevelModules();
|
|
const { employees = [], loading } = useEmployeesByProject(selectedProject);
|
|
|
|
const {
|
|
register,
|
|
watch,
|
|
handleSubmit,
|
|
reset,
|
|
control,
|
|
setValue,
|
|
formState: { errors, isDirty },
|
|
} = useForm({
|
|
resolver: zodResolver(ProjectPermissionSchema),
|
|
defaultValues: {
|
|
employeeId: "",
|
|
selectedPermissions: [],
|
|
},
|
|
});
|
|
|
|
const selectedEmployee = watch("employeeId");
|
|
|
|
const { data: selectedEmpPermissions } = useProjectLevelEmployeePermission(
|
|
selectedEmployee || "",
|
|
selectedProject
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!selectedEmployee) return;
|
|
|
|
const enabledPerms =
|
|
selectedEmpPermissions?.permissions
|
|
?.filter((perm) => perm.isEnabled)
|
|
?.map((perm) => perm.id) || [];
|
|
|
|
setValue("selectedPermissions", enabledPerms, { shouldValidate: true });
|
|
}, [selectedEmpPermissions, setValue, selectedEmployee]);
|
|
|
|
const selectedPermissions = watch("selectedPermissions") || [];
|
|
|
|
const existingEnabledIds =
|
|
selectedEmpPermissions?.permissions
|
|
?.filter((p) => p.isEnabled)
|
|
?.map((p) => p.id) || [];
|
|
|
|
const hasChanges =
|
|
selectedPermissions.length !== existingEnabledIds.length ||
|
|
selectedPermissions.some((id) => !existingEnabledIds.includes(id));
|
|
|
|
const { mutate: updatePermission, isPending } =
|
|
useUpdateProjectLevelEmployeePermission();
|
|
|
|
const onSubmit = (formData) => {
|
|
if (!formData.employeeId) {
|
|
showToast("Please select an employee", "warn");
|
|
return;
|
|
}
|
|
|
|
const existingPermissions = selectedEmpPermissions?.permissions || [];
|
|
const existingEnabledIds = existingPermissions
|
|
.filter((p) => p.isEnabled)
|
|
.map((p) => p.id);
|
|
|
|
const newSelectedIds = formData.selectedPermissions || [];
|
|
|
|
const added = newSelectedIds
|
|
.filter((id) => !existingEnabledIds.includes(id))
|
|
.map((id) => ({ id, isEnabled: true }));
|
|
|
|
const removed = existingEnabledIds
|
|
.filter((id) => !newSelectedIds.includes(id))
|
|
.map((id) => ({ id, isEnabled: false }));
|
|
|
|
const payloadPermissions = [...added, ...removed];
|
|
|
|
if (payloadPermissions.length === 0) {
|
|
showToast("No changes detected", "info");
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
employeeId: formData.employeeId,
|
|
projectId: selectedProject,
|
|
permission: payloadPermissions,
|
|
};
|
|
|
|
console.log("Final payload:", payload);
|
|
updatePermission(payload);
|
|
};
|
|
|
|
return (
|
|
<div className="w-100 p py-1 ">
|
|
<div className="text-start m-0">
|
|
<p className="fw-semibold fs-6">Project Permission</p>
|
|
</div>
|
|
<form className="row" onSubmit={handleSubmit(onSubmit)}>
|
|
<div className="d-flex justify-content-between align-items-end gap-2 mb-3">
|
|
<div className="text-start d-flex align-items-center gap-2">
|
|
<div className="d-block">
|
|
<label className="form-label">Select Employee</label>
|
|
</div>
|
|
<div className="d-block">
|
|
{" "}
|
|
<select
|
|
className="form-select form-select-sm"
|
|
{...register("employeeId")}
|
|
disabled={isPending}
|
|
>
|
|
{loading ? (
|
|
<option value="">Loading...</option>
|
|
) : (
|
|
<>
|
|
<option value="">-- Select Employee --</option>
|
|
{[...employees]
|
|
?.sort((a, b) =>
|
|
`${a?.firstName} ${a?.firstName}`?.localeCompare(
|
|
`${b?.firstName} ${b?.lastName}`
|
|
)
|
|
)
|
|
?.map((emp) => (
|
|
<option key={emp.id} value={emp.id}>
|
|
{emp.firstName} {emp.lastName}
|
|
</option>
|
|
))}
|
|
</>
|
|
)}
|
|
</select>
|
|
{errors.employeeId && (
|
|
<div className="d-block text-danger small">
|
|
{errors.employeeId.message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-3 text-end">
|
|
{hasChanges && (
|
|
<button
|
|
type="submit"
|
|
className="btn btn-sm btn-primary"
|
|
disabled={isPending || loading}
|
|
>
|
|
{isPending ? "Please Wait..." : "Save Permission"}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{ProjectModules.map((feature) => (
|
|
<div
|
|
key={feature.id}
|
|
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
|
|
>
|
|
<div className="card text-start border-1 p-1">
|
|
<p className="card-title fs-6 fw-semibold">{feature.name}</p>
|
|
<div className="px-2">
|
|
<ul className="list-unstyled">
|
|
{feature.featurePermissions?.map((perm) => (
|
|
<div className="d-flex my-2" key={perm.id}>
|
|
<Controller
|
|
name="selectedPermissions"
|
|
control={control}
|
|
render={({ field }) => {
|
|
const value = field.value || [];
|
|
const isChecked = value.includes(perm.id);
|
|
|
|
return (
|
|
<label
|
|
className="form-check-label d-flex align-items-center"
|
|
htmlFor={perm.id}
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
className="form-check-input me-2"
|
|
id={perm.id}
|
|
checked={isChecked}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
field.onChange([...value, perm.id]); // add
|
|
} else {
|
|
field.onChange(
|
|
value.filter((v) => v !== perm.id)
|
|
); // remove
|
|
}
|
|
}}
|
|
/>
|
|
{perm.name}
|
|
</label>
|
|
);
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProjectPermission;
|