diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 9d4de50a..48b63387 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -5,6 +5,7 @@ import { convertShortTime } from "../../utils/dateUtils"; import RenderAttendanceStatus from "./RenderAttendanceStatus"; import usePagination from "../../hooks/usePagination"; import { useNavigate } from "react-router-dom"; +import {ITEMS_PER_PAGE} from "../../utils/constants"; const Attendance = ({ attendance, getRole, handleModalData }) => { const [loading, setLoading] = useState(false); @@ -32,7 +33,7 @@ const Attendance = ({ attendance, getRole, handleModalData }) => { const { currentPage, totalPages, currentItems, paginate } = usePagination( filteredData, - 10 + ITEMS_PER_PAGE ); return ( <> diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 71bc74b3..b173581f 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -8,6 +8,7 @@ import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; import DateRangePicker from "../common/DateRangePicker"; import { getCachedData } from "../../slices/apiDataManager"; import usePagination from "../../hooks/usePagination"; +import {ITEMS_PER_PAGE} from "../../utils/constants"; const AttendanceLog = ({ handleModalData, projectId }) => { const [attendances, setAttendnaces] = useState([]); @@ -70,7 +71,7 @@ const AttendanceLog = ({ handleModalData, projectId }) => { const currentDate = new Date().toLocaleDateString("en-CA"); const { currentPage, totalPages, currentItems, paginate } = usePagination( sortedFinalList, - 10 + ITEMS_PER_PAGE ); useEffect(() => { diff --git a/src/components/Directory/CardViewDirectory.jsx b/src/components/Directory/CardViewDirectory.jsx new file mode 100644 index 00000000..50838bd3 --- /dev/null +++ b/src/components/Directory/CardViewDirectory.jsx @@ -0,0 +1,101 @@ +import React from "react"; +import Avatar from "../common/Avatar"; + +const CardViewDirectory = ({ contact,setSelectedContact , setIsOpenModal}) => { + return ( +
+
+
+ {" "} +

{contact.name}

+
+
+
+ + +
+
+
+ +
+ ); +}; + +export default CardViewDirectory; diff --git a/src/components/Directory/DirectorySchema.js b/src/components/Directory/DirectorySchema.js index 45e2904a..82d13dca 100644 --- a/src/components/Directory/DirectorySchema.js +++ b/src/components/Directory/DirectorySchema.js @@ -24,7 +24,7 @@ export const ContactSchema = z phoneNumber: z .string() .min(6, "Invalid Number") - .max(10, "Invalid Number") + .max(13, "Invalid Number") .regex(/^[\d\s+()-]+$/, "Invalid phone number format").or(z.literal("")), }) ) diff --git a/src/components/Directory/ListViewDirectory.jsx b/src/components/Directory/ListViewDirectory.jsx index 441aee45..538bdd3a 100644 --- a/src/components/Directory/ListViewDirectory.jsx +++ b/src/components/Directory/ListViewDirectory.jsx @@ -1,4 +1,5 @@ import React from 'react' +import Avatar from '../common/Avatar'; const getEmailIcon = (type) => { switch (type) { @@ -23,10 +24,22 @@ const getPhoneIcon = (type) => { } }; -const ListViewDirectory = ({ contact,setSelectedContact,setIsOpenModal }) => { +const ListViewDirectory = ({ contact,setSelectedContact,setIsOpenModal}) => { return ( - - {`${contact.name}`} + + +
+ + + {contact?.name || ""} +
+ + + {/* Emails */} @@ -52,12 +65,12 @@ const ListViewDirectory = ({ contact,setSelectedContact,setIsOpenModal }) => { - {contact.organization} + {contact.organization}
- {contact?.contactCategory?.name } + {contact?.contactCategory?.name }
diff --git a/src/components/Directory/ManageDirectory.jsx b/src/components/Directory/ManageDirectory.jsx index 03101aee..a460f1b5 100644 --- a/src/components/Directory/ManageDirectory.jsx +++ b/src/components/Directory/ManageDirectory.jsx @@ -144,7 +144,6 @@ useEffect(() => {
- {" "}
Create New Contact
@@ -211,7 +210,7 @@ useEffect(() => { onClick={handleAddEmail} style={{ width: "24px", height: "24px" }} > - + ) : ( )}
@@ -271,7 +270,7 @@ useEffect(() => { onClick={handleAddPhone} style={{ width: "24px", height: "24px" }} > - + ) : ( )} @@ -311,7 +310,7 @@ useEffect(() => { ) : ( <> - {contactCategory?.map((cate) => ( diff --git a/src/components/Directory/UpdateContact.jsx b/src/components/Directory/UpdateContact.jsx index cb2310c4..d210435c 100644 --- a/src/components/Directory/UpdateContact.jsx +++ b/src/components/Directory/UpdateContact.jsx @@ -111,19 +111,29 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { }; const onSubmit = async (data) => { - debugger; - const cleaned = { - ...data, - contactEmails: (data.contactEmails || []).filter( - (e) => e.emailAddress?.trim() !== "" - ), - contactPhones: (data.contactPhones || []).filter( - (p) => p.phoneNumber?.trim() !== "" - ), - }; +const cleaned = { + ...data, + contactEmails: (data.contactEmails || []) + .filter((e) => e.emailAddress?.trim() !== "") + .map((email, index) => { + const existingEmail = existingContact.contactEmails?.[index]; + return existingEmail + ? { ...email, id: existingEmail.id } + : email; + }), + contactPhones: (data.contactPhones || []) + .filter((p) => p.phoneNumber?.trim() !== "") + .map((phone, index) => { + const existingPhone = existingContact.contactPhones?.[index]; + return existingPhone + ? { ...phone, id: existingPhone.id } + : phone; + }), +}; + +setSubmitting(true); +await submitContact({ ...cleaned, id: existingContact.id }); - setSubmitting(true); - await submitContact({ ...cleaned, id: existingContact.id }); setSubmitting(false); }; @@ -161,7 +171,7 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { setIsInitialized(true) } - return()=> reset() + // return()=> reset() }, [ existingContact, buckets, projects ] ); @@ -169,7 +179,6 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => {
- {" "}
Update Contact
@@ -236,7 +245,7 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { onClick={handleAddEmail} style={{ width: "24px", height: "24px" }} > - + ) : ( )}
@@ -296,7 +305,7 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { onClick={handleAddPhone} style={{ width: "24px", height: "24px" }} > - + ) : ( )} @@ -336,7 +345,7 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { ) : ( <> - {contactCategory?.map((cate) => ( diff --git a/src/components/common/GlobalModel.jsx b/src/components/common/GlobalModel.jsx index bebdd5ca..afac3c42 100644 --- a/src/components/common/GlobalModel.jsx +++ b/src/components/common/GlobalModel.jsx @@ -15,7 +15,7 @@ const GlobalModel = ({ useEffect(() => { const modalElement = modalRef.current; const modalInstance = new window.bootstrap.Modal(modalElement, { - backdrop: false // Disable backdrop + backdrop: false, }); if (isOpen) { @@ -26,16 +26,27 @@ useEffect(() => { const handleHideModal = () => { closeModal(); + + // ✅ FIX: Remove any lingering body classes/styles + document.body.classList.remove('modal-open'); + document.body.style.overflow = ''; + document.body.style.paddingRight = ''; }; modalElement.addEventListener('hidden.bs.modal', handleHideModal); return () => { modalElement.removeEventListener('hidden.bs.modal', handleHideModal); + + // Also clean up just in case component unmounts + document.body.classList.remove('modal-open'); + document.body.style.overflow = ''; + document.body.style.paddingRight = ''; }; }, [isOpen, closeModal]); + // Dynamically set the modal size classes (modal-sm, modal-lg, modal-xl) const modalSizeClass = size ? `modal-${size}` : ''; // Default is empty if no size is specified diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx index e7396339..f231cd67 100644 --- a/src/pages/Directory/Directory.jsx +++ b/src/pages/Directory/Directory.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import Breadcrumb from "../../components/common/Breadcrumb"; import IconButton from "../../components/common/IconButton"; import GlobalModel from "../../components/common/GlobalModel"; @@ -9,13 +9,22 @@ import { DirectoryRepository } from "../../repositories/DirectoryRepository"; import { getCachedData } from "../../slices/apiDataManager"; import showToast from "../../services/toastService"; import UpdateContact from "../../components/Directory/UpdateContact"; +import CardViewDirectory from "../../components/Directory/CardViewDirectory"; +import { useContactCategory } from "../../hooks/masterHook/useMaster"; +import usePagination from "../../hooks/usePagination"; +import {ITEMS_PER_PAGE} from "../../utils/constants"; const Directory = () => { const [isOpenModal, setIsOpenModal] = useState(false); const [selectedContact, setSelectedContact] = useState(null); const [ContatList, setContactList] = useState([]); + const [contactCategories, setContactCategories] = useState([]); + const [ searchText, setSearchText ] = useState( "" ); + const [listView, setListView] = useState(true); const { contacts, loading } = useDirectory(); + const { contactCategory, loading: contactCategoryLoading } = + useContactCategory(); const submitContact = async (data) => { try { let response; @@ -23,13 +32,12 @@ const Directory = () => { const contacts_cache = getCachedData("contacts") || []; if (selectedContact) { - response = await DirectoryRepository.UpdateContact(data.id, data); updatedContacts = contacts_cache.map((contact) => contact.id === data.id ? response.data : contact ); showToast("Contact updated successfully", "success"); - setIsOpenModal( false ); + setIsOpenModal(false); setSelectedContact(null); } else { response = await DirectoryRepository.CreateContact(data); @@ -55,6 +63,40 @@ const Directory = () => { useEffect(() => { setContactList(contacts); }, [contacts]); + + const [selectedCategoryIds, setSelectedCategoryIds] = useState( + contactCategory.map((category) => category.id) + ); + + const usedCategoryIds = [ + ...new Set(contacts.map((c) => c.contactCategory?.id)), + ]; + const filteredCategories = contactCategory.filter((category) => + usedCategoryIds.includes(category.id) + ); + const handleCategoryChange = (id) => { + setSelectedCategoryIds((prev) => + prev.includes(id) ? prev.filter((cid) => cid !== id) : [...prev, id] + ); + }; + const filteredContacts = useMemo(() => { + return ContatList + .filter((c) => { + const matchesSearch = + c.name.toLowerCase().includes(searchText.toLowerCase()) || + c.organization.toLowerCase().includes(searchText.toLowerCase()); + const matchesCategory = + selectedCategoryIds.length === 0 || + selectedCategoryIds.includes(c.contactCategory?.id); + return matchesSearch && matchesCategory; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [ContatList, searchText, selectedCategoryIds]); + const { currentPage, totalPages, currentItems, paginate } = usePagination( + filteredContacts, + ITEMS_PER_PAGE + ); + return (
{ )} )} -
-
-
+
+
setSearchText(e.target.value)} /> +
+ + +
+
+ +
    +

    Apply Filter

    + {filteredCategories.map(({ id, name }) => ( +
  • +
    + handleCategoryChange(id)} + /> + +
    +
  • + ))} +
+
-
+ +
-
+ { + listView ? ( +
@@ -152,40 +254,7 @@ const Directory = () => { Organization - + )} {!loading && - ContatList.map((contact) => ( + currentItems.map((contact) => ( {
-
- - {/*
    - {[ - { id: 1, label: "Active" }, - { id: 2, label: "On Hold" }, - { id: 3, label: "Inactive" }, - { id: 4, label: "Completed" }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
- */} -
-
Category {
+ ) : ( +
+ {currentItems.map((contact, index) => ( +
+ +
+ ))} +
+ ) + } + + + + + + {!loading && ( + + )}
); diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index 6f92be63..8c28bd72 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { useSelector } from "react-redux"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { MANAGE_MASTER } from "../../utils/constants"; +import { ITEMS_PER_PAGE, MANAGE_MASTER } from "../../utils/constants"; import showToast from "../../services/toastService"; const MasterTable = ({ data, columns, loading, handleModalData }) => { @@ -21,7 +21,7 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { const safeData = Array.isArray(data) ? data : []; const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage] = useState(20); + const [itemsPerPage] = useState(ITEMS_PER_PAGE); const sortKeys = { "Application Role": "role", diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx index 0f1dca9b..23af0a5c 100644 --- a/src/pages/project/ProjectList.jsx +++ b/src/pages/project/ProjectList.jsx @@ -9,7 +9,7 @@ import showToast from "../../services/toastService"; import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useProfile } from "../../hooks/useProfile"; -import { MANAGE_PROJECT } from "../../utils/constants"; +import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; import ProjectListView from "./ProjectListView"; const ProjectList = () => { @@ -25,7 +25,7 @@ const ProjectList = () => { const dispatch = useDispatch(); const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage] = useState(10); + const [itemsPerPage] = useState(ITEMS_PER_PAGE); const [searchTerm, setSearchTerm] = useState(""); const [selectedStatuses, setSelectedStatuses] = useState([ "b74da4c2-d07e-46f2-9919-e75e49b12731", diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index cd1f0ced..dfbea3c3 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,5 +1,6 @@ export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes +export const ITEMS_PER_PAGE = 20; export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";