diff --git a/src/components/Employee/DocumentForm.jsx b/src/components/Employee/DocumentForm.jsx new file mode 100644 index 00000000..fbbba23c --- /dev/null +++ b/src/components/Employee/DocumentForm.jsx @@ -0,0 +1,281 @@ +// 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 ( +
+
+
+
+ {initialData ? "Edit Document" : "Create Document"} +
+ +
+
+
+ {/* Document Name */} +
+ + + {errors.name && ( +
{errors.name}
+ )} +
+ + {/* Document Number */} +
+ + + {errors.documentNumber && ( +
{errors.documentNumber}
+ )} +
+ + {/* Category Dropdown */} +
+ + + {errors.category && ( +
{errors.category}
+ )} +
+ + {/* Document Type Dropdown */} +
+ + + {errors.documentType && ( +
{errors.documentType}
+ )} +
+ + {/* File Uploader */} +
+
+ +
+ document.getElementById("billAttachments").click() + } + > + + + Click to select or click here to browse + + + (PDF, JPG, PNG, max 5MB) + + (e.target.value = null)} + /> +
+ {errors.billAttachments && ( +
+ {errors.billAttachments.map((err, idx) => ( +
{err.message || err.fileSize?.message}
+ ))} +
+ )} + {files.length > 0 && ( + + )} +
+
+ + {/* Buttons */} +
+ + +
+
+
+
+
+ ); +}; + +export default DocumentForm; \ No newline at end of file diff --git a/src/components/Employee/EmpDocuments.jsx b/src/components/Employee/EmpDocuments.jsx index 46aa33d0..80e6a61f 100644 --- a/src/components/Employee/EmpDocuments.jsx +++ b/src/components/Employee/EmpDocuments.jsx @@ -1,11 +1,240 @@ -import React, { useState, useEffect } from "react"; -import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage"; +// DocumentManager.js +import React, { useState } from "react"; +import DocumentForm from "./DocumentForm"; +import ConfirmModal from "../common/ConfirmModal"; + +const EmpDocuments = () => { + const [searchText, setSearchText] = useState(""); + const [documents, setDocuments] = useState([ + { + name: "Adhar Card", + uploadedDate: "21 Aug, 2025", + uploadedBy: "Alice Johnson", + fileType: "PDF", + url: "#", + initials: "AC", + files: [], + }, + { + name: "Pan Card", + uploadedDate: "15 Jul, 2025", + uploadedBy: "Bob Smith", + fileType: "Excel", + url: "#", + initials: "PC", + files: [], + }, + { + name: "Driving Licence", + uploadedDate: "10 Aug, 2025", + uploadedBy: "Carol Lee", + fileType: "Word", + url: "#", + initials: "DL", + files: [], + }, + { + name: "", + uploadedDate: "05 Aug, 2025", + uploadedBy: "David Kim", + fileType: "PNG", + url: "#", + initials: "DM", + files: [], + }, + { + name: "Client Presentation", + uploadedDate: "18 Aug, 2025", + uploadedBy: "Eve Brown", + fileType: "PPT", + url: "#", + initials: "CP", + files: [], + }, + ]); + + const [showForm, setShowForm] = useState(false); + const [currentDocument, setCurrentDocument] = useState(null); + + // New state for the delete modal + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [documentToDelete, setDocumentToDelete] = useState(null); + + const filteredDocuments = documents.filter((doc) => + (doc.name || "Unnamed Document").toLowerCase().includes(searchText.toLowerCase()) + ); + + const handleSearch = (e) => { + setSearchText(e.target.value); + }; + + const handleCreateClick = () => { + setCurrentDocument(null); + setShowForm(true); + }; + + const handleEditClick = (doc) => { + setCurrentDocument(doc); + setShowForm(true); + }; + + const handleSave = (newDoc) => { + if (currentDocument) { + // Edit an existing document + setDocuments( + documents.map((doc) => (doc === currentDocument ? { ...newDoc, initials: doc.initials } : doc)) + ); + } else { + // Create a new document + setDocuments([...documents, { ...newDoc, initials: newDoc.name ? newDoc.name.slice(0, 2).toUpperCase() : "--" }]); + } + setShowForm(false); + }; + + const handleCancel = () => { + setShowForm(false); + setCurrentDocument(null); + }; + + const handleDelete = (doc) => { + // Set the document to be deleted and open the modal + setDocumentToDelete(doc); + setIsDeleteModalOpen(true); + }; + + // New function to handle the confirmed deletion + const handleConfirmDelete = () => { + if (documentToDelete) { + setDocuments(documents.filter((doc) => doc !== documentToDelete)); + setIsDeleteModalOpen(false); // Close the modal + setDocumentToDelete(null); // Clear the document to delete + } + }; + + // Function to close the delete modal + const handleCloseDeleteModal = () => { + setIsDeleteModalOpen(false); + setDocumentToDelete(null); + }; -const EmpDocuments = ({ profile, loggedInUser }) => { return ( - <> - - +
+ {/* Header: Search & Create */} +
+
+ +
+
+ +
+
+ + {/* Table */} +
+ + + + + + + + + + + + {filteredDocuments.length > 0 ? ( + filteredDocuments.map((doc, index) => ( + + + + + + + + )) + ) : ( + + + + )} + +
Document NameUploaded DateUploaded ByFile TypeAction
+
+
+ {doc.initials || (doc.name ? doc.name.slice(0, 2).toUpperCase() : "--")} +
+ + +
+
{doc.uploadedDate}{doc.uploadedBy}{doc.fileType} + handleEditClick(doc)} + > + handleDelete(doc)} + > +
+ No documents available. +
+
+ + {/* Document Form Modal */} + {showForm && ( +
+ +
+ )} + + {/* Delete Confirmation Modal */} + {isDeleteModalOpen && ( +
+ +
+ )} +
); }; diff --git a/src/components/Project/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx index 0a7f960e..f72ed3f5 100644 --- a/src/components/Project/ProjectNav.jsx +++ b/src/components/Project/ProjectNav.jsx @@ -64,6 +64,19 @@ const ProjectNav = ({ onPillClick, activePill }) => { )} +
  • + { + e.preventDefault(); // Prevent page reload + onPillClick("document"); + }} + > + + Document + +
  • { @@ -118,6 +119,10 @@ const ProjectDetails = () => { ); + case "document": + return ( + + ); default: return ;