264 lines
8.3 KiB
JavaScript
264 lines
8.3 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
useActiveInActiveDocument,
|
|
useDocumentListByEntityId,
|
|
} from "../../hooks/useDocument";
|
|
import {
|
|
DELETE_DOCUMENT,
|
|
ITEMS_PER_PAGE,
|
|
MODIFY_DOCUMENT,
|
|
} from "../../utils/constants";
|
|
import Avatar from "../common/Avatar";
|
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
|
import { useDebounce } from "../../utils/appUtils";
|
|
import { DocumentTableSkeleton } from "./DocumentSkeleton";
|
|
import { getDocuementsStatus, useDocumentContext } from "./Documents";
|
|
import Pagination from "../common/Pagination";
|
|
import ConfirmModal from "../common/ConfirmModal";
|
|
import { isPending } from "@reduxjs/toolkit";
|
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|
import { useProfile } from "../../hooks/useProfile";
|
|
import { useParams } from "react-router-dom";
|
|
import showToast from "../../services/toastService";
|
|
|
|
const DocumentsList = ({
|
|
Document_Entity,
|
|
Entity,
|
|
filters,
|
|
searchText,
|
|
setIsRefetching,
|
|
setRefetchFn,
|
|
isActive,
|
|
}) => {
|
|
const { employeeId } = useParams();
|
|
const [isSelf, setIsSelf] = useState(false);
|
|
const { profile } = useProfile();
|
|
const canDeleteDocument = useHasUserPermission(DELETE_DOCUMENT);
|
|
const canModifyDocument = useHasUserPermission(MODIFY_DOCUMENT);
|
|
useEffect(() => {
|
|
if (profile?.employeeInfo?.id && employeeId) {
|
|
setIsSelf(String(profile.employeeInfo.id) === String(employeeId));
|
|
}
|
|
}, [profile?.employeeInfo?.id, employeeId]);
|
|
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
const [deletingId, setDeletingId] = useState(null);
|
|
const [restoringIds, setRestoringIds] = useState([]);
|
|
const debouncedSearch = useDebounce(searchText, 500);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
|
const { data, isError, isLoading, error, refetch, isFetching } =
|
|
useDocumentListByEntityId(
|
|
Document_Entity,
|
|
Entity,
|
|
ITEMS_PER_PAGE,
|
|
currentPage,
|
|
filters,
|
|
debouncedSearch,
|
|
isActive
|
|
);
|
|
|
|
useEffect(() => {
|
|
setRefetchFn(() => refetch);
|
|
}, [setRefetchFn, refetch]);
|
|
|
|
useEffect(() => {
|
|
setIsRefetching(isFetching);
|
|
}, [isFetching, setIsRefetching]);
|
|
|
|
const { setManageDoc, setViewDoc,removeFilterChip } = useDocumentContext();
|
|
const { mutate: ActiveInActive, isPending } = useActiveInActiveDocument();
|
|
|
|
const paginate = (page) => {
|
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
|
setCurrentPage(page);
|
|
}
|
|
};
|
|
|
|
const noData = !isLoading && !isError && data?.data.length === 0;
|
|
const isSearchEmpty = noData && !!debouncedSearch;
|
|
const isFilterEmpty = noData && !!filters && Object.keys(filters).length > 0;
|
|
const isInitialEmpty = noData && !debouncedSearch && !isFilterEmpty;
|
|
|
|
if (isLoading || isFetching) return <DocumentTableSkeleton />;
|
|
if (isError)
|
|
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
|
if (isInitialEmpty) return <div className="py-12 my-12">No documents found yet.</div>;
|
|
if (isSearchEmpty) return <div className="py-12 my-12">No results found for "{debouncedSearch}"</div>;
|
|
if (isFilterEmpty) return <div className="py-12 my-12">No documents match your filter.</div>;
|
|
|
|
const handleDelete = () => {
|
|
ActiveInActive(
|
|
{ documentId: deletingId, isActive: !isActive },
|
|
{
|
|
onSettled: () => {
|
|
setDeletingId(null);
|
|
setIsDeleteModalOpen(false);
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleRestore = (docId) => {
|
|
setRestoringIds((prev) => [...prev, docId]);
|
|
|
|
ActiveInActive(
|
|
{ documentId: docId, isActive: true },
|
|
{
|
|
onSettled: () => {
|
|
setRestoringIds((prev) => prev.filter((id) => id !== docId));
|
|
refetch();
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const DocumentColumns = [
|
|
{
|
|
key: "name",
|
|
label: "Name",
|
|
getValue: (e) => e.name || "N/A",
|
|
align: "text-start",
|
|
},
|
|
{
|
|
key: "documentType",
|
|
label: "Document Type",
|
|
getValue: (e) => e.documentType?.name || "N/A",
|
|
align: "text-start",
|
|
},
|
|
{
|
|
key: "uploadedBy",
|
|
label: "Uploaded By",
|
|
align: "text-start",
|
|
customRender: (e) => (
|
|
<div className="d-flex align-items-center">
|
|
<Avatar
|
|
size="xs"
|
|
classAvatar="m-0"
|
|
firstName={e.uploadedBy?.firstName}
|
|
lastName={e.uploadedBy?.lastName}
|
|
/>
|
|
<span className="text-truncate ms-1">
|
|
{`${e.uploadedBy?.firstName ?? ""} ${
|
|
e.uploadedBy?.lastName ?? ""
|
|
}`.trim() || "N/A"}
|
|
</span>
|
|
</div>
|
|
),
|
|
getValue: (e) =>
|
|
`${e.uploadedBy?.firstName ?? ""} ${
|
|
e.uploadedBy?.lastName ?? ""
|
|
}`.trim() || "N/A",
|
|
},
|
|
{
|
|
key: "uploadedAt",
|
|
label: "Uploaded on",
|
|
getValue: (e) => formatUTCToLocalTime(e.uploadedAt),
|
|
align: "text-center",
|
|
isAlwaysVisible: true,
|
|
},
|
|
{
|
|
key: "Status",
|
|
label: "Status",
|
|
getValue: (e) => getDocuementsStatus(e.isVerified),
|
|
align: "text-center",
|
|
isAlwaysVisible: true,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{IsDeleteModalOpen && (
|
|
<ConfirmModal
|
|
isOpen={IsDeleteModalOpen}
|
|
type="delete"
|
|
header="Delete Document"
|
|
message="Are you sure you want to delete this document?"
|
|
onSubmit={handleDelete}
|
|
onClose={() => setIsDeleteModalOpen(false)}
|
|
loading={!!isPending}
|
|
paramData={deletingId}
|
|
/>
|
|
)}
|
|
|
|
<div className="table-responsive p-2">
|
|
<table className="table border-top dataTable text-nowrap">
|
|
<thead className="">
|
|
<tr className="py-2 ">
|
|
{DocumentColumns.map((col) => (
|
|
<th key={col.key} className={`sorting ${col.align}`}>
|
|
{col.label}
|
|
</th>
|
|
))}
|
|
<th className="sticky-action-column bg-white text-center">
|
|
Action
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="text-start">
|
|
{data?.data?.map((doc) => {
|
|
const isRestoring = restoringIds.includes(doc.id);
|
|
|
|
return (
|
|
<tr key={doc.id}>
|
|
{DocumentColumns.map((col) => (
|
|
<td key={col.key} className={`sorting ${col.align}`}>
|
|
{col.customRender
|
|
? col.customRender(doc)
|
|
: col.getValue(doc)}
|
|
</td>
|
|
))}
|
|
<td className="text-center">
|
|
{doc.isActive ? (
|
|
<div className="d-flex justify-content-center gap-2">
|
|
<i
|
|
className="bx bx-show text-primary cursor-pointer"
|
|
onClick={() =>
|
|
setViewDoc({ document: doc.id, isOpen: true })
|
|
}
|
|
></i>
|
|
|
|
{(isSelf || canModifyDocument) && (
|
|
<i
|
|
className="bx bx-edit text-secondary cursor-pointer"
|
|
onClick={() =>
|
|
setManageDoc({ document: doc.id, isOpen: true })
|
|
}
|
|
></i>
|
|
)}
|
|
|
|
{(isSelf || canDeleteDocument) && (
|
|
<i
|
|
className="bx bx-trash text-danger cursor-pointer"
|
|
onClick={() => {
|
|
setIsDeleteModalOpen(true);
|
|
setDeletingId(doc.id);
|
|
}}
|
|
></i>
|
|
)}
|
|
</div>
|
|
) : isRestoring ? (
|
|
<div
|
|
className="spinner-border spinner-border-sm text-primary"
|
|
role="status"
|
|
>
|
|
<span className="visually-hidden">Loading...</span>
|
|
</div>
|
|
) : (
|
|
<i
|
|
className="bx bx-recycle me-1 text-primary cursor-pointer"
|
|
onClick={() => handleRestore(doc.id)}
|
|
></i>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default DocumentsList;
|