From 7490b805b60cc8fc5254a0f9a91e2d652b819e3e Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 21 Aug 2025 16:45:32 +0530 Subject: [PATCH 1/2] Creating a new component for Document Manager. --- src/components/Employee/DocumentForm.jsx | 281 +++++++++++++++++++++++ src/components/Employee/EmpDocuments.jsx | 241 ++++++++++++++++++- src/components/Project/ProjectNav.jsx | 13 ++ src/pages/project/ProjectDetails.jsx | 5 + 4 files changed, 534 insertions(+), 6 deletions(-) create mode 100644 src/components/Employee/DocumentForm.jsx 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 ; From cc7a4707ed010a2ab926eb71de1477777bfcb1cb Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 22 Aug 2025 10:23:12 +0530 Subject: [PATCH 2/2] Changes in document manager. --- src/components/Employee/DocumentForm.jsx | 28 ++++----- src/components/Employee/EmpDocuments.jsx | 75 ++++++++++++------------ 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/components/Employee/DocumentForm.jsx b/src/components/Employee/DocumentForm.jsx index fbbba23c..70c72a4e 100644 --- a/src/components/Employee/DocumentForm.jsx +++ b/src/components/Employee/DocumentForm.jsx @@ -1,15 +1,14 @@ -// 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]; -// }; +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({ @@ -25,7 +24,7 @@ const DocumentSchema = z.object({ .refine( (files) => files.every((file) => file.fileSize <= 5 * 1024 * 1024), { - message: "File size exceeds 5MB.", + message: "File size must be 5MB or less.", } ), }); @@ -89,10 +88,9 @@ const DocumentForm = ({ initialData, onSave, onCancel }) => { onSave({ ...formData, files }); } else { const fieldErrors = result.error.flatten().fieldErrors; - const fileErrors = fieldErrors.files ? [{ message: fieldErrors.files.join(", ") }] : []; setErrors({ ...fieldErrors, - billAttachments: fileErrors, + files: fieldErrors.files ? fieldErrors.files[0] : null, }); } }; @@ -219,11 +217,9 @@ const DocumentForm = ({ initialData, onSave, onCancel }) => { onClick={(e) => (e.target.value = null)} /> - {errors.billAttachments && ( + {errors.files && (
    - {errors.billAttachments.map((err, idx) => ( -
    {err.message || err.fileSize?.message}
    - ))} + {errors.files}
    )} {files.length > 0 && ( @@ -241,7 +237,7 @@ const DocumentForm = ({ initialData, onSave, onCancel }) => { {file.fileName} - {/* {formatFileSize(file.fileSize)} */} + {formatFileSize(file.fileSize)} { const [documents, setDocuments] = useState([ { name: "Adhar Card", + documentNumber: "1234-5678-9012", + category: "private", + documentType: "Aadhar Card", uploadedDate: "21 Aug, 2025", uploadedBy: "Alice Johnson", fileType: "PDF", @@ -17,6 +19,9 @@ const EmpDocuments = () => { }, { name: "Pan Card", + documentNumber: "ABCDE1234F", + category: "public", + documentType: "Pan Card", uploadedDate: "15 Jul, 2025", uploadedBy: "Bob Smith", fileType: "Excel", @@ -26,6 +31,9 @@ const EmpDocuments = () => { }, { name: "Driving Licence", + documentNumber: "DL-2025-12345", + category: "private", + documentType: "Driving License", uploadedDate: "10 Aug, 2025", uploadedBy: "Carol Lee", fileType: "Word", @@ -33,30 +41,11 @@ const EmpDocuments = () => { 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); @@ -79,16 +68,33 @@ const EmpDocuments = () => { }; const handleSave = (newDoc) => { + // Helper function to get the file type from the file name + const getFileType = (fileName) => { + const parts = fileName.split('.'); + return parts.length > 1 ? parts.pop().toUpperCase() : 'N/A'; + }; + + // Create a new document object with all required fields + const updatedDoc = { + ...newDoc, + uploadedDate: new Date().toLocaleDateString("en-US", { day: '2-digit', month: 'short', year: 'numeric' }), + uploadedBy: "Current User", // Replace with actual user name + initials: newDoc.name ? newDoc.name.slice(0, 2).toUpperCase() : "--", + // Infer fileType from the first file name if available + fileType: newDoc.files && newDoc.files.length > 0 ? getFileType(newDoc.files[0].fileName) : 'N/A', + }; + if (currentDocument) { // Edit an existing document setDocuments( - documents.map((doc) => (doc === currentDocument ? { ...newDoc, initials: doc.initials } : doc)) + documents.map((doc) => (doc === currentDocument ? { ...updatedDoc, initials: doc.initials } : doc)) ); } else { // Create a new document - setDocuments([...documents, { ...newDoc, initials: newDoc.name ? newDoc.name.slice(0, 2).toUpperCase() : "--" }]); + setDocuments([updatedDoc, ...documents]); // Prepend new doc for better visibility } setShowForm(false); + setCurrentDocument(null); }; const handleCancel = () => { @@ -97,21 +103,18 @@ const EmpDocuments = () => { }; 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 + setIsDeleteModalOpen(false); + setDocumentToDelete(null); } }; - // Function to close the delete modal const handleCloseDeleteModal = () => { setIsDeleteModalOpen(false); setDocumentToDelete(null); @@ -145,10 +148,11 @@ const EmpDocuments = () => { - + + + - - + {/* */} @@ -156,7 +160,7 @@ const EmpDocuments = () => { {filteredDocuments.length > 0 ? ( filteredDocuments.map((doc, index) => ( - + + - - + {/* */}
    Document NameDocument NameDocument NumberDocument Type Uploaded DateUploaded ByFile TypeUploaded ByAction
    +
    {
    {doc.documentNumber}{doc.documentType} {doc.uploadedDate}{doc.uploadedBy}{doc.fileType}{doc.uploadedBy} { message={`Are you sure you want to delete "${documentToDelete?.name || "Unnamed Document"}"?`} onSubmit={handleConfirmDelete} onClose={handleCloseDeleteModal} - // `loading` prop is not needed for this example but can be added - // `paramData` is also not needed since we're using a state variable /> )} @@ -238,4 +241,4 @@ const EmpDocuments = () => { ); }; -export default EmpDocuments; +export default EmpDocuments; \ No newline at end of file