chore: update application configuration and UI components for improved styling and functionality
This commit is contained in:
parent
0af6b17792
commit
37ab8d7bab
54 changed files with 23293 additions and 1666 deletions
191
composables/usePdfExport.ts
Normal file
191
composables/usePdfExport.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
export const usePdfExport = () => {
|
||||
const generatePDF = async (
|
||||
element: HTMLElement,
|
||||
filename: string = "document.pdf",
|
||||
options: any = {}
|
||||
): Promise<void> => {
|
||||
// Default options optimized for document templates
|
||||
const defaultOptions = {
|
||||
margin: [0.5, 0.5, 0.5, 0.5], // top, left, bottom, right in inches
|
||||
filename,
|
||||
image: { type: "jpeg", quality: 0.98 },
|
||||
html2canvas: {
|
||||
scale: 2, // Higher scale for better quality
|
||||
useCORS: true,
|
||||
allowTaint: false,
|
||||
letterRendering: true,
|
||||
logging: false,
|
||||
dpi: 300,
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
},
|
||||
jsPDF: {
|
||||
unit: "in",
|
||||
format: "letter",
|
||||
orientation: "portrait",
|
||||
compress: true,
|
||||
},
|
||||
pagebreak: {
|
||||
mode: ["avoid-all", "css", "legacy"],
|
||||
before: ".page-break-before",
|
||||
after: ".page-break-after",
|
||||
avoid: ".no-page-break",
|
||||
},
|
||||
};
|
||||
|
||||
// Merge provided options with defaults
|
||||
const finalOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
html2canvas: {
|
||||
...defaultOptions.html2canvas,
|
||||
...(options.html2canvas || {}),
|
||||
},
|
||||
jsPDF: {
|
||||
...defaultOptions.jsPDF,
|
||||
...(options.jsPDF || {}),
|
||||
},
|
||||
pagebreak: {
|
||||
...defaultOptions.pagebreak,
|
||||
...(options.pagebreak || {}),
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
// Dynamic import for client-side only
|
||||
if (process.server) {
|
||||
throw new Error("PDF generation is only available on the client side");
|
||||
}
|
||||
|
||||
// Import html2pdf dynamically with better error handling
|
||||
let html2pdf;
|
||||
try {
|
||||
const module = await import("html2pdf.js");
|
||||
html2pdf = module.default || module;
|
||||
} catch (importError) {
|
||||
console.error("Failed to import html2pdf.js:", importError);
|
||||
throw new Error(
|
||||
"Failed to load PDF library. Please refresh the page and try again."
|
||||
);
|
||||
}
|
||||
|
||||
// Clone the element to avoid modifying the original
|
||||
const clonedElement = element.cloneNode(true) as HTMLElement;
|
||||
|
||||
// Apply PDF-specific styling to the clone while preserving font choices
|
||||
const currentFontClass = element.className.match(/font-[\w-]+/)?.[0];
|
||||
let fontFamily = '"Times New Roman", "Times", serif'; // default
|
||||
|
||||
// Preserve selected font for PDF
|
||||
if (currentFontClass) {
|
||||
if (currentFontClass.includes("source-serif")) {
|
||||
fontFamily = '"Source Serif 4", "Times New Roman", serif';
|
||||
} else if (currentFontClass.includes("ubuntu")) {
|
||||
fontFamily =
|
||||
'"Ubuntu", -apple-system, BlinkMacSystemFont, sans-serif';
|
||||
} else if (currentFontClass.includes("inter")) {
|
||||
fontFamily = '"Inter", -apple-system, BlinkMacSystemFont, sans-serif';
|
||||
}
|
||||
}
|
||||
|
||||
clonedElement.style.fontFamily = fontFamily;
|
||||
clonedElement.style.fontSize = "11pt";
|
||||
clonedElement.style.lineHeight = "1.5";
|
||||
clonedElement.style.color = "#000000";
|
||||
clonedElement.style.backgroundColor = "#ffffff";
|
||||
clonedElement.style.width = "8.5in";
|
||||
clonedElement.style.maxWidth = "8.5in";
|
||||
clonedElement.style.padding = "0.5in";
|
||||
clonedElement.style.boxSizing = "border-box";
|
||||
|
||||
// Hide export controls in the clone
|
||||
const exportControls = clonedElement.querySelector(".export-controls");
|
||||
if (exportControls) {
|
||||
(exportControls as HTMLElement).style.display = "none";
|
||||
}
|
||||
|
||||
// Ensure proper font loading by adding a slight delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// Generate the PDF with better error handling
|
||||
console.log("Starting PDF generation with options:", finalOptions);
|
||||
|
||||
if (typeof html2pdf !== "function") {
|
||||
throw new Error(
|
||||
"html2pdf is not a function. Library may not have loaded correctly."
|
||||
);
|
||||
}
|
||||
|
||||
const pdfInstance = html2pdf();
|
||||
|
||||
if (!pdfInstance || typeof pdfInstance.set !== "function") {
|
||||
throw new Error("html2pdf instance is invalid");
|
||||
}
|
||||
|
||||
await pdfInstance.set(finalOptions).from(clonedElement).save();
|
||||
} catch (error: any) {
|
||||
console.error("PDF generation failed:", error);
|
||||
const errorMessage =
|
||||
error?.message || error?.toString() || "Unknown error";
|
||||
throw new Error(`PDF generation failed: ${errorMessage}`);
|
||||
}
|
||||
};
|
||||
|
||||
const generatePDFFromContent = async (
|
||||
htmlContent: string,
|
||||
filename: string = "document.pdf",
|
||||
options: any = {}
|
||||
): Promise<void> => {
|
||||
if (process.server) {
|
||||
throw new Error("PDF generation is only available on the client side");
|
||||
}
|
||||
|
||||
// Create a temporary element with the HTML content
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = htmlContent;
|
||||
tempDiv.style.position = "absolute";
|
||||
tempDiv.style.left = "-9999px";
|
||||
tempDiv.style.top = "-9999px";
|
||||
tempDiv.style.width = "8.5in";
|
||||
tempDiv.style.fontFamily = '"Times New Roman", "Times", serif';
|
||||
tempDiv.style.fontSize = "11pt";
|
||||
tempDiv.style.lineHeight = "1.5";
|
||||
tempDiv.style.color = "#000000";
|
||||
tempDiv.style.backgroundColor = "#ffffff";
|
||||
|
||||
document.body.appendChild(tempDiv);
|
||||
|
||||
try {
|
||||
await generatePDF(tempDiv, filename, options);
|
||||
} finally {
|
||||
document.body.removeChild(tempDiv);
|
||||
}
|
||||
};
|
||||
|
||||
const exportDocumentAsPDF = async (
|
||||
selector: string = ".document-page",
|
||||
filename?: string
|
||||
): Promise<void> => {
|
||||
if (process.server) {
|
||||
throw new Error("PDF generation is only available on the client side");
|
||||
}
|
||||
|
||||
const element = document.querySelector(selector) as HTMLElement;
|
||||
if (!element) {
|
||||
throw new Error(`Element with selector "${selector}" not found`);
|
||||
}
|
||||
|
||||
// Generate filename based on current date if not provided
|
||||
const defaultFilename = `document-${
|
||||
new Date().toISOString().split("T")[0]
|
||||
}.pdf`;
|
||||
|
||||
await generatePDF(element, filename || defaultFilename);
|
||||
};
|
||||
|
||||
return {
|
||||
generatePDF,
|
||||
generatePDFFromContent,
|
||||
exportDocumentAsPDF,
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue