initially setup update document

This commit is contained in:
pramod mahajan 2025-09-01 18:15:30 +05:30
parent 518928e439
commit 2f24d4a7ff
6 changed files with 191 additions and 80 deletions

View File

@ -34,13 +34,13 @@ export const TagSchema = z.object({
isActive: z.boolean().default(true),
});
export const DocumentPayloadSchema = (docConfig = {}) => {
const {
isMandatory,
regexExpression,
allowedContentType,
maxSizeAllowedInMB,
isUpdateForm,
} = docConfig;
let documentIdSchema = z.string();
@ -58,20 +58,31 @@ export const DocumentPayloadSchema = (docConfig = {}) => {
);
}
// Base attachment schema
let attachmentSchema = AttachmentSchema(
allowedContentType,
maxSizeAllowedInMB
).nullable();
// If not update form, require attachment
if (!isUpdateForm) {
attachmentSchema = attachmentSchema.refine((val) => val !== null, {
message: "Attachment is required",
});
}
return z.object({
name: z.string().min(1, "Name is required"),
documentId: documentIdSchema,
description: z.string().min(1, { message: "Description is required" }),
// entityId: z.string().min(1, { message: "Please Select Document Entity" }),
documentTypeId: z.string().min(1, { message: "Please Select Document Type" }),
documentTypeId: z
.string()
.min(1, { message: "Please Select Document Type" }),
documentCategoryId: z
.string()
.min(1, { message: "Please Select Document Category" }),
attachment: AttachmentSchema(allowedContentType, maxSizeAllowedInMB).nullable().refine(
(val) => val !== null,
{ message: "Attachment is required" }
),
tags: z.array(TagSchema).optional().default([]),
attachment: attachmentSchema,
tags: z.array(TagSchema).optional().default([]),
});
};
@ -83,14 +94,15 @@ export const defaultDocumentValues = {
// entityId: "",
documentTypeId: "",
documentCategoryId: "",
attachment: {
fileName: "",
base64Data: "",
contentType: "",
fileSize: 0,
description: "",
isActive: true,
},
// attachment: {
// fileName: "",
// base64Data: "",
// contentType: "",
// fileSize: 0,
// description: "",
// isActive: true,
// },
attachment:null,
tags: [],
};

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { createContext, useContext, useEffect, useState } from "react";
import GlobalModel from "../common/GlobalModel";
import NewDocument from "./ManageDocument";
import { DOCUMENTS_ENTITIES } from "../../utils/constants";
@ -14,13 +14,28 @@ import {
import { zodResolver } from "@hookform/resolvers/zod";
import ManageDocument from "./ManageDocument";
// Context
export const DocumentContext = createContext();
export const useDocumentContext = () => {
const context = useContext(DocumentContext);
if (!context) {
throw new Error("useExpenseContext must be used within an ExpenseProvider");
}
return context;
};
const Documents = ({ Document_Entity, Entity }) => {
const [searchText, setSearchText] = useState("");
const [filters, setFilter] = useState();
const [isRefetching, setIsRefetching] = useState(false);
const [refetchFn, setRefetchFn] = useState(null);
const { employeeId } = useParams();
const [isUpload, setUpload] = useState(false);
const [ManageDoc, setManageDoc] = useState({
document: null,
isOpen: false,
});
const { setOffcanvasContent, setShowTrigger } = useFab();
const methods = useForm({
@ -47,9 +62,16 @@ const Documents = ({ Document_Entity, Entity }) => {
setOffcanvasContent("", null);
};
}, []);
const contextValues = {
ManageDoc,
setManageDoc,
}
return (
<div className="mt-5">
<DocumentContext.Provider value={contextValues}>
<div className="mt-5">
<div className="card d-flex p-2">
<div className="row align-items-center">
{/* Search */}
@ -86,7 +108,10 @@ const Documents = ({ Document_Entity, Entity }) => {
type="button"
title="Add New Document"
className="p-1 bg-primary rounded-circle cursor-pointer"
onClick={() => setUpload(true)}
onClick={() => setManageDoc({
document: null,
isOpen: true,
})}
>
<i className="bx bx-plus fs-4 text-white"></i>
</button>
@ -102,16 +127,31 @@ const Documents = ({ Document_Entity, Entity }) => {
/>
</div>
{isUpload && (
<GlobalModel isOpen={isUpload} closeModal={() => setUpload(false)}>
{ManageDoc.isOpen && (
<GlobalModel
isOpen={ManageDoc.isOpen}
closeModal={() =>
setManageDoc({
document: null,
isOpen: false,
})
}
>
<ManageDocument
closeModal={() => setUpload(false)}
closeModal={() =>
setManageDoc({
document: null,
isOpen: false,
})
}
Document_Entity={Document_Entity}
Entity={Entity}
/>
</GlobalModel>
)}
</div>
</DocumentContext.Provider>
);
};

View File

@ -6,6 +6,7 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Loader from "../common/Loader";
import { useDebounce } from "../../utils/appUtils";
import { DocumentTableSkeleton } from "./DocumentSkeleton";
import { useDocumentContext } from "./Documents";
export const getDocuementsStatus = (status) => {
switch (status) {
@ -53,6 +54,8 @@ const DocumentsList = ({
setIsRefetching(isFetching);
}, [isFetching, setIsRefetching]);
const {setManageDoc} = useDocumentContext()
// check no data scenarios
const noData = !isLoading && !isError && data?.length === 0;
const isSearchEmpty = noData && !!debouncedSearch;
@ -146,7 +149,7 @@ const DocumentsList = ({
<div className="d-flex justify-content-center gap-2">
<i className="bx bx-show text-primary cursor-pointer"></i>
<i className="bx bx-edit text-secondary cursor-pointer"></i>
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>setManageDoc({document:doc?.id,isOpen:true})}></i>
<i className="bx bx-trash text-danger cursor-pointer"></i>
</div>

View File

@ -8,8 +8,13 @@ import {
useDocumentTypes,
} from "../../hooks/masterHook/useMaster";
import TagInput from "../common/TagInput";
import { useUploadDocument } from "../../hooks/useDocument";
import {
useDocumentDetails,
useUpdateDocument,
useUploadDocument,
} from "../../hooks/useDocument";
import showToast from "../../services/toastService";
import { useDocumentContext } from "./Documents";
const toBase64 = (file) =>
new Promise((resolve, reject) => {
@ -19,11 +24,14 @@ const toBase64 = (file) =>
reader.onerror = (err) => reject(err);
});
const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
const { ManageDoc } = useDocumentContext();
const isUpdateForm = Boolean(ManageDoc?.document);
const [selectedType, setSelectedType] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null);
const [schema, setSchema] = useState(() => DocumentPayloadSchema({}));
const [schema, setSchema] = useState(() =>
DocumentPayloadSchema({ isUpdateForm })
);
const methods = useForm({
resolver: zodResolver(schema),
defaultValues: defaultDocumentValues,
@ -41,19 +49,35 @@ const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
showToast("Document Uploaded Successfully", "success");
closeModal();
});
const { mutate: UpdateDocument, isPending: isUpdatinDoc } = useUpdateDocument(
() => {
showToast("Document Updated Successfully", "success");
closeModal();
}
);
const onSubmit = (data) => {
const DocumentPayload = { ...data, entityId: Entity };
UploadDocument(DocumentPayload);
if (ManageDoc?.document) {
const DocumentPayload = { ...DocData, ...data };
UpdateDocument({ documentId: DocData?.id, DocumentPayload });
} else {
const DocumentPayload = { ...data, entityId: Entity };
UploadDocument(DocumentPayload);
}
};
const {
data: DocData,
isLoading: isDocLoading,
isError: isDocError,
DocError,
} = useDocumentDetails(ManageDoc?.document);
const file = watch("attachment");
const documentTypeId = watch("documentTypeId");
// This hooks calling api base Entity(Employee) and Category
const { DocumentCategories, isLoading } = useDocumentCategories(
Document_Entity
);
const { DocumentCategories, isLoading } =
useDocumentCategories(Document_Entity);
const categoryId = watch("documentCategoryId");
const { DocumentTypes, isLoading: isTypeLoading } = useDocumentTypes(
@ -63,26 +87,29 @@ const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
// Update schema whenever document type changes
useEffect(() => {
if (!documentTypeId) return;
const type = DocumentTypes?.find((t) => t.id === documentTypeId);
setSelectedType(type || null);
if (type) {
const newSchema = DocumentPayloadSchema({
isMandatory: type.isMandatory ?? false,
regexExpression: type.regexExpression ?? null,
allowedContentType: type.allowedContentType ?? [
"application/pdf",
"image/jpeg",
"image/png",
],
maxSizeAllowedInMB: type.maxSizeAllowedInMB ?? 25,
});
const type = DocumentTypes?.find(
(t) => String(t.id) === String(documentTypeId)
);
if (!type) return;
setSchema(() => newSchema);
const newSchema = DocumentPayloadSchema({
isMandatory: type.isMandatory ?? false,
regexExpression: type.regexExpression ?? null,
allowedContentType: type.allowedContentType ?? [
"application/pdf",
"image/jpeg",
"image/png",
],
maxSizeAllowedInMB: type.maxSizeAllowedInMB ?? 25,
isUpdateForm,
});
reset({ ...methods.getValues() }, { keepValues: true });
}
}, [documentTypeId, DocumentTypes, reset]);
setSchema(() => newSchema);
methods.reset(methods.getValues(), { keepValues: true });
methods.formState.errors; // triggers revalidation
}, [documentTypeId, DocumentTypes, isUpdateForm]);
// File Upload
const onFileChange = async (e) => {
@ -127,11 +154,33 @@ const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
: ""
)
.join(",") || "";
useEffect(() => {
if (DocData) {
reset({
...defaultDocumentValues,
name: DocData?.name ?? "",
documentCategoryId: DocData?.documentType?.documentCategory?.id
? String(DocData.documentType.documentCategory.id)
: "",
documentTypeId: DocData?.documentType?.id
? String(DocData.documentType.id)
: "",
documentId: DocData?.documentId ?? "",
description: DocData?.description ?? "",
attachment: DocData?.attachment ?? null,
tags: DocData?.tags ?? [],
});
}
}, [DocData, reset]);
if (isDocLoading) return <div>Loading...</div>;
if (isDocError) return <div>{DocError.message}</div>;
return (
<div className="p-2">
<p className="fw-bold fs-6">Upload New Document</p>
<FormProvider key={documentTypeId} {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
{/* Document Name */}
<div className="mb-2">
<Label htmlFor="name" required>
@ -174,28 +223,31 @@ const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
</div>
{/* Type */}
{categoryId && (<div className="mb-2">
<Label htmlFor="documentTypeId">Document Type</Label>
<select
{...register("documentTypeId")}
className="form-select form-select-sm"
>
{isTypeLoading && (
<option disabled value="">
Loading...
</option>
{categoryId && (
<div className="mb-2">
<Label htmlFor="documentTypeId">Document Type</Label>
<select
{...register("documentTypeId")}
className="form-select form-select-sm"
>
{isTypeLoading && (
<option disabled value="">
Loading...
</option>
)}
{DocumentTypes?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{errors.documentTypeId && (
<div className="danger-text">
{errors.documentTypeId.message}
</div>
)}
{DocumentTypes?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{errors.documentTypeId && (
<div className="danger-text">{errors.documentTypeId.message}</div>
)}
</div>)}
</div>
)}
{/* Document ID */}
<div className="mb-2">
@ -308,7 +360,7 @@ const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
type="reset"
className="btn btn-secondary btn-sm"
disabled={isPending}
onClick={()=>closeModal()}
onClick={closeModal}
>
Cancel
</button>

View File

@ -42,8 +42,13 @@ export const useDocumentFilterEntities =(entityTypeId)=>{
export const useDocumentDetails =(documentId)=>{
return useQuery({
queryKey:["Document",documentId],
queryFn:async()=> await DocumentRepository.getDocumentById(documentId)
queryFn:async()=> {
const resp = await DocumentRepository.getDocumentById(documentId);
return resp.data;
},
enabled:!!documentId
})
}
//----------------------- MUTATION -------------------------
@ -68,7 +73,7 @@ export const useUploadDocument =(onSuccessCallBack)=>{
export const useUpdateDocument =(onSuccessCallBack)=>{
const queryClient = useQueryClient()
return useMutation(({
mutationFn:async(documentId,DocumentPayload)=>DocumentRepository.UpdateDocument(documentId,DocumentPayload),
mutationFn:async({documentId,DocumentPayload})=>DocumentRepository.UpdateDocument(documentId,DocumentPayload),
onSuccess:(data,variables)=>{
queryClient.invalidateQueries({queryKey:["DocumentList"]});
if(onSuccessCallBack) onSuccessCallBack()

View File

@ -6,10 +6,9 @@ export const DocumentRepository = {
const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/Document/list/${entityTypeId}/entity/${entityId}/?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`)
},
getDocumentById:(id)=>api.get(`/api/Document/${id}`),
getDocumentById:(id)=>api.get(`/api/Document/get/details/${id}`),
getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`),
UpdateDocument:(documentId,data)=>api.get(`/api/Expense/edit/${documentId}`,data)
}
UpdateDocument:(documentId,data)=>api.put(`/api/Document/edit/${documentId}`,data)
}