Directory - Contacts Exported to Excel does not format properly

This commit is contained in:
Kartik Sharma 2025-09-15 13:05:03 +05:30 committed by pramod.mahajan
parent 4927680fe3
commit e179a267aa
4 changed files with 145 additions and 57 deletions

View File

@ -11,8 +11,21 @@ import Pagination from "../../components/common/Pagination";
import ListViewContact from "../../components/Directory/ListViewContact";
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 }) => {
const ContactsPage = ({ projectId, searchText, onExport }) => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilter] = useState(defaultContactFilter);
const debouncedSearch = useDebounce(searchText, 500);
@ -26,9 +39,9 @@ const ContactsPage = ({projectId ,searchText }) => {
debouncedSearch
);
const { setOffcanvasContent, setShowTrigger } = useFab();
const clearFilter = () => {
setFilter(defaultContactFilter);
};
const clearFilter = () => setFilter(defaultContactFilter);
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent(
@ -42,6 +55,13 @@ const ContactsPage = ({projectId ,searchText }) => {
};
}, []);
// 🔹 Format contacts for export
useEffect(() => {
if (data?.data && onExport) {
onExport(formatExportData(data.data));
}
}, [data?.data]);
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
@ -49,49 +69,44 @@ const ContactsPage = ({projectId ,searchText }) => {
};
if (isError) return <div>{error.message}</div>;
if (isLoading) return <ListViewContactSkeleton/>;
return (
<div className="row mt-5">
{gridView ? (
<>
{data?.data?.map((contact) => (
<div
key={contact.id}
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
>
<CardViewContact IsActive={showActive} contact={contact} />
</div>
))}
if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
{data?.data?.length > 0 && (
<div className="col-12 d-flex justify-content-start mt-3">
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
return (
<div className="row mt-5">
{gridView ? (
<>
{data?.data?.map((contact) => (
<div key={contact.id} className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4">
<CardViewContact IsActive={showActive} contact={contact} />
</div>
))}
{data?.data?.length > 0 && (
<div className="col-12 d-flex justify-content-start mt-3">
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
</div>
)}
</>
) : (
<div className="col-12">
<ListViewContact
data={data.data}
Pagination={
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
}
/>
</div>
)}
</>
) : (
<div className="col-12">
<ListViewContact
data={data.data}
Pagination={
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
}
/>
</div>
)}
</div>
)
);
};
export default ContactsPage;

View File

@ -15,6 +15,7 @@ import BucketList from "../../components/Directory/BucketList";
import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
import ContactProfile from "../../components/Directory/ContactProfile";
import GlobalModel from "../../components/common/GlobalModel";
import { exportToCSV } from "../../utils/exportUtils";
const NotesPage = lazy(() => import("./NotesPage"));
const ContactsPage = lazy(() => import("./ContactsPage"));
@ -32,7 +33,7 @@ export const useDirectoryContext = () => {
}
return context;
};
export default function DirectoryPage({ IsPage = true, projectId=null }) {
export default function DirectoryPage({ IsPage = true, projectId = null }) {
const [searchContact, setsearchContact] = useState("");
const [searchNote, setSearchNote] = useState("");
const [activeTab, setActiveTab] = useState("notes");
@ -49,6 +50,18 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
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 handleTabClick = (tab, e) => {
@ -94,8 +107,8 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
return (
<>
<DirectoryContext.Provider value={contextValues}>
<div className={`${IsPage ? "container-fluid":""}`}>
{IsPage && ( <Breadcrumb
<div className={`${IsPage ? "container-fluid" : ""}`}>
{IsPage && (<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Directory", link: null },
@ -108,7 +121,7 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
<a
className={`nav-link ${
activeTab === "notes" ? "active" : ""
} fs-6`}
} fs-6`}
onClick={(e) => handleTabClick("notes", e)}
>
<i className="bx bx-note bx-sm me-1_5"></i>
@ -119,7 +132,7 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
<a
className={`nav-link ${
activeTab === "contacts" ? "active" : ""
} fs-6`}
} fs-6`}
onClick={(e) => handleTabClick("contacts", e)}
>
<i className="bx bxs-contact bx-sm me-1_5"></i>
@ -139,7 +152,11 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
</button>
<ul className="dropdown-menu">
<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
</a>
</li>
@ -173,7 +190,7 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
<button
className={`btn btn-xs ${
!gridView ? "btn-primary" : "btn-outline-secondary"
}`}
}`}
onClick={() => setGridView(false)}
>
<i className="bx bx-list-ul"></i>
@ -182,7 +199,7 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
<button
className={`btn btn-xs ${
gridView ? "btn-primary" : "btn-outline-secondary"
}`}
}`}
onClick={() => setGridView(true)}
>
<i className="bx bx-grid-alt"></i>
@ -214,10 +231,10 @@ export default function DirectoryPage({ IsPage = true, projectId=null }) {
<div>
<Suspense fallback={<MainDirectoryPageSkeleton />}>
{activeTab === "notes" && (
<NotesPage projectId={projectId} searchText={searchNote} />
<NotesPage projectId={projectId} searchText={searchNote} onExport={setNotesData} />
)}
{activeTab === "contacts" && (
<ContactsPage projectId={projectId} searchText={searchContact} />
<ContactsPage projectId={projectId} searchText={searchContact} onExport={setContactData} />
)}
</Suspense>
</div>

View File

@ -1,6 +1,7 @@
// NotesPage.jsx
import React, { useEffect, useState } from "react";
import { useFab } from "../../Context/FabContext";
import { useNoteFilter, useNotes } from "../../hooks/useDirectory";
import { useNotes } from "../../hooks/useDirectory";
import NoteFilterPanel from "./NoteFilterPanel";
import { defaultNotesFilter } from "../../components/Directory/DirectorySchema";
import { ITEMS_PER_PAGE } from "../../utils/constants";
@ -9,10 +10,11 @@ import NoteCardDirectoryEditable from "../../components/Directory/NoteCardDirect
import Pagination from "../../components/common/Pagination";
import { NoteCardSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
const NotesPage = ({ projectId, searchText }) => {
const NotesPage = ({ projectId, searchText, onExport }) => {
const [filters, setFilter] = useState(defaultNotesFilter);
const [currentPage, setCurrentPage] = useState(1);
const debouncedSearch = useDebounce(searchText, 500);
const { data, isLoading, isError, error } = useNotes(
projectId,
ITEMS_PER_PAGE,
@ -20,11 +22,13 @@ const NotesPage = ({ projectId, searchText }) => {
filters,
debouncedSearch
);
const { setOffcanvasContent, setShowTrigger } = useFab();
const clearFilter = () => {
setFilter(defaultContactFilter);
setFilter(defaultNotesFilter);
};
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent(
@ -38,6 +42,30 @@ const NotesPage = ({ projectId, searchText }) => {
};
}, []);
// 🔹 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) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
@ -45,7 +73,8 @@ const NotesPage = ({ projectId, searchText }) => {
};
if (isError) return <div>{error.message}</div>;
if (isLoading) return <NoteCardSkeleton/>;
if (isLoading) return <NoteCardSkeleton />;
return (
<div className="d-flex flex-column text-start mt-5">
{data?.data?.length > 0 ? (
@ -79,4 +108,4 @@ const NotesPage = ({ projectId, searchText }) => {
);
};
export default NotesPage;
export default NotesPage;

27
src/utils/exportUtils.js Normal file
View File

@ -0,0 +1,27 @@
// 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();
};