281 lines
9.6 KiB
JavaScript
281 lines
9.6 KiB
JavaScript
// DocumentForm.js
|
|
import React, { useState, useEffect } from "react";
|
|
import { z } from "zod";
|
|
|
|
// Helper function to format file size
|
|
// const formatFileSize = (bytes) => {
|
|
// if (bytes === 0) return "0 Bytes";
|
|
// const k = 1024;
|
|
// const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
// const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
// return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
// };
|
|
|
|
// Define the Zod schema for the form
|
|
const DocumentSchema = z.object({
|
|
name: z.string().min(1, { message: "Document Name is required." }),
|
|
documentNumber: z.string().min(1, { message: "Document Number is required." }),
|
|
category: z.enum(["public", "private"], {
|
|
errorMap: () => ({ message: "Category is required." }),
|
|
}),
|
|
documentType: z.string().min(1, { message: "Document Type is required." }),
|
|
files: z
|
|
.array(z.any())
|
|
.min(1, { message: "At least one file must be uploaded." })
|
|
.refine(
|
|
(files) => files.every((file) => file.fileSize <= 5 * 1024 * 1024),
|
|
{
|
|
message: "File size exceeds 5MB.",
|
|
}
|
|
),
|
|
});
|
|
|
|
const DocumentForm = ({ initialData, onSave, onCancel }) => {
|
|
const formKey = initialData ? initialData.name : "new";
|
|
|
|
const [formData, setFormData] = useState({
|
|
name: "",
|
|
documentNumber: "",
|
|
category: "",
|
|
documentType: "",
|
|
files: initialData ? initialData.files || [] : [],
|
|
...initialData,
|
|
});
|
|
|
|
const [files, setFiles] = useState(initialData?.files || []);
|
|
const [errors, setErrors] = useState({});
|
|
|
|
useEffect(() => {
|
|
if (initialData && initialData.files) {
|
|
setFiles(initialData.files);
|
|
}
|
|
}, [initialData]);
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
[name]: value,
|
|
}));
|
|
};
|
|
|
|
const onFileChange = (e) => {
|
|
const newFiles = Array.from(e.target.files).map((file) => ({
|
|
file,
|
|
fileName: file.name,
|
|
fileSize: file.size,
|
|
preSignedUrl: URL.createObjectURL(file),
|
|
}));
|
|
|
|
setFiles((prevFiles) => [...prevFiles, ...newFiles]);
|
|
};
|
|
|
|
const removeFile = (fileToRemove) => {
|
|
setFiles((prevFiles) => prevFiles.filter((file) => file !== fileToRemove));
|
|
};
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
const validationData = {
|
|
...formData,
|
|
files,
|
|
};
|
|
|
|
const result = DocumentSchema.safeParse(validationData);
|
|
|
|
if (result.success) {
|
|
setErrors({});
|
|
onSave({ ...formData, files });
|
|
} else {
|
|
const fieldErrors = result.error.flatten().fieldErrors;
|
|
const fileErrors = fieldErrors.files ? [{ message: fieldErrors.files.join(", ") }] : [];
|
|
setErrors({
|
|
...fieldErrors,
|
|
billAttachments: fileErrors,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div key={formKey} className="modal-dialog">
|
|
<div className="modal-content">
|
|
<div className="modal-header">
|
|
<h5 className="modal-title text-start">
|
|
{initialData ? "Edit Document" : "Create Document"}
|
|
</h5>
|
|
<button type="button" className="btn-close" onClick={onCancel}></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<form onSubmit={handleSubmit}>
|
|
{/* Document Name */}
|
|
<div className="mb-3 text-start">
|
|
<label htmlFor="name" className="form-label ">
|
|
Document Name <span className="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
className="form-control"
|
|
id="name"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
/>
|
|
{errors.name && (
|
|
<div className="invalid-feedback d-block">{errors.name}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Document Number */}
|
|
<div className="mb-3 text-start">
|
|
<label htmlFor="documentNumber" className="form-label ">
|
|
Document Number <span className="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
className="form-control"
|
|
id="documentNumber"
|
|
name="documentNumber"
|
|
value={formData.documentNumber}
|
|
onChange={handleChange}
|
|
/>
|
|
{errors.documentNumber && (
|
|
<div className="invalid-feedback d-block">{errors.documentNumber}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Category Dropdown */}
|
|
<div className="mb-3 text-start">
|
|
<label htmlFor="category" className="form-label ">
|
|
Category <span className="text-danger">*</span>
|
|
</label>
|
|
<select
|
|
className="form-select"
|
|
id="category"
|
|
name="category"
|
|
value={formData.category}
|
|
onChange={handleChange}
|
|
>
|
|
<option value="">Select Category</option>
|
|
<option value="public">Public</option>
|
|
<option value="private">Private</option>
|
|
</select>
|
|
{errors.category && (
|
|
<div className="invalid-feedback d-block">{errors.category}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Document Type Dropdown */}
|
|
<div className="mb-3 text-start">
|
|
<label htmlFor="documentType" className="form-label ">
|
|
Document Type <span className="text-danger">*</span>
|
|
</label>
|
|
<select
|
|
className="form-select"
|
|
id="documentType"
|
|
name="documentType"
|
|
value={formData.documentType}
|
|
onChange={handleChange}
|
|
>
|
|
<option value="">Select Document Type</option>
|
|
<option value="Aadhar Card">Aadhar Card</option>
|
|
<option value="Pan Card">Pan Card</option>
|
|
<option value="Driving License">Driving License</option>
|
|
<option value="Passport">Passport</option>
|
|
<option value="Other">Other</option>
|
|
</select>
|
|
{errors.documentType && (
|
|
<div className="invalid-feedback d-block">{errors.documentType}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* File Uploader */}
|
|
<div className="row my-2 text-start">
|
|
<div className="col-md-12">
|
|
<label className="form-label ">
|
|
Upload Document <span className="text-danger">*</span>
|
|
</label>
|
|
<div
|
|
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
|
|
style={{ cursor: "pointer" }}
|
|
onClick={() =>
|
|
document.getElementById("billAttachments").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">
|
|
(PDF, JPG, PNG, max 5MB)
|
|
</small>
|
|
<input
|
|
type="file"
|
|
id="billAttachments"
|
|
accept=".pdf,.jpg,.jpeg,.png"
|
|
multiple
|
|
style={{ display: "none" }}
|
|
onChange={onFileChange}
|
|
onClick={(e) => (e.target.value = null)}
|
|
/>
|
|
</div>
|
|
{errors.billAttachments && (
|
|
<div className="text-danger small mt-1">
|
|
{errors.billAttachments.map((err, idx) => (
|
|
<div key={idx}>{err.message || err.fileSize?.message}</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
{files.length > 0 && (
|
|
<div className="d-block mt-2">
|
|
{files.map((file, idx) => (
|
|
<a
|
|
key={idx}
|
|
className="d-flex justify-content-between text-start p-1 border-bottom"
|
|
href={file.preSignedUrl || "#"}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<div>
|
|
<span className="mb-0 text-secondary small d-block">
|
|
{file.fileName}
|
|
</span>
|
|
<span className="text-body-secondary small d-block">
|
|
{/* {formatFileSize(file.fileSize)} */}
|
|
</span>
|
|
</div>
|
|
<i
|
|
className="bx bx-trash bx-sm cursor-pointer text-danger"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
removeFile(file);
|
|
}}
|
|
></i>
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="d-flex justify-content-end gap-2 mt-4">
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary btn-sm"
|
|
onClick={onCancel}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button type="submit" className="btn btn-primary btn-sm">
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DocumentForm; |