diff --git a/src/components/Directory/CardViewDirectory.jsx b/src/components/Directory/CardViewContact.jsx similarity index 89% rename from src/components/Directory/CardViewDirectory.jsx rename to src/components/Directory/CardViewContact.jsx index b623ef0e..7df4ec23 100644 --- a/src/components/Directory/CardViewDirectory.jsx +++ b/src/components/Directory/CardViewContact.jsx @@ -4,7 +4,8 @@ import { getBucketNameById } from "./DirectoryUtils"; import { useBuckets } from "../../hooks/useDirectory"; import { getPhoneIcon } from "./DirectoryUtils"; import { useDir } from "../../Context/DireContext"; -const CardViewDirectory = ({ +import { useDirectoryContext } from "../../pages/Directory/DirectoryPage"; +const CardViewContact = ({ IsActive, contact, setSelectedContact, @@ -14,7 +15,7 @@ const CardViewDirectory = ({ IsDeleted, restore, }) => { - const { buckets } = useBuckets(); + const { data } = useDirectoryContext(); const { dirActions, setDirActions } = useDir(); return ( @@ -25,8 +26,9 @@ const CardViewDirectory = ({
{ if (IsActive) { setIsOpenModalNote(true); @@ -89,10 +91,11 @@ const CardViewDirectory = ({ )} {!IsActive && ( { setDirActions({ action: false, id: contact.id }); @@ -104,17 +107,15 @@ const CardViewDirectory = ({
    - {/*
  • - -
  • */}
  • - {contact.organization} + {contact?.organization}
{ if (IsActive) { setIsOpenModalNote(true); @@ -123,10 +124,10 @@ const CardViewDirectory = ({ }} >
- {contact.designation && ( + {contact?.designation && (
  • - +
  • {contact.designation} @@ -188,7 +189,7 @@ const CardViewDirectory = ({ > - {getBucketNameById(buckets, bucketId)} + {getBucketNameById(data, bucketId)}
  • @@ -199,4 +200,4 @@ const CardViewDirectory = ({ ); }; -export default CardViewDirectory; +export default CardViewContact; diff --git a/src/components/Directory/DirectorySchema.js b/src/components/Directory/DirectorySchema.js index 1b2b6275..6c4ebaf6 100644 --- a/src/components/Directory/DirectorySchema.js +++ b/src/components/Directory/DirectorySchema.js @@ -1,47 +1,49 @@ -import { z } from "zod"; -export const ContactSchema = z - .object({ - name: z.string().min(1, "Name is required"), - organization: z.string().min(1, "Organization name is required"), - contactCategoryId: z.string().nullable().optional(), - address: z.string().optional(), - description: z.string().min(1, { message: "Description is required" }), - designation: z.string().min(1, {message:"Designation is requried"}), - projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required") - contactEmails: z - .array( - z.object({ - label: z.string(), - emailAddress: z.string().email("Invalid email").or(z.literal("")), - }) - ) - .optional() - .default([]), +import { array, z } from "zod"; +export const ContactSchema = z.object({ + name: z.string().min(1, "Name is required"), + organization: z.string().min(1, "Organization name is required"), + contactCategoryId: z.string().nullable().optional(), + address: z.string().optional(), + description: z.string().min(1, { message: "Description is required" }), + designation: z.string().min(1, { message: "Designation is requried" }), + projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required") + contactEmails: z + .array( + z.object({ + label: z.string(), + emailAddress: z.string().email("Invalid email").or(z.literal("")), + }) + ) + .optional() + .default([]), - contactPhones: z - .array( - z.object({ - label: z.string(), - phoneNumber: z - .string() - .min(6, "Invalid Number") - .max(13, "Invalid Number") - .regex(/^[\d\s+()-]+$/, "Invalid phone number format").or(z.literal("")), - }) - ) - .optional() - .default([]), + contactPhones: z + .array( + z.object({ + label: z.string(), + phoneNumber: z + .string() + .min(6, "Invalid Number") + .max(13, "Invalid Number") + .regex(/^[\d\s+()-]+$/, "Invalid phone number format") + .or(z.literal("")), + }) + ) + .optional() + .default([]), - tags: z - .array( - z.object({ - id: z.string().nullable(), - name: z.string(), - }) - ) - .min(1, { message: "At least one tag is required" }), -bucketIds: z.array(z.string()).nonempty({ message: "At least one bucket is required" }) - }) + tags: z + .array( + z.object({ + id: z.string().nullable(), + name: z.string(), + }) + ) + .min(1, { message: "At least one tag is required" }), + bucketIds: z + .array(z.string()) + .nonempty({ message: "At least one bucket is required" }), +}); // .refine((data) => { // const hasValidEmail = (data.contactEmails || []).some( @@ -57,24 +59,33 @@ bucketIds: z.array(z.string()).nonempty({ message: "At least one bucket is requi // path: ["contactPhone"], // }); - // Buckets -export const bucketScheam = z.object( { - name: z.string().min( 1, {message: "Name is required"} ), - description:z.string().min(1,{message:"Description is required"}) -}) +export const bucketScheam = z.object({ + name: z.string().min(1, { message: "Name is required" }), + description: z.string().min(1, { message: "Description is required" }), +}); export const defaultContactValue = { - name: "", - organization: "", - contactCategoryId: null, - address: "", - description: "", - designation: "", - projectIds: [], - contactEmails: [], - contactPhones: [], - tags: [], - bucketIds: [], - } \ No newline at end of file + name: "", + organization: "", + contactCategoryId: null, + address: "", + description: "", + designation: "", + projectIds: [], + contactEmails: [], + contactPhones: [], + tags: [], + bucketIds: [], +}; + +export const contactsFilter = z.object({ + buckets: z.array(z.string()).optional(), + contactCategories: z.array(z.string()).optional(), +}); + +export const defaultContactFilter = { + buckets: [], + contactCategories: [], +}; diff --git a/src/components/Directory/DirectoryUtils.js b/src/components/Directory/DirectoryUtils.js index 7dc436be..9722a528 100644 --- a/src/components/Directory/DirectoryUtils.js +++ b/src/components/Directory/DirectoryUtils.js @@ -21,6 +21,6 @@ export const getPhoneIcon = (type) => { 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'; }; diff --git a/src/components/Directory/ListViewContact.jsx b/src/components/Directory/ListViewContact.jsx new file mode 100644 index 00000000..f36738e2 --- /dev/null +++ b/src/components/Directory/ListViewContact.jsx @@ -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) => ( +
    + + + {e?.name || "N/A"} + +
    + ), + align: "text-start", + }, + { + key: "email", + label: "Email", + getValue: (e) => ( + + {e?.contactEmails?.[0]?.emailAddress || "N/A"} + + ), + align: "text-start", + }, + { + key: "organization", + label: "Organization", + getValue: (e) => ( + + {e?.organization || "N/A"} + + ), + align: "text-start", + }, + { + key: "category", + label: "Category", + getValue: (e) => ( + + {e?.contactCategory?.name || "N/A"} + + ), + align: "text-start", + }, + ]; + + return ( +
    +
    +
    + + + + {contactList?.map((col) => ( + + ))} + + + + + {Array.isArray(data) && data.length > 0 ? ( + data.map((row, i) => ( + + {contactList.map((col) => ( + + ))} + + + )) + ) : ( + + + + )} + +
    + {col.label} + + Action +
    + {col.getValue(row)} + +
    + + + + + +
    +
    + No contacts found +
    + {Pagination && ( +
    {Pagination}
    + )} +
    +
    +
    + ); +}; + +export default ListViewContact; diff --git a/src/hooks/useDirectory.js b/src/hooks/useDirectory.js index e110e2b6..65fd9484 100644 --- a/src/hooks/useDirectory.js +++ b/src/hooks/useDirectory.js @@ -239,9 +239,10 @@ export const useDirectoryNotes = ( ), }); }; + const cleanFilter = (filter) => { const cleaned = { ...filter }; - ["bucketIds", "categories"].forEach((key) => { + ["bucketIds", "contactCategories"].forEach((key) => { if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) { delete cleaned[key]; } @@ -255,7 +256,7 @@ export const useContactList = ( pageSize, pageNumber, filter, - searchString + searchString="" ) => { return useQuery({ queryKey: [ @@ -264,12 +265,12 @@ export const useContactList = ( projectId, pageSize, pageNumber, - JSON.stringify(filter), + filter, searchString, ], queryFn: async () => { const cleanedFilter = cleanFilter(filter); - const resp = await DirectoryRepository.GetContacts( + const resp = await DirectoryRepository.GetContact( isActive, projectId, pageSize, @@ -277,11 +278,21 @@ export const useContactList = ( cleanedFilter, 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------------------------------------------------------------------ diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx new file mode 100644 index 00000000..642f98f6 --- /dev/null +++ b/src/pages/Directory/ContactFilterPanel.jsx @@ -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 ; + if (isError && isFetched) + return
    Something went wrong Here- {error.message}
    ; + return ( + +
    +
    + + item.name} + valueKey="id" + /> +
    +
    + + +
    +
    +
    + ); +}; + +export default ContactFilterPanel; diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index 0e2e43ee..dc9af864 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -1,14 +1,38 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useFab } from "../../Context/FabContext"; 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 {data,isError,isLoading,error} = useContactList() +const ContactsPage = ({ searchText }) => { + const [currentPage, setCurrentPage] = useState(1); + 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 clearFilter = () => { + setFilter(defaultContactFilter); + }; useEffect(() => { setShowTrigger(true); - setOffcanvasContent("Contacts Filters",
    hlleo
    ); + setOffcanvasContent( + "Contacts Filters", + + ); return () => { setShowTrigger(false); @@ -16,9 +40,56 @@ const ContactsPage = () => { }; }, []); - if(isError) return
    {error.message}
    - if(isLoading) return
    Loading...
    - return
    ; + const paginate = (page) => { + if (page >= 1 && page <= (data?.totalPages ?? 1)) { + setCurrentPage(page); + } + }; + + if (isError) return
    {error.message}
    ; + if (isLoading) return
    Loading...
    ; + return ( +
    + {gridView ? ( + <> + {data?.data?.map((contact) => ( +
    + +
    + ))} + + {data?.data?.length > 0 && ( +
    + +
    + )} + + ) : ( +
    + + } + /> +
    + )} +
    + + + +) }; export default ContactsPage; diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx index ed87d592..ed5a0ed0 100644 --- a/src/pages/Directory/Directory.jsx +++ b/src/pages/Directory/Directory.jsx @@ -9,7 +9,7 @@ import { DirectoryRepository } from "../../repositories/DirectoryRepository"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; import showToast from "../../services/toastService"; 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 usePagination from "../../hooks/usePagination"; import { ITEMS_PER_PAGE } from "../../utils/constants"; diff --git a/src/pages/Directory/DirectoryPage.jsx b/src/pages/Directory/DirectoryPage.jsx index ed555f05..444efd05 100644 --- a/src/pages/Directory/DirectoryPage.jsx +++ b/src/pages/Directory/DirectoryPage.jsx @@ -13,6 +13,7 @@ import GlobalModel from "../../components/common/GlobalModel"; import ManageBucket from "../../components/Directory/ManageBucket"; import ManageBucket1 from "../../components/Directory/ManageBucket1"; import ManageContact from "../../components/Directory/ManageContact"; +import BucketList from "../../components/Directory/BucketList"; const NotesPage = lazy(() => import("./NotesPage")); const ContactsPage = lazy(() => import("./ContactsPage")); @@ -31,10 +32,14 @@ export const useDirectoryContext = () => { return context; }; export default function DirectoryPage({ IsPage = true }) { + const [searchContact, setsearchContact] = useState(""); + const [searchNote, setSearchNote] = useState(""); const [activeTab, setActiveTab] = useState("notes"); const { setActions } = useFab(); + const [gridView, setGridView] = 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(); @@ -59,7 +64,7 @@ export default function DirectoryPage({ IsPage = true }) { label: "New Contact", icon: "bx bx-plus-circle", color: "warning", - onClick: ()=>setManageContact(true), + onClick: () => setManageContact(true), }); } @@ -68,11 +73,17 @@ export default function DirectoryPage({ IsPage = true }) { return () => setActions([]); }, [IsPage, data]); + const contextValues = { + showActive, + gridView, + data, + }; + if (isLoading) return
    Loading...
    ; if (isError) return
    {error.message}
    ; return ( <> - +
    setSearchNote(e.target.value)} /> )} {activeTab === "contacts" && ( -
    - - - +
    +
    + setsearchContact(e.target.value)} + /> + + + +
    +
    + setShowActive(e.target.checked)} + /> + +
    )}
    @@ -170,7 +211,9 @@ export default function DirectoryPage({ IsPage = true }) { } > {activeTab === "notes" && } - {activeTab === "contacts" && } + {activeTab === "contacts" && ( + + )}
    @@ -185,9 +228,13 @@ export default function DirectoryPage({ IsPage = true }) { )} {isManageContact && ( - setManageContact(false)}> - setManageContact(false)}/> - + setManageContact(false)} + > + setManageContact(false)} /> + )}
diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index c214a893..02af6286 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -3,7 +3,6 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useSelector } from "react-redux"; -// Components import ExpenseList from "../../components/Expenses/ExpenseList"; import ViewExpense from "../../components/Expenses/ViewExpense"; import Breadcrumb from "../../components/common/Breadcrumb"; diff --git a/src/repositories/DirectoryRepository.jsx b/src/repositories/DirectoryRepository.jsx index 40c58ce3..a8daa6b6 100644 --- a/src/repositories/DirectoryRepository.jsx +++ b/src/repositories/DirectoryRepository.jsx @@ -17,14 +17,13 @@ export const DirectoryRepository = { GetContact: (isActive, projectId, pageSize, pageNumber, filter, searchString) => { const payloadJsonString = JSON.stringify(filter); return api.get( - `/api/directory/notes?active=${isActive}` + + `/api/Directory/list?active=${isActive}` + (projectId ? `&projectId=${projectId}` : "") + `&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${encodeURIComponent(payloadJsonString)}&searchString=${encodeURIComponent(searchString)}` ); }, - - GetContactFilter:()=>api.get("directory/contact/filter"), + GetContactFilter:()=>api.get("/api/directory/contact/filter"), CreateContact: (data) => api.post("/api/directory", data), UpdateContact: (id, data) => api.put(`/api/directory/${id}`, data), DeleteContact: (id, isActive) =>