diff --git a/src/components/Documents/DocumentDetailsSkeleton .jsx b/src/components/Documents/DocumentDetailsSkeleton .jsx new file mode 100644 index 00000000..c647dedf --- /dev/null +++ b/src/components/Documents/DocumentDetailsSkeleton .jsx @@ -0,0 +1,88 @@ +import React from "react"; +import VersionListSkeleton from "./VersionListSkeleton"; + +const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => ( +
+); + +const DocumentDetailsSkeleton = () => { + return ( +
+

Document Details

+ + {/* Row 1 */} +
+
+
+ + +
+
+
+
+ + +
+
+
+ + {/* Row 2 */} +
+
+
+ + +
+
+
+
+ + +
+
+
+ + {/* Row 3 */} +
+
+
+ + +
+
+
+
+ + +
+
+
+ + + + {/* Row 6 - Description */} +
+
+
+ + +
+
+
+ + {/* Version list skeleton */} +
+ +
+
+ ); +}; + +export default DocumentDetailsSkeleton; diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 44c95195..fbb89d69 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -13,6 +13,7 @@ import { } from "./DocumentSchema"; import { zodResolver } from "@hookform/resolvers/zod"; import ManageDocument from "./ManageDocument"; +import ViewDocument from "./ViewDocument"; // Context export const DocumentContext = createContext(); @@ -24,6 +25,24 @@ export const useDocumentContext = () => { return context; }; + +export const getDocuementsStatus = (status) => { + switch (status) { + case true: + return ( + Verified + ); + case false: + return ( + Rejected + ); + case null: + default: + return ( + Pending + ); + } +}; const Documents = ({ Document_Entity, Entity }) => { const [searchText, setSearchText] = useState(""); const [filters, setFilter] = useState(); @@ -35,6 +54,10 @@ const Documents = ({ Document_Entity, Entity }) => { document: null, isOpen: false, }); + const [viewDoc, setViewDoc] = useState({ + document: null, + isOpen: false, + }); const { setOffcanvasContent, setShowTrigger } = useFab(); @@ -67,6 +90,8 @@ const Documents = ({ Document_Entity, Entity }) => { const contextValues = { ManageDoc, setManageDoc, + viewDoc, + setViewDoc } useEffect(()=>{ @@ -93,7 +118,7 @@ const Documents = ({ Document_Entity, Entity }) => { {/* Actions */}
- { @@ -108,7 +133,7 @@ const Documents = ({ Document_Entity, Entity }) => { isRefetching ? "bx-spin" : "" }`} > - + */}
diff --git a/src/components/Documents/VersionListSkeleton.jsx b/src/components/Documents/VersionListSkeleton.jsx new file mode 100644 index 00000000..d239315c --- /dev/null +++ b/src/components/Documents/VersionListSkeleton.jsx @@ -0,0 +1,47 @@ +import React from "react"; + +const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => ( +
+); + +const VersionListSkeleton = ({ items = 5 }) => { + return ( +
+ {[...Array(items)].map((_, idx) => ( +
+ {/* Top row: document name + version/status */} +
+ +
+ + +
+
+ + {/* Upload by row */} +
+ + +
+ + {/* Updated at row */} +
+ +
+
+ ))} +
+ ); +}; + +export default VersionListSkeleton; diff --git a/src/components/Documents/ViewDocument.jsx b/src/components/Documents/ViewDocument.jsx new file mode 100644 index 00000000..f22e836e --- /dev/null +++ b/src/components/Documents/ViewDocument.jsx @@ -0,0 +1,223 @@ +import React, { useState } from "react"; +import { + useDocumentDetails, + useDocumentVersionList, +} from "../../hooks/useDocument"; +import { getDocuementsStatus, useDocumentContext } from "./Documents"; +import { formatUTCToLocalTime } from "../../utils/dateUtils"; +import Avatar from "../common/Avatar"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; +import Pagination from "../common/Pagination"; +import VersionListSkeleton from "./VersionListSkeleton"; +import DocumentDetailsSkeleton from "./DocumentDetailsSkeleton "; + +const ViewDocument = () => { + const { viewDoc, setViewDoc } = useDocumentContext(); + const [currentPage, setCurrentPage] = useState(1); + const { data, isLoading, isError, error } = useDocumentDetails( + viewDoc?.document + ); + const { + data: versionList, + isError: isVersionError, + isLoading: versionLoding, + error: versionError, + } = useDocumentVersionList( + data?.parentAttachmentId, + ITEMS_PER_PAGE, + currentPage + ); + const paginate = (page) => { + if (page >= 1 && page <= (versionList?.totalPages ?? 1)) { + setCurrentPage(page); + } + }; + + if (isLoading) return ; + if (isError) return
{error.message}
; + return ( +
+

Document Details

+ + {/* Row 1 */} + {/* Row 1 */} +
+
+
+ + Document Name: + + {data.name || "-"} +
+
+
+
+ + Document ID: + + {data.documentId || "-"} +
+
+
+ + {/* Row 2 */} +
+
+
+ + Version: + + {data.version || "-"} +
+
+
+
+ + Uploaded At: + + + {formatUTCToLocalTime(data.uploadedAt)} + +
+
+
+ + {/* Row 3 */} +
+
+
+ + Uploaded By: + + + {data.uploadedBy?.firstName || "-"} + +
+
+
+
+ + Updated At: + + + {formatUTCToLocalTime(data.updatedAt) || "-"} + +
+
+
+ + {/* Row 4 */} +
+
+
+ + Category: + + + {data.documentType?.documentCategory?.name || "-"} + +
+
+
+
+ + Type: + + {data.documentType?.name || "-"} +
+
+
+ + {/* Row 5 - Tags full width */} +
+
+
+ + Tags: + +
+ {data.tags?.length > 0 ? ( + data.tags.map((t, i) => ( + + {t.name} + + )) + ) : ( + - + )} +
+
+
+
+ + {/* Row 6 - Description full width */} +
+
+
+ + Description: + + {data.description || "-"} +
+
+
+ +
+

Documents

+ {versionLoding && } + {!versionLoding &&(
+ { + versionList?.data.map((document) => ( + +
+

+ {document.name}{" "} + + {formatUTCToLocalTime(document?.uploadedAt)} + +

+
+ Version {document.version} + {getDocuementsStatus(document.isVerified)} +
+
+
+ Upload By + + + {`${document.uploadedBy?.firstName ?? ""} ${ + document.uploadedBy?.lastName ?? "" + }`.trim() || "N/A"} + +
+
+ {document?.updatedAt && ( + + Updated At : {formatUTCToLocalTime(document.updatedAt)} + + )} +
+
+ ))} +
)} + {!versionLoding && versionList?.data?.length > 0 && ( + + )} +
+
+ ); +}; + +export default ViewDocument; diff --git a/src/hooks/useDocument.js b/src/hooks/useDocument.js index abdee444..609955c6 100644 --- a/src/hooks/useDocument.js +++ b/src/hooks/useDocument.js @@ -1,4 +1,4 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import showToast from "../services/toastService"; import { DocumentRepository } from "../repositories/DocumentRepository"; @@ -6,7 +6,12 @@ import { DocumentRepository } from "../repositories/DocumentRepository"; const cleanFilter = (filter) => { const cleaned = { ...filter }; - ["uploadedByIds", "documentCategoryIds", "documentTypeIds", "documentTagIds"].forEach((key) => { + [ + "uploadedByIds", + "documentCategoryIds", + "documentTypeIds", + "documentTagIds", + ].forEach((key) => { if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) { delete cleaned[key]; } @@ -20,73 +25,112 @@ const cleanFilter = (filter) => { return cleaned; }; -export const useDocumentListByEntityId=(entityTypeId,entityId,pageSize, pageNumber, filter,searchString="")=>{ +export const useDocumentListByEntityId = ( + entityTypeId, + entityId, + pageSize, + pageNumber, + filter, + searchString = "" +) => { return useQuery({ - queryKey:["DocumentList",entityTypeId,entityId,pageSize, pageNumber, filter,searchString], - queryFn:async()=>{ + queryKey: [ + "DocumentList", + entityTypeId, + entityId, + pageSize, + pageNumber, + filter, + searchString, + ], + queryFn: async () => { const cleanedFilter = cleanFilter(filter); - const resp = await DocumentRepository.getDocumentList(entityTypeId,entityId,pageSize, pageNumber,cleanedFilter,searchString); - return resp.data; + const resp = await DocumentRepository.getDocumentList( + entityTypeId, + entityId, + pageSize, + pageNumber, + cleanedFilter, + searchString + ); + return resp.data + }, - enabled:!!entityTypeId && !! entityId - }) -} + enabled: !!entityTypeId && !!entityId, + }); +}; -export const useDocumentFilterEntities =(entityTypeId)=>{ +export const useDocumentFilterEntities = (entityTypeId) => { return useQuery({ - queryKey:["DFilter",entityTypeId], - queryFn:async()=> await DocumentRepository.getFilterEntities(entityTypeId) - }) -} + queryKey: ["DFilter", entityTypeId], + queryFn: async () => + await DocumentRepository.getFilterEntities(entityTypeId), + }); +}; -export const useDocumentDetails =(documentId)=>{ +export const useDocumentDetails = (documentId) => { return useQuery({ - queryKey:["Document",documentId], - queryFn:async()=> { + queryKey: ["Document", documentId], + queryFn: async () => { const resp = await DocumentRepository.getDocumentById(documentId); return resp.data; }, - enabled:!!documentId - }) + enabled: !!documentId, + }); +}; -} +export const useDocumentVersionList = (parentAttachmentId,pageSize,pageNumber) => { + return useQuery({ + queryKey: ["DocumentVersionList", parentAttachmentId,pageSize,pageNumber], + queryFn: async () => { + const resp = await DocumentRepository.getDocumentVersionList(parentAttachmentId,pageSize,pageNumber); + return resp.data + }, + + enabled: !!parentAttachmentId, + }); +}; //----------------------- MUTATION ------------------------- -export const useUploadDocument =(onSuccessCallBack)=>{ - const queryClient = useQueryClient() - return useMutation(({ - mutationFn:async(DocumentPayload)=>DocumentRepository.uploadDocument(DocumentPayload), - onSuccess:(data,variables)=>{ - queryClient.invalidateQueries({queryKey:["DocumentList"]}); - - if(onSuccessCallBack) onSuccessCallBack() - }, - onError: (error) => { - console.log(error) - showToast( - error.response.data.message || "Something went wrong please try again !", +export const useUploadDocument = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (DocumentPayload) => + DocumentRepository.uploadDocument(DocumentPayload), + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: ["DocumentList"] }); + + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + console.log(error); + showToast( + error.response.data.message || + "Something went wrong please try again !", "error" ); }, - })) -} -export const useUpdateDocument =(onSuccessCallBack)=>{ - const queryClient = useQueryClient() - return useMutation(({ - mutationFn:async({documentId,DocumentPayload})=>DocumentRepository.UpdateDocument(documentId,DocumentPayload), - onSuccess:(data,variables)=>{ - const {documentId} = variables; - queryClient.invalidateQueries({queryKey:["DocumentList"]}); - queryClient.invalidateQueries({queryKey:["Document",documentId]}) - if(onSuccessCallBack) onSuccessCallBack() - }, - onError: (error) => { - console.log(error) - showToast( - error.response.data.message || "Something went wrong please try again !", + }); +}; +export const useUpdateDocument = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ documentId, DocumentPayload }) => + DocumentRepository.UpdateDocument(documentId, DocumentPayload), + onSuccess: (data, variables) => { + const { documentId } = variables; + queryClient.invalidateQueries({ queryKey: ["DocumentList"] }); + queryClient.invalidateQueries({ queryKey: ["Document", documentId] }); + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + console.log(error); + showToast( + error.response.data.message || + "Something went wrong please try again !", "error" ); }, - })) -} \ No newline at end of file + }); +}; diff --git a/src/repositories/DocumentRepository.jsx b/src/repositories/DocumentRepository.jsx index be75b9eb..b26a5fa3 100644 --- a/src/repositories/DocumentRepository.jsx +++ b/src/repositories/DocumentRepository.jsx @@ -10,5 +10,15 @@ export const DocumentRepository = { getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`), - UpdateDocument:(documentId,data)=>api.put(`/api/Document/edit/${documentId}`,data) + UpdateDocument:(documentId,data)=>api.put(`/api/Document/edit/${documentId}`,data), + + getDocumentVersionList:(parentAttachmentId,pageSize,pageNumber)=>api.get(`/api/Document/list/versions/${parentAttachmentId}/?pageSize=${pageSize}&pageNumber=${pageNumber}`), + + getDocumentVersion:(id)=>api.get(`/api/Document/get/version/${id}`), + + verifyDocument:(id)=>api.post(`/api/Document/verify/${id}`), + + deleteDocument:(id)=>api.delete(`/api/Document/delete/${id}`) + + } \ No newline at end of file