From 65b90bc9acf0178602a5cf4853735ee8cacd624c Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 28 Aug 2025 11:17:54 +0530 Subject: [PATCH 01/51] initial setup document page --- src/pages/Documents/DocumentPage.jsx | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/pages/Documents/DocumentPage.jsx diff --git a/src/pages/Documents/DocumentPage.jsx b/src/pages/Documents/DocumentPage.jsx new file mode 100644 index 00000000..f519bb5e --- /dev/null +++ b/src/pages/Documents/DocumentPage.jsx @@ -0,0 +1,47 @@ +import React from 'react' +import Breadcrumb from '../../components/common/Breadcrumb' + +const DocumentPage = () => { + return ( +
+ +
+
+ {/* Search */} +
+ +
+ + {/* Actions */} +
+ + Refresh + < i className={`bx bx-refresh ms-1 `}> + + + +
+
+
+ +
+ ) +} + +export default DocumentPage \ No newline at end of file From 14fba75d193aa3ea98b4f2584095ac76e561a78d Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 28 Aug 2025 11:31:01 +0530 Subject: [PATCH 02/51] setup routes --- src/router/AppRoutes.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 0e269a6f..dbe83fc2 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -44,6 +44,7 @@ import ExpensePage from "../pages/Expense/ExpensePage"; import TenantDetails from "../pages/Tenant/TenantDetails"; import SelfTenantDetails from "../pages/Tenant/SelfTenantDetails"; import SuperTenantDetails from "../pages/Tenant/SuperTenantDetails"; +import DocumentPage from "../pages/Documents/DocumentPage"; const router = createBrowserRouter( [ @@ -69,6 +70,7 @@ const router = createBrowserRouter( { path: "/", element: }, { path: "/dashboard", element: }, { path: "/projects", element: }, + { path: "/document", element: }, { path: "/projects/details", element: }, { path: "/project/manage/:projectId", element: }, { path: "/employees", element: }, From d4804e4c3f9731c0ac17c3cb8638e45ce7f79eb6 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 26 Aug 2025 14:51:07 +0530 Subject: [PATCH 03/51] updated Master modal --- src/components/master/MasterModal.jsx | 28 ++------------------------- src/pages/master/MasterPage.jsx | 15 ++++---------- src/pages/master/MasterTable.jsx | 9 ++------- 3 files changed, 8 insertions(+), 44 deletions(-) diff --git a/src/components/master/MasterModal.jsx b/src/components/master/MasterModal.jsx index 0a472727..44282be8 100644 --- a/src/components/master/MasterModal.jsx +++ b/src/components/master/MasterModal.jsx @@ -107,33 +107,9 @@ const MasterModal = ({ modaldata, closeModal }) => { ); return ( - + ); }; diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index 72710679..3d6295c9 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -10,6 +10,7 @@ import { getCachedData } from "../../slices/apiDataManager"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_MASTER } from "../../utils/constants"; import { useQueryClient } from "@tanstack/react-query"; +import GlobalModel from "../../components/common/GlobalModel"; const MasterPage = () => { @@ -32,15 +33,6 @@ const MasterPage = () => { setIsCreateModalOpen(false); setModalConfig(null); - // Clean up Bootstrap modal manually - const modalEl = document.getElementById('master-modal'); - modalEl?.classList.remove('show'); - if (modalEl) modalEl.style.display = 'none'; - - document.body.classList.remove('modal-open'); - document.body.style.overflow = 'auto'; - - document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove()); }; const handleModalData = (modalType, item, masterType = selectedMaster) => { @@ -91,8 +83,9 @@ const MasterPage = () => { return ( <> {isCreateModalOpen && ( - - + setIsCreateModalOpen(false)} size={modalConfig.masterType === "Application Role" ? "lg":"md"}> + + )}
diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index bc7c10c0..bf27147d 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -75,13 +75,8 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { {loading ? (

Loading...

) : ( - - +
+ From d42790628c1ff585e2ae813c922f646a6510bca3 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 28 Aug 2025 15:19:15 +0530 Subject: [PATCH 04/51] successfully master fetched of documentType and documentCategor --- src/hooks/masterHook/useMaster.js | 4 ++++ src/pages/master/MasterTable.jsx | 15 +++++++++++++-- src/repositories/DocumentRepository.jsx | 2 ++ src/repositories/MastersRepository.jsx | 4 ++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/repositories/DocumentRepository.jsx diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index fcbb821d..5675aba7 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -193,6 +193,10 @@ const fetchMasterData = async (masterType) => { return (await MasterRespository.getPaymentMode()).data; case "Expense Status": return (await MasterRespository.getExpenseStatus()).data; + case "Document Type": + return (await MasterRespository.getDocumentTypes()).data; + case "Document Category": + return (await MasterRespository.getDocumentCategories()).data; case "Status": return [ { diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index bf27147d..1c4da079 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -20,7 +20,14 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { "noOfPersonsRequired", "color", "displayName", - "permissionIds" + "permissionIds", + "entityTypeId", + "regexExpression", + "isMandatory", + "maxFilesAllowed", + "maxSizeAllowedInMB", + "isValidationRequired", + "documentCategory" ]; const safeData = Array.isArray(data) ? data : []; @@ -80,7 +87,11 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { - + diff --git a/src/repositories/DocumentRepository.jsx b/src/repositories/DocumentRepository.jsx new file mode 100644 index 00000000..052b1ad6 --- /dev/null +++ b/src/repositories/DocumentRepository.jsx @@ -0,0 +1,2 @@ +import { api } from "../utils/axiosClient"; + diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index cfb9a890..d084bf86 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -81,4 +81,8 @@ export const MasterRespository = { createExpenseStatus: (data) => api.post("/api/Master/expenses-status", data), updateExepnseStatus: (id, data) => api.put(`/api/Master/expenses-status/edit/${id}`, data), + + + getDocumentCategories:()=>api.get("/api/Master/document-category/list"), + getDocumentTypes:()=>api.get("/api/Master/document-type/list") }; From b93eaf6b95c93ff1a35ad3a00217153520f88f8a Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 28 Aug 2025 20:22:17 +0530 Subject: [PATCH 05/51] initial setuo new document --- src/components/Documents/DocumentSchema.js | 63 +++++ src/components/Documents/Documents.jsx | 52 +++++ src/components/Documents/NewDocument.jsx | 260 +++++++++++++++++++++ src/components/Employee/EmpDocuments.jsx | 4 +- src/components/common/DateRangePicker.jsx | 4 +- src/hooks/masterHook/useMaster.js | 50 ++++ src/pages/Documents/DocumentPage.jsx | 14 +- src/repositories/DocumentRepository.jsx | 3 + src/repositories/MastersRepository.jsx | 8 +- src/utils/constants.jsx | 6 + 10 files changed, 449 insertions(+), 15 deletions(-) create mode 100644 src/components/Documents/DocumentSchema.js create mode 100644 src/components/Documents/Documents.jsx create mode 100644 src/components/Documents/NewDocument.jsx diff --git a/src/components/Documents/DocumentSchema.js b/src/components/Documents/DocumentSchema.js new file mode 100644 index 00000000..5842e488 --- /dev/null +++ b/src/components/Documents/DocumentSchema.js @@ -0,0 +1,63 @@ +import { z } from "zod"; + +export const AttachmentSchema = z.object({ + fileName: z.string().min(1, {message:"File name is required"}), + base64Data: z.string().min(1, {message:"File data is required"}), + contentType: z.string().min(1, {message:"MIME type is required"}), + fileSize: z + .number() + .int() + .nonnegative("fileSize must be ≥ 0") + .max(25 * 1024 * 1024, "fileSize must be ≤ 25MB"), + description: z.string().optional().default(""), + isActive: z.boolean(), +}); + +export const TagSchema = z.object({ + name: z.string().min(1, {message:"Tag name is required"}), + isActive: z.boolean(), +}); + +export const DocumentPayloadSchema = (isMandatory, regularExp) => { + let documentIdSchema = z.string(); + + if (isMandatory) { + documentIdSchema = documentIdSchema.min(1, {message:"DocumentId is required"}); + } + + if (regularExp) { + documentIdSchema = documentIdSchema.regex( + new RegExp(regularExp), + "Invalid DocumentId format" + ); + } + + 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"}), + documentCategoryId:z.string().min(1,{message:"Please Select Document Category"}), + attachment: AttachmentSchema, + tags: z.array(TagSchema).default([]), + }); +}; + +export const defaultDocumentValues = { + name: "", + documentId: "", + description: "", + entityId: "", + documentTypeId: "", + documentCategoryId:"", + attachment: { + fileName: "", + base64Data: "", + contentType: "", + fileSize: 0, + description: "", + isActive: true, + }, + tags: [], +}; diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx new file mode 100644 index 00000000..343310d8 --- /dev/null +++ b/src/components/Documents/Documents.jsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react' +import GlobalModel from '../common/GlobalModel' +import NewDocument from './NewDocument' + + +const Documents = () => { + const [isUpload,setUpload] =useState(false); + + return ( +
+ +
+
+ {/* Search */} +
+ +
+ + {/* Actions */} +
+ + Refresh + < i className={`bx bx-refresh ms-1 `}> + + + +
+
+
+ {isUpload && ( + setUpload(false)}> + + + )} + +
+ ) +} + +export default Documents \ No newline at end of file diff --git a/src/components/Documents/NewDocument.jsx b/src/components/Documents/NewDocument.jsx new file mode 100644 index 00000000..175f46f4 --- /dev/null +++ b/src/components/Documents/NewDocument.jsx @@ -0,0 +1,260 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import React, { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { defaultDocumentValues, DocumentPayloadSchema } from "./DocumentSchema"; +import Label from "../common/Label"; +import { DOCUMENTS_ENTITIES } from "../../utils/constants"; +import { + useDocumentCategories, + useDocumentTypes, +} from "../../hooks/masterHook/useMaster"; + +// util fn: convert file → base64 +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 = () => { + const [selectedType, setType] = useState(""); + + const DocumentUpload = DocumentPayloadSchema( + selectedType?.isMandatory ?? null, + selectedType?.regexExpression ?? null + ); + + const { + register, + handleSubmit, + watch, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(DocumentUpload), + defaultValues: defaultDocumentValues, + }); + + const onSubmit = (data) => { + console.log("Form submitted:", data); + }; + + const documentTypeId = watch("documentTypeId"); + const categoryId = watch("documentCategoryId"); + const file = watch("attachment"); + + // API Data + const { DocumentCategories, isLoading } = useDocumentCategories( + DOCUMENTS_ENTITIES.EmployeeEntity + ); + + const { DocumentTypes, isLoading: isTypeLoading } = + useDocumentTypes(categoryId); + + // 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, + }); + }; + + useEffect(() => { + if (documentTypeId) { + setType(DocumentTypes?.find((type) => type.id === documentTypeId)); + } + }, [documentTypeId, DocumentTypes]); + + return ( +
+

Upload New Document

+ +
+ {/* Document Name */} +
+ + + {errors.name && ( +
{errors.name.message}
+ )} +
+ + {/* Category */} +
+ + + {errors.documentCategoryId && ( +
+ {errors.documentCategoryId.message} +
+ )} +
+ + {/* Type */} +
+ + + {errors.documentTypeId && ( +
{errors.documentTypeId.message}
+ )} +
+ + {/* Document ID */} +
+ + + {errors.documentId && ( +
{errors.documentId.message}
+ )} +
+ + {/* Upload */} +
+
+ + +
document.getElementById("attachment").click()} + > + + + Click to select or click here to browse + + (PDF, JPG, PNG, max 25MB) + + { + onFileChange(e); + e.target.value = ""; // reset input + }} + /> +
+ + {errors.attachment && ( + + {errors.attachment.fileName?.message || + errors.attachment.base64Data?.message || + errors.attachment.contentType?.message || + errors.attachment.fileSize?.message} + + )} + + {file && ( +
+
+ + {file.fileName} + + + {(file.fileSize / 1024).toFixed(1)} KB + +
+ +
+ )} +
+
+ + {/* Description */} +
+ + + {errors.description && ( +
{errors.description.message}
+ )} +
+ + {/* Buttons */} +
+ + +
+ +
+ ); +}; + +export default NewDocument; diff --git a/src/components/Employee/EmpDocuments.jsx b/src/components/Employee/EmpDocuments.jsx index 46aa33d0..39d6e900 100644 --- a/src/components/Employee/EmpDocuments.jsx +++ b/src/components/Employee/EmpDocuments.jsx @@ -1,10 +1,12 @@ import React, { useState, useEffect } from "react"; import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage"; +import DocumentPage from "../../pages/Documents/DocumentPage"; +import Documents from "../Documents/Documents"; const EmpDocuments = ({ profile, loggedInUser }) => { return ( <> - + ); }; diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index 9b7505fe..90ea9b24 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -57,8 +57,8 @@ const DateRangePicker = ({ /> ); diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 5675aba7..9a31dcf8 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -171,6 +171,56 @@ const { return { ExpenseStatus, loading, error }; } +export const useDocumentTypes =(category)=>{ +const { + data: DocumentTypes = [], + error, + isError, + isLoading + } = useQuery({ + queryKey: ["Document Type"], + queryFn: async () => { + const res = await MasterRespository.getDocumentTypes(category) + return res.data; + }, + enabled:!!category, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Expense Status", + "error" + ); + }, + }); + + return { DocumentTypes, isError, isLoading, error }; +} +export const useDocumentCategories =(EntityType)=>{ +const { + data: DocumentCategories = [], + error, + isError, + isLoading + } = useQuery({ + queryKey: ["Document Category"], + queryFn: async () => { + const res = await MasterRespository.getDocumentCategories(EntityType) + return res.data; + }, + enabled:!!EntityType, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Expense Status", + "error" + ); + }, + }); + + return { DocumentCategories, isError, isLoading, error }; +} // ===Application Masters Query================================================= const fetchMasterData = async (masterType) => { diff --git a/src/pages/Documents/DocumentPage.jsx b/src/pages/Documents/DocumentPage.jsx index f519bb5e..ba71070d 100644 --- a/src/pages/Documents/DocumentPage.jsx +++ b/src/pages/Documents/DocumentPage.jsx @@ -3,13 +3,8 @@ import Breadcrumb from '../../components/common/Breadcrumb' const DocumentPage = () => { return ( -
- +
+
{/* Search */} @@ -18,7 +13,7 @@ const DocumentPage = () => { type="search" className="form-control form-control-sm" - placeholder="Search Tenant" + placeholder="Search Document" />
@@ -31,7 +26,7 @@ const DocumentPage = () => {
- ) } diff --git a/src/repositories/DocumentRepository.jsx b/src/repositories/DocumentRepository.jsx index 052b1ad6..d01d7984 100644 --- a/src/repositories/DocumentRepository.jsx +++ b/src/repositories/DocumentRepository.jsx @@ -1,2 +1,5 @@ import { api } from "../utils/axiosClient"; +export const DocumentRepository = { + uploadDocument:(data)=> api.post(`/api/Document/upload`,data) +} diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index d084bf86..f8a885aa 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -83,6 +83,10 @@ export const MasterRespository = { api.put(`/api/Master/expenses-status/edit/${id}`, data), - getDocumentCategories:()=>api.get("/api/Master/document-category/list"), - getDocumentTypes:()=>api.get("/api/Master/document-type/list") + getDocumentCategories: (entityType) => + api.get(`/api/Master/document-category/list${entityType ? `?entityTypeId=${entityType}` : ""}`), + +getDocumentTypes: (category) => + api.get(`/api/Master/document-type/list${category ? `?documentCategoryId=${category}` : ""}`), + }; diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 555e3a0b..b5daeb44 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -79,6 +79,12 @@ export const TENANT_STATUS = [ {id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"} ] +export const DOCUMENTS_ENTITIES = { + ProjectEntity : "c8fe7115-aa27-43bc-99f4-7b05fabe436e", + EmployeeEntity:"dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", +} + + export const CONSTANT_TEXT = { } From daf7f11310a2e8ba77c90352e60da8f5f4566b29 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Fri, 29 Aug 2025 16:05:54 +0530 Subject: [PATCH 06/51] Upload new document api integrated and use can upload document at employee --- src/components/Documents/DocumentSchema.js | 88 +++-- src/components/Documents/Documents.jsx | 2 +- src/components/Documents/NewDocument.jsx | 421 ++++++++++++--------- src/components/common/TagInput.jsx | 136 +++---- src/hooks/useDocument.js | 22 ++ src/repositories/DocumentRepository.jsx | 10 +- src/utils/appUtils.js | 8 + 7 files changed, 388 insertions(+), 299 deletions(-) create mode 100644 src/hooks/useDocument.js diff --git a/src/components/Documents/DocumentSchema.js b/src/components/Documents/DocumentSchema.js index 5842e488..bb20e8b0 100644 --- a/src/components/Documents/DocumentSchema.js +++ b/src/components/Documents/DocumentSchema.js @@ -1,33 +1,59 @@ import { z } from "zod"; +import { normalizeAllowedContentTypes } from "../../utils/appUtils"; -export const AttachmentSchema = z.object({ - fileName: z.string().min(1, {message:"File name is required"}), - base64Data: z.string().min(1, {message:"File data is required"}), - contentType: z.string().min(1, {message:"MIME type is required"}), - fileSize: z - .number() - .int() - .nonnegative("fileSize must be ≥ 0") - .max(25 * 1024 * 1024, "fileSize must be ≤ 25MB"), - description: z.string().optional().default(""), - isActive: z.boolean(), -}); +export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => { + const allowedTypes = normalizeAllowedContentTypes(allowedContentType); + + return z.object({ + fileName: z.string().min(1, { message: "File name is required" }), + base64Data: z.string().min(1, { message: "File data is required" }), + contentType: z + .string() + .min(1, { message: "MIME type is required" }) + .refine( + (val) => (allowedTypes.length ? allowedTypes.includes(val) : true), + { + message: `File type must be one of: ${allowedTypes.join(", ")}`, + } + ), + fileSize: z + .number() + .int() + .nonnegative("fileSize must be ≥ 0") + .max( + (maxSizeAllowedInMB ?? 25) * 1024 * 1024, + `fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB` + ), + description: z.string().optional().default(""), + isActive: z.boolean(), + }); +}; export const TagSchema = z.object({ - name: z.string().min(1, {message:"Tag name is required"}), - isActive: z.boolean(), + name: z.string().min(1, "Tag name is required"), + isActive: z.boolean().default(true), }); -export const DocumentPayloadSchema = (isMandatory, regularExp) => { + +export const DocumentPayloadSchema = (docConfig = {}) => { + const { + isMandatory, + regexExpression, + allowedContentType, + maxSizeAllowedInMB, + } = docConfig; + let documentIdSchema = z.string(); if (isMandatory) { - documentIdSchema = documentIdSchema.min(1, {message:"DocumentId is required"}); + documentIdSchema = documentIdSchema.min(1, { + message: "DocumentId is required", + }); } - if (regularExp) { + if (regexExpression) { documentIdSchema = documentIdSchema.regex( - new RegExp(regularExp), + new RegExp(regexExpression), "Invalid DocumentId format" ); } @@ -35,22 +61,30 @@ export const DocumentPayloadSchema = (isMandatory, regularExp) => { 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"}), - documentCategoryId:z.string().min(1,{message:"Please Select Document Category"}), - attachment: AttachmentSchema, - tags: z.array(TagSchema).default([]), + 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" }), + 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([]), + }); }; + export const defaultDocumentValues = { name: "", documentId: "", description: "", - entityId: "", + // entityId: "", documentTypeId: "", - documentCategoryId:"", + documentCategoryId: "", attachment: { fileName: "", base64Data: "", @@ -59,5 +93,5 @@ export const defaultDocumentValues = { description: "", isActive: true, }, - tags: [], + tags: [], }; diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 343310d8..5c8042d5 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -41,7 +41,7 @@ const Documents = () => { {isUpload && ( setUpload(false)}> - + setUpload(false)}/> )} diff --git a/src/components/Documents/NewDocument.jsx b/src/components/Documents/NewDocument.jsx index 175f46f4..7bdfa352 100644 --- a/src/components/Documents/NewDocument.jsx +++ b/src/components/Documents/NewDocument.jsx @@ -1,6 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; +import { useForm, FormProvider } from "react-hook-form"; import { defaultDocumentValues, DocumentPayloadSchema } from "./DocumentSchema"; import Label from "../common/Label"; import { DOCUMENTS_ENTITIES } from "../../utils/constants"; @@ -8,8 +8,11 @@ import { useDocumentCategories, useDocumentTypes, } from "../../hooks/masterHook/useMaster"; +import TagInput from "../common/TagInput"; +import { useUploadDocument } from "../../hooks/useDocument"; +import showToast from "../../services/toastService"; +import { useParams } from "react-router-dom"; -// util fn: convert file → base64 const toBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); @@ -18,40 +21,70 @@ const toBase64 = (file) => reader.onerror = (err) => reject(err); }); -const NewDocument = () => { - const [selectedType, setType] = useState(""); - - const DocumentUpload = DocumentPayloadSchema( - selectedType?.isMandatory ?? null, - selectedType?.regexExpression ?? null - ); +const NewDocument = ({closeModal}) => { + const { employeeId } = useParams(); + 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 }, - } = useForm({ - resolver: zodResolver(DocumentUpload), - defaultValues: defaultDocumentValues, + } = methods; + const { mutate: UploadDocument, isPending } = useUploadDocument(() => { + showToast("Document Uploaded Successfully", "success"); + closeModal(); }); - const onSubmit = (data) => { - console.log("Form submitted:", data); + const DocumentPayload = { ...data, entityId: employeeId }; + UploadDocument(DocumentPayload); }; - const documentTypeId = watch("documentTypeId"); - const categoryId = watch("documentCategoryId"); const file = watch("attachment"); - // API Data + const documentTypeId = watch("documentTypeId"); + + // This hooks calling api base Entity(Employee) and Category const { DocumentCategories, isLoading } = useDocumentCategories( DOCUMENTS_ENTITIES.EmployeeEntity ); - const { DocumentTypes, isLoading: isTypeLoading } = - useDocumentTypes(categoryId); + 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) => { @@ -82,177 +115,207 @@ const NewDocument = () => { }); }; - useEffect(() => { - if (documentTypeId) { - setType(DocumentTypes?.find((type) => type.id === documentTypeId)); - } - }, [documentTypeId, DocumentTypes]); - + // 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 (

Upload New Document

- -
- {/* Document Name */} -
- - - {errors.name && ( -
{errors.name.message}
- )} -
- - {/* Category */} -
- - + {errors.name && ( +
{errors.name.message}
)} - {DocumentCategories?.map((type) => ( - - ))} - - {errors.documentCategoryId && ( -
- {errors.documentCategoryId.message} -
- )} -
+
- {/* Type */} -
- - - {errors.documentTypeId && ( -
{errors.documentTypeId.message}
- )} -
- - {/* Document ID */} -
- - - {errors.documentId && ( -
{errors.documentId.message}
- )} -
- - {/* Upload */} -
-
- - -
document.getElementById("attachment").click()} + {/* Category */} +
+ + { - onFileChange(e); - e.target.value = ""; // reset input - }} - /> -
- - {errors.attachment && ( - - {errors.attachment.fileName?.message || - errors.attachment.base64Data?.message || - errors.attachment.contentType?.message || - errors.attachment.fileSize?.message} - - )} - - {file && ( -
-
- - {file.fileName} - - - {(file.fileSize / 1024).toFixed(1)} KB - -
- + {isLoading && ( + + )} + {!isLoading && } + {DocumentCategories?.map((type) => ( + + ))} + + {errors.documentCategoryId && ( +
+ {errors.documentCategoryId.message}
)}
-
- {/* Description */} -
- - - {errors.description && ( -
{errors.description.message}
- )} -
+ {/* Type */} +
+ + + {errors.documentTypeId && ( +
{errors.documentTypeId.message}
+ )} +
- {/* Buttons */} -
- - -
- + {/* Document ID */} +
+ + + {errors.documentId && ( +
{errors.documentId.message}
+ )} +
+ + {/* Upload */} +
+
+ + +
document.getElementById("attachment").click()} + > + + + Click to select or click here to browse + + + ({selectedType?.allowedContentType || "PDF/JPG/PNG"}, max{" "} + {selectedType?.maxSizeAllowedInMB ?? 25}MB) + + + { + onFileChange(e); + e.target.value = ""; // reset input + }} + /> +
+ + {errors.attachment && ( + + {errors.attachment.message + ? errors.attachment.message + : errors.attachment.fileName?.message || + errors.attachment.base64Data?.message || + errors.attachment.contentType?.message || + errors.attachment.fileSize?.message} + + )} + + {file?.base64Data && ( +
+
+ + {file.fileName} + + + {(file.fileSize / 1024).toFixed(1)} KB + +
+ +
+ )} +
+
+
+ + {errors.tags && ( + {errors.tags.message} + )} +
+ + {/* Description */} +
+ + + {errors.description && ( +
{errors.description.message}
+ )} +
+ + {/* Buttons */} +
+ + +
+ +
); }; diff --git a/src/components/common/TagInput.jsx b/src/components/common/TagInput.jsx index d0517223..bbcfda78 100644 --- a/src/components/common/TagInput.jsx +++ b/src/components/common/TagInput.jsx @@ -1,5 +1,5 @@ -import { useFormContext, useWatch } from "react-hook-form"; -import React, { useEffect, useState } from "react"; +import React, { useState, useEffect } from "react"; +import { useFormContext } from "react-hook-form"; const TagInput = ({ label = "Tags", @@ -8,104 +8,58 @@ const TagInput = ({ color = "#e9ecef", options = [], }) => { - const [tags, setTags] = useState([]); + const [tags, setTags] = useState([]); // now array of objects const [input, setInput] = useState(""); const [suggestions, setSuggestions] = useState([]); - const { setValue, trigger, control } = useFormContext(); - const watchedTags = useWatch({ control, name }); + const { setValue } = useFormContext(); -useEffect(() => { - if ( - Array.isArray(watchedTags) && - JSON.stringify(tags) !== JSON.stringify(watchedTags) - ) { - setTags(watchedTags); - } -}, [JSON.stringify(watchedTags)]); - + // Keep form value synced useEffect(() => { - if (input.trim() === "") { - setSuggestions([]); - } else { - const filtered = options?.filter( - (opt) => - opt?.name?.toLowerCase()?.includes(input.toLowerCase()) && - !tags?.some((tag) => tag.name === opt.name) - ); - setSuggestions(filtered); - } - }, [input, options, tags]); + setValue(name, tags, { shouldValidate: true }); + }, [tags, name, setValue]); - const addTag = async (tagObj) => { - if (!tags.some((tag) => tag.name === tagObj.name)) { - const cleanedTag = { - id: tagObj.id ?? null, - name: tagObj.name, - }; - const newTags = [...tags, cleanedTag]; - setTags(newTags); - setValue(name, newTags, { shouldValidate: true }); - await trigger(name); + const handleKeyDown = (e) => { + if (e.key === "Enter" && input.trim()) { + e.preventDefault(); + if (!tags.some((t) => t.name === input.trim())) { + setTags((prev) => [...prev, { name: input.trim(), isActive: true }]); + } setInput(""); setSuggestions([]); } }; - const removeTag = (indexToRemove) => { - const newTags = tags.filter((_, i) => i !== indexToRemove); - setTags(newTags); - setValue(name, newTags, { shouldValidate: true }); - trigger(name); + const handleRemove = (tagName) => { + setTags((prev) => prev.filter((t) => t.name !== tagName)); }; - const handleInputKeyDown = (e) => { - if ((e.key === "Enter" || e.key === " ")&& input.trim() !== "") { - e.preventDefault(); - const existing = options.find( - (opt) => opt.name.toLowerCase() === input.trim().toLowerCase() + const handleChange = (e) => { + const val = e.target.value; + setInput(val); + if (val) { + setSuggestions( + options + .filter( + (opt) => + opt.toLowerCase().includes(val.toLowerCase()) && + !tags.some((t) => t.name === opt) + ) + .map((s) => ({ name: s, isActive: true })) ); - const newTag = existing - ? existing - : { - id: null, - name: input.trim(), - description: input.trim(), - }; - addTag(newTag); - } else if (e.key === "Backspace" && input === "") { - setTags((prev) => prev.slice(0, -1)); + } else { + setSuggestions([]); } }; - const handleInputKey = (e) => { - const key = e.key?.toLowerCase(); - - if ((key === "enter" || key === " " || e.code === "Space") && input.trim() !== "") { - e.preventDefault(); - const existing = options.find( - (opt) => opt.name.toLowerCase() === input.trim().toLowerCase() - ); - const newTag = existing - ? existing - : { - id: null, - name: input.trim(), - description: input.trim(), - }; - addTag(newTag); - } else if ((key === "backspace" || e.code === "Backspace") && input === "") { - setTags((prev) => prev.slice(0, -1)); - } -}; - const handleSuggestionClick = (suggestion) => { - addTag(suggestion); + if (!tags.some((t) => t.name === suggestion.name)) { + setTags((prev) => [...prev, suggestion]); + } + setInput(""); + setSuggestions([]); }; - const backgroundColor = color || "#f8f9fa"; - const iconColor = `var(--bs-${color})`; - return ( <>
{selectedMaster === "Activity" ? "Activity" : "Name"}
{selectedMaster === "Activity" ? "Activity" : "Name"} {selectedMaster === "Activity" ? "Unit" : "Description"} {selectedMaster === "Activity" + ? "Unit" + : selectedMaster === "Document Type" + ? "Content Type" + : "Description"} Actions
+ + + {DocumentColumns.map((col) => ( + + ))} + + + + + {data?.map((doc) => ( + + {DocumentColumns.map((col) => ( + + ))} + + + ))} + +
+ {col.label} + + Action +
+ {col.customRender ? col.customRender(doc) : col.getValue(doc)} + +
+ + + + + +
+
+
+ ); +}; + +export default DocumentsList; diff --git a/src/components/Documents/NewDocument.jsx b/src/components/Documents/NewDocument.jsx index 7bdfa352..180ee24e 100644 --- a/src/components/Documents/NewDocument.jsx +++ b/src/components/Documents/NewDocument.jsx @@ -3,7 +3,6 @@ import React, { useEffect, useState } from "react"; import { useForm, FormProvider } from "react-hook-form"; import { defaultDocumentValues, DocumentPayloadSchema } from "./DocumentSchema"; import Label from "../common/Label"; -import { DOCUMENTS_ENTITIES } from "../../utils/constants"; import { useDocumentCategories, useDocumentTypes, @@ -11,7 +10,6 @@ import { import TagInput from "../common/TagInput"; import { useUploadDocument } from "../../hooks/useDocument"; import showToast from "../../services/toastService"; -import { useParams } from "react-router-dom"; const toBase64 = (file) => new Promise((resolve, reject) => { @@ -21,8 +19,8 @@ const toBase64 = (file) => reader.onerror = (err) => reject(err); }); -const NewDocument = ({closeModal}) => { - const { employeeId } = useParams(); +const NewDocument = ({closeModal,Document_Entity,Entity}) => { + const [selectedType, setSelectedType] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); const [schema, setSchema] = useState(() => DocumentPayloadSchema({})); @@ -44,7 +42,7 @@ const NewDocument = ({closeModal}) => { closeModal(); }); const onSubmit = (data) => { - const DocumentPayload = { ...data, entityId: employeeId }; + const DocumentPayload = { ...data, entityId: Entity }; UploadDocument(DocumentPayload); }; @@ -54,7 +52,7 @@ const NewDocument = ({closeModal}) => { // This hooks calling api base Entity(Employee) and Category const { DocumentCategories, isLoading } = useDocumentCategories( - DOCUMENTS_ENTITIES.EmployeeEntity + Document_Entity ); const categoryId = watch("documentCategoryId"); diff --git a/src/components/Employee/EmpDashboard.jsx b/src/components/Employee/EmpDashboard.jsx index b25f6803..85c97836 100644 --- a/src/components/Employee/EmpDashboard.jsx +++ b/src/components/Employee/EmpDashboard.jsx @@ -9,7 +9,6 @@ const EmpDashboard = ({ profile }) => { refetch, } = useProjectsAllocationByEmployee(profile?.id); - console.log(projectList); return ( <>
diff --git a/src/components/Employee/EmpDocuments.jsx b/src/components/Employee/EmpDocuments.jsx index 39d6e900..b4c8046b 100644 --- a/src/components/Employee/EmpDocuments.jsx +++ b/src/components/Employee/EmpDocuments.jsx @@ -2,11 +2,14 @@ import React, { useState, useEffect } from "react"; import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage"; import DocumentPage from "../../pages/Documents/DocumentPage"; import Documents from "../Documents/Documents"; +import { useParams } from "react-router-dom"; +import { DOCUMENTS_ENTITIES } from "../../utils/constants"; const EmpDocuments = ({ profile, loggedInUser }) => { + const {employeeId} = useParams() return ( <> - + ); }; diff --git a/src/components/Project/ProjectDocuments.jsx b/src/components/Project/ProjectDocuments.jsx new file mode 100644 index 00000000..061a25ec --- /dev/null +++ b/src/components/Project/ProjectDocuments.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import Documents from "../Documents/Documents"; +import { useSelectedproject } from "../../slices/apiDataManager"; +import { DOCUMENTS_ENTITIES } from "../../utils/constants"; +const ProjectDocuments = () => { + const selectedProject = useSelectedproject() + return ( + <> + + + ); +}; + +export default ProjectDocuments; diff --git a/src/components/Project/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx index 0a7f960e..633be7d7 100644 --- a/src/components/Project/ProjectNav.jsx +++ b/src/components/Project/ProjectNav.jsx @@ -67,15 +67,15 @@ const ProjectNav = ({ onPillClick, activePill }) => {
  • { e.preventDefault(); // Prevent page reload - onPillClick("imagegallary"); + onPillClick("documents"); }} > - project Setup + Documents
  • diff --git a/src/hooks/useDocument.js b/src/hooks/useDocument.js index 4885359b..c9262491 100644 --- a/src/hooks/useDocument.js +++ b/src/hooks/useDocument.js @@ -1,20 +1,46 @@ - - -//----------------------- MUTATION ------------------------- - -import { useMutation } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import showToast from "../services/toastService"; import { DocumentRepository } from "../repositories/DocumentRepository"; +// ----------------------Query------------------------------- +const cleanFilter = (filter) => { + const cleaned = { ...filter }; + + ["uploadedByIds", "documentCategoryIds", "documentTypeIds", "documentTagIds"].forEach((key) => { + if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) { + delete cleaned[key]; + } + + }); + + return cleaned; +}; +export const useDocumentListByEntityId=(entityTypeId,entityId,pageSize, pageNumber, filter,searchString="")=>{ + return useQuery({ + queryKey:["DocumentList",entityTypeId,entityId,pageSize, pageNumber, filter,searchString], + queryFn:async()=>{ + const cleanedFilter = cleanFilter(filter); + const resp = await DocumentRepository.getDocumentList(entityTypeId,entityId,pageSize, pageNumber,cleanedFilter,searchString); + return resp.data; + }, + enabled:!!entityTypeId && !! entityId + }) +} + +//----------------------- MUTATION ------------------------- + export const useUploadDocument =(onSuccessCallBack)=>{ + const queryClient = useQueryClient() return useMutation(({ mutationFn:async(DocumentPayload)=>DocumentRepository.uploadDocument(DocumentPayload), onSuccess:(data,variables)=>{ + queryClient.invalidateQueries({queryKey:["DocumentList"]}); if(onSuccessCallBack) onSuccessCallBack() }, onError: (error) => { + console.log(error) showToast( - error.message || "Something went wrong please try again !", + error.response.data.message || "Something went wrong please try again !", "error" ); }, diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index 75f7705b..78b2b662 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -16,9 +16,7 @@ import { useSelectedproject, } from "../../slices/apiDataManager"; import "./ProjectDetails.css"; -import { - useProjectDetails, -} from "../../hooks/useProjects"; +import { useProjectDetails } from "../../hooks/useProjects"; import { ComingSoonPage } from "../Misc/ComingSoonPage"; import Directory from "../Directory/Directory"; import eventBus from "../../services/eventBus"; @@ -26,19 +24,20 @@ import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChar import { useProjectName } from "../../hooks/useProjects"; import AttendanceOverview from "../../components/Dashboard/AttendanceChart"; import { setProjectId } from "../../slices/localVariablesSlice"; +import ProjectDocument from "../../components/Project/ProjectDocuments"; +import ProjectDocuments from "../../components/Project/ProjectDocuments"; const ProjectDetails = () => { - - const projectId = useSelectedproject() + const projectId = useSelectedproject(); const { projectNames, fetchData } = useProjectName(); - const dispatch = useDispatch() + const dispatch = useDispatch(); useEffect(() => { if (projectId == null) { dispatch(setProjectId(projectNames[0]?.id)); } - }, [projectNames]) + }, [projectNames]); const { projects_Details, @@ -49,12 +48,15 @@ const ProjectDetails = () => { // const [activePill, setActivePill] = useState("profile"); const [activePill, setActivePill] = useState(() => { - return localStorage.getItem("lastActiveProjectTab") || "profile"; -}); + return localStorage.getItem("lastActiveProjectTab") || "profile"; + }); const handler = useCallback( (msg) => { - if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) { + if ( + msg.keyword === "Update_Project" && + projects_Details?.id === msg.response.id + ) { refetch(); } }, @@ -66,11 +68,10 @@ const ProjectDetails = () => { return () => eventBus.off("project", handler); }, [handler]); - const handlePillClick = (pillKey) => { - setActivePill(pillKey); - localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage -}; - + const handlePillClick = (pillKey) => { + setActivePill(pillKey); + localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage + }; const renderContent = () => { if (projectLoading || !projects_Details) return ; @@ -85,9 +86,14 @@ const ProjectDetails = () => {
    - -
    - + +
    + {" "} + +
    @@ -103,14 +109,10 @@ const ProjectDetails = () => { ); case "infra": - return ( - - ); + return ; case "workplan": - return ( - - ); + return ; case "directory": return ( @@ -118,6 +120,12 @@ const ProjectDetails = () => { ); + case "documents": + return ( +
    + +
    + ); default: return ; @@ -142,4 +150,4 @@ const ProjectDetails = () => { ); }; -export default ProjectDetails; \ No newline at end of file +export default ProjectDetails; From cec16ded3e8d1a2c259bd7db185c7e6aca197396 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Fri, 29 Aug 2025 20:52:36 +0530 Subject: [PATCH 08/51] added skeleton for document list --- src/components/Documents/DocumentSkeleton.jsx | 70 +++++++++++++++++++ src/components/Documents/DocumentsList.jsx | 3 +- src/components/Documents/NewDocument.jsx | 5 +- 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/components/Documents/DocumentSkeleton.jsx diff --git a/src/components/Documents/DocumentSkeleton.jsx b/src/components/Documents/DocumentSkeleton.jsx new file mode 100644 index 00000000..af75fd01 --- /dev/null +++ b/src/components/Documents/DocumentSkeleton.jsx @@ -0,0 +1,70 @@ +import React from "react"; + +const SkeletonCell = ({ + width = "100%", + height = 20, + className = "", + style = {}, +}) => ( +
    +); + +export const DocumentTableSkeleton = ({ rows = 5 }) => { + return ( +
    + + + + + + + + + + + + + {[...Array(rows)].map((_, idx) => ( + + {/* Name */} + + + {/* Document Type */} + + + {/* Uploaded By (Avatar + Name) */} + + + {/* Uploaded on */} + + + {/* Status */} + + + ))} + +
    NameDocument TypeUploaded ByUploaded onStatus
    + + + + +
    + + +
    +
    + + + +
    +
    + ); +}; diff --git a/src/components/Documents/DocumentsList.jsx b/src/components/Documents/DocumentsList.jsx index 11bbb649..aef0c14c 100644 --- a/src/components/Documents/DocumentsList.jsx +++ b/src/components/Documents/DocumentsList.jsx @@ -5,6 +5,7 @@ import Avatar from "../common/Avatar"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import Loader from "../common/Loader"; import { useDebounce } from "../../utils/appUtils"; +import { DocumentTableSkeleton } from "./DocumentSkeleton"; export const getDocuementsStatus = (status) => { switch (status) { @@ -43,7 +44,7 @@ const DocumentsList = ({ Document_Entity, Entity,searchText ,setIsRefetching, setIsRefetching(isFetching); }, [isFetching, setIsRefetching]); - if (isLoading) return ; + if (isLoading) return ; if (isError) return

    Error: {error?.message || "Something went wrong"}

    ; const DocumentColumns = [ diff --git a/src/components/Documents/NewDocument.jsx b/src/components/Documents/NewDocument.jsx index 180ee24e..df94bab4 100644 --- a/src/components/Documents/NewDocument.jsx +++ b/src/components/Documents/NewDocument.jsx @@ -174,7 +174,7 @@ const NewDocument = ({closeModal,Document_Entity,Entity}) => {
    {/* Type */} -
    + {categoryId && (
    Date: Fri, 29 Aug 2025 12:03:47 +0530 Subject: [PATCH 17/51] The calendar icon should display correctly when we increase the zoom. --- src/components/Employee/EmpAttendance.jsx | 2 +- src/components/common/DateRangePicker.jsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/Employee/EmpAttendance.jsx b/src/components/Employee/EmpAttendance.jsx index b8ff8b83..26ae9531 100644 --- a/src/components/Employee/EmpAttendance.jsx +++ b/src/components/Employee/EmpAttendance.jsx @@ -126,7 +126,7 @@ const EmpAttendance = ({ employee }) => { className="dataTables_length text-start py-2 d-flex justify-content-between " id="DataTables_Table_0_length" > -
    +
    { + if (inputRef.current) { + inputRef.current._flatpickr.open(); // directly opens flatpickr + } + }; + return (
    @@ -77,7 +83,7 @@ export const DateRangePicker1 = ({ className = "", allowText = false, resetSignal, - defaultRange = true, + defaultRange = true, ...rest }) => { const inputRef = useRef(null); From f569ed635c64dca637f33ad4fc4e026c8488f559 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 29 Aug 2025 11:29:24 +0530 Subject: [PATCH 18/51] Cancel button not closing All fields popup in masters --- src/components/master/CreateActivity.jsx | 265 +++++++++--------- .../master/CreateContactCategory.jsx | 115 ++++---- src/components/master/CreateContactTag.jsx | 115 ++++---- src/components/master/CreateJobRole.jsx | 111 ++++---- src/components/master/CreateRole.jsx | 6 +- src/components/master/CreateWorkCategory.jsx | 178 ++++++------ src/components/master/DeleteMaster.jsx | 8 +- src/components/master/EditActivity.jsx | 2 +- src/components/master/EditContactCategory.jsx | 132 ++++----- src/components/master/EditContactTag.jsx | 134 ++++----- src/components/master/EditJobRole.jsx | 110 ++++---- src/components/master/EditRole.jsx | 154 +++++----- src/components/master/EditWorkCategory.jsx | 176 ++++++------ src/components/master/ManagePaymentMode.jsx | 34 +-- 14 files changed, 778 insertions(+), 762 deletions(-) diff --git a/src/components/master/CreateActivity.jsx b/src/components/master/CreateActivity.jsx index bb366c9c..79f8ec3b 100644 --- a/src/components/master/CreateActivity.jsx +++ b/src/components/master/CreateActivity.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect,useCallback } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -7,7 +7,7 @@ import { MasterRespository } from "../../repositories/MastersRepository"; import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { getCachedData, cacheData } from "../../slices/apiDataManager"; import showToast from "../../services/toastService"; -import {useCreateActivity} from "../../hooks/masterHook/useMaster"; +import { useCreateActivity } from "../../hooks/masterHook/useMaster"; const schema = z.object({ activityName: z.string().min(1, { message: "Activity Name is required" }), @@ -24,74 +24,74 @@ const schema = z.object({ }); const CreateActivity = ({ onClose }) => { -const maxDescriptionLength = 255; -const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.()); + const maxDescriptionLength = 255; + const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.()); -const { - register, - handleSubmit, - control, - setValue, - clearErrors, - setError, - getValues, - reset, - formState: { errors }, -} = useForm({ - resolver: zodResolver(schema), - defaultValues: { - activityName: "", - unitOfMeasurement: "", - checkList: [], - }, -}); + const { + register, + handleSubmit, + control, + setValue, + clearErrors, + setError, + getValues, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + activityName: "", + unitOfMeasurement: "", + checkList: [], + }, + }); -const { - fields: checkListItems, - append, - remove, -} = useFieldArray({ - control, - name: "checkList", -}); + const { + fields: checkListItems, + append, + remove, + } = useFieldArray({ + control, + name: "checkList", + }); -const addChecklistItem = useCallback(() => { - const values = getValues("checkList"); - const lastIndex = checkListItems.length - 1; + const addChecklistItem = useCallback(() => { + const values = getValues("checkList"); + const lastIndex = checkListItems.length - 1; - if ( - checkListItems.length > 0 && - (!values?.[lastIndex] || values[lastIndex].description.trim() === "") - ) { - setError(`checkList.${lastIndex}.description`, { - type: "manual", - message: "Please fill this checklist item before adding another.", - }); - return; - } + if ( + checkListItems.length > 0 && + (!values?.[lastIndex] || values[lastIndex].description.trim() === "") + ) { + setError(`checkList.${lastIndex}.description`, { + type: "manual", + message: "Please fill this checklist item before adding another.", + }); + return; + } - clearErrors(`checkList.${lastIndex}.description`); - append({ id: null, description: "", isMandatory: false }); -}, [checkListItems, getValues, append, setError, clearErrors]); + clearErrors(`checkList.${lastIndex}.description`); + append({ id: null, description: "", isMandatory: false }); + }, [checkListItems, getValues, append, setError, clearErrors]); -const removeChecklistItem = useCallback((index) => { - remove(index); -}, [remove]); + const removeChecklistItem = useCallback((index) => { + remove(index); + }, [remove]); -const handleChecklistChange = useCallback((index, value) => { - setValue(`checkList.${index}`, value); -}, [setValue]); + const handleChecklistChange = useCallback((index, value) => { + setValue(`checkList.${index}`, value); + }, [setValue]); -const onSubmit = (formData) => { - createActivity(formData); -}; + const onSubmit = (formData) => { + createActivity(formData); + }; // const onSubmit = (data) => { // setIsLoading(true); // MasterRespository.createActivity(data) // .then( ( resp ) => // { - + // const cachedData = getCachedData("Activity"); // const updatedData = [ ...cachedData, resp?.data ]; // cacheData("Activity", updatedData); @@ -104,15 +104,15 @@ const onSubmit = (formData) => { // setIsLoading(false); // }); // }; -const handleClose = useCallback(() => { - reset(); - onClose(); -}, [reset, onClose]); + const handleClose = useCallback(() => { + reset(); + onClose(); + }, [reset, onClose]); -useEffect(() => { - const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); -}, []); + useEffect(() => { + const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); + }, []); return (
    @@ -123,9 +123,8 @@ useEffect(() => { {errors.activityName && (

    {errors.activityName.message}

    @@ -137,9 +136,8 @@ useEffect(() => { {errors.unitOfMeasurement && (

    {errors.unitOfMeasurement.message}

    @@ -147,68 +145,68 @@ useEffect(() => {
    -

    {checkListItems.length > 0 ? "Check List" : "Add Check List" }

    +

    {checkListItems.length > 0 ? "Check List" : "Add Check List"}

    {checkListItems.length > 0 && ( - - - - - - - - - - {checkListItems.map((item, index) => ( - - - - +
    - Name - - Is Mandatory - Action
    - - - handleChecklistChange(index, e.target.value) - } - /> - {errors.checkList?.[index]?.description && ( - - {errors.checkList[index]?.description?.message} - - )} - - - - -
    + + + + + - ))} - -
    + Name + + Is Mandatory + Action
    + + + {checkListItems.map((item, index) => ( + + + + + handleChecklistChange(index, e.target.value) + } + /> + {errors.checkList?.[index]?.description && ( + + {errors.checkList[index]?.description?.message} + + )} + + + + + + + + + ))} + + )}
    @@ -226,12 +224,13 @@ useEffect(() => { {isLoading ? "Please Wait" : "Submit"} +
    diff --git a/src/components/master/CreateContactCategory.jsx b/src/components/master/CreateContactCategory.jsx index e5923c91..14707a76 100644 --- a/src/components/master/CreateContactCategory.jsx +++ b/src/components/master/CreateContactCategory.jsx @@ -1,43 +1,43 @@ -import React, { useEffect,useState } from 'react' +import React, { useEffect, useState } from 'react' import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { MasterRespository } from '../../repositories/MastersRepository'; import { clearApiCacheKey } from '../../slices/apiCacheSlice'; -import { getCachedData,cacheData } from '../../slices/apiDataManager'; +import { getCachedData, cacheData } from '../../slices/apiDataManager'; import showToast from '../../services/toastService'; -import {useCreateContactCategory} from '../../hooks/masterHook/useMaster'; +import { useCreateContactCategory } from '../../hooks/masterHook/useMaster'; const schema = z.object({ name: z.string().min(1, { message: "Category name is required" }), description: z.string().min(1, { message: "Description is required" }) - .max(255, { message: "Description cannot exceed 255 characters" }), + .max(255, { message: "Description cannot exceed 255 characters" }), }); -const CreateContactCategory = ({onClose}) => { +const CreateContactCategory = ({ onClose }) => { -const { - register, - handleSubmit, - formState: { errors }, - reset, -} = useForm({ - resolver: zodResolver(schema), - defaultValues: { - name: "", - description: "", - }, -}); + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + name: "", + description: "", + }, + }); -const [descriptionLength, setDescriptionLength] = useState(0); -const maxDescriptionLength = 255; + const [descriptionLength, setDescriptionLength] = useState(0); + const maxDescriptionLength = 255; -const { mutate: createContactCategory, isPending: isLoading } = useCreateContactCategory(() => onClose?.()); + const { mutate: createContactCategory, isPending: isLoading } = useCreateContactCategory(() => onClose?.()); -const onSubmit = (payload) => { - createContactCategory(payload); -}; + const onSubmit = (payload) => { + createContactCategory(payload); + }; // const onSubmit = (data) => { // setIsLoading(true) // MasterRespository.createContactCategory(data).then((resp)=>{ @@ -54,27 +54,27 @@ const onSubmit = (payload) => { // setIsLoading(false) // }) // }; -const resetForm = () => { - reset({ name: "", description: "" }); - setDescriptionLength(0); -}; + const resetForm = () => { + reset({ name: "", description: "" }); + setDescriptionLength(0); + }; + + useEffect(() => { + return () => resetForm(); + }, []); -useEffect(() => { - return () => resetForm(); -}, []); - return (<> -
    -
    - - - {errors.name &&

    {errors.name.message}

    } -
    -
    - + +
    + + + {errors.name &&

    {errors.name.message}

    } +
    +
    +