323 lines
10 KiB
JavaScript
323 lines
10 KiB
JavaScript
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import React, { useEffect, useState } from "react";
|
|
import { useForm, FormProvider } from "react-hook-form";
|
|
import { defaultDocumentValues, DocumentPayloadSchema } from "./DocumentSchema";
|
|
import Label from "../common/Label";
|
|
import {
|
|
useDocumentCategories,
|
|
useDocumentTypes,
|
|
} from "../../hooks/masterHook/useMaster";
|
|
import TagInput from "../common/TagInput";
|
|
import { useUploadDocument } from "../../hooks/useDocument";
|
|
import showToast from "../../services/toastService";
|
|
|
|
const toBase64 = (file) =>
|
|
new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.readAsDataURL(file);
|
|
reader.onload = () => resolve(reader.result);
|
|
reader.onerror = (err) => reject(err);
|
|
});
|
|
|
|
const NewDocument = ({closeModal,Document_Entity,Entity}) => {
|
|
|
|
const [selectedType, setSelectedType] = useState(null);
|
|
const [selectedCategory, setSelectedCategory] = useState(null);
|
|
const [schema, setSchema] = useState(() => DocumentPayloadSchema({}));
|
|
const methods = useForm({
|
|
resolver: zodResolver(schema),
|
|
defaultValues: defaultDocumentValues,
|
|
});
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
watch,
|
|
setValue,
|
|
reset,
|
|
formState: { errors },
|
|
} = methods;
|
|
const { mutate: UploadDocument, isPending } = useUploadDocument(() => {
|
|
showToast("Document Uploaded Successfully", "success");
|
|
closeModal();
|
|
});
|
|
const onSubmit = (data) => {
|
|
const DocumentPayload = { ...data, entityId: Entity };
|
|
UploadDocument(DocumentPayload);
|
|
};
|
|
|
|
const file = watch("attachment");
|
|
|
|
const documentTypeId = watch("documentTypeId");
|
|
|
|
// This hooks calling api base Entity(Employee) and Category
|
|
const { DocumentCategories, isLoading } = useDocumentCategories(
|
|
Document_Entity
|
|
);
|
|
|
|
const categoryId = watch("documentCategoryId");
|
|
const { DocumentTypes, isLoading: isTypeLoading } = useDocumentTypes(
|
|
categoryId || null
|
|
);
|
|
|
|
// 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,
|
|
});
|
|
|
|
setSchema(() => newSchema);
|
|
|
|
reset({ ...methods.getValues() }, { keepValues: true });
|
|
}
|
|
}, [documentTypeId, DocumentTypes, reset]);
|
|
|
|
// File Upload
|
|
const onFileChange = async (e) => {
|
|
const uploaded = e.target.files[0];
|
|
if (!uploaded) return;
|
|
|
|
const base64Data = await toBase64(uploaded);
|
|
|
|
const parsedFile = {
|
|
fileName: uploaded.name,
|
|
base64Data,
|
|
contentType: uploaded.type,
|
|
fileSize: uploaded.size,
|
|
description: "",
|
|
isActive: true,
|
|
};
|
|
|
|
setValue("attachment", parsedFile, {
|
|
shouldDirty: true,
|
|
shouldValidate: true,
|
|
});
|
|
};
|
|
|
|
const removeFile = () => {
|
|
setValue("attachment", null, {
|
|
shouldDirty: true,
|
|
shouldValidate: true,
|
|
});
|
|
};
|
|
|
|
// build dynamic file accept string
|
|
const fileAccept =
|
|
selectedType?.allowedContentType
|
|
?.split(",")
|
|
.map((t) =>
|
|
t === "application/pdf"
|
|
? ".pdf"
|
|
: t === "image/jpeg"
|
|
? ".jpg,.jpeg"
|
|
: t === "image/png"
|
|
? ".png"
|
|
: ""
|
|
)
|
|
.join(",") || "";
|
|
return (
|
|
<div className="p-2">
|
|
<p className="fw-bold fs-6">Upload New Document</p>
|
|
<FormProvider key={documentTypeId} {...methods}>
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
{/* Document Name */}
|
|
<div className="mb-2">
|
|
<Label htmlFor="name" required>
|
|
Document Name
|
|
</Label>
|
|
<input
|
|
type="text"
|
|
className="form-control form-control-sm"
|
|
{...register("name")}
|
|
/>
|
|
{errors.name && (
|
|
<div className="danger-text">{errors.name.message}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Category */}
|
|
<div className="mb-2">
|
|
<Label htmlFor="documentCategoryId">Document Category</Label>
|
|
<select
|
|
{...register("documentCategoryId")}
|
|
className="form-select form-select-sm"
|
|
>
|
|
{isLoading && (
|
|
<option disabled value="">
|
|
Loading...
|
|
</option>
|
|
)}
|
|
{!isLoading && <option value="">Select Category</option>}
|
|
{DocumentCategories?.map((type) => (
|
|
<option key={type.id} value={type.id}>
|
|
{type.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{errors.documentCategoryId && (
|
|
<div className="danger-text">
|
|
{errors.documentCategoryId.message}
|
|
</div>
|
|
)}
|
|
</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>
|
|
)}
|
|
{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>)}
|
|
|
|
|
|
{/* Document ID */}
|
|
<div className="mb-2">
|
|
<Label
|
|
htmlFor="documentId"
|
|
required={selectedType?.isMandatory ?? false}
|
|
>
|
|
Document ID
|
|
</Label>
|
|
<input
|
|
type="text"
|
|
className="form-control form-control-sm"
|
|
{...register("documentId")}
|
|
/>
|
|
{errors.documentId && (
|
|
<div className="danger-text">{errors.documentId.message}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Upload */}
|
|
<div className="row my-2">
|
|
<div className="col-md-12">
|
|
<label className="form-label">Upload Document</label>
|
|
|
|
<div
|
|
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
|
|
style={{ cursor: "pointer" }}
|
|
onClick={() => document.getElementById("attachment").click()}
|
|
>
|
|
<i className="bx bx-cloud-upload d-block bx-lg"></i>
|
|
<span className="text-muted d-block">
|
|
Click to select or click here to browse
|
|
</span>
|
|
<small className="text-muted">
|
|
({selectedType?.allowedContentType || "PDF/JPG/PNG"}, max{" "}
|
|
{selectedType?.maxSizeAllowedInMB ?? 25}MB)
|
|
</small>
|
|
|
|
<input
|
|
type="file"
|
|
id="attachment"
|
|
accept={selectedType?.allowedContentType}
|
|
style={{ display: "none" }}
|
|
onChange={(e) => {
|
|
onFileChange(e);
|
|
e.target.value = ""; // reset input
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{errors.attachment && (
|
|
<small className="danger-text">
|
|
{errors.attachment.message
|
|
? errors.attachment.message
|
|
: errors.attachment.fileName?.message ||
|
|
errors.attachment.base64Data?.message ||
|
|
errors.attachment.contentType?.message ||
|
|
errors.attachment.fileSize?.message}
|
|
</small>
|
|
)}
|
|
|
|
{file?.base64Data && (
|
|
<div className="d-flex justify-content-between text-start p-1 mt-2">
|
|
<div>
|
|
<span className="mb-0 text-secondary small d-block">
|
|
{file.fileName}
|
|
</span>
|
|
<span className="text-body-secondary small d-block">
|
|
{(file.fileSize / 1024).toFixed(1)} KB
|
|
</span>
|
|
</div>
|
|
<i
|
|
className="bx bx-trash bx-sm cursor-pointer text-danger"
|
|
onClick={removeFile}
|
|
></i>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="mb-2">
|
|
<TagInput name="tags" label="Tags" placeholder="Tags.." />
|
|
{errors.tags && (
|
|
<small className="danger-text">{errors.tags.message}</small>
|
|
)}
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div className="mb-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<textarea
|
|
rows="2"
|
|
className="form-control"
|
|
{...register("description")}
|
|
></textarea>
|
|
{errors.description && (
|
|
<div className="danger-text">{errors.description.message}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="d-flex justify-content-center gap-3">
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary btn-sm"
|
|
disabled={isPending}
|
|
>
|
|
{isPending ? "Please Wait..." : " Submit"}
|
|
</button>
|
|
<button
|
|
type="reset"
|
|
className="btn btn-secondary btn-sm"
|
|
disabled={isPending}
|
|
onClick={()=>closeModal()}
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</FormProvider>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default NewDocument;
|