Search , Refresh featured added and initial setup for filter
This commit is contained in:
parent
daf7f11310
commit
b70d03af8a
9
src/components/Documents/DocumentFilterPanel.jsx
Normal file
9
src/components/Documents/DocumentFilterPanel.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const DocumentFilterPanel = () => {
|
||||
return (
|
||||
<h1>filter</h1>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocumentFilterPanel
|
@ -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;
|
||||
|
142
src/components/Documents/DocumentsList.jsx
Normal file
142
src/components/Documents/DocumentsList.jsx
Normal 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;
|
@ -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");
|
||||
|
@ -9,7 +9,6 @@ const EmpDashboard = ({ profile }) => {
|
||||
refetch,
|
||||
} = useProjectsAllocationByEmployee(profile?.id);
|
||||
|
||||
console.log(projectList);
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
14
src/components/Project/ProjectDocuments.jsx
Normal file
14
src/components/Project/ProjectDocuments.jsx
Normal 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;
|
@ -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>
|
||||
|
||||
|
@ -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"
|
||||
);
|
||||
},
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user