From 5e9e4a7bbf07a698ef8a66549059d04c08dddf3c Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 25 Jun 2025 12:28:58 +0530 Subject: [PATCH 01/10] In directory Creating a tab view and add notes view. --- .../Directory/NoteCardDirectoryEditable.jsx | 165 +++++++ .../Directory/NotesCardViewDirectory.jsx | 140 ++++++ src/pages/Directory/Directory.jsx | 102 ++--- src/pages/Directory/DirectoryPageHeader.jsx | 417 +++++++++--------- src/repositories/DirectoryRepository.jsx | 3 + 5 files changed, 575 insertions(+), 252 deletions(-) create mode 100644 src/components/Directory/NoteCardDirectoryEditable.jsx create mode 100644 src/components/Directory/NotesCardViewDirectory.jsx diff --git a/src/components/Directory/NoteCardDirectoryEditable.jsx b/src/components/Directory/NoteCardDirectoryEditable.jsx new file mode 100644 index 00000000..4f5bf410 --- /dev/null +++ b/src/components/Directory/NoteCardDirectoryEditable.jsx @@ -0,0 +1,165 @@ +import React, { useState } from "react"; +import ReactQuill from "react-quill"; +import moment from "moment"; +import Avatar from "../common/Avatar"; +import { DirectoryRepository } from "../../repositories/DirectoryRepository"; +import showToast from "../../services/toastService"; +import { cacheData, getCachedData } from "../../slices/apiDataManager"; +import "../common/TextEditor/Editor.css"; + +const NoteCardDirectoryEditable = ({ + noteItem, + contactId, + onNoteUpdate, + onNoteDelete, +}) => { + const [editing, setEditing] = useState(false); + const [editorValue, setEditorValue] = useState(noteItem.note); + const [isLoading, setIsLoading] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [isRestoring, setIsRestoring] = useState(false); + + const handleUpdateNote = async () => { + try { + setIsLoading(true); + const payload = { + id: noteItem.id, + note: editorValue, + contactId, + }; + const response = await DirectoryRepository.UpdateNote(noteItem.id, payload); + + // Optional cache update + const cachedContactProfile = getCachedData("Contact Profile"); + if (cachedContactProfile?.contactId === contactId) { + const updatedCache = { + ...cachedContactProfile, + data: { + ...cachedContactProfile.data, + notes: cachedContactProfile.data.notes.map((note) => + note.id === noteItem.id ? response.data : note + ), + }, + }; + cacheData("Contact Profile", updatedCache); + } + + // Notify parent + onNoteUpdate?.(response.data); + setEditing(false); + showToast("Note updated successfully", "success"); + } catch (error) { + showToast("Failed to update note", "error"); + } finally { + setIsLoading(false); + } + }; + + const handleDeleteOrRestore = async (shouldRestore) => { + try { + shouldRestore ? setIsRestoring(true) : setIsDeleting(true); + await DirectoryRepository.DeleteNote(noteItem.id, shouldRestore); + onNoteDelete?.(noteItem.id); + showToast(`Note ${shouldRestore ? "restored" : "deleted"} successfully`, "success"); + } catch (error) { + showToast("Failed to process note", "error"); + } finally { + setIsDeleting(false); + setIsRestoring(false); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+ + {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} + + + {moment + .utc(noteItem?.createdAt) + .add(5, "hours") + .add(30, "minutes") + .format("MMMM DD, YYYY [at] hh:mm A")} + +
+
+ {/* Action Icons */} +
+ {noteItem.isActive ? ( + <> + setEditing(true)} + title="Edit" + > + {!isDeleting ? ( + handleDeleteOrRestore(false)} + title="Delete" + > + ) : ( +
+ )} + + ) : isRestoring ? ( + + ) : ( + handleDeleteOrRestore(true)} + title="Restore" + > + )} +
+
+
+ {/* Editor or Content */} + {editing ? ( + <> + +
+ setEditing(false)} + > + Cancel + + + {isLoading ? "Saving..." : "Submit"} + +
+ + ) : ( +
+ )} +
+ ); +}; + +export default NoteCardDirectoryEditable; \ No newline at end of file diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx new file mode 100644 index 00000000..7f5e53f3 --- /dev/null +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -0,0 +1,140 @@ +import React, { useEffect, useState, useMemo } from "react"; +import { DirectoryRepository } from "../../repositories/DirectoryRepository"; +import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable"; + +const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { + const [allNotes, setAllNotes] = useState([]); + const [filteredNotes, setFilteredNotes] = useState([]); + const [loading, setLoading] = useState(true); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const pageSize = 20; + + const fetchNotes = async () => { + setLoading(true); + try { + const response = await DirectoryRepository.GetNotes(1000, 1); // fetch all for search + const fetchedNotes = response.data?.data || []; + setAllNotes(fetchedNotes); + } catch (error) { + console.error("Failed to fetch notes:", error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchNotes(); + }, []); + + // Search + update pagination + exportable data + useEffect(() => { + const lowerSearch = searchText?.toLowerCase() || ""; + const filtered = allNotes.filter((noteItem) => { + const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); + const fullName = `${noteItem?.contact?.firstName || ""} ${noteItem?.contact?.lastName || ""}`.toLowerCase(); + const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); + + return ( + plainNote.includes(lowerSearch) || + fullName.includes(lowerSearch) || + createdDate.includes(lowerSearch) + ); + }); + + setFilteredNotes(filtered); + setNotes(filtered); // for export + setCurrentPage(1); + setTotalPages(Math.ceil(filtered.length / pageSize)); + }, [searchText, allNotes]); + + const currentItems = useMemo(() => { + const startIndex = (currentPage - 1) * pageSize; + return filteredNotes.slice(startIndex, startIndex + pageSize); + }, [filteredNotes, currentPage]); + + const handlePageClick = (page) => { + if (page !== currentPage) { + setCurrentPage(page); + } + }; + + if (loading) { + return

Loading notes...

; + } + + if (!filteredNotes.length) { + return

No matching notes found

; + } + + return ( +
+
+ {currentItems.map((noteItem) => ( + { + setAllNotes((prevNotes) => + prevNotes.map((n) => (n.id === updatedNote.id ? updatedNote : n)) + ); + }} + onNoteDelete={() => { + fetchNotes(); // refresh after delete + }} + /> + ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+
+ + + {[...Array(totalPages)].map((_, i) => { + const page = i + 1; + return ( + + ); + })} + + +
+
+ )} +
+ ); +}; + +export default NotesCardViewDirectory; diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx index fe013661..e0bd2598 100644 --- a/src/pages/Directory/Directory.jsx +++ b/src/pages/Directory/Directory.jsx @@ -20,6 +20,7 @@ import DirectoryPageHeader from "./DirectoryPageHeader"; import ManageBucket from "../../components/Directory/ManageBucket"; import { useFab } from "../../Context/FabContext"; import { DireProvider, useDir } from "../../Context/DireContext"; +import NotesCardViewDirectory from "../../components/Directory/NotesCardViewDirectory"; const Directory = ({ IsPage = true, prefernceContacts }) => { const [projectPrefernce, setPerfence] = useState(null); @@ -31,11 +32,12 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { const [ContactList, setContactList] = useState([]); const [contactCategories, setContactCategories] = useState([]); const [searchText, setSearchText] = useState(""); - const [listView, setListView] = useState(false); + const [viewType, setViewType] = useState("notes"); const [selectedBucketIds, setSelectedBucketIds] = useState([]); const [deleteContact, setDeleteContact] = useState(null); const [IsDeleting, setDeleting] = useState(false); const [openBucketModal, setOpenBucketModal] = useState(false); + const [notes, setNotes] = useState([]); const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]); const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]); @@ -332,8 +334,8 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { searchText={searchText} setSearchText={setSearchText} setIsActive={setIsActive} - listView={listView} - setListView={setListView} + viewType={viewType} + setViewType={setViewType} filteredBuckets={filteredBuckets} tempSelectedBucketIds={tempSelectedBucketIds} handleTempBucketChange={handleTempBucketChange} @@ -346,56 +348,39 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { IsActive={IsActive} setOpenBucketModal={setOpenBucketModal} contactsToExport={contacts} + notesToExport={notes} />
- {/* Messages when listView is false */} - {!listView && ( -
- {loading &&

Loading...

} - {!loading && contacts?.length === 0 && ( + {/* Common empty/loading messages */} + {(viewType === "card" || viewType === "list" || viewType === "notes") && ( +
+ {/* {loading &&

Loading...

} */} + + {/* Notes View */} + {/* {!loading && viewType === "notes" && notes?.length > 0 && ( +

No matching note found

+ )} */} + + {/* Contact (card/list) View */} + {!loading && (viewType === "card" || viewType === "list") && contacts?.length === 0 && (

No contact found

)} - {!loading && contacts?.length > 0 && currentItems.length === 0 && ( -

No matching contact found

- )} + {!loading && + (viewType === "card" || viewType === "list") && + contacts?.length > 0 && + currentItems.length === 0 && ( +

No matching contact found

+ )}
)} - {/* Table view (listView === true) */} - - {listView ? ( + {/* List View */} + {viewType === "list" && (
- {loading && ( - - - {" "} -

Loading...

{" "} - - - )} - - {!loading && contacts?.length === 0 && ( - - -

No contact found

- - - )} - - {!loading && - currentItems.length === 0 && - contacts?.length > 0 && ( - - -

No matching contact found

- - - )} - {!loading && currentItems.map((contact) => ( {
- ) : ( + )} + + {/* Card View */} + {viewType === "card" && (
{!loading && currentItems.map((contact) => ( @@ -436,15 +424,25 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
)} + {/* Notes View */} + {viewType === "notes" && ( +
+ +
+ )} + {/* Pagination */} {!loading && + viewType !== "notes" && contacts?.length > 0 && currentItems.length > ITEMS_PER_PAGE && ( )}
+
); }; -export default Directory; +export default Directory; \ No newline at end of file diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index cec3147a..ae577e98 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -1,13 +1,12 @@ -import React, { useEffect, useState, useRef } from "react"; -import { ITEMS_PER_PAGE } from "../../utils/constants"; +import React, { useEffect, useState } from "react"; import { exportToCSV, exportToExcel, printTable, exportToPDF } from "../../utils/tableExportUtils"; const DirectoryPageHeader = ({ searchText, setSearchText, setIsActive, - listView, - setListView, + viewType, + setViewType, filteredBuckets, tempSelectedBucketIds, handleTempBucketChange, @@ -18,54 +17,88 @@ const DirectoryPageHeader = ({ applyFilter, loading, IsActive, - setOpenBucketModal, - contactsToExport, // This prop receives the paginated data (currentItems) + contactsToExport, + notesToExport, // ✅ Add this prop }) => { const [filtered, setFiltered] = useState(0); const handleExport = (type) => { - // Check if there's data to export - if (!contactsToExport || contactsToExport.length === 0) { - console.warn("No data to export. The current view is empty."); - // Optionally, you might want to show a user-friendly toast message here - // showToast("No data to export on the current page.", "info"); - return; + let dataToExport = []; + + if (viewType === "notes") { + if (!notesToExport || notesToExport.length === 0) { + console.warn("No notes to export."); + return; + } + + const decodeHtmlEntities = (html) => { + const textarea = document.createElement("textarea"); + textarea.innerHTML = html; + return textarea.value; + }; + + const cleanNoteText = (html) => { + if (!html) return ""; + const stripped = html.replace(/<[^>]+>/g, ""); // remove HTML tags + const decoded = decodeHtmlEntities(stripped); + return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); // fix non-breaking space + }; + + const cleanName = (name) => { + if (!name) return ""; + return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); // sanitize name + }; + + dataToExport = notesToExport.map(note => ({ + "Name": cleanName(`${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`), + "Notes": cleanNoteText(note.note), + "Created At": note.createdAt + ? new Date(note.createdAt).toLocaleString("en-IN") + : "", + "Updated At": note.updatedAt + ? new Date(note.updatedAt).toLocaleString("en-IN") + : "", + "Updated By": cleanName( + `${note.updatedBy?.firstName || ""} ${note.updatedBy?.lastName || ""}` + ), + })); + + } else { + if (!contactsToExport || contactsToExport.length === 0) { + console.warn("No contacts to export."); + return; + } + + dataToExport = contactsToExport.map(contact => ({ + Name: contact.name || '', + Organization: contact.organization || '', + Email: contact.contactEmails?.map(email => email.emailAddress).join(', ') || '', + Phone: contact.contactPhones?.map(phone => phone.phoneNumber).join(', ') || '', + Category: contact.contactCategory?.name || '', + Tags: contact.tags?.map(tag => tag.name).join(', ') || '', + })); } - // --- Core Change: Map contactsToExport to a simplified format --- - // const simplifiedContacts = contactsToExport.map(contact => ({ - // Name: contact.name || '', - // Organization: contact.organization || '', // Added Organization - // Email: contact.contactEmails && contact.contactEmails.length > 0 ? contact.contactEmails[0].emailAddress : '', - // Phone: contact.contactPhones && contact.contactPhones.length > 0 ? contact.contactPhones[0].phoneNumber : '', // Changed 'Contact' to 'Phone' for clarity - // Category: contact.contactCategory ? contact.contactCategory.name : '', // Changed 'Role' to 'Category' - // })); + const today = new Date(); + const formattedDate = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`; - const simplifiedContacts = contactsToExport.map(contact => ({ - Name: contact.name || '', - Organization: contact.organization || '', - Email: contact.contactEmails && contact.contactEmails.length > 0 - ? contact.contactEmails.map(email => email.emailAddress).join(', ') - : '', - Phone: contact.contactPhones && contact.contactPhones.length > 0 - ? contact.contactPhones.map(phone => phone.phoneNumber).join(', ') - : '', - Category: contact.contactCategory ? contact.contactCategory.name : '', - })); + const filename = + viewType === "notes" + ? `Directory_Notes_${formattedDate}` + : `Directory_Contacts_${formattedDate}`; - console.log("Kaerik", simplifiedContacts) switch (type) { case "csv": - exportToCSV(simplifiedContacts, "directory_contacts"); + exportToCSV(dataToExport, filename); break; case "excel": - exportToExcel(simplifiedContacts, "directory_contacts"); + exportToExcel(dataToExport, filename); break; case "pdf": - exportToPDF(simplifiedContacts, "directory_contacts"); + exportToPDF(dataToExport, filename); break; case "print": - printTable(simplifiedContacts, "directory_contacts"); + printTable(dataToExport, filename); break; default: break; @@ -73,15 +106,43 @@ const DirectoryPageHeader = ({ }; useEffect(() => { - setFiltered( - tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length - ); + setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); }, [tempSelectedBucketIds, tempSelectedCategoryIds]); return ( <> -
-
+ {/* Top Tabs */} +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
+
+ + {/* Controls: Search, Filter, View, Toggle, Export */} +
+
+ + {/* Search */} setSearchText(e.target.value)} style={{ width: "200px" }} /> -
- - -
-
-
- - -
-
- +
-
-
-
- + )} - {/* Export Dropdown */} -
- + +
+ +
+
+ +
+ {/* Show Inactive Toggle - only for list/card */} + {(viewType === "list" || viewType === "card") && ( + + )} + + {/* Export */} +
+
@@ -278,4 +296,7 @@ const DirectoryPageHeader = ({ ); }; -export default DirectoryPageHeader; \ No newline at end of file +export default DirectoryPageHeader; + + + diff --git a/src/repositories/DirectoryRepository.jsx b/src/repositories/DirectoryRepository.jsx index 502ea80b..c99601ed 100644 --- a/src/repositories/DirectoryRepository.jsx +++ b/src/repositories/DirectoryRepository.jsx @@ -32,4 +32,7 @@ export const DirectoryRepository = { UpdateNote: (id, data) => api.put(`/api/directory/note/${id}`, data), DeleteNote: (id, isActive) => api.delete(`/api/directory/note/${id}?active=${isActive}`), + + GetNotes: (pageSize, pageNumber) => + api.get(`/api/directory/notes?pageSize=${pageSize}&pageNumber=${pageNumber}`), }; From 8916e25940f54b3714ab0d752ccec49606a2ff82 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 25 Jun 2025 17:58:11 +0530 Subject: [PATCH 02/10] Adding filteration according to names. --- .../Directory/NoteCardDirectoryEditable.jsx | 231 +++++++++------- .../Directory/NotesCardViewDirectory.jsx | 40 +-- src/pages/Directory/Directory.jsx | 28 +- src/pages/Directory/DirectoryPageHeader.jsx | 247 ++++++++++++------ 4 files changed, 328 insertions(+), 218 deletions(-) diff --git a/src/components/Directory/NoteCardDirectoryEditable.jsx b/src/components/Directory/NoteCardDirectoryEditable.jsx index 4f5bf410..6a6f9be7 100644 --- a/src/components/Directory/NoteCardDirectoryEditable.jsx +++ b/src/components/Directory/NoteCardDirectoryEditable.jsx @@ -5,6 +5,7 @@ import Avatar from "../common/Avatar"; import { DirectoryRepository } from "../../repositories/DirectoryRepository"; import showToast from "../../services/toastService"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; +import ConfirmModal from "../common/ConfirmModal"; // Make sure path is correct import "../common/TextEditor/Editor.css"; const NoteCardDirectoryEditable = ({ @@ -18,6 +19,7 @@ const NoteCardDirectoryEditable = ({ const [isLoading, setIsLoading] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isRestoring, setIsRestoring] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const handleUpdateNote = async () => { try { @@ -29,7 +31,6 @@ const NoteCardDirectoryEditable = ({ }; const response = await DirectoryRepository.UpdateNote(noteItem.id, payload); - // Optional cache update const cachedContactProfile = getCachedData("Contact Profile"); if (cachedContactProfile?.contactId === contactId) { const updatedCache = { @@ -43,8 +44,7 @@ const NoteCardDirectoryEditable = ({ }; cacheData("Contact Profile", updatedCache); } - - // Notify parent + onNoteUpdate?.(response.data); setEditing(false); showToast("Note updated successfully", "success"); @@ -55,111 +55,156 @@ const NoteCardDirectoryEditable = ({ } }; - const handleDeleteOrRestore = async (shouldRestore) => { + const suspendEmployee = async () => { try { - shouldRestore ? setIsRestoring(true) : setIsDeleting(true); - await DirectoryRepository.DeleteNote(noteItem.id, shouldRestore); + setIsDeleting(true); + await DirectoryRepository.DeleteNote(noteItem.id, false); onNoteDelete?.(noteItem.id); - showToast(`Note ${shouldRestore ? "restored" : "deleted"} successfully`, "success"); + setIsDeleteModalOpen(false); + showToast("Note deleted successfully", "success"); } catch (error) { - showToast("Failed to process note", "error"); + showToast("Failed to delete note", "error"); } finally { setIsDeleting(false); + } + }; + + const handleRestore = async () => { + try { + setIsRestoring(true); + await DirectoryRepository.DeleteNote(noteItem.id, true); + onNoteDelete?.(noteItem.id); + showToast("Note restored successfully", "success"); + } catch (error) { + showToast("Failed to restore note", "error"); + } finally { setIsRestoring(false); } }; return ( -
- {/* Header */} -
-
- -
- - {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} - - - {moment - .utc(noteItem?.createdAt) - .add(5, "hours") - .add(30, "minutes") - .format("MMMM DD, YYYY [at] hh:mm A")} - + <> +
+ {/* Header */} +
+
+ +
+ + {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} + + + {moment + .utc(noteItem?.createdAt) + .add(5, "hours") + .add(30, "minutes") + .format("MMMM DD, YYYY [at] hh:mm A")} + +
-
- {/* Action Icons */} -
- {noteItem.isActive ? ( - <> - setEditing(true)} - title="Edit" - > - {!isDeleting ? ( + + {/* Action Icons */} +
+ {noteItem.isActive ? ( + <> handleDeleteOrRestore(false)} - title="Delete" + className="bx bxs-edit bx-sm me-2 text-primary cursor-pointer" + onClick={() => setEditing(true)} + title="Edit" > - ) : ( -
- )} - - ) : isRestoring ? ( - - ) : ( - handleDeleteOrRestore(true)} - title="Restore" - > - )} -
-
-
- {/* Editor or Content */} - {editing ? ( - <> - -
- setEditing(false)} - > - Cancel - - - {isLoading ? "Saving..." : "Submit"} - + {!isDeleting ? ( + setIsDeleteModalOpen(true)} + title="Delete" + > + ) : ( +
+ )} + + ) : isRestoring ? ( + + ) : ( + + )}
- - ) : ( -
+
+ +
+ + {/* Editor or Content */} + {editing ? ( + <> + +
+ setEditing(false)} + > + Cancel + + + {isLoading ? "Saving..." : "Submit"} + +
+ + ) : ( +
+ )} +
+ + {/* Delete Confirm Modal */} + {isDeleteModalOpen && ( +
+ setIsDeleteModalOpen(false)} + loading={isDeleting} + paramData={noteItem} + /> +
)} -
+ ); }; -export default NoteCardDirectoryEditable; \ No newline at end of file +export default NoteCardDirectoryEditable; diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx index 7f5e53f3..943ea3a2 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useMemo } from "react"; import { DirectoryRepository } from "../../repositories/DirectoryRepository"; import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable"; -const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { +const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames }) => { // ✅ Changed to array const [allNotes, setAllNotes] = useState([]); const [filteredNotes, setFilteredNotes] = useState([]); const [loading, setLoading] = useState(true); @@ -13,7 +13,7 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { const fetchNotes = async () => { setLoading(true); try { - const response = await DirectoryRepository.GetNotes(1000, 1); // fetch all for search + const response = await DirectoryRepository.GetNotes(1000, 1); const fetchedNotes = response.data?.data || []; setAllNotes(fetchedNotes); } catch (error) { @@ -27,26 +27,33 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { fetchNotes(); }, []); - // Search + update pagination + exportable data useEffect(() => { const lowerSearch = searchText?.toLowerCase() || ""; + const filtered = allNotes.filter((noteItem) => { const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); - const fullName = `${noteItem?.contact?.firstName || ""} ${noteItem?.contact?.lastName || ""}`.toLowerCase(); + const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); // Get full name + const lowerFullName = fullName.toLowerCase(); // Convert to lowercase for comparison const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); - return ( + const matchesSearch = plainNote.includes(lowerSearch) || - fullName.includes(lowerSearch) || - createdDate.includes(lowerSearch) - ); + lowerFullName.includes(lowerSearch) || + createdDate.includes(lowerSearch); + + // ✅ Filter logic for multiple selected names + const matchesNameFilter = + selectedNoteNames.length === 0 || // If no names are selected, all notes pass this filter + selectedNoteNames.includes(fullName); // Check if the note's creator is in the selected names array + + return matchesSearch && matchesNameFilter; }); setFilteredNotes(filtered); - setNotes(filtered); // for export + setNotes(filtered); setCurrentPage(1); setTotalPages(Math.ceil(filtered.length / pageSize)); - }, [searchText, allNotes]); + }, [searchText, allNotes, selectedNoteNames]); // ✅ Add selectedNoteNames to dependencies const currentItems = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; @@ -70,14 +77,8 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { return (
-
+
{currentItems.map((noteItem) => ( { ); }} onNoteDelete={() => { - fetchNotes(); // refresh after delete + fetchNotes(); }} /> ))}
- {/* Pagination */} {totalPages > 1 && (
@@ -137,4 +137,4 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText }) => { ); }; -export default NotesCardViewDirectory; +export default NotesCardViewDirectory; \ No newline at end of file diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx index e0bd2598..f9c4e8ab 100644 --- a/src/pages/Directory/Directory.jsx +++ b/src/pages/Directory/Directory.jsx @@ -39,6 +39,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { const [openBucketModal, setOpenBucketModal] = useState(false); const [notes, setNotes] = useState([]); + // ✅ Changed to an array for multiple selections + const [selectedNoteNames, setSelectedNoteNames] = useState([]); + const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]); const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]); const { setActions } = useFab(); @@ -73,8 +76,6 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { setIsOpenModal(false); } - // cacheData("Contacts", {data:updatedContacts,isActive:IsActive}); - // setContactList(updatedContacts); refetch(IsActive, prefernceContacts); refetchBucket(); } catch (error) { @@ -251,6 +252,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { return () => setActions([]); }, [IsPage, buckets]); + useEffect(() => { setPerfence(prefernceContacts); }, [prefernceContacts]); @@ -328,7 +330,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { )} -
+
{ setOpenBucketModal={setOpenBucketModal} contactsToExport={contacts} notesToExport={notes} + selectedNoteNames={selectedNoteNames} + setSelectedNoteNames={setSelectedNoteNames} />
-
- {/* Common empty/loading messages */} +
{(viewType === "card" || viewType === "list" || viewType === "notes") && (
- {/* {loading &&

Loading...

} */} - - {/* Notes View */} - {/* {!loading && viewType === "notes" && notes?.length > 0 && ( -

No matching note found

- )} */} - - {/* Contact (card/list) View */} {!loading && (viewType === "card" || viewType === "list") && contacts?.length === 0 && (

No contact found

)} @@ -376,7 +371,6 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
)} - {/* List View */} {viewType === "list" && (
@@ -400,9 +394,8 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
)} - {/* Card View */} {viewType === "card" && ( -
+
{!loading && currentItems.map((contact) => (
{
)} - {/* Notes View */} {viewType === "notes" && (
)} @@ -478,7 +471,6 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { )}
-
); }; diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index ae577e98..7f8e4e07 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -18,10 +18,43 @@ const DirectoryPageHeader = ({ loading, IsActive, contactsToExport, - notesToExport, // ✅ Add this prop + notesToExport, + selectedNoteNames, // ✅ Changed to array + setSelectedNoteNames, // ✅ Changed to array }) => { const [filtered, setFiltered] = useState(0); + const [noteCreators, setNoteCreators] = useState([]); + + useEffect(() => { + setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); + }, [tempSelectedBucketIds, tempSelectedCategoryIds]); + + useEffect(() => { + if (viewType === "notes" && notesToExport && notesToExport.length > 0) { + const uniqueNames = [...new Set(notesToExport.map(note => { + const firstName = note.createdBy?.firstName || ""; + const lastName = note.createdBy?.lastName || ""; + return `${firstName} ${lastName}`.trim(); + }).filter(name => name !== ""))]; + setNoteCreators(uniqueNames.sort()); // Sort names for consistent display + } else { + setNoteCreators([]); + setSelectedNoteNames([]); // Reset to empty array for multiple selection + } + }, [notesToExport, viewType, setSelectedNoteNames]); // Add setSelectedNoteNames to dependencies + + // ✅ New handler for multiple name selections + const handleToggleNoteName = (name) => { + setSelectedNoteNames(prevSelectedNames => { + if (prevSelectedNames.includes(name)) { + return prevSelectedNames.filter(n => n !== name); + } else { + return [...prevSelectedNames, name]; + } + }); + }; + const handleExport = (type) => { let dataToExport = []; @@ -39,14 +72,14 @@ const DirectoryPageHeader = ({ const cleanNoteText = (html) => { if (!html) return ""; - const stripped = html.replace(/<[^>]+>/g, ""); // remove HTML tags + const stripped = html.replace(/<[^>]+>/g, ""); const decoded = decodeHtmlEntities(stripped); - return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); // fix non-breaking space + return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); }; const cleanName = (name) => { if (!name) return ""; - return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); // sanitize name + return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); }; dataToExport = notesToExport.map(note => ({ @@ -105,13 +138,8 @@ const DirectoryPageHeader = ({ } }; - useEffect(() => { - setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); - }, [tempSelectedBucketIds, tempSelectedCategoryIds]); - return ( <> - {/* Top Tabs */}
    @@ -138,21 +166,68 @@ const DirectoryPageHeader = ({

- {/* Controls: Search, Filter, View, Toggle, Export */}
-
+
- {/* Search */} setSearchText(e.target.value)} style={{ width: "200px" }} /> - {/* View Toggle Buttons - only for list/card */} + {/* Name Filter Dropdown - now with checkboxes */} + {viewType === "notes" && noteCreators.length > 0 && ( +
+ + +
+ )} + + {(viewType === "card" || viewType === "list") && (
@@ -174,75 +249,76 @@ const DirectoryPageHeader = ({
)} - {/* Filter */} -
- + {viewType !== "notes" && ( +
+ -
    -

    Filter by

    +
      +

      Filter by

      - {/* Buckets */} -
      -

      Buckets

      -
      - {filteredBuckets.map(({ id, name }) => ( -
      - handleTempBucketChange(id)} - /> - +
      +
      +

      Buckets

      +
      + {filteredBuckets.map(({ id, name }) => ( +
      + handleTempBucketChange(id)} + /> + +
      + ))}
      - ))} -
      -
      - {/* Categories */} -
      -

      Categories

      -
      - {filteredCategories.map(({ id, name }) => ( -
      - handleTempCategoryChange(id)} - /> - +
      +
      +

      Categories

      +
      + {filteredCategories.map(({ id, name }) => ( +
      + handleTempCategoryChange(id)} + /> + +
      + ))}
      - ))} +
      -
      -
      - - -
      -
    -
+
+ + +
+ +
+ )} +
-
- {/* Show Inactive Toggle - only for list/card */} +
{(viewType === "list" || viewType === "card") && (
); }; -export default DirectoryPageHeader; - - - +export default DirectoryPageHeader; \ No newline at end of file From 7898d1ad81b09a3441d9816957d90bd1ac3f1253 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 10:09:24 +0530 Subject: [PATCH 03/10] Some Changes in Directory in Search functionality and Filter by Names. --- .../Directory/NotesCardViewDirectory.jsx | 63 ++++++++++++++++--- src/pages/Directory/DirectoryPageHeader.jsx | 31 ++++++--- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx index 943ea3a2..ab80522b 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -27,24 +27,66 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames fetchNotes(); }, []); + // useEffect(() => { + // const lowerSearch = searchText?.toLowerCase() || ""; + + // const filtered = allNotes.filter((noteItem) => { + // const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); + // const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); // Get full name + // const lowerFullName = fullName.toLowerCase(); // Convert to lowercase for comparison + // const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); + + // const matchesSearch = + // plainNote.includes(lowerSearch) || + // lowerFullName.includes(lowerSearch) || + // createdDate.includes(lowerSearch); + + // // ✅ Filter logic for multiple selected names + // const matchesNameFilter = + // selectedNoteNames.length === 0 || // If no names are selected, all notes pass this filter + // selectedNoteNames.includes(fullName); // Check if the note's creator is in the selected names array + + // return matchesSearch && matchesNameFilter; + // }); + + // setFilteredNotes(filtered); + // setNotes(filtered); + // setCurrentPage(1); + // setTotalPages(Math.ceil(filtered.length / pageSize)); + // }, [searchText, allNotes, selectedNoteNames]); // ✅ Add selectedNoteNames to dependencies + useEffect(() => { const lowerSearch = searchText?.toLowerCase() || ""; const filtered = allNotes.filter((noteItem) => { const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); - const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); // Get full name - const lowerFullName = fullName.toLowerCase(); // Convert to lowercase for comparison + const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); + const lowerFullName = fullName.toLowerCase(); const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); - const matchesSearch = - plainNote.includes(lowerSearch) || - lowerFullName.includes(lowerSearch) || - createdDate.includes(lowerSearch); + // ✅ Collect all string values in the note object to search through + const stringValues = []; + + const extractStrings = (obj) => { + for (const key in obj) { + if (!obj.hasOwnProperty(key)) continue; + const value = obj[key]; + if (typeof value === "string") { + stringValues.push(value.toLowerCase()); + } else if (typeof value === "object" && value !== null) { + extractStrings(value); // Recursively extract from nested objects + } + } + }; + + extractStrings(noteItem); + // Add manually stripped note, full name, date, etc. + stringValues.push(plainNote, lowerFullName, createdDate); + + const matchesSearch = stringValues.some((val) => val.includes(lowerSearch)); - // ✅ Filter logic for multiple selected names const matchesNameFilter = - selectedNoteNames.length === 0 || // If no names are selected, all notes pass this filter - selectedNoteNames.includes(fullName); // Check if the note's creator is in the selected names array + selectedNoteNames.length === 0 || selectedNoteNames.includes(fullName); return matchesSearch && matchesNameFilter; }); @@ -53,7 +95,8 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames setNotes(filtered); setCurrentPage(1); setTotalPages(Math.ceil(filtered.length / pageSize)); - }, [searchText, allNotes, selectedNoteNames]); // ✅ Add selectedNoteNames to dependencies + }, [searchText, allNotes, selectedNoteNames]); + const currentItems = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 7f8e4e07..157f58d9 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -30,21 +30,32 @@ const DirectoryPageHeader = ({ setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); }, [tempSelectedBucketIds, tempSelectedCategoryIds]); + useEffect(() => { - if (viewType === "notes" && notesToExport && notesToExport.length > 0) { - const uniqueNames = [...new Set(notesToExport.map(note => { - const firstName = note.createdBy?.firstName || ""; - const lastName = note.createdBy?.lastName || ""; - return `${firstName} ${lastName}`.trim(); - }).filter(name => name !== ""))]; - setNoteCreators(uniqueNames.sort()); // Sort names for consistent display + if (viewType === "notes") { + if (notesToExport && notesToExport.length > 0) { + const uniqueNames = [...new Set(notesToExport.map(note => { + const firstName = note.createdBy?.firstName || ""; + const lastName = note.createdBy?.lastName || ""; + return `${firstName} ${lastName}`.trim(); + }).filter(name => name !== ""))]; + setNoteCreators(uniqueNames.sort()); + } else { + setNoteCreators([]); + } } else { setNoteCreators([]); - setSelectedNoteNames([]); // Reset to empty array for multiple selection } - }, [notesToExport, viewType, setSelectedNoteNames]); // Add setSelectedNoteNames to dependencies + }, [notesToExport, viewType]); + + // Separate effect to clear selection only when switching away from notes + useEffect(() => { + if (viewType !== "notes" && selectedNoteNames.length > 0) { + setSelectedNoteNames([]); + } + }, [viewType]); + - // ✅ New handler for multiple name selections const handleToggleNoteName = (name) => { setSelectedNoteNames(prevSelectedNames => { if (prevSelectedNames.includes(name)) { From 0a6d5746ed197c7d5971a827aee9c347cd77b09f Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 10:32:43 +0530 Subject: [PATCH 04/10] Changes in Directory. --- src/components/Directory/NotesCardViewDirectory.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx index ab80522b..6e72dff3 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -13,7 +13,7 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames const fetchNotes = async () => { setLoading(true); try { - const response = await DirectoryRepository.GetNotes(1000, 1); + const response = await DirectoryRepository.GetNotes(20, 1); const fetchedNotes = response.data?.data || []; setAllNotes(fetchedNotes); } catch (error) { From d6d0fd9d14364c969a7ff9ce7fe876314b6e7179 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 11:30:07 +0530 Subject: [PATCH 05/10] Chnages in Directory for fetching data. --- .../Directory/NotesCardViewDirectory.jsx | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx index 6e72dff3..57130112 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -13,7 +13,7 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames const fetchNotes = async () => { setLoading(true); try { - const response = await DirectoryRepository.GetNotes(20, 1); + const response = await DirectoryRepository.GetNotes(1000, 1); const fetchedNotes = response.data?.data || []; setAllNotes(fetchedNotes); } catch (error) { @@ -27,34 +27,6 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames fetchNotes(); }, []); - // useEffect(() => { - // const lowerSearch = searchText?.toLowerCase() || ""; - - // const filtered = allNotes.filter((noteItem) => { - // const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); - // const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); // Get full name - // const lowerFullName = fullName.toLowerCase(); // Convert to lowercase for comparison - // const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); - - // const matchesSearch = - // plainNote.includes(lowerSearch) || - // lowerFullName.includes(lowerSearch) || - // createdDate.includes(lowerSearch); - - // // ✅ Filter logic for multiple selected names - // const matchesNameFilter = - // selectedNoteNames.length === 0 || // If no names are selected, all notes pass this filter - // selectedNoteNames.includes(fullName); // Check if the note's creator is in the selected names array - - // return matchesSearch && matchesNameFilter; - // }); - - // setFilteredNotes(filtered); - // setNotes(filtered); - // setCurrentPage(1); - // setTotalPages(Math.ceil(filtered.length / pageSize)); - // }, [searchText, allNotes, selectedNoteNames]); // ✅ Add selectedNoteNames to dependencies - useEffect(() => { const lowerSearch = searchText?.toLowerCase() || ""; From abc8bd8629f47b0bf6c74aa84207330289751e13 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 17:11:54 +0530 Subject: [PATCH 06/10] In Directory Inhance Filter in Notes add Clear button and space between checkbox and name. --- src/pages/Directory/DirectoryPageHeader.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 157f58d9..40c19f36 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -214,7 +214,7 @@ const DirectoryPageHeader = ({ setSelectedNoteNames([]); // Clear all }} > - All Names + Clear

  • @@ -228,7 +228,7 @@ const DirectoryPageHeader = ({ checked={selectedNoteNames.includes(name)} onChange={() => handleToggleNoteName(name)} /> -
    From deb71008997afb905c6a2023953bea3ba15083b6 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 15:47:36 +0530 Subject: [PATCH 07/10] In Directory changing filter logic and adding popup. --- .../Directory/NoteCardDirectoryEditable.jsx | 68 +++++- .../Directory/NotesCardViewDirectory.jsx | 90 ++++---- src/pages/Directory/Directory.jsx | 9 +- src/pages/Directory/DirectoryPageHeader.jsx | 204 ++++++++++++++---- 4 files changed, 279 insertions(+), 92 deletions(-) diff --git a/src/components/Directory/NoteCardDirectoryEditable.jsx b/src/components/Directory/NoteCardDirectoryEditable.jsx index 6a6f9be7..35f0b946 100644 --- a/src/components/Directory/NoteCardDirectoryEditable.jsx +++ b/src/components/Directory/NoteCardDirectoryEditable.jsx @@ -7,6 +7,8 @@ import showToast from "../../services/toastService"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; import ConfirmModal from "../common/ConfirmModal"; // Make sure path is correct import "../common/TextEditor/Editor.css"; +import ProfileContactDirectory from "./ProfileContactDirectory"; +import GlobalModel from "../common/GlobalModel"; const NoteCardDirectoryEditable = ({ noteItem, @@ -20,6 +22,8 @@ const NoteCardDirectoryEditable = ({ const [isDeleting, setIsDeleting] = useState(false); const [isRestoring, setIsRestoring] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [open_contact, setOpen_contact] = useState(null); + const [isOpenModalNote, setIsOpenModalNote] = useState(false); const handleUpdateNote = async () => { try { @@ -69,6 +73,13 @@ const NoteCardDirectoryEditable = ({ } }; + const contactProfile = (contactId) => { + DirectoryRepository.GetContactProfile(contactId).then((res) => { + setOpen_contact(res?.data); + setIsOpenModalNote(true); + }); + }; + const handleRestore = async () => { try { setIsRestoring(true); @@ -84,6 +95,25 @@ const NoteCardDirectoryEditable = ({ return ( <> + + {isOpenModalNote && ( + { + setOpen_contact(null); + setIsOpenModalNote(false); + }} + size="xl" + > + {open_contact && ( + setIsOpenModalNote(false)} + /> + )} + + )}
    + -
    - - {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} - - - {moment - .utc(noteItem?.createdAt) - .add(5, "hours") - .add(30, "minutes") - .format("MMMM DD, YYYY [at] hh:mm A")} - +
    +
    contactProfile(noteItem.contactId)}> + + {noteItem?.contactName} + ( {noteItem?.organizationName}) + + + +
    +
    + + +
    +
    + + by {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} +   + on {moment + .utc(noteItem?.createdAt) + .add(5, "hours") + .add(30, "minutes") + .format("MMMM DD, YYYY [at] hh:mm A")} + + + +
    diff --git a/src/components/Directory/NotesCardViewDirectory.jsx b/src/components/Directory/NotesCardViewDirectory.jsx index 57130112..be207ffd 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -2,20 +2,39 @@ import React, { useEffect, useState, useMemo } from "react"; import { DirectoryRepository } from "../../repositories/DirectoryRepository"; import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable"; -const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames }) => { // ✅ Changed to array +const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAppliedNotes }) => { const [allNotes, setAllNotes] = useState([]); const [filteredNotes, setFilteredNotes] = useState([]); const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); + const [selectedCreators, setSelectedCreators] = useState([]); + const [selectedOrgs, setSelectedOrgs] = useState([]); const pageSize = 20; + useEffect(() => { + fetchNotes(); + }, []); + const fetchNotes = async () => { setLoading(true); try { const response = await DirectoryRepository.GetNotes(1000, 1); const fetchedNotes = response.data?.data || []; setAllNotes(fetchedNotes); + setNotesForFilter(fetchedNotes) + + const creatorsSet = new Set(); + const orgsSet = new Set(); + + fetchedNotes.forEach((note) => { + const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim(); + if (creator) creatorsSet.add(creator); + + const org = note.organizationName; + if (org) orgsSet.add(org); + }); + } catch (error) { console.error("Failed to fetch notes:", error); } finally { @@ -23,52 +42,51 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames } }; - useEffect(() => { - fetchNotes(); - }, []); - useEffect(() => { + + const applyCombinedFilter = () => { const lowerSearch = searchText?.toLowerCase() || ""; const filtered = allNotes.filter((noteItem) => { + const creator = `${noteItem.createdBy?.firstName || ""} ${noteItem.createdBy?.lastName || ""}`.trim(); + const org = noteItem.organizationName; + + const matchesCreator = selectedCreators.length === 0 || selectedCreators.includes(creator); + const matchesOrg = selectedOrgs.length === 0 || selectedOrgs.includes(org); + const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); - const fullName = `${noteItem?.createdBy?.firstName || ""} ${noteItem?.createdBy?.lastName || ""}`.trim(); - const lowerFullName = fullName.toLowerCase(); - const createdDate = new Date(noteItem?.createdAt).toLocaleDateString("en-IN").toLowerCase(); - // ✅ Collect all string values in the note object to search through const stringValues = []; - const extractStrings = (obj) => { for (const key in obj) { - if (!obj.hasOwnProperty(key)) continue; const value = obj[key]; if (typeof value === "string") { stringValues.push(value.toLowerCase()); } else if (typeof value === "object" && value !== null) { - extractStrings(value); // Recursively extract from nested objects + extractStrings(value); } } }; - extractStrings(noteItem); - // Add manually stripped note, full name, date, etc. - stringValues.push(plainNote, lowerFullName, createdDate); + stringValues.push(plainNote, creator.toLowerCase()); const matchesSearch = stringValues.some((val) => val.includes(lowerSearch)); - const matchesNameFilter = - selectedNoteNames.length === 0 || selectedNoteNames.includes(fullName); - - return matchesSearch && matchesNameFilter; + return matchesCreator && matchesOrg && matchesSearch; }); setFilteredNotes(filtered); - setNotes(filtered); setCurrentPage(1); setTotalPages(Math.ceil(filtered.length / pageSize)); - }, [searchText, allNotes, selectedNoteNames]); + }; + useEffect(() => { + applyCombinedFilter(); + }, [searchText, allNotes]); + + useEffect(() => { + setFilteredNotes(filterAppliedNotes); + }, [filterAppliedNotes]) const currentItems = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; @@ -81,19 +99,19 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames } }; - if (loading) { - return

    Loading notes...

    ; - } + if (loading) return

    Loading notes...

    ; + + if (!filteredNotes.length) return

    No matching notes found

    ; - if (!filteredNotes.length) { - return

    No matching notes found

    ; - } return ( -
    -
    +
    + {/* Filter Dropdown */} +
    +
    + + {/* Notes List */} +
    {currentItems.map((noteItem) => ( (n.id === updatedNote.id ? updatedNote : n)) ); }} - onNoteDelete={() => { - fetchNotes(); - }} + onNoteDelete={() => fetchNotes()} /> ))}
    + {/* Pagination */} {totalPages > 1 && (
    @@ -128,7 +145,8 @@ const NotesCardViewDirectory = ({ notes, setNotes, searchText, selectedNoteNames return (
    @@ -421,9 +425,10 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
    )} diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 40c19f36..e0152532 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -19,12 +19,19 @@ const DirectoryPageHeader = ({ IsActive, contactsToExport, notesToExport, - selectedNoteNames, // ✅ Changed to array - setSelectedNoteNames, // ✅ Changed to array + selectedNoteNames, + setSelectedNoteNames, + notesForFilter, + setFilterAppliedNotes }) => { const [filtered, setFiltered] = useState(0); - + const [filteredNotes, setFilteredNotes] = useState([]); const [noteCreators, setNoteCreators] = useState([]); + const [allCreators, setAllCreators] = useState([]); + const [allOrganizations, setAllOrganizations] = useState([]); + const [filteredOrganizations, setFilteredOrganizations] = useState([]); + const [selectedCreators, setSelectedCreators] = useState([]); // Corrected to setSelectedCreators + const [selectedOrgs, setSelectedOrgs] = useState([]); useEffect(() => { setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); @@ -55,6 +62,23 @@ const DirectoryPageHeader = ({ } }, [viewType]); + useEffect(() => { + const creatorsSet = new Set(); + const orgsSet = new Set(); + + notesForFilter.forEach((note) => { + const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim(); + if (creator) creatorsSet.add(creator); + + const org = note.organizationName; + if (org) orgsSet.add(org); + }); + + setAllCreators([...creatorsSet].sort()); + setAllOrganizations([...orgsSet].sort()); + setFilteredOrganizations([...orgsSet].sort()); + }, [notesForFilter]) + const handleToggleNoteName = (name) => { setSelectedNoteNames(prevSelectedNames => { @@ -66,6 +90,45 @@ const DirectoryPageHeader = ({ }); }; + const updateFilteredOrganizations = () => { + if (selectedCreators.length === 0) { + setFilteredOrganizations(allOrganizations); + return; + } + + const filteredOrgsSet = new Set(); + notesForFilter.forEach((note) => { + const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim(); + if (selectedCreators.includes(creator)) { + if (note.organizationName) { + filteredOrgsSet.add(note.organizationName); + } + } + }); + + setFilteredOrganizations([...filteredOrgsSet].sort()); + }; + + const handleToggleCreator = (name) => { + const updated = selectedCreators.includes(name) + ? selectedCreators.filter((n) => n !== name) + : [...selectedCreators, name]; + + setSelectedCreators(updated); + }; + + const handleToggleOrg = (name) => { + const updated = selectedOrgs.includes(name) + ? selectedOrgs.filter((n) => n !== name) + : [...selectedOrgs, name]; + + setSelectedOrgs(updated); + }; + + useEffect(() => { + updateFilteredOrganizations(); + }, [selectedCreators]); + const handleExport = (type) => { let dataToExport = []; @@ -149,6 +212,41 @@ const DirectoryPageHeader = ({ } }; + const applyCombinedFilter = () => { + const lowerSearch = searchText?.toLowerCase() || ""; + + const filtered = notesForFilter.filter((noteItem) => { + const creator = `${noteItem.createdBy?.firstName || ""} ${noteItem.createdBy?.lastName || ""}`.trim(); + const org = noteItem.organizationName; + + const matchesCreator = selectedCreators.length === 0 || selectedCreators.includes(creator); + const matchesOrg = selectedOrgs.length === 0 || selectedOrgs.includes(org); + + const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase(); + + const stringValues = []; + const extractStrings = (obj) => { + for (const key in obj) { + const value = obj[key]; + if (typeof value === "string") { + stringValues.push(value.toLowerCase()); + } else if (typeof value === "object" && value !== null) { + extractStrings(value); + } + } + }; + extractStrings(noteItem); + stringValues.push(plainNote, creator.toLowerCase()); + + const matchesSearch = stringValues.some((val) => val.includes(lowerSearch)); + + return matchesCreator && matchesOrg && matchesSearch; + }); + + setFilteredNotes(filtered); + setFilterAppliedNotes(filtered); + }; + return ( <>
    @@ -189,52 +287,72 @@ const DirectoryPageHeader = ({ style={{ width: "200px" }} /> - {/* Name Filter Dropdown - now with checkboxes */} - {viewType === "notes" && noteCreators.length > 0 && ( -
    - + + +
    +
    )} From 77cd8357cd6a38bf531c8b3c8cebd22e9b9bc862 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 16:04:15 +0530 Subject: [PATCH 08/10] Clear button is working at filter. --- src/pages/Directory/DirectoryPageHeader.jsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index e0152532..1fa86f3d 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -30,7 +30,7 @@ const DirectoryPageHeader = ({ const [allCreators, setAllCreators] = useState([]); const [allOrganizations, setAllOrganizations] = useState([]); const [filteredOrganizations, setFilteredOrganizations] = useState([]); - const [selectedCreators, setSelectedCreators] = useState([]); // Corrected to setSelectedCreators + const [selectedCreators, setSelectedCreators] = useState([]); const [selectedOrgs, setSelectedOrgs] = useState([]); useEffect(() => { @@ -287,20 +287,16 @@ const DirectoryPageHeader = ({ style={{ width: "200px" }} /> - {/* Moved the "Filter by" dropdown to be triggered by the funnel icon */} + {/* Filter by funnel icon for Notes view */} {viewType === "notes" && ( -
    +
    {/* Added minWidth here */}
    @@ -343,7 +339,8 @@ const DirectoryPageHeader = ({ setSelectedCreators([]); setSelectedOrgs([]); setFilteredOrganizations(allOrganizations); - applyCombinedFilter(); + setFilterAppliedNotes(notesForFilter); + }} > Clear @@ -378,6 +375,7 @@ const DirectoryPageHeader = ({
    )} + {/* Filter by funnel icon for Contacts view (retains numerical badge) */} {viewType !== "notes" && (
    Date: Fri, 27 Jun 2025 16:57:57 +0530 Subject: [PATCH 09/10] upadating directory filter in design. --- .../Directory/NoteCardDirectoryEditable.jsx | 2 +- src/pages/Directory/DirectoryPageHeader.jsx | 83 ++++++++++++------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/components/Directory/NoteCardDirectoryEditable.jsx b/src/components/Directory/NoteCardDirectoryEditable.jsx index 35f0b946..e6991c2e 100644 --- a/src/components/Directory/NoteCardDirectoryEditable.jsx +++ b/src/components/Directory/NoteCardDirectoryEditable.jsx @@ -115,7 +115,7 @@ const NoteCardDirectoryEditable = ({ )}
    -
    -

    Created By

    - {allCreators.map((name, idx) => ( -
    - handleToggleCreator(name)} - /> - +
    +
    + {/* Created By */} +
    +

    Created By

    + {allCreators.map((name, idx) => ( +
    + handleToggleCreator(name)} + /> + +
    + ))}
    - ))} -

    Organization

    - {filteredOrganizations.map((org, idx) => ( -
    - handleToggleOrg(org)} - /> - + {/* Divider */} +
    + + {/* Organization */} +
    +

    Organization

    + {filteredOrganizations.map((org, idx) => ( +
    + handleToggleOrg(org)} + /> + +
    + ))}
    - ))} +
    -
    + {/* Buttons */} +
    +
    )} From b39aa2302da300b3be9f125f1eef07533dae0074 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 16:59:24 +0530 Subject: [PATCH 10/10] remove divider line. --- src/pages/Directory/DirectoryPageHeader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 6e4ad713..015b5d1b 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -330,7 +330,7 @@ const DirectoryPageHeader = ({
    {/* Divider */} -
    + {/*
    */} {/* Organization */}