diff --git a/src/pages/Directory/DirectoryPage.jsx b/src/pages/Directory/DirectoryPage.jsx index 5f819e69..7a0c4cce 100644 --- a/src/pages/Directory/DirectoryPage.jsx +++ b/src/pages/Directory/DirectoryPage.jsx @@ -1,3 +1,4 @@ + import { useState, Suspense, @@ -19,9 +20,9 @@ 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"; import ConfirmModal from "../../components/common/ConfirmModal"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { exportToCSV, exportToExcel, exportToPDF, exportToPDF1, printTable } from "../../utils/tableExportUtils"; const NotesPage = lazy(() => import("./NotesPage")); const ContactsPage = lazy(() => import("./ContactsPage")); @@ -64,13 +65,46 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { 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"); - } - }; + let exportData = activeTab === "notes" ? notesData : ContactData; + if (!exportData?.length) return; + + switch (type) { + 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(); @@ -213,19 +247,6 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { - - - -
}> {activeTab === "notes" && ( diff --git a/src/pages/Directory/NotesPage.jsx b/src/pages/Directory/NotesPage.jsx index 58790562..3f0d4348 100644 --- a/src/pages/Directory/NotesPage.jsx +++ b/src/pages/Directory/NotesPage.jsx @@ -42,7 +42,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => { }; }, []); - // 🔹 Format data for export + // Format data for export const formatExportData = (notes) => { return notes.map((n) => ({ 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(() => { if (data?.data && onExport) { onExport(formatExportData(data.data)); diff --git a/src/utils/exportUtils.js b/src/utils/exportUtils.js deleted file mode 100644 index 40f7d423..00000000 --- a/src/utils/exportUtils.js +++ /dev/null @@ -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(); -}; diff --git a/src/utils/tableExportUtils.jsx b/src/utils/tableExportUtils.jsx index 57af704f..768ff5ac 100644 --- a/src/utils/tableExportUtils.jsx +++ b/src/utils/tableExportUtils.jsx @@ -40,39 +40,52 @@ export const exportToExcel = (data, fileName = "data") => { * @param {Array} data - Array of objects to export * @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; const pdfDoc = await PDFDocument.create(); const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - // Landscape dimensions - const pageWidth = 1000; // wider for more space + // Default options + const { + columnWidths = [], // array of widths per column + fontSizeHeader = 12, + fontSizeRow = 10, + rowHeight = 25, + } = options; + + const pageWidth = 1000; const pageHeight = 600; let page = pdfDoc.addPage([pageWidth, pageHeight]); const margin = 30; let y = pageHeight - margin; const headers = columns || Object.keys(data[0]); - const rowHeight = 25; // slightly taller rows for readability - const columnSpacing = 150; // increase space between columns // Draw headers 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; // Draw rows data.forEach(row => { headers.forEach((header, i) => { - const text = row[header] ? row[header].toString() : ''; - page.drawText(text, { x: margin + i * columnSpacing, y, font, size: 10 }); + const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150); + const text = row[header] || ''; + page.drawText(text, { x, y, font, size: fontSizeRow }); }); y -= rowHeight; if (y < margin) { - page = pdfDoc.addPage([pageWidth, pageHeight]); // landscape for new page + page = pdfDoc.addPage([pageWidth, pageHeight]); 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. * @param {HTMLElement} table - The table element (or ref) to print