reafctor contact list with filter
This commit is contained in:
parent
b91487712d
commit
025b13ea64
@ -4,7 +4,8 @@ import { getBucketNameById } from "./DirectoryUtils";
|
|||||||
import { useBuckets } from "../../hooks/useDirectory";
|
import { useBuckets } from "../../hooks/useDirectory";
|
||||||
import { getPhoneIcon } from "./DirectoryUtils";
|
import { getPhoneIcon } from "./DirectoryUtils";
|
||||||
import { useDir } from "../../Context/DireContext";
|
import { useDir } from "../../Context/DireContext";
|
||||||
const CardViewDirectory = ({
|
import { useDirectoryContext } from "../../pages/Directory/DirectoryPage";
|
||||||
|
const CardViewContact = ({
|
||||||
IsActive,
|
IsActive,
|
||||||
contact,
|
contact,
|
||||||
setSelectedContact,
|
setSelectedContact,
|
||||||
@ -14,7 +15,7 @@ const CardViewDirectory = ({
|
|||||||
IsDeleted,
|
IsDeleted,
|
||||||
restore,
|
restore,
|
||||||
}) => {
|
}) => {
|
||||||
const { buckets } = useBuckets();
|
const { data } = useDirectoryContext();
|
||||||
const { dirActions, setDirActions } = useDir();
|
const { dirActions, setDirActions } = useDir();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,8 +26,9 @@ const CardViewDirectory = ({
|
|||||||
<div className="card-body px-1 py-2 pb-0">
|
<div className="card-body px-1 py-2 pb-0">
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<div
|
<div
|
||||||
className={`d-flex align-items-center ${IsActive && "cursor-pointer"
|
className={`d-flex align-items-center ${
|
||||||
}`}
|
IsActive && "cursor-pointer"
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (IsActive) {
|
if (IsActive) {
|
||||||
setIsOpenModalNote(true);
|
setIsOpenModalNote(true);
|
||||||
@ -89,10 +91,11 @@ const CardViewDirectory = ({
|
|||||||
)}
|
)}
|
||||||
{!IsActive && (
|
{!IsActive && (
|
||||||
<i
|
<i
|
||||||
className={`bx ${dirActions.action && dirActions.id === contact.id
|
className={`bx ${
|
||||||
|
dirActions.action && dirActions.id === contact.id
|
||||||
? "bx-loader-alt bx-spin"
|
? "bx-loader-alt bx-spin"
|
||||||
: "bx-recycle"
|
: "bx-recycle"
|
||||||
} me-1 text-primary cursor-pointer`}
|
} me-1 text-primary cursor-pointer`}
|
||||||
title="Restore"
|
title="Restore"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDirActions({ action: false, id: contact.id });
|
setDirActions({ action: false, id: contact.id });
|
||||||
@ -104,17 +107,15 @@ const CardViewDirectory = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className="list-inline m-0 ps-4 d-flex align-items-start">
|
<ul className="list-inline m-0 ps-4 d-flex align-items-start">
|
||||||
{/* <li className="list-inline-item me-1 small">
|
|
||||||
<i className="fa-solid fa-briefcase me-2"></i>
|
|
||||||
</li> */}
|
|
||||||
<li className="list-inline-item text-break small px-1 ms-5">
|
<li className="list-inline-item text-break small px-1 ms-5">
|
||||||
{contact.organization}
|
{contact?.organization}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`card-footer text-start px-9 py-1 ${IsActive && "cursor-pointer"
|
className={`card-footer text-start px-9 py-1 ${
|
||||||
}`}
|
IsActive && "cursor-pointer"
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (IsActive) {
|
if (IsActive) {
|
||||||
setIsOpenModalNote(true);
|
setIsOpenModalNote(true);
|
||||||
@ -123,10 +124,10 @@ const CardViewDirectory = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<hr className="my-0" />
|
<hr className="my-0" />
|
||||||
{contact.designation && (
|
{contact?.designation && (
|
||||||
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
|
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
|
||||||
<li className="me-2">
|
<li className="me-2">
|
||||||
<i class="fa-solid fa-id-badge ms-1"></i>
|
<i className="fa-solid fa-id-badge ms-1"></i>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex-grow-1 text-break small">
|
<li className="flex-grow-1 text-break small">
|
||||||
{contact.designation}
|
{contact.designation}
|
||||||
@ -188,7 +189,7 @@ const CardViewDirectory = ({
|
|||||||
>
|
>
|
||||||
<i className="bx bx-pin bx-xs"></i>
|
<i className="bx bx-pin bx-xs"></i>
|
||||||
<span className="small-text">
|
<span className="small-text">
|
||||||
{getBucketNameById(buckets, bucketId)}
|
{getBucketNameById(data, bucketId)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@ -199,4 +200,4 @@ const CardViewDirectory = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardViewDirectory;
|
export default CardViewContact;
|
@ -1,47 +1,49 @@
|
|||||||
import { z } from "zod";
|
import { array, z } from "zod";
|
||||||
export const ContactSchema = z
|
export const ContactSchema = z.object({
|
||||||
.object({
|
name: z.string().min(1, "Name is required"),
|
||||||
name: z.string().min(1, "Name is required"),
|
organization: z.string().min(1, "Organization name is required"),
|
||||||
organization: z.string().min(1, "Organization name is required"),
|
contactCategoryId: z.string().nullable().optional(),
|
||||||
contactCategoryId: z.string().nullable().optional(),
|
address: z.string().optional(),
|
||||||
address: z.string().optional(),
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" }),
|
designation: z.string().min(1, { message: "Designation is requried" }),
|
||||||
designation: z.string().min(1, {message:"Designation is requried"}),
|
projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required")
|
||||||
projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required")
|
contactEmails: z
|
||||||
contactEmails: z
|
.array(
|
||||||
.array(
|
z.object({
|
||||||
z.object({
|
label: z.string(),
|
||||||
label: z.string(),
|
emailAddress: z.string().email("Invalid email").or(z.literal("")),
|
||||||
emailAddress: z.string().email("Invalid email").or(z.literal("")),
|
})
|
||||||
})
|
)
|
||||||
)
|
.optional()
|
||||||
.optional()
|
.default([]),
|
||||||
.default([]),
|
|
||||||
|
|
||||||
contactPhones: z
|
contactPhones: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
label: z.string(),
|
label: z.string(),
|
||||||
phoneNumber: z
|
phoneNumber: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, "Invalid Number")
|
.min(6, "Invalid Number")
|
||||||
.max(13, "Invalid Number")
|
.max(13, "Invalid Number")
|
||||||
.regex(/^[\d\s+()-]+$/, "Invalid phone number format").or(z.literal("")),
|
.regex(/^[\d\s+()-]+$/, "Invalid phone number format")
|
||||||
})
|
.or(z.literal("")),
|
||||||
)
|
})
|
||||||
.optional()
|
)
|
||||||
.default([]),
|
.optional()
|
||||||
|
.default([]),
|
||||||
|
|
||||||
tags: z
|
tags: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string().nullable(),
|
id: z.string().nullable(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, { message: "At least one tag is required" }),
|
.min(1, { message: "At least one tag is required" }),
|
||||||
bucketIds: z.array(z.string()).nonempty({ message: "At least one bucket is required" })
|
bucketIds: z
|
||||||
})
|
.array(z.string())
|
||||||
|
.nonempty({ message: "At least one bucket is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
// .refine((data) => {
|
// .refine((data) => {
|
||||||
// const hasValidEmail = (data.contactEmails || []).some(
|
// const hasValidEmail = (data.contactEmails || []).some(
|
||||||
@ -57,24 +59,33 @@ bucketIds: z.array(z.string()).nonempty({ message: "At least one bucket is requi
|
|||||||
// path: ["contactPhone"],
|
// path: ["contactPhone"],
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
||||||
// Buckets
|
// Buckets
|
||||||
|
|
||||||
export const bucketScheam = z.object( {
|
export const bucketScheam = z.object({
|
||||||
name: z.string().min( 1, {message: "Name is required"} ),
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
description:z.string().min(1,{message:"Description is required"})
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
})
|
});
|
||||||
|
|
||||||
export const defaultContactValue = {
|
export const defaultContactValue = {
|
||||||
name: "",
|
name: "",
|
||||||
organization: "",
|
organization: "",
|
||||||
contactCategoryId: null,
|
contactCategoryId: null,
|
||||||
address: "",
|
address: "",
|
||||||
description: "",
|
description: "",
|
||||||
designation: "",
|
designation: "",
|
||||||
projectIds: [],
|
projectIds: [],
|
||||||
contactEmails: [],
|
contactEmails: [],
|
||||||
contactPhones: [],
|
contactPhones: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
bucketIds: [],
|
bucketIds: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const contactsFilter = z.object({
|
||||||
|
buckets: z.array(z.string()).optional(),
|
||||||
|
contactCategories: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const defaultContactFilter = {
|
||||||
|
buckets: [],
|
||||||
|
contactCategories: [],
|
||||||
|
};
|
||||||
|
@ -21,6 +21,6 @@ export const getPhoneIcon = (type) => {
|
|||||||
|
|
||||||
|
|
||||||
export const getBucketNameById = (buckets, id) => {
|
export const getBucketNameById = (buckets, id) => {
|
||||||
const bucket = buckets.find(b => b.id === id);
|
const bucket = buckets?.find(b => b.id === id);
|
||||||
return bucket ? bucket.name : 'Unknown';
|
return bucket ? bucket.name : 'Unknown';
|
||||||
};
|
};
|
||||||
|
124
src/components/Directory/ListViewContact.jsx
Normal file
124
src/components/Directory/ListViewContact.jsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Avatar from "../common/Avatar";
|
||||||
|
import Pagination from "../common/Pagination";
|
||||||
|
|
||||||
|
const ListViewContact = ({ data, Pagination }) => {
|
||||||
|
const contactList = [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
label: "Name",
|
||||||
|
getValue: (e) => (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={(e?.name || "").trim().split(" ")[0] || ""}
|
||||||
|
lastName={(e?.name || "").trim().split(" ")[1] || ""}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block "
|
||||||
|
style={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
{e?.name || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "email",
|
||||||
|
label: "Email",
|
||||||
|
getValue: (e) => (
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block"
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
>
|
||||||
|
{e?.contactEmails?.[0]?.emailAddress || "N/A"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "organization",
|
||||||
|
label: "Organization",
|
||||||
|
getValue: (e) => (
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block"
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
>
|
||||||
|
{e?.organization || "N/A"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "category",
|
||||||
|
label: "Category",
|
||||||
|
getValue: (e) => (
|
||||||
|
<span
|
||||||
|
className="text-truncate d-inline-block"
|
||||||
|
style={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
{e?.contactCategory?.name || "N/A"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card ">
|
||||||
|
<div className="card-datatable table-responsive" id="horizontal-example">
|
||||||
|
<div className="dataTables_wrapper no-footer ">
|
||||||
|
<table className="table border-top dataTable text-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr className="shadow-sm">
|
||||||
|
{contactList?.map((col) => (
|
||||||
|
<th key={col.key} className={col.align}>
|
||||||
|
{col.label}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
<th className="sticky-action-column bg-white text-center">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Array.isArray(data) && data.length > 0 ? (
|
||||||
|
data.map((row, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
{contactList.map((col) => (
|
||||||
|
<td key={col.key} className={col.align}>
|
||||||
|
{col.getValue(row)}
|
||||||
|
</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>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={contactList.length + 1} className="text-center">
|
||||||
|
No contacts found
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{Pagination && (
|
||||||
|
<div className="d-flex justify-content-start p-3">{Pagination}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListViewContact;
|
@ -239,9 +239,10 @@ export const useDirectoryNotes = (
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanFilter = (filter) => {
|
const cleanFilter = (filter) => {
|
||||||
const cleaned = { ...filter };
|
const cleaned = { ...filter };
|
||||||
["bucketIds", "categories"].forEach((key) => {
|
["bucketIds", "contactCategories"].forEach((key) => {
|
||||||
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
||||||
delete cleaned[key];
|
delete cleaned[key];
|
||||||
}
|
}
|
||||||
@ -255,7 +256,7 @@ export const useContactList = (
|
|||||||
pageSize,
|
pageSize,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
filter,
|
filter,
|
||||||
searchString
|
searchString=""
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
@ -264,12 +265,12 @@ export const useContactList = (
|
|||||||
projectId,
|
projectId,
|
||||||
pageSize,
|
pageSize,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
JSON.stringify(filter),
|
filter,
|
||||||
searchString,
|
searchString,
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const cleanedFilter = cleanFilter(filter);
|
const cleanedFilter = cleanFilter(filter);
|
||||||
const resp = await DirectoryRepository.GetContacts(
|
const resp = await DirectoryRepository.GetContact(
|
||||||
isActive,
|
isActive,
|
||||||
projectId,
|
projectId,
|
||||||
pageSize,
|
pageSize,
|
||||||
@ -277,11 +278,21 @@ export const useContactList = (
|
|||||||
cleanedFilter,
|
cleanedFilter,
|
||||||
searchString
|
searchString
|
||||||
);
|
);
|
||||||
return resp.data; // returning only the data
|
return resp.data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useContactFilter = ()=>{
|
||||||
|
return useQuery({
|
||||||
|
queryKey:["contactFilter"],
|
||||||
|
queryFn:async()=> {
|
||||||
|
const resp = await DirectoryRepository.GetContactFilter();
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------Mutation------------------------------------------------------------------
|
// ---------------------------Mutation------------------------------------------------------------------
|
||||||
|
|
||||||
|
76
src/pages/Directory/ContactFilterPanel.jsx
Normal file
76
src/pages/Directory/ContactFilterPanel.jsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React from "react";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
contactsFilter,
|
||||||
|
defaultContactFilter,
|
||||||
|
} from "../../components/Directory/DirectorySchema";
|
||||||
|
import { useContactFilter } from "../../hooks/useDirectory";
|
||||||
|
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
|
||||||
|
import SelectMultiple from "../../components/common/SelectMultiple";
|
||||||
|
|
||||||
|
const ContactFilterPanel = ({ onApply, clearFilter }) => {
|
||||||
|
const { data, isError, isLoading, error, isFetched, isFetching } =
|
||||||
|
useContactFilter();
|
||||||
|
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(contactsFilter),
|
||||||
|
defaultValues: defaultContactFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const closePanel = () => {
|
||||||
|
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { register, handleSubmit, reset, watch } = methods;
|
||||||
|
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
onApply(formData);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
reset(defaultContactFilter);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||||
|
if (isError && isFetched)
|
||||||
|
return <div>Something went wrong Here- {error.message} </div>;
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||||
|
<div className="row g-2">
|
||||||
|
<SelectMultiple
|
||||||
|
name="buckets"
|
||||||
|
label="Buckets :"
|
||||||
|
options={data.buckets}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="contactCategories"
|
||||||
|
label="Contact Category :"
|
||||||
|
options={data.contactCategories}
|
||||||
|
labelKey={(item) => item.name}
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary btn-xs"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary btn-xs">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactFilterPanel;
|
@ -1,14 +1,38 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useFab } from "../../Context/FabContext";
|
import { useFab } from "../../Context/FabContext";
|
||||||
import { useContactList } from "../../hooks/useDirectory";
|
import { useContactList } from "../../hooks/useDirectory";
|
||||||
|
import { useDirectoryContext } from "./DirectoryPage";
|
||||||
|
import CardViewContact from "../../components/Directory/CardViewContact";
|
||||||
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
|
import ContactFilterPanel from "./ContactFilterPanel";
|
||||||
|
import { defaultContactFilter } from "../../components/Directory/DirectorySchema";
|
||||||
|
import { useDebounce } from "../../utils/appUtils";
|
||||||
|
import Pagination from "../../components/common/Pagination";
|
||||||
|
import ListViewContact from "../../components/Directory/ListViewContact";
|
||||||
|
|
||||||
const ContactsPage = () => {
|
const ContactsPage = ({ searchText }) => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const {data,isError,isLoading,error} = useContactList()
|
const [filters, setFilter] = useState(defaultContactFilter);
|
||||||
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
const { showActive, gridView } = useDirectoryContext();
|
||||||
|
const { data, isError, isLoading, error } = useContactList(
|
||||||
|
showActive,
|
||||||
|
null,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
currentPage,
|
||||||
|
filters,
|
||||||
|
debouncedSearch
|
||||||
|
);
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
|
const clearFilter = () => {
|
||||||
|
setFilter(defaultContactFilter);
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowTrigger(true);
|
setShowTrigger(true);
|
||||||
setOffcanvasContent("Contacts Filters", <div>hlleo</div>);
|
setOffcanvasContent(
|
||||||
|
"Contacts Filters",
|
||||||
|
<ContactFilterPanel onApply={setFilter} clearFilter={clearFilter} />
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setShowTrigger(false);
|
setShowTrigger(false);
|
||||||
@ -16,9 +40,56 @@ const ContactsPage = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if(isError) return <div>{error.message}</div>
|
const paginate = (page) => {
|
||||||
if(isLoading) return <div>Loading...</div>
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||||
return <div className="container"></div>;
|
setCurrentPage(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isError) return <div>{error.message}</div>;
|
||||||
|
if (isLoading) return <div>Loading...</div>;
|
||||||
|
return (
|
||||||
|
<div className="row mt-5">
|
||||||
|
{gridView ? (
|
||||||
|
<>
|
||||||
|
{data?.data?.map((contact) => (
|
||||||
|
<div
|
||||||
|
key={contact.id}
|
||||||
|
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
|
||||||
|
>
|
||||||
|
<CardViewContact IsActive={showActive} contact={contact} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{data?.data?.length > 0 && (
|
||||||
|
<div className="col-12 d-flex justify-content-start mt-3">
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="col-12">
|
||||||
|
<ListViewContact
|
||||||
|
data={data.data}
|
||||||
|
Pagination={
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContactsPage;
|
export default ContactsPage;
|
||||||
|
@ -9,7 +9,7 @@ import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
|||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import UpdateContact from "../../components/Directory/UpdateContact";
|
import UpdateContact from "../../components/Directory/UpdateContact";
|
||||||
import CardViewDirectory from "../../components/Directory/CardViewDirectory";
|
import CardViewDirectory from "../../components/Directory/CardViewContact";
|
||||||
import { useContactCategory } from "../../hooks/masterHook/useMaster";
|
import { useContactCategory } from "../../hooks/masterHook/useMaster";
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
|
@ -13,6 +13,7 @@ import GlobalModel from "../../components/common/GlobalModel";
|
|||||||
import ManageBucket from "../../components/Directory/ManageBucket";
|
import ManageBucket from "../../components/Directory/ManageBucket";
|
||||||
import ManageBucket1 from "../../components/Directory/ManageBucket1";
|
import ManageBucket1 from "../../components/Directory/ManageBucket1";
|
||||||
import ManageContact from "../../components/Directory/ManageContact";
|
import ManageContact from "../../components/Directory/ManageContact";
|
||||||
|
import BucketList from "../../components/Directory/BucketList";
|
||||||
|
|
||||||
const NotesPage = lazy(() => import("./NotesPage"));
|
const NotesPage = lazy(() => import("./NotesPage"));
|
||||||
const ContactsPage = lazy(() => import("./ContactsPage"));
|
const ContactsPage = lazy(() => import("./ContactsPage"));
|
||||||
@ -31,10 +32,14 @@ export const useDirectoryContext = () => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
export default function DirectoryPage({ IsPage = true }) {
|
export default function DirectoryPage({ IsPage = true }) {
|
||||||
|
const [searchContact, setsearchContact] = useState("");
|
||||||
|
const [searchNote, setSearchNote] = useState("");
|
||||||
const [activeTab, setActiveTab] = useState("notes");
|
const [activeTab, setActiveTab] = useState("notes");
|
||||||
const { setActions } = useFab();
|
const { setActions } = useFab();
|
||||||
|
const [gridView, setGridView] = useState(false);
|
||||||
const [isOpenBucket, setOpenBucket] = useState(false);
|
const [isOpenBucket, setOpenBucket] = useState(false);
|
||||||
const [isManageContact,setManageContact] = useState(false)
|
const [isManageContact, setManageContact] = useState(false);
|
||||||
|
const [showActive, setShowActive] = useState(true);
|
||||||
|
|
||||||
const { data, isLoading, isError, error } = useBucketList();
|
const { data, isLoading, isError, error } = useBucketList();
|
||||||
|
|
||||||
@ -59,7 +64,7 @@ export default function DirectoryPage({ IsPage = true }) {
|
|||||||
label: "New Contact",
|
label: "New Contact",
|
||||||
icon: "bx bx-plus-circle",
|
icon: "bx bx-plus-circle",
|
||||||
color: "warning",
|
color: "warning",
|
||||||
onClick: ()=>setManageContact(true),
|
onClick: () => setManageContact(true),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,11 +73,17 @@ export default function DirectoryPage({ IsPage = true }) {
|
|||||||
return () => setActions([]);
|
return () => setActions([]);
|
||||||
}, [IsPage, data]);
|
}, [IsPage, data]);
|
||||||
|
|
||||||
|
const contextValues = {
|
||||||
|
showActive,
|
||||||
|
gridView,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
if (isLoading) return <div>Loading...</div>;
|
||||||
if (isError) return <div>{error.message}</div>;
|
if (isError) return <div>{error.message}</div>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DirectoryContext.Provider>
|
<DirectoryContext.Provider value={contextValues}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
@ -136,22 +147,52 @@ export default function DirectoryPage({ IsPage = true }) {
|
|||||||
type="search"
|
type="search"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Search notes..."
|
placeholder="Search notes..."
|
||||||
|
value={searchNote}
|
||||||
|
onChange={(e) => setSearchNote(e.target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "contacts" && (
|
{activeTab === "contacts" && (
|
||||||
<div className="d-flex gap-2 align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<input
|
<div className="d-flex gap-2 align-items-center">
|
||||||
type="text"
|
<input
|
||||||
className="form-control form-control-sm"
|
type="text"
|
||||||
placeholder="Search contacts..."
|
className="form-control form-control-sm"
|
||||||
/>
|
placeholder="Search contacts..."
|
||||||
<button className="btn btn-xs btn-outline-secondary">
|
value={searchContact}
|
||||||
<i className="bx bx-list-ul"></i>
|
onChange={(e) => setsearchContact(e.target.value)}
|
||||||
</button>
|
/>
|
||||||
<button className="btn btn-xs btn-outline-secondary">
|
<button
|
||||||
<i className="bx bx-grid-alt"></i>
|
className={`btn btn-xs ${
|
||||||
</button>
|
!gridView ? "btn-primary" : "btn-outline-secondary"
|
||||||
|
}`}
|
||||||
|
onClick={() => setGridView(false)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-list-ul"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`btn btn-xs ${
|
||||||
|
gridView ? "btn-primary" : "btn-outline-secondary"
|
||||||
|
}`}
|
||||||
|
onClick={() => setGridView(true)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-grid-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="form-check form-switch text-start m-0 ms-5">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
role="switch"
|
||||||
|
id="inactiveEmployeesCheckbox"
|
||||||
|
checked={showActive}
|
||||||
|
onChange={(e) => setShowActive(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label ms-0">
|
||||||
|
{showActive ? "In Active" : "Active"}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -170,7 +211,9 @@ export default function DirectoryPage({ IsPage = true }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{activeTab === "notes" && <NotesPage />}
|
{activeTab === "notes" && <NotesPage />}
|
||||||
{activeTab === "contacts" && <ContactsPage />}
|
{activeTab === "contacts" && (
|
||||||
|
<ContactsPage searchText={searchContact} />
|
||||||
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -185,9 +228,13 @@ export default function DirectoryPage({ IsPage = true }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isManageContact && (
|
{isManageContact && (
|
||||||
<GlobalModel size="lg" isOpen={isManageContact} closeModal={()=>setManageContact(false)}>
|
<GlobalModel
|
||||||
<ManageContact closeModal={()=>setManageContact(false)}/>
|
size="lg"
|
||||||
</GlobalModel>
|
isOpen={isManageContact}
|
||||||
|
closeModal={() => setManageContact(false)}
|
||||||
|
>
|
||||||
|
<ManageContact closeModal={() => setManageContact(false)} />
|
||||||
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DirectoryContext.Provider>
|
</DirectoryContext.Provider>
|
||||||
|
@ -3,7 +3,6 @@ import { useForm } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
// Components
|
|
||||||
import ExpenseList from "../../components/Expenses/ExpenseList";
|
import ExpenseList from "../../components/Expenses/ExpenseList";
|
||||||
import ViewExpense from "../../components/Expenses/ViewExpense";
|
import ViewExpense from "../../components/Expenses/ViewExpense";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
|
@ -17,14 +17,13 @@ export const DirectoryRepository = {
|
|||||||
GetContact: (isActive, projectId, pageSize, pageNumber, filter, searchString) => {
|
GetContact: (isActive, projectId, pageSize, pageNumber, filter, searchString) => {
|
||||||
const payloadJsonString = JSON.stringify(filter);
|
const payloadJsonString = JSON.stringify(filter);
|
||||||
return api.get(
|
return api.get(
|
||||||
`/api/directory/notes?active=${isActive}` +
|
`/api/Directory/list?active=${isActive}` +
|
||||||
(projectId ? `&projectId=${projectId}` : "") +
|
(projectId ? `&projectId=${projectId}` : "") +
|
||||||
`&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${encodeURIComponent(payloadJsonString)}&searchString=${encodeURIComponent(searchString)}`
|
`&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${encodeURIComponent(payloadJsonString)}&searchString=${encodeURIComponent(searchString)}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
GetContactFilter:()=>api.get("/api/directory/contact/filter"),
|
||||||
GetContactFilter:()=>api.get("directory/contact/filter"),
|
|
||||||
CreateContact: (data) => api.post("/api/directory", data),
|
CreateContact: (data) => api.post("/api/directory", data),
|
||||||
UpdateContact: (id, data) => api.put(`/api/directory/${id}`, data),
|
UpdateContact: (id, data) => api.put(`/api/directory/${id}`, data),
|
||||||
DeleteContact: (id, isActive) =>
|
DeleteContact: (id, isActive) =>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user