Compare commits
No commits in common. "809f2ef726eb481e199248ece0f007af228aac03" and "06503ac4d302705264030beb3f465a03820fb399" have entirely different histories.
809f2ef726
...
06503ac4d3
@ -1,88 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import VersionListSkeleton from "./VersionListSkeleton";
|
|
||||||
|
|
||||||
const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
|
|
||||||
<div
|
|
||||||
className={`skeleton mb-2 ${className}`}
|
|
||||||
style={{
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
borderRadius: 4,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const DocumentDetailsSkeleton = () => {
|
|
||||||
return (
|
|
||||||
<div className="p-1">
|
|
||||||
<p className="fw-bold fs-6">Document Details</p>
|
|
||||||
|
|
||||||
{/* Row 1 */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="60%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="50%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 2 */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="40%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="60%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3 */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="40%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="130px" />
|
|
||||||
<SkeletonLine width="50%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Row 6 - Description */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="d-flex">
|
|
||||||
|
|
||||||
<SkeletonLine width="100%" height={40} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Version list skeleton */}
|
|
||||||
<div className="row text-start py-2">
|
|
||||||
<VersionListSkeleton items={2} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DocumentDetailsSkeleton;
|
|
||||||
@ -53,15 +53,10 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
if (isLoading) return <div>Loading...</div>;
|
||||||
if (isError)
|
if (isError) return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||||
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
|
||||||
|
|
||||||
const {
|
const { uploadedBy = [], documentCategory = [], documentType = [], documentTag = [] } =
|
||||||
uploadedBy = [],
|
data?.data || {};
|
||||||
documentCategory = [],
|
|
||||||
documentType = [],
|
|
||||||
documentTag = [],
|
|
||||||
} = data?.data || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
@ -134,53 +129,36 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Filter */}
|
{/* Status Filter */}
|
||||||
<div className="text-start my-2">
|
<div className="text-start d-flex align-items-center my-2">
|
||||||
<label className="form-label d-block mb-2">Choose Status:</label>
|
<label className="form-label me-2 my-0">Choose Status:</label>
|
||||||
<div className="d-flex gap-4">
|
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||||
<label className="switch switch-sm">
|
<button
|
||||||
<input
|
type="button"
|
||||||
type="radio"
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
className="switch-input"
|
isVerified === null ? "active btn-primary text-white" : "text-primary"
|
||||||
name="isVerified"
|
}`}
|
||||||
checked={isVerified === null}
|
onClick={() => setValue("isVerified", null)}
|
||||||
onChange={() => setValue("isVerified", null)}
|
>
|
||||||
/>
|
All
|
||||||
<span className="switch-toggle-slider">
|
</button>
|
||||||
<span className="switch-on"></span>
|
<button
|
||||||
<span className="switch-off"></span>
|
type="button"
|
||||||
</span>
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
<span className="switch-label">All</span>
|
isVerified === true ? "active btn-success text-white" : "text-success"
|
||||||
</label>
|
}`}
|
||||||
|
onClick={() => setValue("isVerified", true)}
|
||||||
<label className="switch switch-sm">
|
>
|
||||||
<input
|
Verified
|
||||||
type="radio"
|
</button>
|
||||||
className="switch-input"
|
<button
|
||||||
name="isVerified"
|
type="button"
|
||||||
checked={isVerified === true}
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
onChange={() => setValue("isVerified", true)}
|
isVerified === false ? "active btn-danger text-white" : "text-danger"
|
||||||
/>
|
}`}
|
||||||
<span className="switch-toggle-slider">
|
onClick={() => setValue("isVerified", false)}
|
||||||
<span className="switch-on"></span>
|
>
|
||||||
<span className="switch-off"></span>
|
Rejected
|
||||||
</span>
|
</button>
|
||||||
<span className="switch-label">Verified</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label className="switch switch-sm">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="switch-input"
|
|
||||||
name="isVerified"
|
|
||||||
checked={isVerified === false}
|
|
||||||
onChange={() => setValue("isVerified", false)}
|
|
||||||
/>
|
|
||||||
<span className="switch-toggle-slider">
|
|
||||||
<span className="switch-on"></span>
|
|
||||||
<span className="switch-off"></span>
|
|
||||||
</span>
|
|
||||||
<span className="switch-label">Rejected</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import {
|
|||||||
} from "./DocumentSchema";
|
} from "./DocumentSchema";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import ManageDocument from "./ManageDocument";
|
import ManageDocument from "./ManageDocument";
|
||||||
import ViewDocument from "./ViewDocument";
|
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
export const DocumentContext = createContext();
|
export const DocumentContext = createContext();
|
||||||
@ -25,24 +24,6 @@ export const useDocumentContext = () => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getDocuementsStatus = (status) => {
|
|
||||||
switch (status) {
|
|
||||||
case true:
|
|
||||||
return (
|
|
||||||
<span className="badge rounded-pill bg-label-success">Verified</span>
|
|
||||||
);
|
|
||||||
case false:
|
|
||||||
return (
|
|
||||||
<span className="badge rounded-pill bg-label-danger">Rejected</span>
|
|
||||||
);
|
|
||||||
case null:
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<span className="badge rounded-pill bg-label-warning"> Pending</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const Documents = ({ Document_Entity, Entity }) => {
|
const Documents = ({ Document_Entity, Entity }) => {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [filters, setFilter] = useState();
|
const [filters, setFilter] = useState();
|
||||||
@ -54,10 +35,6 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
document: null,
|
document: null,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
});
|
});
|
||||||
const [viewDoc, setViewDoc] = useState({
|
|
||||||
document: null,
|
|
||||||
isOpen: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
@ -90,8 +67,6 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
const contextValues = {
|
const contextValues = {
|
||||||
ManageDoc,
|
ManageDoc,
|
||||||
setManageDoc,
|
setManageDoc,
|
||||||
viewDoc,
|
|
||||||
setViewDoc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
@ -118,7 +93,7 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="col-6 col-md-6 col-lg-9 text-end">
|
<div className="col-6 col-md-6 col-lg-9 text-end">
|
||||||
{/* <span
|
<span
|
||||||
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
||||||
disabled={isRefetching}
|
disabled={isRefetching}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -133,7 +108,7 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
isRefetching ? "bx-spin" : ""
|
isRefetching ? "bx-spin" : ""
|
||||||
}`}
|
}`}
|
||||||
></i>
|
></i>
|
||||||
</span> */}
|
</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -180,15 +155,6 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
/>
|
/>
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewDoc.isOpen && (
|
|
||||||
<GlobalModel size="lg" isOpen={viewDoc.isOpen} closeModal={()=>setViewDoc({
|
|
||||||
document:null,
|
|
||||||
isOpen:false
|
|
||||||
})}>
|
|
||||||
<ViewDocument />
|
|
||||||
</GlobalModel>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</DocumentContext.Provider>
|
</DocumentContext.Provider>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useDocumentListByEntityId } from "../../hooks/useDocument";
|
import { useDocumentListByEntityId } from "../../hooks/useDocument";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
@ -6,10 +6,25 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
|||||||
import Loader from "../common/Loader";
|
import Loader from "../common/Loader";
|
||||||
import { useDebounce } from "../../utils/appUtils";
|
import { useDebounce } from "../../utils/appUtils";
|
||||||
import { DocumentTableSkeleton } from "./DocumentSkeleton";
|
import { DocumentTableSkeleton } from "./DocumentSkeleton";
|
||||||
import { getDocuementsStatus, useDocumentContext } from "./Documents";
|
import { useDocumentContext } from "./Documents";
|
||||||
import Pagination from "../common/Pagination";
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getDocuementsStatus = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case true:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-success">Verified</span>
|
||||||
|
);
|
||||||
|
case false:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-danger">Rejected</span>
|
||||||
|
);
|
||||||
|
case null:
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-primary">Pending</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
const DocumentsList = ({
|
const DocumentsList = ({
|
||||||
Document_Entity,
|
Document_Entity,
|
||||||
Entity,
|
Entity,
|
||||||
@ -19,13 +34,12 @@ const DocumentsList = ({
|
|||||||
setRefetchFn,
|
setRefetchFn,
|
||||||
}) => {
|
}) => {
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
const [currentPage,setCurrentPage] = useState(1)
|
|
||||||
const { data, isError, isLoading, error, refetch, isFetching } =
|
const { data, isError, isLoading, error, refetch, isFetching } =
|
||||||
useDocumentListByEntityId(
|
useDocumentListByEntityId(
|
||||||
Document_Entity,
|
Document_Entity,
|
||||||
Entity,
|
Entity,
|
||||||
ITEMS_PER_PAGE,
|
ITEMS_PER_PAGE,
|
||||||
currentPage,
|
1,
|
||||||
filters,
|
filters,
|
||||||
debouncedSearch
|
debouncedSearch
|
||||||
);
|
);
|
||||||
@ -40,18 +54,14 @@ const DocumentsList = ({
|
|||||||
setIsRefetching(isFetching);
|
setIsRefetching(isFetching);
|
||||||
}, [isFetching, setIsRefetching]);
|
}, [isFetching, setIsRefetching]);
|
||||||
|
|
||||||
const {setManageDoc,setViewDoc} = useDocumentContext()
|
const {setManageDoc} = useDocumentContext()
|
||||||
|
|
||||||
// check no data scenarios
|
// check no data scenarios
|
||||||
const noData = !isLoading && !isError && data?.data.length === 0;
|
const noData = !isLoading && !isError && data?.length === 0;
|
||||||
const isSearchEmpty = noData && !!debouncedSearch;
|
const isSearchEmpty = noData && !!debouncedSearch;
|
||||||
const isFilterEmpty = noData && false;
|
const isFilterEmpty = noData && false;
|
||||||
const isInitialEmpty = noData && !debouncedSearch;
|
const isInitialEmpty = noData && !debouncedSearch;
|
||||||
const paginate = (page) => {
|
|
||||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
|
||||||
setCurrentPage(page);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isLoading || isFetching) return <DocumentTableSkeleton />
|
if (isLoading || isFetching) return <DocumentTableSkeleton />
|
||||||
if (isError)
|
if (isError)
|
||||||
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
||||||
@ -128,7 +138,7 @@ const DocumentsList = ({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-start">
|
<tbody className="text-start">
|
||||||
{data?.data?.map((doc) => (
|
{data?.map((doc) => (
|
||||||
<tr key={doc.id}>
|
<tr key={doc.id}>
|
||||||
{DocumentColumns.map((col) => (
|
{DocumentColumns.map((col) => (
|
||||||
<td key={col.key} className={`sorting ${col.align}`}>
|
<td key={col.key} className={`sorting ${col.align}`}>
|
||||||
@ -137,7 +147,7 @@ const DocumentsList = ({
|
|||||||
))}
|
))}
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<div className="d-flex justify-content-center gap-2">
|
<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>
|
<i className="bx bx-show text-primary cursor-pointer"></i>
|
||||||
|
|
||||||
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>setManageDoc({document:doc?.id,isOpen:true})}></i>
|
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>setManageDoc({document:doc?.id,isOpen:true})}></i>
|
||||||
|
|
||||||
@ -148,13 +158,6 @@ const DocumentsList = ({
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{data?.data?.length > 0 && (
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={data?.totalPages}
|
|
||||||
onPageChange={paginate}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -128,7 +128,7 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
|||||||
(t) => String(t.id) === String(documentTypeId)
|
(t) => String(t.id) === String(documentTypeId)
|
||||||
);
|
);
|
||||||
if (!type) return;
|
if (!type) return;
|
||||||
setSelectedType(type)
|
|
||||||
const newSchema = DocumentPayloadSchema({
|
const newSchema = DocumentPayloadSchema({
|
||||||
isMandatory: type.isMandatory ?? false,
|
isMandatory: type.isMandatory ?? false,
|
||||||
regexExpression: type.regexExpression ?? null,
|
regexExpression: type.regexExpression ?? null,
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
|
|
||||||
<div
|
|
||||||
className={`skeleton mb-1 ${className}`}
|
|
||||||
style={{
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
borderRadius: 4,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const VersionListSkeleton = ({ items = 5 }) => {
|
|
||||||
return (
|
|
||||||
<div className="list-group mx-0">
|
|
||||||
{[...Array(items)].map((_, idx) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className="list-group-item py-2 border border-bottom border-top-0 border-start-0 border-end-0"
|
|
||||||
>
|
|
||||||
{/* Top row: document name + version/status */}
|
|
||||||
<div className="d-flex w-100 justify-content-between">
|
|
||||||
<SkeletonLine width="40%" height={16} />
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
<SkeletonLine width="60px" height={14} />
|
|
||||||
<SkeletonLine width="80px" height={14} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Upload by row */}
|
|
||||||
<div className="d-flex align-items-center gap-2 mt-2">
|
|
||||||
<SkeletonLine width="24px" height="24px" className="rounded-circle" />
|
|
||||||
<SkeletonLine width="120px" height={14} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Updated at row */}
|
|
||||||
<div className="d-flex gap-2 mt-2">
|
|
||||||
<SkeletonLine width="150px" height={14} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VersionListSkeleton;
|
|
||||||
@ -1,223 +0,0 @@
|
|||||||
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 <DocumentDetailsSkeleton/>;
|
|
||||||
if (isError) return <div>{error.message}</div>;
|
|
||||||
return (
|
|
||||||
<div className="p-1">
|
|
||||||
<p className="fw-bold fs-6">Document Details</p>
|
|
||||||
|
|
||||||
{/* Row 1 */}
|
|
||||||
{/* Row 1 */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex text-start">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Document Name:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">{data.name || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex text-start">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Document ID:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">{data.documentId || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 2 */}
|
|
||||||
<div className="row mb-2">
|
|
||||||
<div className="col-12 col-md-6 text-start">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Version:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">{data.version || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 text-start">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Uploaded At:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">
|
|
||||||
{formatUTCToLocalTime(data.uploadedAt)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3 */}
|
|
||||||
<div className="row mb-2 text-start">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Uploaded By:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">
|
|
||||||
{data.uploadedBy?.firstName || "-"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Updated At:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">
|
|
||||||
{formatUTCToLocalTime(data.updatedAt) || "-"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 4 */}
|
|
||||||
<div className="row mb-2 text-start">
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Category:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">
|
|
||||||
{data.documentType?.documentCategory?.name || "-"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Type:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">{data.documentType?.name || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 5 - Tags full width */}
|
|
||||||
<div className="row mb-2 text-start">
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Tags:
|
|
||||||
</span>
|
|
||||||
<div className="d-flex flex-wrap gap-2">
|
|
||||||
{data.tags?.length > 0 ? (
|
|
||||||
data.tags.map((t, i) => (
|
|
||||||
<span
|
|
||||||
key={i}
|
|
||||||
className="badge rounded-pill bg-label-secondary"
|
|
||||||
>
|
|
||||||
{t.name}
|
|
||||||
</span>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<span className="text-muted">-</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 6 - Description full width */}
|
|
||||||
<div className="row mb-2 text-start">
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="d-flex">
|
|
||||||
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
|
||||||
Description:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted">{data.description || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row text-start py-2">
|
|
||||||
<p className="m-0 fw-semibold : ">Documents</p>
|
|
||||||
{versionLoding && <VersionListSkeleton items={2}/>}
|
|
||||||
{!versionLoding &&(<div className="list-group mx-0">
|
|
||||||
{
|
|
||||||
versionList?.data.map((document) => (
|
|
||||||
<a className="list-group-item list-group-item-action py-1 border border-bottom border-top-0 border-start-0 border-end-0">
|
|
||||||
<div className="d-flex w-100 justify-content-between m-0">
|
|
||||||
<p className="m-0">
|
|
||||||
{document.name}{" "}
|
|
||||||
<em className="text-secondary ms-3">
|
|
||||||
{formatUTCToLocalTime(document?.uploadedAt)}
|
|
||||||
</em>
|
|
||||||
</p>
|
|
||||||
<div className="d-flex align-items-center gap-1">
|
|
||||||
<small>Version {document.version}</small>
|
|
||||||
<small>{getDocuementsStatus(document.isVerified)}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="d-flex align-items-center text-secondary">
|
|
||||||
Upload By
|
|
||||||
<Avatar
|
|
||||||
size="xs"
|
|
||||||
classAvatar="m-0"
|
|
||||||
firstName={document.uploadedBy?.firstName}
|
|
||||||
lastName={document.uploadedBy?.lastName}
|
|
||||||
/>
|
|
||||||
<span className="text-truncate m-0 ">
|
|
||||||
{`${document.uploadedBy?.firstName ?? ""} ${
|
|
||||||
document.uploadedBy?.lastName ?? ""
|
|
||||||
}`.trim() || "N/A"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
{document?.updatedAt && (
|
|
||||||
<span className="small text-secondary">
|
|
||||||
Updated At : {formatUTCToLocalTime(document.updatedAt)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>)}
|
|
||||||
{!versionLoding && versionList?.data?.length > 0 && (
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={versionList?.totalPages}
|
|
||||||
onPageChange={paginate}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ViewDocument;
|
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
APPROVE_EXPENSE,
|
APPROVE_EXPENSE,
|
||||||
EXPENSE_DRAFT,
|
EXPENSE_DRAFT,
|
||||||
EXPENSE_REJECTEDBY,
|
EXPENSE_REJECTEDBY,
|
||||||
ITEMS_PER_PAGE,
|
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
||||||
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
||||||
@ -23,11 +22,12 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
const IsExpenseEditable = useHasUserPermission();
|
const IsExpenseEditable = useHasUserPermission();
|
||||||
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const pageSize = 20;
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
|
||||||
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
||||||
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
||||||
ITEMS_PER_PAGE,
|
pageSize,
|
||||||
currentPage,
|
currentPage,
|
||||||
filters,
|
filters,
|
||||||
debouncedSearch
|
debouncedSearch
|
||||||
|
|||||||
@ -178,7 +178,7 @@ const {
|
|||||||
isError,
|
isError,
|
||||||
isLoading
|
isLoading
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["Document Type",category],
|
queryKey: ["Document Type"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await MasterRespository.getDocumentTypes(category)
|
const res = await MasterRespository.getDocumentTypes(category)
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
@ -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 showToast from "../services/toastService";
|
||||||
import { DocumentRepository } from "../repositories/DocumentRepository";
|
import { DocumentRepository } from "../repositories/DocumentRepository";
|
||||||
|
|
||||||
@ -6,12 +6,7 @@ import { DocumentRepository } from "../repositories/DocumentRepository";
|
|||||||
const cleanFilter = (filter) => {
|
const cleanFilter = (filter) => {
|
||||||
const cleaned = { ...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) {
|
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
||||||
delete cleaned[key];
|
delete cleaned[key];
|
||||||
}
|
}
|
||||||
@ -25,112 +20,73 @@ const cleanFilter = (filter) => {
|
|||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
};
|
};
|
||||||
export const useDocumentListByEntityId = (
|
export const useDocumentListByEntityId=(entityTypeId,entityId,pageSize, pageNumber, filter,searchString="")=>{
|
||||||
entityTypeId,
|
|
||||||
entityId,
|
|
||||||
pageSize,
|
|
||||||
pageNumber,
|
|
||||||
filter,
|
|
||||||
searchString = ""
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey:["DocumentList",entityTypeId,entityId,pageSize, pageNumber, filter,searchString],
|
||||||
"DocumentList",
|
queryFn:async()=>{
|
||||||
entityTypeId,
|
|
||||||
entityId,
|
|
||||||
pageSize,
|
|
||||||
pageNumber,
|
|
||||||
filter,
|
|
||||||
searchString,
|
|
||||||
],
|
|
||||||
queryFn: async () => {
|
|
||||||
const cleanedFilter = cleanFilter(filter);
|
const cleanedFilter = cleanFilter(filter);
|
||||||
const resp = await DocumentRepository.getDocumentList(
|
const resp = await DocumentRepository.getDocumentList(entityTypeId,entityId,pageSize, pageNumber,cleanedFilter,searchString);
|
||||||
entityTypeId,
|
return resp.data;
|
||||||
entityId,
|
|
||||||
pageSize,
|
|
||||||
pageNumber,
|
|
||||||
cleanedFilter,
|
|
||||||
searchString
|
|
||||||
);
|
|
||||||
return resp.data
|
|
||||||
|
|
||||||
},
|
},
|
||||||
enabled: !!entityTypeId && !!entityId,
|
enabled:!!entityTypeId && !! entityId
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useDocumentFilterEntities = (entityTypeId) => {
|
export const useDocumentFilterEntities =(entityTypeId)=>{
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["DFilter", entityTypeId],
|
queryKey:["DFilter",entityTypeId],
|
||||||
queryFn: async () =>
|
queryFn:async()=> await DocumentRepository.getFilterEntities(entityTypeId)
|
||||||
await DocumentRepository.getFilterEntities(entityTypeId),
|
})
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const useDocumentDetails = (documentId) => {
|
export const useDocumentDetails =(documentId)=>{
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["Document", documentId],
|
queryKey:["Document",documentId],
|
||||||
queryFn: async () => {
|
queryFn:async()=> {
|
||||||
const resp = await DocumentRepository.getDocumentById(documentId);
|
const resp = await DocumentRepository.getDocumentById(documentId);
|
||||||
return resp.data;
|
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 -------------------------
|
//----------------------- MUTATION -------------------------
|
||||||
|
|
||||||
export const useUploadDocument = (onSuccessCallBack) => {
|
export const useUploadDocument =(onSuccessCallBack)=>{
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient()
|
||||||
return useMutation({
|
return useMutation(({
|
||||||
mutationFn: async (DocumentPayload) =>
|
mutationFn:async(DocumentPayload)=>DocumentRepository.uploadDocument(DocumentPayload),
|
||||||
DocumentRepository.uploadDocument(DocumentPayload),
|
onSuccess:(data,variables)=>{
|
||||||
onSuccess: (data, variables) => {
|
queryClient.invalidateQueries({queryKey:["DocumentList"]});
|
||||||
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
|
||||||
|
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if(onSuccessCallBack) onSuccessCallBack()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log(error);
|
console.log(error)
|
||||||
showToast(
|
showToast(
|
||||||
error.response.data.message ||
|
error.response.data.message || "Something went wrong please try again !",
|
||||||
"Something went wrong please try again !",
|
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
}))
|
||||||
};
|
}
|
||||||
export const useUpdateDocument = (onSuccessCallBack) => {
|
export const useUpdateDocument =(onSuccessCallBack)=>{
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient()
|
||||||
return useMutation({
|
return useMutation(({
|
||||||
mutationFn: async ({ documentId, DocumentPayload }) =>
|
mutationFn:async({documentId,DocumentPayload})=>DocumentRepository.UpdateDocument(documentId,DocumentPayload),
|
||||||
DocumentRepository.UpdateDocument(documentId, DocumentPayload),
|
onSuccess:(data,variables)=>{
|
||||||
onSuccess: (data, variables) => {
|
const {documentId} = variables;
|
||||||
const { documentId } = variables;
|
queryClient.invalidateQueries({queryKey:["DocumentList"]});
|
||||||
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
queryClient.invalidateQueries({queryKey:["Document",documentId]})
|
||||||
queryClient.invalidateQueries({ queryKey: ["Document", documentId] });
|
if(onSuccessCallBack) onSuccessCallBack()
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log(error);
|
console.log(error)
|
||||||
showToast(
|
showToast(
|
||||||
error.response.data.message ||
|
error.response.data.message || "Something went wrong please try again !",
|
||||||
"Something went wrong please try again !",
|
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
}))
|
||||||
};
|
}
|
||||||
@ -85,14 +85,6 @@ const LoginPage = () => {
|
|||||||
navigate("/auth/login-otp");
|
navigate("/auth/login-otp");
|
||||||
}
|
}
|
||||||
}, [IsLoginWithOTP]);
|
}, [IsLoginWithOTP]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const token = localStorage.getItem("jwtToken");
|
|
||||||
if (token) {
|
|
||||||
navigate("/dashboard");
|
|
||||||
}
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2">Welcome to PMS!</h4>
|
<h4 className="mb-2">Welcome to PMS!</h4>
|
||||||
|
|||||||
@ -10,15 +10,5 @@ export const DocumentRepository = {
|
|||||||
|
|
||||||
getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`),
|
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}`)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user