Compare commits
No commits in common. "e84da2dcb0ad1078a44e3720bfe69a570151c2b4" and "e17c2064acdeaabc6121ac88469eb5d6a80a0891" have entirely different histories.
e84da2dcb0
...
e17c2064ac
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
useState,
|
useState,
|
||||||
Suspense,
|
Suspense,
|
||||||
@ -20,9 +19,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"));
|
||||||
@ -65,46 +64,13 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
const [ContactData, setContactData] = useState([]);
|
const [ContactData, setContactData] = useState([]);
|
||||||
|
|
||||||
const handleExport = (type) => {
|
const handleExport = (type) => {
|
||||||
let exportData = activeTab === "notes" ? notesData : ContactData;
|
if (activeTab === "notes" && type === "csv") {
|
||||||
if (!exportData?.length) return;
|
exportToCSV(notesData, "notes.csv");
|
||||||
|
}
|
||||||
switch (type) {
|
if (activeTab === "contacts" && type === "csv") {
|
||||||
case "csv":
|
exportToCSV(ContactData, "contact.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();
|
||||||
|
|
||||||
@ -247,6 +213,19 @@ 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">
|
||||||
@ -262,31 +241,17 @@ 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" && (
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
27
src/utils/exportUtils.js
Normal file
27
src/utils/exportUtils.js
Normal 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();
|
||||||
|
};
|
||||||
@ -40,52 +40,39 @@ 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)
|
||||||
*/
|
*/
|
||||||
const sanitizeText = (text) => {
|
export const exportToPDF = async (data, fileName = "data", columns = null) => {
|
||||||
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);
|
||||||
|
|
||||||
// Default options
|
// Landscape dimensions
|
||||||
const {
|
const pageWidth = 1000; // wider for more space
|
||||||
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) => {
|
||||||
const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
page.drawText(header, { x: margin + i * columnSpacing, y, font, size: 12 });
|
||||||
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 x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
const text = row[header] ? row[header].toString() : '';
|
||||||
const text = row[header] || '';
|
page.drawText(text, { x: margin + i * columnSpacing, y, font, size: 10 });
|
||||||
page.drawText(text, { x, y, font, size: fontSizeRow });
|
|
||||||
});
|
});
|
||||||
y -= rowHeight;
|
y -= rowHeight;
|
||||||
|
|
||||||
if (y < margin) {
|
if (y < margin) {
|
||||||
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
page = pdfDoc.addPage([pageWidth, pageHeight]); // landscape for new page
|
||||||
y = pageHeight - margin;
|
y = pageHeight - margin;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -100,95 +87,6 @@ export const exportToPDF = async (data, fileName = "data", columns = null, optio
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user