430 lines
15 KiB
JavaScript
430 lines
15 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import IconButton from "../common/IconButton";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { bucketScheam } from "./DirectorySchema";
|
|
import showToast from "../../services/toastService";
|
|
import Directory from "../../pages/Directory/Directory";
|
|
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
|
import { useBuckets } from "../../hooks/useDirectory";
|
|
import EmployeeList from "./EmployeeList";
|
|
import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
|
|
import { useSortableData } from "../../hooks/useSortableData";
|
|
import ConfirmModal from "../common/ConfirmModal";
|
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|
import { DIRECTORY_ADMIN, DIRECTORY_MANAGER } from "../../utils/constants";
|
|
import { useProfile } from "../../hooks/useProfile";
|
|
|
|
const ManageBucket = () => {
|
|
const { profile } = useProfile();
|
|
const [bucketList, setBucketList] = useState([]);
|
|
const { employeesList } = useAllEmployees(false);
|
|
const [selectedEmployee, setSelectEmployee] = useState([]);
|
|
const { buckets, loading, refetch } = useBuckets();
|
|
const [action_bucket, setAction_bucket] = useState(false);
|
|
const [isSubmitting, setSubmitting] = useState(false);
|
|
const [selected_bucket, select_bucket] = useState(null);
|
|
const [deleteBucket, setDeleteBucket] = useState(null);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const DirManager = useHasUserPermission(DIRECTORY_MANAGER);
|
|
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
|
|
const {
|
|
items: sortedBuckteList,
|
|
requestSort,
|
|
sortConfig,
|
|
} = useSortableData(bucketList, {
|
|
key: (e) => `${e.name}`,
|
|
direction: "asc",
|
|
});
|
|
const getSortIcon = () => {
|
|
if (!sortConfig) return null;
|
|
return sortConfig.direction === "asc" ? (
|
|
<i className="bx bx-caret-up text-secondary"></i>
|
|
) : (
|
|
<i className="bx bx-caret-down text-secondary"></i>
|
|
);
|
|
};
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
reset,
|
|
formState: { errors },
|
|
} = useForm({
|
|
resolver: zodResolver(bucketScheam),
|
|
defaultValues: {
|
|
name: "",
|
|
description: "",
|
|
},
|
|
});
|
|
|
|
const onSubmit = async (data) => {
|
|
setSubmitting(true);
|
|
|
|
try {
|
|
const cache_buckets = getCachedData("buckets") || [];
|
|
let response;
|
|
|
|
// Utility: Compare arrays regardless of order
|
|
const arraysAreEqual = (a, b) => {
|
|
if (a.length !== b.length) return false;
|
|
const setA = new Set(a);
|
|
const setB = new Set(b);
|
|
return [...setA].every((id) => setB.has(id));
|
|
};
|
|
|
|
// UPDATE existing bucket
|
|
if (selected_bucket) {
|
|
const payload = { ...data, id: selected_bucket.id };
|
|
|
|
// 1. Update bucket details
|
|
response = await DirectoryRepository.UpdateBuckets(
|
|
selected_bucket.id,
|
|
payload
|
|
);
|
|
|
|
const updatedBuckets = cache_buckets.map((bucket) =>
|
|
bucket.id === selected_bucket.id ? response?.data : bucket
|
|
);
|
|
|
|
cacheData("buckets", updatedBuckets);
|
|
setBucketList(updatedBuckets);
|
|
|
|
// 2. Update employee assignments if they changed
|
|
const existingEmployeeIds = selected_bucket?.employeeIds || [];
|
|
const employeesToUpdate = selectedEmployee.filter((emp) => {
|
|
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
|
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
|
|
});
|
|
|
|
// Create a filtered list of active employee IDs to compare
|
|
const newActiveEmployeeIds = selectedEmployee
|
|
.filter((emp) => {
|
|
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
|
return (
|
|
(!isExisting && emp.isActive) || (isExisting && !emp.isActive)
|
|
);
|
|
})
|
|
.map((emp) => emp.employeeId);
|
|
|
|
if (
|
|
!arraysAreEqual(newActiveEmployeeIds, existingEmployeeIds) &&
|
|
employeesToUpdate.length != 0
|
|
) {
|
|
try {
|
|
response = await DirectoryRepository.AssignedBuckets(
|
|
selected_bucket.id,
|
|
employeesToUpdate
|
|
);
|
|
} catch (assignError) {
|
|
const assignMessage =
|
|
assignError?.response?.data?.message ||
|
|
assignError?.message ||
|
|
"Error assigning employees.";
|
|
showToast(assignMessage, "error");
|
|
}
|
|
}
|
|
const updatedData = cache_buckets?.map((bucket) =>
|
|
bucket.id === response?.data?.id ? response.data : bucket
|
|
);
|
|
|
|
cacheData("buckets", updatedData);
|
|
|
|
setBucketList(updatedData);
|
|
showToast("Bucket Updated Successfully", "success");
|
|
}
|
|
|
|
// CREATE new bucket
|
|
else {
|
|
response = await DirectoryRepository.CreateBuckets(data);
|
|
|
|
const updatedBuckets = [...cache_buckets, response?.data];
|
|
cacheData("buckets", updatedBuckets);
|
|
setBucketList(updatedBuckets);
|
|
|
|
showToast("Bucket Created Successfully", "success");
|
|
}
|
|
|
|
handleBack();
|
|
} catch (error) {
|
|
const message =
|
|
error?.response?.data?.message ||
|
|
error?.message ||
|
|
"Error occurred during API call";
|
|
showToast(message, "error");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteContact = async () => {
|
|
try {
|
|
const resp = await DirectoryRepository.DeleteBucket(deleteBucket);
|
|
const cache_buckets = getCachedData("buckets") || [];
|
|
const updatedBuckets = cache_buckets.filter(
|
|
(bucket) => bucket.id != deleteBucket
|
|
);
|
|
cacheData("buckets", updatedBuckets);
|
|
setBucketList(updatedBuckets);
|
|
showToast("Bucket deleted successfully", "success");
|
|
setDeleteBucket(null);
|
|
} catch (error) {
|
|
const message =
|
|
error?.response?.data?.message ||
|
|
error?.message ||
|
|
"Error occurred during API call.";
|
|
showToast(message, "error");
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
reset({
|
|
name: selected_bucket?.name || "",
|
|
description: selected_bucket?.description || "",
|
|
});
|
|
}, [selected_bucket]);
|
|
|
|
useEffect(() => {
|
|
setBucketList(buckets);
|
|
}, [buckets]);
|
|
|
|
const handleBack = () => {
|
|
select_bucket(null);
|
|
setAction_bucket(false);
|
|
setSubmitting(false);
|
|
};
|
|
|
|
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
|
|
const term = searchTerm?.toLowerCase();
|
|
const name = bucket.name?.toLowerCase();
|
|
return name?.includes(term);
|
|
});
|
|
return (
|
|
<>
|
|
{deleteBucket && (
|
|
<div
|
|
className={`modal fade ${deleteBucket ? "show" : ""}`}
|
|
tabIndex="-1"
|
|
role="dialog"
|
|
style={{
|
|
display: deleteBucket ? "block" : "none",
|
|
backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
|
|
}}
|
|
>
|
|
<ConfirmModal
|
|
type={"delete"}
|
|
header={"Delete Bucket"}
|
|
message={"Are you sure you want delete?"}
|
|
onSubmit={handleDeleteContact}
|
|
onClose={() => setDeleteBucket(null)}
|
|
// loading={IsDeleting}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
|
|
<div className="d-flex justify-content-center">
|
|
<p className="fs-6 fw-semibold m-0">Manage Buckets</p>
|
|
</div>
|
|
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-3 align-items-center ">
|
|
{action_bucket ? (
|
|
<i
|
|
className={`fa-solid fa-arrow-left fs-5 cursor-pointer`}
|
|
onClick={handleBack}
|
|
></i>
|
|
) : (
|
|
<div className="d-flex align-items-center gap-2">
|
|
<input
|
|
type="search"
|
|
className="form-control form-control-sm"
|
|
placeholder="Search Bucket ..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
<i
|
|
className={`bx bx-refresh cursor-pointer fs-4 ${
|
|
loading ? "spin" : ""
|
|
}`}
|
|
title="Refresh"
|
|
onClick={() => refetch()}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
className={`btn btn-sm btn-primary ms-auto ${
|
|
action_bucket ? "d-none" : ""
|
|
}`}
|
|
onClick={() => setAction_bucket(true)}
|
|
>
|
|
<i className="bx bx-plus-circle me-2"></i>
|
|
Add Bucket
|
|
</button>
|
|
</div>
|
|
<div>
|
|
{!action_bucket ? (
|
|
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0 mt-3">
|
|
<table className="table px-2">
|
|
<thead className="p-0">
|
|
<tr className="p-0">
|
|
<th
|
|
colSpan={2}
|
|
className="cursor-pointer"
|
|
onClick={() => requestSort((e) => `${e.name} `)}
|
|
>
|
|
<div className="d-flex justify-content-start align-items-center gap-1 mx-2">
|
|
<span>Name {getSortIcon()}</span>
|
|
</div>
|
|
</th>
|
|
<th className="text-start d-none d-sm-table-cell">
|
|
<div className="d-flex align-items-center justify-content-center gap-1">
|
|
<span>Description</span>
|
|
</div>
|
|
</th>
|
|
<th>Contacts</th>
|
|
<th>
|
|
<div className="d-flex align-items-center justify-content-center gap-1">
|
|
<span>Action</span>
|
|
</div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody className="table-border-bottom-0 overflow-auto">
|
|
{loading && (
|
|
<tr className="mt-10">
|
|
<td colSpan={5}>
|
|
{" "}
|
|
<div className="d-flex justify-content-center align-items-center py-5">
|
|
Loading...
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{!loading && buckets.length == 0 && (
|
|
<tr>
|
|
<td colSpan={5}>
|
|
<div className="d-flex justify-content-center align-items-center py-5">
|
|
Bucket Not Available.
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{!loading && sortedBucktesList.length == 0 && (
|
|
<tr>
|
|
<td className="text-center py-4 h-25" colSpan={5}>
|
|
<div className="d-flex justify-content-center align-items-center py-5">
|
|
No Matching Bucket Found.
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{!loading &&
|
|
sortedBucktesList.map((bucket) => (
|
|
<tr key={bucket.id}>
|
|
<td colSpan={2} className="text-start text-wrap">
|
|
<i className="bx bx-right-arrow-alt me-1"></i>{" "}
|
|
{bucket.name}
|
|
</td>
|
|
<td
|
|
className="text-start d-none d-sm-table-cell text-truncate"
|
|
style={{
|
|
maxWidth: "300px",
|
|
whiteSpace: "wrap",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
}}
|
|
title={bucket.description}
|
|
>
|
|
{bucket.description}
|
|
</td>
|
|
<td>{bucket.numberOfContacts}</td>
|
|
<td className="justify-content-center">
|
|
{(DirManager ||
|
|
DirAdmin ||
|
|
bucket?.createdBy?.id ===
|
|
profile?.employeeInfo?.id) && (
|
|
<div className="d-flex justify-content-center align-items-center gap-2">
|
|
<i
|
|
className="bx bx-edit bx-sm text-primary cursor-pointer "
|
|
onClick={() => {
|
|
select_bucket(bucket);
|
|
setAction_bucket(true);
|
|
}}
|
|
></i>
|
|
<i
|
|
className="bx bx-trash bx-sm text-danger cursor-pointer"
|
|
onClick={() => setDeleteBucket(bucket?.id)}
|
|
></i>
|
|
</div>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
|
|
<div className="">
|
|
<label className="form-label">Bucket Name</label>
|
|
<input
|
|
className="form-control form-control-sm"
|
|
{...register("name")}
|
|
/>
|
|
{errors.name && (
|
|
<small className="danger-text">{errors.name.message}</small>
|
|
)}
|
|
</div>
|
|
<div className="">
|
|
<label className="form-label">Bucket Discription</label>
|
|
<textarea
|
|
className="form-control form-control-sm"
|
|
rows="3"
|
|
{...register("description")}
|
|
/>
|
|
{errors.description && (
|
|
<small className="danger-text">
|
|
{errors.description.message}
|
|
</small>
|
|
)}
|
|
</div>
|
|
|
|
{selected_bucket && (
|
|
<EmployeeList
|
|
employees={employeesList}
|
|
onChange={(data) => setSelectEmployee(data)}
|
|
bucket={selected_bucket}
|
|
/>
|
|
)}
|
|
|
|
<div className="mt-2 d-flex justify-content-center gap-3">
|
|
<button
|
|
onClick={() => handleBack()}
|
|
className="btn btn-sm btn-secondary"
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="btn btn-sm btn-primary"
|
|
disabled={isSubmitting}
|
|
>
|
|
{isSubmitting ? "Please wait..." : "Submit"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ManageBucket;
|