Search , Refresh featured added and initial setup for filter

This commit is contained in:
pramod mahajan 2025-08-29 20:34:37 +05:30
parent daf7f11310
commit b70d03af8a
10 changed files with 319 additions and 88 deletions

View File

@ -0,0 +1,9 @@
import React from 'react'
const DocumentFilterPanel = () => {
return (
<h1>filter</h1>
)
}
export default DocumentFilterPanel

View File

@ -1,52 +1,84 @@
import React, { useState } from 'react'
import GlobalModel from '../common/GlobalModel'
import NewDocument from './NewDocument'
import React, { useEffect, useState } from "react";
import GlobalModel from "../common/GlobalModel";
import NewDocument from "./NewDocument";
import { DOCUMENTS_ENTITIES } from "../../utils/constants";
import { useParams } from "react-router-dom";
import DocumentsList from "./DocumentsList";
import DocumentFilterPanel from "./DocumentFilterPanel";
import { useFab } from "../../Context/FabContext";
const Documents = ({ Document_Entity, Entity }) => {
const [searchText, setSearchText] = useState("");
const [isRefetching, setIsRefetching] = useState(false);
const [refetchFn, setRefetchFn] = useState(null);
const { employeeId } = useParams();
const [isUpload, setUpload] = useState(false);
const { setOffcanvasContent, setShowTrigger } = useFab();
const Documents = () => {
const [isUpload,setUpload] =useState(false);
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent("Document Filters", <DocumentFilterPanel />);
return () => {
setShowTrigger(false);
setOffcanvasContent("", null);
};
}, []);
return (
<div className=''>
<div className="card d-flex p-2">
<div className="row align-items-center">
{/* Search */}
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search Document"
/>
</div>
{/* Actions */}
<div className="col-6 col-md-6 col-lg-9 text-end">
<span className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer">
Refresh
< i className={`bx bx-refresh ms-1 `}></i>
</span>
<button
type="button"
title="Add New Document"
className="p-1 bg-primary rounded-circle cursor-pointer"
onClick={()=>setUpload(true)}
>
<i className="bx bx-plus fs-4 text-white"></i>
</button>
</div>
</div>
<div className="mt-5">
<div className="card d-flex p-2">
<div className="row align-items-center">
{/* Search */}
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search Document"
/>
</div>
{isUpload && (
<GlobalModel isOpen={isUpload} closeModal={()=>setUpload(false)}>
<NewDocument closeModal={()=>setUpload(false)}/>
</GlobalModel>
)}
</div>
)
}
export default Documents
{/* Actions */}
<div className="col-6 col-md-6 col-lg-9 text-end">
<span className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer" disabled={isRefetching}
onClick={() => refetchFn && refetchFn()}>
Refresh
<i className={`bx bx-refresh ms-1 ${
isRefetching ? "bx-spin" : ""
}`}></i>
</span>
<button
type="button"
title="Add New Document"
className="p-1 bg-primary rounded-circle cursor-pointer"
onClick={() => setUpload(true)}
>
<i className="bx bx-plus fs-4 text-white"></i>
</button>
</div>
</div>
<DocumentsList
Document_Entity={Document_Entity}
Entity={Entity}
searchText={searchText}
setIsRefetching={setIsRefetching}
setRefetchFn={setRefetchFn}
/>
</div>
{isUpload && (
<GlobalModel isOpen={isUpload} closeModal={() => setUpload(false)}>
<NewDocument
closeModal={() => setUpload(false)}
Document_Entity={Document_Entity}
Entity={Entity}
/>
</GlobalModel>
)}
</div>
);
};
export default Documents;

View File

@ -0,0 +1,142 @@
import React, { useEffect } from "react";
import { useDocumentListByEntityId } from "../../hooks/useDocument";
import { ITEMS_PER_PAGE } from "../../utils/constants";
import Avatar from "../common/Avatar";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Loader from "../common/Loader";
import { useDebounce } from "../../utils/appUtils";
export const getDocuementsStatus = (status) => {
switch (status) {
case true:
return (
<span className="badge rounded-pill bg-label-success">Verified</span>
);
case false:
return (
<span className="badge rounded-pill bg-label-danger">Rejected</span>
);
case null:
default:
return (
<span className="badge rounded-pill bg-label-primary">Pending</span>
);
}
};
const DocumentsList = ({ Document_Entity, Entity,searchText ,setIsRefetching,
setRefetchFn,}) => {
const debouncedSearch = useDebounce(searchText, 500);
const { data, isError, isLoading, error,refetch,isFetching } = useDocumentListByEntityId(
Document_Entity,
Entity,
ITEMS_PER_PAGE,
1,{},debouncedSearch
);
// Pass the refetch function to parent when component mounts
useEffect(() => {
setRefetchFn(() => refetch);
}, [setRefetchFn, refetch]);
// Sync fetching status with parent
useEffect(() => {
setIsRefetching(isFetching);
}, [isFetching, setIsRefetching]);
if (isLoading) return <Loader />;
if (isError) return <p>Error: {error?.message || "Something went wrong"}</p>;
const DocumentColumns = [
{
key: "name",
label: "Name",
getValue: (e) => e.name || "N/A",
align: "text-start",
},
{
key: "documentType",
label: "Document Type",
getValue: (e) => e.documentType?.name || "N/A",
align: "text-start",
},
{
key: "uploadedBy",
label: "Uploaded By",
align: "text-start",
getValue: (e) =>
`${e.uploadedBy?.firstName ?? ""} ${
e.uploadedBy?.lastName ?? ""
}`.trim() || "N/A",
customRender: (e) => (
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0"
firstName={e.uploadedBy?.firstName}
lastName={e.uploadedBy?.lastName}
/>
<span className="text-truncate ">
{`${e.uploadedBy?.firstName ?? ""} ${
e.uploadedBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
),
},
{
key: "uploadedAt",
label: "Uploaded on",
getValue: (e) => formatUTCToLocalTime(e?.uploadedAt),
isAlwaysVisible: true,
align: "text-center",
},
{
key: "Status",
label: "status",
getValue: (e) => getDocuementsStatus(e.isVerified),
isAlwaysVisible: true,
align: "text-center",
},
];
return (
<div className="table-responsive">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr className="shadow-sm">
{DocumentColumns.map((col) => (
<th key={col.key} className={`sorting ${col.align}`}>
{col.label}
</th>
))}
<th className="sticky-action-column bg-white text-center">
Action
</th>
</tr>
</thead>
<tbody className="text-start">
{data?.map((doc) => (
<tr key={doc.id}>
{DocumentColumns.map((col) => (
<td key={col.key} className={`sorting ${col.align}`}>
{col.customRender ? col.customRender(doc) : col.getValue(doc)}
</td>
))}
<td className="text-center">
<div className="d-flex justify-content-center gap-2">
<i className="bx bx-show text-primary cursor-pointer"></i>
<i className="bx bx-edit text-secondary cursor-pointer"></i>
<i className="bx bx-trash text-danger cursor-pointer"></i>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default DocumentsList;

View File

@ -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");

View File

@ -9,7 +9,6 @@ const EmpDashboard = ({ profile }) => {
refetch,
} = useProjectsAllocationByEmployee(profile?.id);
console.log(projectList);
return (
<>
<div className="row">

View File

@ -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 (
<>
<Documents/>
<Documents Document_Entity={DOCUMENTS_ENTITIES.EmployeeEntity} Entity={employeeId} />
</>
);
};

View File

@ -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 (
<>
<Documents Document_Entity={DOCUMENTS_ENTITIES.ProjectEntity} Entity={selectedProject} />
</>
);
};
export default ProjectDocuments;

View File

@ -67,15 +67,15 @@ const ProjectNav = ({ onPillClick, activePill }) => {
<li className="nav-item">
<a
className={`nav-link ${
activePill === "imagegallary" ? "active" : ""
activePill === "documents" ? "active" : ""
} fs-6`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
onPillClick("imagegallary");
onPillClick("documents");
}}
>
<i className='bx bxs-cog bx-sm me-1_5'></i> <span className="d-none d-md-inline">project Setup</span>
<i className='bx bxs-cog bx-sm me-1_5'></i> <span className="d-none d-md-inline">Documents</span>
</a>
</li>

View File

@ -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"
);
},

View File

@ -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 <Loader />;
@ -85,9 +86,14 @@ const ProjectDetails = () => {
<ProjectOverview project={projectId} />
</div>
<div className="col-lg-8 col-md-7 mt-5">
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
<div className="mt-5"> <AttendanceOverview /></div>
<ProjectProgressChart
ShowAllProject="false"
DefaultRange="1M"
/>
<div className="mt-5">
{" "}
<AttendanceOverview />
</div>
</div>
</div>
</>
@ -103,14 +109,10 @@ const ProjectDetails = () => {
);
case "infra":
return (
<ProjectInfra data={projects_Details} onDataChange={refetch} />
);
return <ProjectInfra data={projects_Details} onDataChange={refetch} />;
case "workplan":
return (
<WorkPlan data={projects_Details} onDataChange={refetch} />
);
return <WorkPlan data={projects_Details} onDataChange={refetch} />;
case "directory":
return (
@ -118,6 +120,12 @@ const ProjectDetails = () => {
<Directory IsPage={false} prefernceContacts={projects_Details.id} />
</div>
);
case "documents":
return (
<div className="row">
<ProjectDocuments />
</div>
);
default:
return <ComingSoonPage />;
@ -142,4 +150,4 @@ const ProjectDetails = () => {
);
};
export default ProjectDetails;
export default ProjectDetails;