412 lines
14 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;
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));
};
if (selected_bucket) {
const payload = { ...data, id: selected_bucket.id };
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);
const existingEmployeeIds = selected_bucket?.employeeIds || [];
const employeesToUpdate = selectedEmployee.filter((emp) => {
const isExisting = existingEmployeeIds.includes(emp.employeeId);
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
});
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");
} 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);
reset({ name: "", description: "" });
setSelectEmployee([]);
};
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
const term = searchTerm?.toLowerCase();
const name = bucket.name?.toLowerCase();
return name?.includes(term);
});
return (
<>
{deleteBucket && (
<ConfirmModal
isOpen={!!deleteBucket}
type="delete"
header="Delete Bucket"
message="Are you sure you want to delete this bucket?"
onSubmit={handleDeleteContact}
onClose={() => setDeleteBucket(null)}
/>
)}
<div className="container m-0 p-0" style={{ minHeight: "00px" }}>
<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);
select_bucket(null);
reset({ name: "", description: "" });
setSelectEmployee([]);
}}
>
<i className="bx bx-plus-circle me-2"></i>
Add Bucket
</button>
</div>
<div>
{!action_bucket ? (
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
{loading && (
<div className="col-12">
<div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
Loading...
</div>
</div>
)}
{!loading && buckets.length === 0 && searchTerm.trim() === "" && (
<div className="col-12">
<div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No buckets available.
</div>
</div>
)}
{!loading &&
buckets.length > 0 &&
sortedBucktesList.length === 0 && (
<div className="col-12">
<div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No matching buckets found.
</div>
</div>
)}
{!loading &&
sortedBucktesList.map((bucket) => (
<div className="col" key={bucket.id}>
<div className="card h-100">
<div className="card-body p-4">
<h6 className="card-title d-flex justify-content-between align-items-center">
<span>{bucket.name}</span>
{(DirManager ||
DirAdmin ||
bucket?.createdBy?.id ===
profile?.employeeInfo?.id) && (
<div className="d-flex gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
const initialSelectedEmployees = employeesList
.filter((emp) =>
bucket.employeeIds?.includes(
emp.employeeId
)
)
.map((emp) => ({ ...emp, isActive: true }));
setSelectEmployee(initialSelectedEmployees);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
</div>
)}
</h6>
<h6 className="card-subtitle mb-2 text-muted text-start">
Contacts:{" "}
{bucket.numberOfContacts
? bucket.numberOfContacts
: 0}
</h6>
<p
className="card-text text-start"
title={bucket.description}
>
{bucket.description || "No description available."}
</p>
</div>
</div>
</div>
))}
</div>
) : (
<>
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
<div className="mb-3">
<label htmlFor="bucketName" className="form-label">
Bucket Name
</label>
<input
id="bucketName"
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div>
<div className="mb-3">
<label htmlFor="bucketDescription" className="form-label">
Bucket Description
</label>
<textarea
id="bucketDescription"
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-4 d-flex justify-content-center gap-3">
<button
type="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;