adedd fillter sidepanel and handle filter object on api level
This commit is contained in:
parent
cec16ded3e
commit
518928e439
@ -1,9 +1,183 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from "react";
|
||||
import { useDocumentFilterEntities } from "../../hooks/useDocument";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
DocumentFilterDefaultValues,
|
||||
DocumentFilterSchema,
|
||||
} from "./DocumentSchema";
|
||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
import moment from "moment";
|
||||
|
||||
const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
||||
const [resetKey, setResetKey] = useState(0);
|
||||
|
||||
const { data, isError, isLoading, error } =
|
||||
useDocumentFilterEntities(entityTypeId);
|
||||
|
||||
const methods = useForm({
|
||||
resolver: zodResolver(DocumentFilterSchema),
|
||||
defaultValues: DocumentFilterDefaultValues,
|
||||
});
|
||||
|
||||
const { handleSubmit, reset, setValue, watch } = methods;
|
||||
|
||||
// Watch values from form
|
||||
const isUploadedAt = watch("isUploadedAt");
|
||||
const isVerified = watch("isVerified");
|
||||
|
||||
// Close the offcanvas (bootstrap specific)
|
||||
const closePanel = () => {
|
||||
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||
};
|
||||
|
||||
const onSubmit = (values) => {
|
||||
onApply({
|
||||
...values,
|
||||
startDate: values.startDate
|
||||
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
endDate: values.endDate
|
||||
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
});
|
||||
closePanel();
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
reset(DocumentFilterDefaultValues);
|
||||
setResetKey((prev) => prev + 1);
|
||||
onApply(DocumentFilterDefaultValues);
|
||||
closePanel();
|
||||
};
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (isError) return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||
|
||||
const { uploadedBy = [], documentCategory = [], documentType = [], documentTag = [] } =
|
||||
data?.data || {};
|
||||
|
||||
const DocumentFilterPanel = () => {
|
||||
return (
|
||||
<h1>filter</h1>
|
||||
)
|
||||
}
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Date Range Section */}
|
||||
<div className="mb-2">
|
||||
<div className="text-start d-flex align-items-center my-1">
|
||||
<label className="form-label me-2 my-0">Choose Date:</label>
|
||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", true)}
|
||||
>
|
||||
Uploaded On
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
!isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", false)}
|
||||
>
|
||||
Updated On
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default DocumentFilterPanel
|
||||
<DateRangePicker1
|
||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||
startField="startDate"
|
||||
endField="endDate"
|
||||
defaultRange={false}
|
||||
resetSignal={resetKey}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Filters */}
|
||||
<div className="row g-2 text-start">
|
||||
<SelectMultiple
|
||||
name="uploadedByIds"
|
||||
label="Uploaded By:"
|
||||
options={uploadedBy}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
/>
|
||||
<SelectMultiple
|
||||
name="documentCategoryIds"
|
||||
label="Document Category:"
|
||||
options={documentCategory}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
/>
|
||||
<SelectMultiple
|
||||
name="documentTypeIds"
|
||||
label="Document Type:"
|
||||
options={documentType}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
/>
|
||||
<SelectMultiple
|
||||
name="documentTagIds"
|
||||
label="Tags:"
|
||||
options={documentTag}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div className="text-start d-flex align-items-center my-2">
|
||||
<label className="form-label me-2 my-0">Choose Status:</label>
|
||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
isVerified === null ? "active btn-primary text-white" : "text-primary"
|
||||
}`}
|
||||
onClick={() => setValue("isVerified", null)}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
isVerified === true ? "active btn-success text-white" : "text-success"
|
||||
}`}
|
||||
onClick={() => setValue("isVerified", true)}
|
||||
>
|
||||
Verified
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
isVerified === false ? "active btn-danger text-white" : "text-danger"
|
||||
}`}
|
||||
onClick={() => setValue("isVerified", false)}
|
||||
>
|
||||
Rejected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Buttons */}
|
||||
<div className="d-flex justify-content-end py-3 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-xs"
|
||||
onClick={onClear}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary btn-xs">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentFilterPanel;
|
||||
|
@ -71,9 +71,7 @@ export const DocumentPayloadSchema = (docConfig = {}) => {
|
||||
(val) => val !== null,
|
||||
{ message: "Attachment is required" }
|
||||
),
|
||||
|
||||
tags: z.array(TagSchema).optional().default([]),
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
@ -95,3 +93,28 @@ export const defaultDocumentValues = {
|
||||
},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
|
||||
//--------------------Filter-------------------------
|
||||
|
||||
export const DocumentFilterSchema = z.object({
|
||||
uploadedByIds: z.array(z.string()).default([]),
|
||||
documentCategoryIds: z.array(z.string()).default([]),
|
||||
documentTypeIds: z.array(z.string()).default([]),
|
||||
documentTagIds: z.array(z.string()).default([]),
|
||||
isUploadedAt: z.boolean().default(true),
|
||||
isVerified: z.boolean().nullable().optional(),
|
||||
startDate: z.string().nullable().optional(),
|
||||
endDate: z.string().nullable().optional(),
|
||||
});
|
||||
export const DocumentFilterDefaultValues = {
|
||||
uploadedByIds: [],
|
||||
documentCategoryIds: [],
|
||||
documentTypeIds: [],
|
||||
documentTagIds: [],
|
||||
isUploadedAt: true,
|
||||
isVerified: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
};
|
||||
|
||||
|
@ -19,7 +19,7 @@ const SkeletonCell = ({
|
||||
|
||||
export const DocumentTableSkeleton = ({ rows = 5 }) => {
|
||||
return (
|
||||
<div className="card px-2">
|
||||
|
||||
<table className="card-body table border-top dataTable no-footer dtr-column text-nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -65,6 +65,6 @@ export const DocumentTableSkeleton = ({ rows = 5 }) => {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -1,29 +1,53 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import NewDocument from "./NewDocument";
|
||||
import NewDocument from "./ManageDocument";
|
||||
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";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
DocumentFilterDefaultValues,
|
||||
DocumentFilterSchema,
|
||||
} from "./DocumentSchema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import ManageDocument from "./ManageDocument";
|
||||
|
||||
const Documents = ({ Document_Entity, Entity }) => {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [filters, setFilter] = useState();
|
||||
const [isRefetching, setIsRefetching] = useState(false);
|
||||
const [refetchFn, setRefetchFn] = useState(null);
|
||||
const { employeeId } = useParams();
|
||||
const [isUpload, setUpload] = useState(false);
|
||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||
|
||||
const methods = useForm({
|
||||
resolver: zodResolver(DocumentFilterSchema),
|
||||
defaultValues: DocumentFilterDefaultValues,
|
||||
});
|
||||
|
||||
const { reset } = methods;
|
||||
|
||||
const clearFilter = () => {
|
||||
setFilter(DocumentFilterDefaultValues);
|
||||
reset();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setShowTrigger(true);
|
||||
setOffcanvasContent("Document Filters", <DocumentFilterPanel />);
|
||||
setOffcanvasContent(
|
||||
"Document Filters",
|
||||
<DocumentFilterPanel entityTypeId={Document_Entity} onApply={setFilter} />
|
||||
);
|
||||
|
||||
return () => {
|
||||
setShowTrigger(false);
|
||||
setOffcanvasContent("", null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="card d-flex p-2">
|
||||
@ -41,12 +65,21 @@ const Documents = ({ Document_Entity, Entity }) => {
|
||||
|
||||
{/* 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()}>
|
||||
<span
|
||||
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
||||
disabled={isRefetching}
|
||||
onClick={() => {
|
||||
setSearchText("");
|
||||
setFilter(DocumentFilterDefaultValues);
|
||||
refetchFn && refetchFn();
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
<i className={`bx bx-refresh ms-1 ${
|
||||
isRefetching ? "bx-spin" : ""
|
||||
}`}></i>
|
||||
<i
|
||||
className={`bx bx-refresh ms-1 ${
|
||||
isRefetching ? "bx-spin" : ""
|
||||
}`}
|
||||
></i>
|
||||
</span>
|
||||
|
||||
<button
|
||||
@ -62,6 +95,7 @@ const Documents = ({ Document_Entity, Entity }) => {
|
||||
<DocumentsList
|
||||
Document_Entity={Document_Entity}
|
||||
Entity={Entity}
|
||||
filters={filters}
|
||||
searchText={searchText}
|
||||
setIsRefetching={setIsRefetching}
|
||||
setRefetchFn={setRefetchFn}
|
||||
@ -70,7 +104,7 @@ const Documents = ({ Document_Entity, Entity }) => {
|
||||
|
||||
{isUpload && (
|
||||
<GlobalModel isOpen={isUpload} closeModal={() => setUpload(false)}>
|
||||
<NewDocument
|
||||
<ManageDocument
|
||||
closeModal={() => setUpload(false)}
|
||||
Document_Entity={Document_Entity}
|
||||
Entity={Entity}
|
||||
|
@ -24,28 +24,47 @@ export const getDocuementsStatus = (status) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
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]);
|
||||
const DocumentsList = ({
|
||||
Document_Entity,
|
||||
Entity,
|
||||
filters,
|
||||
searchText,
|
||||
setIsRefetching,
|
||||
setRefetchFn,
|
||||
}) => {
|
||||
const debouncedSearch = useDebounce(searchText, 500);
|
||||
const { data, isError, isLoading, error, refetch, isFetching } =
|
||||
useDocumentListByEntityId(
|
||||
Document_Entity,
|
||||
Entity,
|
||||
ITEMS_PER_PAGE,
|
||||
1,
|
||||
filters,
|
||||
debouncedSearch
|
||||
);
|
||||
|
||||
if (isLoading) return <DocumentTableSkeleton/>;
|
||||
if (isError) return <p>Error: {error?.message || "Something went wrong"}</p>;
|
||||
// Pass the refetch function to parent when component mounts
|
||||
useEffect(() => {
|
||||
setRefetchFn(() => refetch);
|
||||
}, [setRefetchFn, refetch]);
|
||||
|
||||
// Sync fetching status with parent
|
||||
useEffect(() => {
|
||||
setIsRefetching(isFetching);
|
||||
}, [isFetching, setIsRefetching]);
|
||||
|
||||
// check no data scenarios
|
||||
const noData = !isLoading && !isError && data?.length === 0;
|
||||
const isSearchEmpty = noData && !!debouncedSearch;
|
||||
const isFilterEmpty = noData && false;
|
||||
const isInitialEmpty = noData && !debouncedSearch;
|
||||
|
||||
if (isLoading || isFetching) return <DocumentTableSkeleton />
|
||||
if (isError)
|
||||
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
||||
if (isInitialEmpty) return <div>No documents found yet.</div>;
|
||||
if (isSearchEmpty) return <div>No results found for "{debouncedSearch}"</div>;
|
||||
if (isFilterEmpty) return <div>No documents match your filter.</div>;
|
||||
|
||||
const DocumentColumns = [
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ const toBase64 = (file) =>
|
||||
reader.onerror = (err) => reject(err);
|
||||
});
|
||||
|
||||
const NewDocument = ({closeModal,Document_Entity,Entity}) => {
|
||||
const ManageDocument = ({closeModal,Document_Entity,Entity}) => {
|
||||
|
||||
const [selectedType, setSelectedType] = useState(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
@ -319,4 +319,4 @@ const NewDocument = ({closeModal,Document_Entity,Entity}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default NewDocument;
|
||||
export default ManageDocument;
|
@ -71,7 +71,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
closePanel();
|
||||
};
|
||||
|
||||
// ✅ Close popup when navigating to another component
|
||||
// Close popup when navigating to another component
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
closePanel();
|
||||
|
@ -10,9 +10,14 @@ const cleanFilter = (filter) => {
|
||||
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
||||
delete cleaned[key];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
["startDate", "endDate", "isVerified"].forEach((key) => {
|
||||
if (cleaned[key] === null) {
|
||||
delete cleaned[key];
|
||||
}
|
||||
});
|
||||
|
||||
return cleaned;
|
||||
};
|
||||
export const useDocumentListByEntityId=(entityTypeId,entityId,pageSize, pageNumber, filter,searchString="")=>{
|
||||
@ -27,6 +32,20 @@ export const useDocumentListByEntityId=(entityTypeId,entityId,pageSize, pageNumb
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentFilterEntities =(entityTypeId)=>{
|
||||
return useQuery({
|
||||
queryKey:["DFilter",entityTypeId],
|
||||
queryFn:async()=> await DocumentRepository.getFilterEntities(entityTypeId)
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentDetails =(documentId)=>{
|
||||
return useQuery({
|
||||
queryKey:["Document",documentId],
|
||||
queryFn:async()=> await DocumentRepository.getDocumentById(documentId)
|
||||
})
|
||||
}
|
||||
|
||||
//----------------------- MUTATION -------------------------
|
||||
|
||||
export const useUploadDocument =(onSuccessCallBack)=>{
|
||||
@ -45,4 +64,21 @@ export const useUploadDocument =(onSuccessCallBack)=>{
|
||||
);
|
||||
},
|
||||
}))
|
||||
}
|
||||
export const useUpdateDocument =(onSuccessCallBack)=>{
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation(({
|
||||
mutationFn:async(documentId,DocumentPayload)=>DocumentRepository.UpdateDocument(documentId,DocumentPayload),
|
||||
onSuccess:(data,variables)=>{
|
||||
queryClient.invalidateQueries({queryKey:["DocumentList"]});
|
||||
if(onSuccessCallBack) onSuccessCallBack()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error)
|
||||
showToast(
|
||||
error.response.data.message || "Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
}))
|
||||
}
|
@ -8,6 +8,8 @@ export const DocumentRepository = {
|
||||
},
|
||||
getDocumentById:(id)=>api.get(`/api/Document/${id}`),
|
||||
|
||||
getFilterEntities:(entityId)=>api.get(`/api/Document/get/filter${entityTypeId}`),
|
||||
getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`),
|
||||
|
||||
UpdateDocument:(documentId,data)=>api.get(`/api/Expense/edit/${documentId}`,data)
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user