From 8ac9c64bf42b0ceafbb9e926f9129cea94f1f164 Mon Sep 17 00:00:00 2001 From: Vikas Nale Date: Fri, 20 Jun 2025 14:33:51 +0530 Subject: [PATCH 01/59] Add cards in project list and diectory list views --- public/assets/css/default.css | 5 + src/pages/Directory/Directory.jsx | 126 +++--- src/pages/Directory/DirectoryPageHeader.jsx | 8 +- src/pages/project/ProjectList.jsx | 405 ++++++++++---------- src/pages/project/ProjectListView.jsx | 4 +- 5 files changed, 289 insertions(+), 259 deletions(-) diff --git a/public/assets/css/default.css b/public/assets/css/default.css index 32b8644b..7da3c592 100644 --- a/public/assets/css/default.css +++ b/public/assets/css/default.css @@ -213,3 +213,8 @@ .ql-editor { max-height: 200px; } + +/* Remove Table Header Top Line */ +thead tr { + border-top: 1px solid white; +} diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx index ff8457e2..eb348e10 100644 --- a/src/pages/Directory/Directory.jsx +++ b/src/pages/Directory/Directory.jsx @@ -326,26 +326,29 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { )} -
- - +
+
+ +
+
+
{/* Messages when listView is false */} {!listView && (
@@ -360,48 +363,55 @@ const Directory = ({ IsPage = true, prefernceContacts }) => { )} {/* Table view (listView === true) */} + {listView ? ( - - {loading && ( - - - {" "} -

Loading...

{" "} - - - )} +
+
+ + {loading && ( + + + {" "} +

Loading...

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

No contact found

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

No contact found

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

No matching contact found

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

No matching contact found

+ + + )} - {!loading && - currentItems.map((contact) => ( - - ))} -
+ {!loading && + currentItems.map((contact) => ( + + ))} + +
+
) : (
{!loading && diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index c4a1e77a..7de25693 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -32,16 +32,16 @@ const DirectoryPageHeader = ({
setSearchText(e.target.value)} style={{ width: "200px" }} /> -
+
+ +
-
- - -
- -
- -
    - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
+
+ +
    + {[ + { + id: "b74da4c2-d07e-46f2-9919-e75e49b12731", + label: "Active", + }, + { + id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", + label: "On Hold", + }, + { + id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", + label: "Inactive", + }, + { + id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", + label: "Completed", + }, + ].map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
- -
-
- +
+ +
+
- {loading &&

Loading...

} {!loading && filteredProjects.length === 0 && !listView && (

No projects found.

)} -
- {listView ? ( -
- - - - - - - - - - - - - - - {currentItems.length === 0 ? ( + {listView ? ( +
+
+
+
- Project Name - Contact PersonSTART DATEDEADLINETaskProgress -
- -
    - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- Action -
+ - + + + + + + + + - ) : ( - currentItems.map((project) => ( - - )) - )} - -
- No projects found - + Project Name + Contact PersonSTART DATEDEADLINETaskProgress +
+ +
    + {[ + { + id: "b74da4c2-d07e-46f2-9919-e75e49b12731", + label: "Active", + }, + { + id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", + label: "On Hold", + }, + { + id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", + label: "Inactive", + }, + { + id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", + label: "Completed", + }, + ].map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
+
+ Action +
-
- ) : ( - currentItems.map((project) => ( - - )) - )} -
+ + + {currentItems.length === 0 ? ( + + + No projects found + + + ) : ( + currentItems.map((project) => ( + + )) + )} + + +
{" "} +
{" "} +
+ ) : ( +
+ {currentItems.map((project) => ( + + ))} +
+ )} {!loading && totalPages > 1 && ( )} - @@ -694,4 +695,4 @@ const EmployeeList = () => { ); }; -export default EmployeeList; \ No newline at end of file +export default EmployeeList; From a2a8e90350c58b155c7213ef02502149673388b1 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 18 Jun 2025 10:33:13 +0530 Subject: [PATCH 04/59] The current behavior is that when the "All Employees" filter is off, the system should display employees only for the globally selected project, not all employees. --- src/pages/employee/EmployeeList.jsx | 46 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 24ce0556..b9a6e8de 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -25,22 +25,32 @@ import ConfirmModal from "../../components/common/ConfirmModal"; import { useSelector } from "react-redux"; const EmployeeList = () => { - const selectedProjectId = useSelector((store) => store.localVariables.projectId); - const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || ""); - const { projects, loading: projectLoading } = useProjects(); + // const selectedProjectId = useSelector((store) => store.localVariables.projectId); + // const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || ""); + // const { projects, loading: projectLoading } = useProjects(); + + const selectedProjectId = useSelector( + (store) => store.localVariables.projectId + ); + const [showInactive, setShowInactive] = useState(false); const [showAllEmployees, setShowAllEmployees] = useState(false); const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES); + const { employees, loading, setLoading, error, recallEmployeeData } = - useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive); + // useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive); + useEmployeesAllOrByProjectId( + showAllEmployees ? null : selectedProjectId, + showInactive + ); const [employeeList, setEmployeeList] = useState([]); const [modelConfig, setModelConfig] = useState(); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(ITEMS_PER_PAGE); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); - const [searchText, setSearchText] = useState(""); - const [filteredData, setFilteredData] = useState([]); + const [searchText, setSearchText] = useState(""); + const [filteredData, setFilteredData] = useState([]); const [showModal, setShowModal] = useState(false); const [selectedEmployeeId, setSelecedEmployeeId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -98,7 +108,8 @@ const EmployeeList = () => { setEmployeeList([]); setFilteredData([]); } - }, [loading, employees, showAllEmployees, searchText, selectedProject]); + }, [loading, employees, showAllEmployees, searchText, selectedProjectId]); + const displayData = filteredData; const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; @@ -198,8 +209,13 @@ const EmployeeList = () => { const isChecked = e.target.checked; setShowInactive(false); setShowAllEmployees(isChecked); + if (isChecked) { + recallEmployeeData(false, null); + } else { + recallEmployeeData(false, selectedProjectId); + } - recallEmployeeData(false, isChecked ? null : selectedProject); + // recallEmployeeData(false, isChecked ? null : selectedProject); }; const handleEmployeeModel = (id) => { @@ -212,15 +228,15 @@ const EmployeeList = () => { setIsDeleteModalOpen(true); }; - const handleProjectSelection = (e) => { - const newProjectId = e.target.value; - setSelectedProject(newProjectId); - setShowAllEmployees(false); - }; + // useEffect(() => { + // setSelectedProject(selectedProjectId || ""); + // }, [selectedProjectId]); useEffect(() => { - setSelectedProject(selectedProjectId || ""); - }, [selectedProjectId]); + if (!showAllEmployees) { + recallEmployeeData(showInactive, selectedProjectId); + } + }, [selectedProjectId, showInactive, showAllEmployees, recallEmployeeData]); return ( From a5161756c30e1ca1b0b69ffdfd6cbf56324a35fe Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 19 Jun 2025 10:43:42 +0530 Subject: [PATCH 05/59] Project Address Text Overflow. --- public/assets/vendor/css/core.css | 1 + src/components/Activities/Attendance.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index e65d3662..d7f7cfcb 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -4978,6 +4978,7 @@ fieldset:disabled .btn { flex: 1 1 auto; padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); color: var(--bs-card-color); + word-break: break-word; } .card-title { diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 71b0b966..01ebc105 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -143,7 +143,7 @@ const Attendance = ({ - {!loading > 20 && ( + {!loading && filteredData.length > 20 && ( )} - @@ -709,4 +711,4 @@ const EmployeeList = () => { ); }; -export default EmployeeList; \ No newline at end of file +export default EmployeeList; From 41af98e3403ba9a4a68df0550aa8ae23eeb659ca Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 18 Jun 2025 10:33:13 +0530 Subject: [PATCH 11/59] The current behavior is that when the "All Employees" filter is off, the system should display employees only for the globally selected project, not all employees. --- src/pages/employee/EmployeeList.jsx | 46 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 1369a787..0324ef63 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -27,22 +27,32 @@ import eventBus from "../../services/eventBus"; import { newlineChars } from "pdf-lib"; const EmployeeList = () => { - const selectedProjectId = useSelector((store) => store.localVariables.projectId); - const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || ""); - const { projects, loading: projectLoading } = useProjects(); + // const selectedProjectId = useSelector((store) => store.localVariables.projectId); + // const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || ""); + // const { projects, loading: projectLoading } = useProjects(); + + const selectedProjectId = useSelector( + (store) => store.localVariables.projectId + ); + const [showInactive, setShowInactive] = useState(false); const [showAllEmployees, setShowAllEmployees] = useState(false); const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES); + const { employees, loading, setLoading, error, recallEmployeeData } = - useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive); + // useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive); + useEmployeesAllOrByProjectId( + showAllEmployees ? null : selectedProjectId, + showInactive + ); const [employeeList, setEmployeeList] = useState([]); const [modelConfig, setModelConfig] = useState(); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(ITEMS_PER_PAGE); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); - const [searchText, setSearchText] = useState(""); - const [filteredData, setFilteredData] = useState([]); + const [searchText, setSearchText] = useState(""); + const [filteredData, setFilteredData] = useState([]); const [showModal, setShowModal] = useState(false); const [selectedEmployeeId, setSelecedEmployeeId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -100,7 +110,8 @@ const EmployeeList = () => { setEmployeeList([]); setFilteredData([]); } - }, [loading, employees, showAllEmployees, searchText, selectedProject]); + }, [loading, employees, showAllEmployees, searchText, selectedProjectId]); + const displayData = filteredData; const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; @@ -200,8 +211,13 @@ const EmployeeList = () => { const isChecked = e.target.checked; setShowInactive(false); setShowAllEmployees(isChecked); + if (isChecked) { + recallEmployeeData(false, null); + } else { + recallEmployeeData(false, selectedProjectId); + } - recallEmployeeData(false, isChecked ? null : selectedProject); + // recallEmployeeData(false, isChecked ? null : selectedProject); }; const handleEmployeeModel = (id) => { @@ -214,15 +230,15 @@ const EmployeeList = () => { setIsDeleteModalOpen(true); }; - const handleProjectSelection = (e) => { - const newProjectId = e.target.value; - setSelectedProject(newProjectId); - setShowAllEmployees(false); - }; + // useEffect(() => { + // setSelectedProject(selectedProjectId || ""); + // }, [selectedProjectId]); useEffect(() => { - setSelectedProject(selectedProjectId || ""); - }, [selectedProjectId]); + if (!showAllEmployees) { + recallEmployeeData(showInactive, selectedProjectId); + } + }, [selectedProjectId, showInactive, showAllEmployees, recallEmployeeData]); const handler = useCallback( (msg) => { From 882e3dd4140e8d193b631d820a492cfbd295b4dd Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 19 Jun 2025 10:43:42 +0530 Subject: [PATCH 12/59] Project Address Text Overflow. --- public/assets/vendor/css/core.css | 1 + src/components/Activities/Attendance.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index e65d3662..d7f7cfcb 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -4978,6 +4978,7 @@ fieldset:disabled .btn { flex: 1 1 auto; padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); color: var(--bs-card-color); + word-break: break-word; } .card-title { diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 71b0b966..01ebc105 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -143,7 +143,7 @@ const Attendance = ({ - {!loading > 20 && ( + {!loading && filteredData.length > 20 && ( )} - ); }; 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 22/59] 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 23/59] 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 24/59] 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 25/59] 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 d1250739f9ee42bb3b5c9c0d2680bdb84b85af3f Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 27 Jun 2025 10:59:41 +0530 Subject: [PATCH 26/59] fixed vertical scrolling layout --- index.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 026b8b8f..f2e16b22 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,11 @@ - + From f932a4c5a466512d494dcaa10a230e2452951f1e Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 27 Jun 2025 11:45:29 +0530 Subject: [PATCH 27/59] added condition for directory, for hide project dropdown --- src/components/Dashboard/Dashboard.jsx | 2 +- src/components/Layout/Header.jsx | 13 +++++++------ src/components/common/Breadcrumb.jsx | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 9f3e1908..8ca64981 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -17,7 +17,7 @@ const Dashboard = () => { const { tasksCardData } = useDashboardTasksCardData(); return ( -
    +
    {/* Projects Card */}
    diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index b02b9e32..4d010d76 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -103,7 +103,8 @@ const Header = () => { }, [projectNames]); /** Check if current page id project details page */ - const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname); + const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname) + const isDirectoryPath = /^\/directory$/.test(location.pathname); const handler = useCallback( async (data) => { @@ -162,11 +163,11 @@ const Header = () => { > {projectNames?.length > 0 && (
    - {!isProjectPath && ( + {(!isProjectPath && !isDirectoryPath) && ( <>
    @@ -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 29/59] 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 30/59] 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 31/59] 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 */}
    From 3e0b4edf4a63396910212bf4c19e4d6523f56539 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 25 Jun 2025 12:28:58 +0530 Subject: [PATCH 32/59] 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 af6535b8..b4fd9c91 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 691a1699..5dae573f 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 52b796700dc117357eabcb48a160d9bdb59e3b7e Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 25 Jun 2025 17:58:11 +0530 Subject: [PATCH 33/59] 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 b4fd9c91..b285365e 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 5dae573f..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 35d5310ceea25950d1da611a61811e9931c913fd Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 10:09:24 +0530 Subject: [PATCH 34/59] 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 2df8187ade544b79df6c487dbde818ea4d1832cf Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 10:32:43 +0530 Subject: [PATCH 35/59] 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 a2761bdd4c9a93296f2d87c26c8d9f5cdb90a52d Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 11:30:07 +0530 Subject: [PATCH 36/59] 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 d64671163782992e8c65a21a8ca906a81b10cfc0 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Thu, 26 Jun 2025 17:11:54 +0530 Subject: [PATCH 37/59] 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 9396caea575fb551073b145b8b78488cdbed148d Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 15:47:36 +0530 Subject: [PATCH 38/59] 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 92ad8485f622c81b6343571188241f8511861524 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 16:04:15 +0530 Subject: [PATCH 39/59] 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 40/59] 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 58c17ffe37fddb3f14f2fa3ecc817062002e036a Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 27 Jun 2025 16:59:24 +0530 Subject: [PATCH 41/59] 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 */}
    From 67b694c6976243bdb0ac6cbca488530ca9500812 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sat, 28 Jun 2025 15:06:54 +0530 Subject: [PATCH 42/59] =?UTF-8?q?"Search=20field=20height=20mismatch=20in?= =?UTF-8?q?=20Directory=20=E2=80=93=20should=20match=20Card=20and=20List?= =?UTF-8?q?=20view=20height"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 015b5d1b..6f4270a8 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -284,7 +284,7 @@ const DirectoryPageHeader = ({ placeholder={viewType === "notes" ? "Search Notes..." : "Search Contact..."} value={searchText} onChange={(e) => setSearchText(e.target.value)} - style={{ width: "200px" }} + style={{ width: "200px",height: "30px" }} /> {/* Filter by funnel icon for Notes view */} From 48d1592b8ceb8a34e242146f4493043d938414f0 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Sat, 28 Jun 2025 16:44:26 +0530 Subject: [PATCH 43/59] =?UTF-8?q?"Directory=20filter=20popup=20closes=20on?= =?UTF-8?q?=20'Clear'=20button=20click=20=E2=80=93=20it=20should=20remain?= =?UTF-8?q?=20open"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Directory/DirectoryPageHeader.jsx | 36 +++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 6f4270a8..b23e8a34 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -289,14 +289,13 @@ const DirectoryPageHeader = ({ {/* Filter by funnel icon for Notes view */} {viewType === "notes" && ( -
    {/* Added minWidth here */} +
    - {/* Divider */} - {/*
    */} - {/* Organization */}

    Organization

    @@ -356,7 +352,8 @@ const DirectoryPageHeader = ({
    -
    @@ -457,13 +459,27 @@ const DirectoryPageHeader = ({
    - - + +
    )} -
    From da69ae6fec4734ab623732d7782c2be7f8241bc2 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 11:22:22 +0530 Subject: [PATCH 44/59] "In the Directory Notes, applying a filter changes the Clear button's state and color." --- src/pages/Directory/DirectoryPageHeader.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index b23e8a34..48e28f28 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -349,11 +349,11 @@ const DirectoryPageHeader = ({
    {/* Buttons */} -
    +
    +
    + {/* Previous Button */} + - {[...Array(totalPages)].map((_, i) => { - const page = i + 1; - return ( - - ); - })} + {/* Page Number Buttons */} + {[...Array(totalPages)].map((_, i) => { + const page = i + 1; + return ( + + ); + })} - -
    + {/* Next Button */} +
    )}
    From 111325968dd7d75b99863c6f0b07abe4e0bd05dd Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 11:32:36 +0530 Subject: [PATCH 46/59] "Adding margin-bottom to the pagination section in Directory Notes." --- 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 75a16b06..d29354d7 100644 --- a/src/components/Directory/NotesCardViewDirectory.jsx +++ b/src/components/Directory/NotesCardViewDirectory.jsx @@ -130,7 +130,7 @@ const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAp {/* Pagination */} {totalPages > 1 && (
    + style={{ marginBottom: '70px' }}> {/* Previous Button */} @@ -148,10 +148,8 @@ const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAp return ( diff --git a/src/components/Directory/NotesDirectory.jsx b/src/components/Directory/NotesDirectory.jsx index 5ddb37c8..61908853 100644 --- a/src/components/Directory/NotesDirectory.jsx +++ b/src/components/Directory/NotesDirectory.jsx @@ -135,7 +135,7 @@ const NotesDirectory = ({
    setAddNote(!addNote)} > {addNote ? "Hide Editor" : "Add a Note"} diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 48e28f28..3e2b3d7b 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -2,538 +2,558 @@ import React, { useEffect, useState } from "react"; import { exportToCSV, exportToExcel, printTable, exportToPDF } from "../../utils/tableExportUtils"; const DirectoryPageHeader = ({ - searchText, - setSearchText, - setIsActive, - viewType, - setViewType, - filteredBuckets, - tempSelectedBucketIds, - handleTempBucketChange, - filteredCategories, - tempSelectedCategoryIds, - handleTempCategoryChange, - clearFilter, - applyFilter, - loading, - IsActive, - contactsToExport, - notesToExport, - selectedNoteNames, - setSelectedNoteNames, - notesForFilter, - setFilterAppliedNotes + searchText, + setSearchText, + setIsActive, + viewType, + setViewType, + filteredBuckets, + tempSelectedBucketIds, + handleTempBucketChange, + filteredCategories, + tempSelectedCategoryIds, + handleTempCategoryChange, + clearFilter, + applyFilter, + loading, + IsActive, + contactsToExport, + notesToExport, + 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([]); - const [selectedOrgs, setSelectedOrgs] = useState([]); + 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([]); + const [selectedOrgs, setSelectedOrgs] = useState([]); - useEffect(() => { - setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); - }, [tempSelectedBucketIds, tempSelectedCategoryIds]); + useEffect(() => { + setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length); + }, [tempSelectedBucketIds, tempSelectedCategoryIds]); + // New state to track active filters for notes + const [notesFilterCount, setNotesFilterCount] = useState(0); - useEffect(() => { - 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([]); - } - }, [notesToExport, viewType]); + useEffect(() => { + // Calculate the number of active filters for notes + setNotesFilterCount(selectedCreators.length + selectedOrgs.length); + }, [selectedCreators, selectedOrgs]); - // Separate effect to clear selection only when switching away from notes - useEffect(() => { - if (viewType !== "notes" && selectedNoteNames.length > 0) { - setSelectedNoteNames([]); - } - }, [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 => { - if (prevSelectedNames.includes(name)) { - return prevSelectedNames.filter(n => n !== name); - } else { - return [...prevSelectedNames, name]; - } - }); - }; - - 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); + useEffect(() => { + 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([]); } - } - }); + }, [notesToExport, viewType]); - 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 = []; - - 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, ""); - const decoded = decodeHtmlEntities(stripped); - return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); - }; - - const cleanName = (name) => { - if (!name) return ""; - return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); - }; - - 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(', ') || '', - })); - } - - const today = new Date(); - const formattedDate = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`; - - const filename = - viewType === "notes" - ? `Directory_Notes_${formattedDate}` - : `Directory_Contacts_${formattedDate}`; - - switch (type) { - case "csv": - exportToCSV(dataToExport, filename); - break; - case "excel": - exportToExcel(dataToExport, filename); - break; - case "pdf": - exportToPDF(dataToExport, filename); - break; - case "print": - printTable(dataToExport, filename); - break; - default: - break; - } - }; - - 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); - } + // Separate effect to clear selection only when switching away from notes + useEffect(() => { + if (viewType !== "notes" && selectedNoteNames.length > 0) { + setSelectedNoteNames([]); } - }; - extractStrings(noteItem); - stringValues.push(plainNote, creator.toLowerCase()); + }, [viewType]); - const matchesSearch = stringValues.some((val) => val.includes(lowerSearch)); + useEffect(() => { + const creatorsSet = new Set(); + const orgsSet = new Set(); - return matchesCreator && matchesOrg && matchesSearch; - }); + notesForFilter.forEach((note) => { + const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim(); + if (creator) creatorsSet.add(creator); - setFilteredNotes(filtered); - setFilterAppliedNotes(filtered); - }; + const org = note.organizationName; + if (org) orgsSet.add(org); + }); - return ( - <> -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    + setAllCreators([...creatorsSet].sort()); + setAllOrganizations([...orgsSet].sort()); + setFilteredOrganizations([...orgsSet].sort()); + }, [notesForFilter]) -
    -
    - setSearchText(e.target.value)} - style={{ width: "200px",height: "30px" }} - /> + const handleToggleNoteName = (name) => { + setSelectedNoteNames(prevSelectedNames => { + if (prevSelectedNames.includes(name)) { + return prevSelectedNames.filter(n => n !== name); + } else { + return [...prevSelectedNames, name]; + } + }); + }; - {/* Filter by funnel icon for Notes view */} - {viewType === "notes" && ( -
    - + const updateFilteredOrganizations = () => { + if (selectedCreators.length === 0) { + setFilteredOrganizations(allOrganizations); + return; + } -
    -
    - {/* Created By */} -
    -

    Created By

    - {allCreators.map((name, idx) => ( -
    - handleToggleCreator(name)} - /> - -
    - ))} -
    + 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); + } + } + }); - {/* Organization */} -
    -

    Organization

    - {filteredOrganizations.map((org, idx) => ( -
    - handleToggleOrg(org)} - /> - -
    - ))} -
    + 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); + }; + + const handleExport = (type) => { + 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, ""); + const decoded = decodeHtmlEntities(stripped); + return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); + }; + + const cleanName = (name) => { + if (!name) return ""; + return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim(); + }; + + 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(', ') || '', + })); + } + + const today = new Date(); + const formattedDate = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`; + + const filename = + viewType === "notes" + ? `Directory_Notes_${formattedDate}` + : `Directory_Contacts_${formattedDate}`; + + switch (type) { + case "csv": + exportToCSV(dataToExport, filename); + break; + case "excel": + exportToExcel(dataToExport, filename); + break; + case "pdf": + exportToPDF(dataToExport, filename); + break; + case "print": + printTable(dataToExport, filename); + break; + default: + break; + } + }; + + 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 ( + <> +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    - - {/* Buttons */} -
    - - -
    -
    -
    - )} +
    +
    +
    - {(viewType === "card" || viewType === "list") && ( -
    + setSearchText(e.target.value)} + style={{ width: "200px", height: "30px" }} + /> - - -
    - )} +
    + {/* Scrollable Filter Content */} +
    +
    + {/* Created By */} +
    +
    +

    Created By

    +
    + {allCreators.map((name, idx) => ( +
    + handleToggleCreator(name)} + /> + +
    + ))} +
    - {/* Filter by funnel icon for Contacts view (retains numerical badge) */} - {viewType !== "notes" && ( -
    - + {/* Organization */} +
    +
    +

    Organization

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

      Filter by

      +
    -
    -
    -

    Buckets

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

    Categories

    -
    - {filteredCategories.map(({ id, name }) => ( -
    - handleTempCategoryChange(id)} - /> - + )} + + + {(viewType === "card" || viewType === "list") && ( +
    + + +
    - ))} + )} + + {/* Filter by funnel icon for Contacts view (retains numerical badge) */} + {viewType !== "notes" && ( +
    + + +
      +

      Filter by

      + +
      +
      +

      Buckets

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

      Categories

      +
      + {filteredCategories.map(({ id, name }) => ( +
      + handleTempCategoryChange(id)} + /> + +
      + ))} +
      +
      +
      + +
      + + +
      +
    +
    + )} +
    + +
    + {(viewType === "list" || viewType === "card") && ( + + )} + + -
    -
    -
    - -
    -
    - )} -
    - -
    - {(viewType === "list" || viewType === "card") && ( - - )} - - - -
    -
    - - ); + + ); }; export default DirectoryPageHeader; \ No newline at end of file From 0b54e0b5f190f098671ec757f1f9530c2c3b5a0e Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 16:13:04 +0530 Subject: [PATCH 49/59] Changes in Directory Notes Filter. --- src/pages/Directory/DirectoryPageHeader.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Directory/DirectoryPageHeader.jsx b/src/pages/Directory/DirectoryPageHeader.jsx index 3e2b3d7b..6a3ae418 100644 --- a/src/pages/Directory/DirectoryPageHeader.jsx +++ b/src/pages/Directory/DirectoryPageHeader.jsx @@ -306,7 +306,7 @@ const DirectoryPageHeader = ({ )} -
    +
    {/* Scrollable Filter Content */}
    {/* Created By */} -
    +

    Created By

    @@ -340,7 +340,7 @@ const DirectoryPageHeader = ({
    {/* Organization */} -
    +

    Organization

    From 562a4ca46d3deb33109943b56d8b360efeb96a35 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 16:27:12 +0530 Subject: [PATCH 50/59] "Changes in the employee popup size." --- src/components/Employee/ManageEmployee.jsx | 2 +- src/pages/Activities/TaskPlannng.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 7cb4b0f1..58194227 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -239,7 +239,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => {

    Loading Employee Data...

    )} */} -
    +

    {employee ? "Update Employee" : "Create Employee"}

    diff --git a/src/pages/Activities/TaskPlannng.jsx b/src/pages/Activities/TaskPlannng.jsx index d4fcd2a1..d532696e 100644 --- a/src/pages/Activities/TaskPlannng.jsx +++ b/src/pages/Activities/TaskPlannng.jsx @@ -112,7 +112,7 @@ const TaskPlannng = () => { {project_listLoader &&

    Loading..

    } From 84563812b709246f81dfb269c40da1bc269ca6e7 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 16:42:58 +0530 Subject: [PATCH 51/59] Adding cross button in Employee popup. --- src/components/Employee/ManageEmployee.jsx | 822 +++++++++++---------- 1 file changed, 414 insertions(+), 408 deletions(-) diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 58194227..e3c34b30 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -172,8 +172,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => { .then((response) => { cacheData("employeeProfileInfo", data); showToast( - `Employee details ${ - data.id == null ? "created" : "updated" + `Employee details ${data.id == null ? "created" : "updated" } successfully.`, "success" ); @@ -207,24 +206,24 @@ const ManageEmployee = ({ employeeId, onClosed }) => { reset( currentEmployee ? { - id: currentEmployee.id || null, - firstName: currentEmployee.firstName || "", - middleName: currentEmployee.middleName || "", - lastName: currentEmployee.lastName || "", - email: currentEmployee.email || "", - currentAddress: currentEmployee.currentAddress || "", - birthDate: formatDate(currentEmployee.birthDate) || "", - joiningDate: formatDate(currentEmployee.joiningDate) || "", - emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", - emergencyContactPerson: - currentEmployee.emergencyContactPerson || "", - aadharNumber: currentEmployee.aadharNumber || "", - gender: currentEmployee.gender || "", - panNumber: currentEmployee.panNumber || "", - permanentAddress: currentEmployee.permanentAddress || "", - phoneNumber: currentEmployee.phoneNumber || "", - jobRoleId: currentEmployee.jobRoleId?.toString() || "", - } + id: currentEmployee.id || null, + firstName: currentEmployee.firstName || "", + middleName: currentEmployee.middleName || "", + lastName: currentEmployee.lastName || "", + email: currentEmployee.email || "", + currentAddress: currentEmployee.currentAddress || "", + birthDate: formatDate(currentEmployee.birthDate) || "", + joiningDate: formatDate(currentEmployee.joiningDate) || "", + emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", + emergencyContactPerson: + currentEmployee.emergencyContactPerson || "", + aadharNumber: currentEmployee.aadharNumber || "", + gender: currentEmployee.gender || "", + panNumber: currentEmployee.panNumber || "", + permanentAddress: currentEmployee.permanentAddress || "", + phoneNumber: currentEmployee.phoneNumber || "", + jobRoleId: currentEmployee.jobRoleId?.toString() || "", + } : {} // Empty object resets the form ); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); @@ -233,410 +232,417 @@ const ManageEmployee = ({ employeeId, onClosed }) => { return ( <> - - {/*
    + + {/*
    {!currentEmployee && empLoading && employeeId && (

    Loading Employee Data...

    )} */} - + + {/* Cross button */} +

    {employee ? "Update Employee" : "Create Employee"}

    -
    -
    - {" "} -
    First Name
    - - {errors.firstName && ( -
    - {errors.firstName.message} -
    - )} -
    {" "} -
    -
    Middle Name
    +
    +
    + {" "} +
    First Name
    + + {errors.firstName && ( +
    + {errors.firstName.message} +
    + )} +
    {" "} +
    +
    Middle Name
    - - {errors.middleName && ( -
    - {errors.middleName.message} -
    - )} -
    -
    -
    Last Name
    - - {errors.lastName && ( -
    - {errors.lastName.message} -
    - )} -
    -
    -
    -
    -
    Email
    - - {errors.email && ( -
    - {errors.email.message} -
    - )} -
    -
    -
    Phone Number
    - - {errors.phoneNumber && ( -
    - {errors.phoneNumber.message} -
    - )} -
    -
    -
    -
    -
    -
    Gender
    + + {errors.middleName && ( +
    + {errors.middleName.message} +
    + )} +
    +
    +
    Last Name
    + + {errors.lastName && ( +
    + {errors.lastName.message} +
    + )} +
    +
    +
    +
    +
    Email
    + + {errors.email && ( +
    + {errors.email.message} +
    + )} +
    +
    +
    Phone Number
    + + {errors.phoneNumber && ( +
    + {errors.phoneNumber.message} +
    + )} +
    +
    +
    +
    +
    +
    Gender
    -
    - -
    - {errors.gender && ( -
    - {errors.gender.message} -
    - )} -
    -
    -
    Birth Date
    +
    + +
    + {errors.gender && ( +
    + {errors.gender.message} +
    + )} +
    +
    +
    Birth Date
    -
    - -
    - {errors.birthDate && ( -
    - {errors.birthDate.message} -
    - )} -
    -
    -
    Joining Date
    +
    + +
    + {errors.birthDate && ( +
    + {errors.birthDate.message} +
    + )} +
    +
    +
    Joining Date
    -
    - -
    - {errors.joiningDate && ( -
    - {errors.joiningDate.message} -
    - )} -
    -
    -
    -
    -
    Current Address
    +
    + +
    + {errors.joiningDate && ( +
    + {errors.joiningDate.message} +
    + )} +
    +
    +
    +
    +
    Current Address
    - -
    - - {" "} - {500 - currentAddressLength} characters left - -
    - {errors.currentAddress && ( -
    - {errors.currentAddress.message} -
    - )} -
    -
    -
    - Permanent Address -
    + +
    + + {" "} + {500 - currentAddressLength} characters left + +
    + {errors.currentAddress && ( +
    + {errors.currentAddress.message} +
    + )} +
    +
    +
    + Permanent Address +
    - -
    - - {500 - permanentAddressLength} characters left - -
    - {errors.permanentAddress && ( -
    - {errors.permanentAddress.message} -
    - )} -
    -
    -
    - {" "} -
    -
    Other Information
    -
    -
    -
    -
    -
    Role
    -
    - -
    - {errors.jobRoleId && ( -
    - {errors.jobRoleId.message} -
    - )} -
    -
    -
    - Emergency Contact Person -
    - - {errors.emergencyContactPerson && ( -
    - {errors.emergencyContactPerson.message} -
    - )} -
    -
    -
    - Emergency Phone Number -
    - - {errors.emergencyPhoneNumber && ( -
    - {errors.emergencyPhoneNumber.message} -
    - )} -
    -
    -
    -
    -
    AADHAR Number
    + +
    + + {500 - permanentAddressLength} characters left + +
    + {errors.permanentAddress && ( +
    + {errors.permanentAddress.message} +
    + )} +
    +
    +
    + {" "} +
    +
    Other Information
    +
    +
    +
    +
    +
    Role
    +
    + +
    + {errors.jobRoleId && ( +
    + {errors.jobRoleId.message} +
    + )} +
    +
    +
    + Emergency Contact Person +
    + + {errors.emergencyContactPerson && ( +
    + {errors.emergencyContactPerson.message} +
    + )} +
    +
    +
    + Emergency Phone Number +
    + + {errors.emergencyPhoneNumber && ( +
    + {errors.emergencyPhoneNumber.message} +
    + )} +
    +
    +
    +
    +
    AADHAR Number
    - - {errors.aadharNumber && ( -
    - {errors.aadharNumber.message} -
    - )} -
    -
    -
    PAN Number
    + + {errors.aadharNumber && ( +
    + {errors.aadharNumber.message} +
    + )} +
    +
    +
    PAN Number
    - - {errors.panNumber && ( -
    - {errors.panNumber.message} -
    - )} -
    -
    + + {errors.panNumber && ( +
    + {errors.panNumber.message} +
    + )} +
    +
    - {employeeId && ( -
    -
    - -
    -
    - )} + {employeeId && ( +
    +
    + +
    +
    + )} -
    -
    - +
    +
    + + + +
    +
    + - -
    -
    - - ); }; From 7615bb9c24a66582153e7df0559cd22b9e85c9ff Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 30 Jun 2025 17:03:52 +0530 Subject: [PATCH 52/59] Added line after each module --- src/components/master/CreateRole.jsx | 2 +- src/components/master/EditRole.jsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/master/CreateRole.jsx b/src/components/master/CreateRole.jsx index 03ccf297..1d7cb040 100644 --- a/src/components/master/CreateRole.jsx +++ b/src/components/master/CreateRole.jsx @@ -132,7 +132,7 @@ const CreateRole = ({ modalType, onClose }) => {
    -
    +
    {masterFeatures.map((feature, featureIndex) => ( diff --git a/src/components/master/EditRole.jsx b/src/components/master/EditRole.jsx index 51e483f8..d67400b1 100644 --- a/src/components/master/EditRole.jsx +++ b/src/components/master/EditRole.jsx @@ -177,10 +177,10 @@ const EditMaster = ({ master, onClose }) => { )}
    -
    +
    {masterFeatures.map((feature, featureIndex) => ( -
    +
    {feature.name} @@ -188,7 +188,7 @@ const EditMaster = ({ master, onClose }) => {
    -
    +
    {feature.featurePermissions.map((perm, permIndex) => { const refIndex = (featureIndex * 10) + permIndex; return ( @@ -240,6 +240,7 @@ const EditMaster = ({ master, onClose }) => {
    +
    ))} {errors.permissions && ( From be0577e6d825577cf68c724dc9006a0a317ff506 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 17:12:05 +0530 Subject: [PATCH 53/59] Changing link of ProjectNames to Projects. --- src/components/Layout/Header.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 4d010d76..53fbec05 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -258,7 +258,7 @@ const Header = () => {
    navigate(`/projectNames`)} + onClick={() => navigate(`/projects`)} className="text-heading text-truncate cursor-pointer" > From bb9c3511899e5d35ceaa91a55cc56c3431adc542 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Mon, 30 Jun 2025 18:29:03 +0530 Subject: [PATCH 54/59] Changes in Search text. --- src/pages/employee/EmployeeList.jsx | 14 ++++++++++---- src/services/signalRService.js | 6 ++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 002f09d2..8066a2e2 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -67,9 +67,17 @@ const EmployeeList = () => { if (!text) { return data; } - const lowercasedText = text.toLowerCase(); + const lowercasedText = text.toLowerCase().trim(); // Ensure search text is trimmed and lowercase + return data.filter((item) => { - const fullName = `${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase(); + // **IMPROVED FULL NAME CONSTRUCTION** + const firstName = item.firstName || ""; + const middleName = item.middleName || ""; + const lastName = item.lastName || ""; + + // Join parts, then trim any excess spaces if a middle name is missing + const fullName = `${firstName} ${middleName} ${lastName}`.toLowerCase().trim().replace(/\s+/g, ' '); + const email = item.email ? item.email.toLowerCase() : ""; const phoneNumber = item.phoneNumber ? item.phoneNumber.toLowerCase() : ""; const jobRole = item.jobRole ? item.jobRole.toLowerCase() : ""; @@ -212,8 +220,6 @@ const handleAllEmployeesToggle = (e) => { } }; - - const handleEmployeeModel = (id) => { setSelecedEmployeeId(id); setShowModal(true); diff --git a/src/services/signalRService.js b/src/services/signalRService.js index 49ad0cc8..63cabc9c 100644 --- a/src/services/signalRService.js +++ b/src/services/signalRService.js @@ -70,7 +70,7 @@ export function startSignalR(loggedUser) { cacheData("hasReceived", false); eventBus.emit("assign_project_one", data); } catch (e) { - console.error("Error in cacheData:", e); + // console.error("Error in cacheData:", e); } } eventBus.emit("assign_project_all", data); @@ -96,9 +96,7 @@ export function startSignalR(loggedUser) { }); connection - .start() - .then(() => console.log("SignalR connected")) - .catch((err) => console.error("SignalR error:", err)); + .start(); } export function stopSignalR() { From e09413b23ac84cae0b597f911436a9f297e13753 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Tue, 1 Jul 2025 12:19:25 +0530 Subject: [PATCH 55/59] 401 Unauthorized Error on Attendance Menu. --- src/components/Activities/AttendcesLogs.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 7bf6a989..32c46617 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -334,11 +334,11 @@ const AttendanceLog = ({ {!loading && !isRefreshing && data.length === 0 && ( No employee logs )} - {error && !loading && !isRefreshing && ( + {/* {error && !loading && !isRefreshing && ( {error} - )} + )} */}
    {!loading && !isRefreshing && processedData.length > 10 && (