Adding Excel and PDF export in Directory.

This commit is contained in:
Kartik Sharma 2025-10-10 14:43:11 +05:30
parent c1a8b87a2c
commit cb7b0144a8
3 changed files with 173 additions and 36 deletions

View File

@ -1,3 +1,4 @@
import { import {
useState, useState,
Suspense, Suspense,
@ -19,9 +20,9 @@ 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";
import ConfirmModal from "../../components/common/ConfirmModal"; import ConfirmModal from "../../components/common/ConfirmModal";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { exportToCSV, exportToExcel, exportToPDF, exportToPDF1, printTable } from "../../utils/tableExportUtils";
const NotesPage = lazy(() => import("./NotesPage")); const NotesPage = lazy(() => import("./NotesPage"));
const ContactsPage = lazy(() => import("./ContactsPage")); const ContactsPage = lazy(() => import("./ContactsPage"));
@ -64,13 +65,46 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
const [ContactData, setContactData] = useState([]); const [ContactData, setContactData] = useState([]);
const handleExport = (type) => { const handleExport = (type) => {
if (activeTab === "notes" && type === "csv") { let exportData = activeTab === "notes" ? notesData : ContactData;
exportToCSV(notesData, "notes.csv"); if (!exportData?.length) return;
}
if (activeTab === "contacts" && type === "csv") { switch (type) {
exportToCSV(ContactData, "contact.csv"); case "csv":
} exportToCSV(exportData, activeTab === "notes" ? "Notes" : "Contacts");
}; break;
case "excel":
exportToExcel(exportData, activeTab === "notes" ? "Notes" : "Contacts");
break;
case "pdf":
if (activeTab === "notes") {
exportToPDF1(exportData, "Notes");
} else {
// Columns for Contacts PDF
const columns = ["Email", "Phone", "Organization", "Category", "Tags"];
// Sanitize and trim long text to avoid PDF overflow
const sanitizedData = exportData.map(item => ({
Email: (item.Email || "").slice(0, 40),
Phone: (item.Phone || "").slice(0, 20),
Organization: (item.Organization || "").slice(0, 30),
Category: (item.Category || "").slice(0, 20),
Tags: (item.Tags || "").slice(0, 40),
}));
// Export with proper spacing
exportToPDF(sanitizedData, "Contacts", columns, {
columnWidths: [200, 120, 180, 120, 200], // Adjust widths per column
fontSizeHeader: 12,
fontSizeRow: 10,
rowHeight: 25,
});
}
break;
default:
console.warn("Unsupported export type");
}
};
const { data, isLoading, isError, error } = useBucketList(); const { data, isLoading, isError, error } = useBucketList();
@ -213,19 +247,6 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
</button> </button>
<ul className="dropdown-menu dropdown-menu-end shadow-sm p-2" style={{ minWidth: "220px" }}> <ul className="dropdown-menu dropdown-menu-end shadow-sm p-2" style={{ minWidth: "220px" }}>
<li>
<button
className="dropdown-item d-flex align-items-center gap-2"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file"></i>
<span>Export to CSV</span>
</button>
</li>
{/* Divider */}
{activeTab === "contacts" && <li><hr className="dropdown-divider" /></li>}
{activeTab === "contacts" && ( {activeTab === "contacts" && (
<li className="dropdown-item d-flex align-items-center"> <li className="dropdown-item d-flex align-items-center">
<div className="form-check form-switch mb-0"> <div className="form-check form-switch mb-0">
@ -241,17 +262,31 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
<span>{showActive ? "Active Contacts" : "Inactive Contacts"}</span> <span>{showActive ? "Active Contacts" : "Inactive Contacts"}</span>
</li> </li>
)} )}
<li>
<button className="dropdown-item d-flex align-items-center gap-2" onClick={() => handleExport("csv")}>
<i className="bx bx-file"></i><span>Export to CSV</span>
</button>
</li>
<li>
<button className="dropdown-item d-flex align-items-center gap-2" onClick={() => handleExport("excel")}>
<i className="bx bx-spreadsheet"></i><span>Export to Excel</span>
</button>
</li>
<li>
<button className="dropdown-item d-flex align-items-center gap-2" onClick={() => handleExport("pdf")}>
<i className="bx bxs-file-pdf"></i><span>Export to PDF</span>
</button>
</li>
{/* Divider */}
{activeTab === "contacts" && <li><hr className="dropdown-divider" /></li>}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<Suspense fallback={<MainDirectoryPageSkeleton />}> <Suspense fallback={<MainDirectoryPageSkeleton />}>
{activeTab === "notes" && ( {activeTab === "notes" && (

View File

@ -42,7 +42,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
}; };
}, []); }, []);
// 🔹 Format data for export // Format data for export
const formatExportData = (notes) => { const formatExportData = (notes) => {
return notes.map((n) => ({ return notes.map((n) => ({
ContactName: n.contactName || "", ContactName: n.contactName || "",
@ -59,7 +59,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
})); }));
}; };
// 🔹 Pass formatted notes to parent for export // Pass formatted notes to parent for export
useEffect(() => { useEffect(() => {
if (data?.data && onExport) { if (data?.data && onExport) {
onExport(formatExportData(data.data)); onExport(formatExportData(data.data));

View File

@ -40,39 +40,52 @@ export const exportToExcel = (data, fileName = "data") => {
* @param {Array} data - Array of objects to export * @param {Array} data - Array of objects to export
* @param {string} fileName - File name for the PDF (optional) * @param {string} fileName - File name for the PDF (optional)
*/ */
export const exportToPDF = async (data, fileName = "data", columns = null) => { const sanitizeText = (text) => {
if (!text) return "";
// Replace all non-ASCII characters with "?" or remove them
return text.replace(/[^\x00-\x7F]/g, "?");
};
export const exportToPDF = async (data, fileName = "data", columns = null, options = {}) => {
if (!data || data.length === 0) return; if (!data || data.length === 0) return;
const pdfDoc = await PDFDocument.create(); const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica); const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
// Landscape dimensions // Default options
const pageWidth = 1000; // wider for more space const {
columnWidths = [], // array of widths per column
fontSizeHeader = 12,
fontSizeRow = 10,
rowHeight = 25,
} = options;
const pageWidth = 1000;
const pageHeight = 600; const pageHeight = 600;
let page = pdfDoc.addPage([pageWidth, pageHeight]); let page = pdfDoc.addPage([pageWidth, pageHeight]);
const margin = 30; const margin = 30;
let y = pageHeight - margin; let y = pageHeight - margin;
const headers = columns || Object.keys(data[0]); const headers = columns || Object.keys(data[0]);
const rowHeight = 25; // slightly taller rows for readability
const columnSpacing = 150; // increase space between columns
// Draw headers // Draw headers
headers.forEach((header, i) => { headers.forEach((header, i) => {
page.drawText(header, { x: margin + i * columnSpacing, y, font, size: 12 }); const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
page.drawText(header, { x, y, font, size: fontSizeHeader });
}); });
y -= rowHeight; y -= rowHeight;
// Draw rows // Draw rows
data.forEach(row => { data.forEach(row => {
headers.forEach((header, i) => { headers.forEach((header, i) => {
const text = row[header] ? row[header].toString() : ''; const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
page.drawText(text, { x: margin + i * columnSpacing, y, font, size: 10 }); const text = row[header] || '';
page.drawText(text, { x, y, font, size: fontSizeRow });
}); });
y -= rowHeight; y -= rowHeight;
if (y < margin) { if (y < margin) {
page = pdfDoc.addPage([pageWidth, pageHeight]); // landscape for new page page = pdfDoc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin; y = pageHeight - margin;
} }
}); });
@ -87,6 +100,95 @@ export const exportToPDF = async (data, fileName = "data", columns = null) => {
/**
* Export JSON data to PDF in a card-style format
* @param {Array} data - Array of objects to export
* @param {string} fileName - File name for the PDF (optional)
*/
export const exportToPDF1 = async (data, fileName = "data") => {
if (!data || data.length === 0) return;
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const pageWidth = 600;
const pageHeight = 800;
const margin = 30;
const cardSpacing = 20;
const cardPadding = 10;
let page = pdfDoc.addPage([pageWidth, pageHeight]);
let y = pageHeight - margin;
for (const item of data) {
const title = item.ContactName || "";
const subtitle = `by ${item.CreatedBy || ""} on ${item.CreatedAt || ""}`;
const body = item.Note || "";
const cardHeight = 80 + (body.length / 60) * 14; // approximate height for body text
if (y - cardHeight < margin) {
page = pdfDoc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin;
}
// Draw card border
page.drawRectangle({
x: margin,
y: y - cardHeight,
width: pageWidth - 2 * margin,
height: cardHeight,
borderColor: rgb(0.7, 0.7, 0.7),
borderWidth: 1,
color: rgb(1, 1, 1),
});
// Draw title
page.drawText(title, {
x: margin + cardPadding,
y: y - 20,
font: boldFont,
size: 12,
color: rgb(0.1, 0.1, 0.1),
});
// Draw subtitle
page.drawText(subtitle, {
x: margin + cardPadding,
y: y - 35,
font,
size: 10,
color: rgb(0.4, 0.4, 0.4),
});
// Draw body text (wrap manually)
const lines = body.match(/(.|[\r\n]){1,80}/g) || [];
lines.forEach((line, i) => {
page.drawText(line, {
x: margin + cardPadding,
y: y - 50 - i * 12,
font,
size: 10,
color: rgb(0.2, 0.2, 0.2),
});
});
y -= cardHeight + cardSpacing;
}
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${fileName}.pdf`;
link.click();
};
/** /**
* Print the HTML table by accepting the table element or a reference. * Print the HTML table by accepting the table element or a reference.
* @param {HTMLElement} table - The table element (or ref) to print * @param {HTMLElement} table - The table element (or ref) to print