Compare commits
No commits in common. "45bd5a7f667008759d1422a3ab117cf764aa991a" and "4927680fe356ed85e2797ffa69024cd37ee36d31" have entirely different histories.
45bd5a7f66
...
4927680fe3
@ -11,21 +11,8 @@ import Pagination from "../../components/common/Pagination";
|
|||||||
import ListViewContact from "../../components/Directory/ListViewContact";
|
import ListViewContact from "../../components/Directory/ListViewContact";
|
||||||
import { CardViewContactSkeleton, ListViewContactSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
import { CardViewContactSkeleton, ListViewContactSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
||||||
|
|
||||||
// Utility function to format contacts for CSV export
|
|
||||||
const formatExportData = (contacts) => {
|
|
||||||
return contacts.map(contact => ({
|
|
||||||
Email: contact.contactEmails?.map(e => e.emailAddress).join(", ") || "",
|
|
||||||
Phone: contact.contactPhones?.map(p => p.phoneNumber).join(", ") || "",
|
|
||||||
Created: contact.createdAt ? new Date(contact.createdAt).toLocaleString() : "",
|
|
||||||
Location: contact.address || "",
|
|
||||||
Organization: contact.organization || "",
|
|
||||||
Category: contact.contactCategory?.name || "",
|
|
||||||
Tags: contact.tags?.map(t => t.name).join(", ") || "",
|
|
||||||
Buckets: contact.bucketIds?.join(", ") || "",
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContactsPage = ({ projectId, searchText, onExport }) => {
|
const ContactsPage = ({projectId ,searchText }) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [filters, setFilter] = useState(defaultContactFilter);
|
const [filters, setFilter] = useState(defaultContactFilter);
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
@ -39,9 +26,9 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
debouncedSearch
|
debouncedSearch
|
||||||
);
|
);
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
|
const clearFilter = () => {
|
||||||
const clearFilter = () => setFilter(defaultContactFilter);
|
setFilter(defaultContactFilter);
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowTrigger(true);
|
setShowTrigger(true);
|
||||||
setOffcanvasContent(
|
setOffcanvasContent(
|
||||||
@ -55,13 +42,6 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 🔹 Format contacts for export
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.data && onExport) {
|
|
||||||
onExport(formatExportData(data.data));
|
|
||||||
}
|
|
||||||
}, [data?.data]);
|
|
||||||
|
|
||||||
const paginate = (page) => {
|
const paginate = (page) => {
|
||||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
@ -69,44 +49,49 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isError) return <div>{error.message}</div>;
|
if (isError) return <div>{error.message}</div>;
|
||||||
if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
|
if (isLoading) return <ListViewContactSkeleton/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row mt-5">
|
<div className="row mt-5">
|
||||||
{gridView ? (
|
{gridView ? (
|
||||||
<>
|
<>
|
||||||
{data?.data?.map((contact) => (
|
{data?.data?.map((contact) => (
|
||||||
<div key={contact.id} className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4">
|
<div
|
||||||
<CardViewContact IsActive={showActive} contact={contact} />
|
key={contact.id}
|
||||||
</div>
|
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
|
||||||
))}
|
>
|
||||||
|
<CardViewContact IsActive={showActive} contact={contact} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
{data?.data?.length > 0 && (
|
{data?.data?.length > 0 && (
|
||||||
<div className="col-12 d-flex justify-content-start mt-3">
|
<div className="col-12 d-flex justify-content-start mt-3">
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
totalPages={data.totalPages}
|
totalPages={data.totalPages}
|
||||||
onPageChange={paginate}
|
onPageChange={paginate}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="col-12">
|
|
||||||
<ListViewContact
|
|
||||||
data={data.data}
|
|
||||||
Pagination={
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={data.totalPages}
|
|
||||||
onPageChange={paginate}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="col-12">
|
||||||
|
<ListViewContact
|
||||||
|
data={data.data}
|
||||||
|
Pagination={
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContactsPage;
|
export default ContactsPage;
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import BucketList from "../../components/Directory/BucketList";
|
|||||||
import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
||||||
import ContactProfile from "../../components/Directory/ContactProfile";
|
import ContactProfile from "../../components/Directory/ContactProfile";
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
import { exportToCSV } from "../../utils/exportUtils";
|
|
||||||
|
|
||||||
const NotesPage = lazy(() => import("./NotesPage"));
|
const NotesPage = lazy(() => import("./NotesPage"));
|
||||||
const ContactsPage = lazy(() => import("./ContactsPage"));
|
const ContactsPage = lazy(() => import("./ContactsPage"));
|
||||||
@ -33,7 +32,7 @@ export const useDirectoryContext = () => {
|
|||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
export default function DirectoryPage({ IsPage = true, projectId=null }) {
|
||||||
const [searchContact, setsearchContact] = useState("");
|
const [searchContact, setsearchContact] = useState("");
|
||||||
const [searchNote, setSearchNote] = useState("");
|
const [searchNote, setSearchNote] = useState("");
|
||||||
const [activeTab, setActiveTab] = useState("notes");
|
const [activeTab, setActiveTab] = useState("notes");
|
||||||
@ -50,18 +49,6 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
Open: false,
|
Open: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [notesData, setNotesData] = useState([]);
|
|
||||||
const [ContactData, setContactData] = useState([]);
|
|
||||||
|
|
||||||
const handleExport = (type) => {
|
|
||||||
if (activeTab === "notes" && type === "csv") {
|
|
||||||
exportToCSV(notesData, "notes.csv");
|
|
||||||
}
|
|
||||||
if (activeTab === "contacts" && type === "csv") {
|
|
||||||
exportToCSV(ContactData, "contact.csv");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, isLoading, isError, error } = useBucketList();
|
const { data, isLoading, isError, error } = useBucketList();
|
||||||
|
|
||||||
const handleTabClick = (tab, e) => {
|
const handleTabClick = (tab, e) => {
|
||||||
@ -107,8 +94,8 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DirectoryContext.Provider value={contextValues}>
|
<DirectoryContext.Provider value={contextValues}>
|
||||||
<div className={`${IsPage ? "container-fluid" : ""}`}>
|
<div className={`${IsPage ? "container-fluid":""}`}>
|
||||||
{IsPage && (<Breadcrumb
|
{IsPage && ( <Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Directory", link: null },
|
{ label: "Directory", link: null },
|
||||||
@ -121,7 +108,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "notes" ? "active" : ""
|
activeTab === "notes" ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={(e) => handleTabClick("notes", e)}
|
onClick={(e) => handleTabClick("notes", e)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-note bx-sm me-1_5"></i>
|
<i className="bx bx-note bx-sm me-1_5"></i>
|
||||||
@ -132,7 +119,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "contacts" ? "active" : ""
|
activeTab === "contacts" ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={(e) => handleTabClick("contacts", e)}
|
onClick={(e) => handleTabClick("contacts", e)}
|
||||||
>
|
>
|
||||||
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
||||||
@ -152,11 +139,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
</button>
|
</button>
|
||||||
<ul className="dropdown-menu">
|
<ul className="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
{/* <a className="dropdown-item" href="#"> */}
|
<a className="dropdown-item" href="#">
|
||||||
<a
|
|
||||||
className="dropdown-item cursor-pointer"
|
|
||||||
onClick={() => handleExport("csv")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-file me-1"></i> CSV
|
<i className="bx bx-file me-1"></i> CSV
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -190,7 +173,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<button
|
<button
|
||||||
className={`btn btn-xs ${
|
className={`btn btn-xs ${
|
||||||
!gridView ? "btn-primary" : "btn-outline-secondary"
|
!gridView ? "btn-primary" : "btn-outline-secondary"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setGridView(false)}
|
onClick={() => setGridView(false)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-list-ul"></i>
|
<i className="bx bx-list-ul"></i>
|
||||||
@ -199,7 +182,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<button
|
<button
|
||||||
className={`btn btn-xs ${
|
className={`btn btn-xs ${
|
||||||
gridView ? "btn-primary" : "btn-outline-secondary"
|
gridView ? "btn-primary" : "btn-outline-secondary"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setGridView(true)}
|
onClick={() => setGridView(true)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-grid-alt"></i>
|
<i className="bx bx-grid-alt"></i>
|
||||||
@ -231,10 +214,10 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<div>
|
<div>
|
||||||
<Suspense fallback={<MainDirectoryPageSkeleton />}>
|
<Suspense fallback={<MainDirectoryPageSkeleton />}>
|
||||||
{activeTab === "notes" && (
|
{activeTab === "notes" && (
|
||||||
<NotesPage projectId={projectId} searchText={searchNote} onExport={setNotesData} />
|
<NotesPage projectId={projectId} searchText={searchNote} />
|
||||||
)}
|
)}
|
||||||
{activeTab === "contacts" && (
|
{activeTab === "contacts" && (
|
||||||
<ContactsPage projectId={projectId} searchText={searchContact} onExport={setContactData} />
|
<ContactsPage projectId={projectId} searchText={searchContact} />
|
||||||
)}
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// NotesPage.jsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useFab } from "../../Context/FabContext";
|
import { useFab } from "../../Context/FabContext";
|
||||||
import { useNotes } from "../../hooks/useDirectory";
|
import { useNoteFilter, useNotes } from "../../hooks/useDirectory";
|
||||||
import NoteFilterPanel from "./NoteFilterPanel";
|
import NoteFilterPanel from "./NoteFilterPanel";
|
||||||
import { defaultNotesFilter } from "../../components/Directory/DirectorySchema";
|
import { defaultNotesFilter } from "../../components/Directory/DirectorySchema";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
@ -10,11 +9,10 @@ import NoteCardDirectoryEditable from "../../components/Directory/NoteCardDirect
|
|||||||
import Pagination from "../../components/common/Pagination";
|
import Pagination from "../../components/common/Pagination";
|
||||||
import { NoteCardSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
import { NoteCardSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
|
||||||
|
|
||||||
const NotesPage = ({ projectId, searchText, onExport }) => {
|
const NotesPage = ({ projectId, searchText }) => {
|
||||||
const [filters, setFilter] = useState(defaultNotesFilter);
|
const [filters, setFilter] = useState(defaultNotesFilter);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
|
||||||
const { data, isLoading, isError, error } = useNotes(
|
const { data, isLoading, isError, error } = useNotes(
|
||||||
projectId,
|
projectId,
|
||||||
ITEMS_PER_PAGE,
|
ITEMS_PER_PAGE,
|
||||||
@ -22,13 +20,11 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
|
|||||||
filters,
|
filters,
|
||||||
debouncedSearch
|
debouncedSearch
|
||||||
);
|
);
|
||||||
|
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
|
|
||||||
const clearFilter = () => {
|
const clearFilter = () => {
|
||||||
setFilter(defaultNotesFilter);
|
setFilter(defaultContactFilter);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowTrigger(true);
|
setShowTrigger(true);
|
||||||
setOffcanvasContent(
|
setOffcanvasContent(
|
||||||
@ -42,30 +38,6 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 🔹 Format data for export
|
|
||||||
const formatExportData = (notes) => {
|
|
||||||
return notes.map((n) => ({
|
|
||||||
ContactName: n.contactName || "",
|
|
||||||
Note: n.note ? n.note.replace(/<[^>]+>/g, "") : "", // strip HTML tags
|
|
||||||
Organization: n.organizationName || "",
|
|
||||||
CreatedBy: n.createdBy
|
|
||||||
? `${n.createdBy.firstName || ""} ${n.createdBy.lastName || ""}`.trim()
|
|
||||||
: "",
|
|
||||||
CreatedAt: n.createdAt ? new Date(n.createdAt).toLocaleString() : "",
|
|
||||||
UpdatedBy: n.updatedBy
|
|
||||||
? `${n.updatedBy.firstName || ""} ${n.updatedBy.lastName || ""}`.trim()
|
|
||||||
: "",
|
|
||||||
UpdatedAt: n.updatedAt ? new Date(n.updatedAt).toLocaleString() : "",
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🔹 Pass formatted notes to parent for export
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.data && onExport) {
|
|
||||||
onExport(formatExportData(data.data));
|
|
||||||
}
|
|
||||||
}, [data?.data]);
|
|
||||||
|
|
||||||
const paginate = (page) => {
|
const paginate = (page) => {
|
||||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
@ -73,8 +45,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isError) return <div>{error.message}</div>;
|
if (isError) return <div>{error.message}</div>;
|
||||||
if (isLoading) return <NoteCardSkeleton />;
|
if (isLoading) return <NoteCardSkeleton/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex flex-column text-start mt-5">
|
<div className="d-flex flex-column text-start mt-5">
|
||||||
{data?.data?.length > 0 ? (
|
{data?.data?.length > 0 ? (
|
||||||
@ -108,4 +79,4 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotesPage;
|
export default NotesPage;
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
// utils/exportUtils.js
|
|
||||||
export const exportToCSV = (data, filename = "export.csv") => {
|
|
||||||
if (!data || data.length === 0) return;
|
|
||||||
|
|
||||||
const headers = Object.keys(data[0]);
|
|
||||||
const csvRows = [];
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
csvRows.push(headers.join(","));
|
|
||||||
|
|
||||||
// Add values
|
|
||||||
data.forEach(row => {
|
|
||||||
const values = headers.map(header => `"${row[header] ?? ""}"`);
|
|
||||||
csvRows.push(values.join(","));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create CSV Blob
|
|
||||||
const csvContent = csvRows.join("\n");
|
|
||||||
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
||||||
|
|
||||||
// Create download link
|
|
||||||
const link = document.createElement("a");
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
link.setAttribute("href", url);
|
|
||||||
link.setAttribute("download", filename);
|
|
||||||
link.click();
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user