import { saveAs } from 'file-saver'; import jsPDF from 'jspdf'; export interface CustomerInfo { firstName: string; lastName: string; postalAddress: string; email: string; } export interface NotaryInfo { name: string; // Add more notary fields as needed } export interface Metadata { fileName: string, createdAt: Date, isDeleted: boolean, updatedAt: Date, commitmentId: string, documentUid: string; documentType: string; } export interface CertificateData { customer: CustomerInfo; notary: NotaryInfo; folderUid: string; documentHash: string; metadata: Metadata; } export default class PdfService { private static instance: PdfService; public static getInstance(): PdfService { if (!PdfService.instance) { PdfService.instance = new PdfService(); } return PdfService.instance; } /** * Generate a certificate PDF for a document * @param certificateData - Data needed for the certificate * @returns Promise - The generated PDF as a blob */ public async generateCertificate(certificateData: CertificateData): Promise { try { const doc = new jsPDF(); // Notary Information Section (Top Left) doc.setFontSize(12); doc.setFont('helvetica', 'bold'); doc.text('Notaire Validateur:', 20, 30); doc.setFont('helvetica', 'normal'); doc.text(certificateData.notary.name, 20, 37); // Customer Information Section (Top Right) doc.setFont('helvetica', 'bold'); doc.text('Fournisseur de Document:', 120, 30); doc.setFont('helvetica', 'normal'); doc.text(`${certificateData.customer.firstName} ${certificateData.customer.lastName}`, 120, 37); doc.text(certificateData.customer.email, 120, 44); doc.text(certificateData.customer.postalAddress, 120, 51); // Centered Title doc.setFontSize(20); doc.setFont('helvetica', 'bold'); doc.text('Certificat de Validation de Document', 105, 80, { align: 'center' }); // Add a line separator doc.setLineWidth(0.5); doc.line(20, 90, 190, 90); // Reset font for content doc.setFontSize(12); doc.setFont('helvetica', 'normal'); let yPosition = 110; // Document Information Section doc.setFont('helvetica', 'bold'); doc.text('Informations du Document', 20, yPosition); yPosition += 10; doc.setFont('helvetica', 'normal'); doc.text(`UID du Document: ${certificateData.metadata.documentUid}`, 20, yPosition); yPosition += 7; doc.text(`Type de Document: ${certificateData.metadata.documentType}`, 20, yPosition); yPosition += 7; doc.text(`UID du Dossier: ${certificateData.folderUid}`, 20, yPosition); yPosition += 7; doc.text(`Créé le: ${certificateData.metadata.createdAt.toLocaleDateString('fr-FR')}`, 20, yPosition); yPosition += 7; doc.text(`Nom du fichier: ${certificateData.metadata.fileName}`, 20, yPosition); yPosition += 7; doc.text(`ID de transaction: ${certificateData.metadata.commitmentId}`, 20, yPosition); yPosition += 7; doc.text(`Dernière modification: ${certificateData.metadata.updatedAt.toLocaleDateString('fr-FR')}`, 20, yPosition); yPosition += 7; doc.text(`Statut: ${certificateData.metadata.isDeleted ? 'Supprimé' : 'Actif'}`, 20, yPosition); yPosition += 15; // Document Hash Section doc.setFont('helvetica', 'bold'); doc.text('Intégrité du Document', 20, yPosition); yPosition += 10; doc.setFont('helvetica', 'normal'); doc.text(`Hash SHA256 du Document:`, 20, yPosition); yPosition += 7; // Split hash into multiple lines if needed const hash = certificateData.documentHash; const maxWidth = 170; // Available width for text const hashLines = this.splitTextToFit(doc, hash, maxWidth); for (const line of hashLines) { doc.text(line, 25, yPosition); yPosition += 7; } yPosition += 8; // Extra space after hash // Footer const pageCount = doc.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(10); doc.setFont('helvetica', 'italic'); doc.text(`Page ${i} sur ${pageCount}`, 105, 280, { align: 'center' }); doc.text(`Généré le ${new Date().toLocaleString('fr-FR')}`, 105, 285, { align: 'center' }); } return doc.output('blob'); } catch (error) { console.error('Error generating certificate:', error); throw new Error('Failed to generate certificate'); } } /** * Split text to fit within a specified width * @param doc - jsPDF document instance * @param text - Text to split * @param maxWidth - Maximum width in points * @returns Array of text lines */ private splitTextToFit(doc: jsPDF, text: string, maxWidth: number): string[] { const lines: string[] = []; let currentLine = ''; // Split by words first const words = text.split(''); for (const char of words) { const testLine = currentLine + char; const testWidth = doc.getTextWidth(testLine); if (testWidth <= maxWidth) { currentLine = testLine; } else { if (currentLine) { lines.push(currentLine); currentLine = char; } else { // If even a single character is too wide, force a break lines.push(char); } } } if (currentLine) { lines.push(currentLine); } return lines; } /** * Download a certificate PDF * @param certificateData - Data needed for the certificate * @param filename - Optional filename for the download */ public async downloadCertificate(certificateData: CertificateData, filename?: string): Promise { try { const pdfBlob = await this.generateCertificate(certificateData); const defaultFilename = `certificate_${certificateData.metadata.documentUid}_${new Date().toISOString().split('T')[0]}.pdf`; saveAs(pdfBlob, filename || defaultFilename); } catch (error) { console.error('Error downloading certificate:', error); throw error; } } /** * Generate certificate for notary documents * @param certificateData - Data needed for the certificate * @returns Promise - The generated PDF as a blob */ public async generateNotaryCertificate(certificateData: CertificateData): Promise { try { const doc = new jsPDF(); // Notary Information Section (Top Left) doc.setFontSize(12); doc.setFont('helvetica', 'bold'); doc.text('Notaire Validateur:', 20, 30); doc.setFont('helvetica', 'normal'); doc.text(certificateData.notary.name, 20, 37); // Customer Information Section (Top Right) doc.setFont('helvetica', 'bold'); doc.text('Fournisseur de Document:', 120, 30); doc.setFont('helvetica', 'normal'); doc.text(`${certificateData.customer.firstName} ${certificateData.customer.lastName}`, 120, 37); doc.text(certificateData.customer.email, 120, 44); doc.text(certificateData.customer.postalAddress, 120, 51); // Centered Title doc.setFontSize(20); doc.setFont('helvetica', 'bold'); doc.text('Certificat Notarial de Validation de Document', 105, 80, { align: 'center' }); // Add a line separator doc.setLineWidth(0.5); doc.line(20, 90, 190, 90); // Reset font for content doc.setFontSize(12); doc.setFont('helvetica', 'normal'); let yPosition = 110; // Document Information Section doc.setFont('helvetica', 'bold'); doc.text('Informations du Document', 20, yPosition); yPosition += 10; doc.setFont('helvetica', 'normal'); doc.text(`Identifiant du Document: ${certificateData.metadata.documentUid}`, 20, yPosition); yPosition += 7; doc.text(`Type de Document: ${certificateData.metadata.documentType}`, 20, yPosition); yPosition += 7; doc.text(`Numéro de dossier: ${certificateData.folderUid}`, 20, yPosition); yPosition += 7; doc.text(`Créé le: ${certificateData.metadata.createdAt.toLocaleDateString('fr-FR')}`, 20, yPosition); yPosition += 7; doc.text(`Nom du fichier: ${certificateData.metadata.fileName}`, 20, yPosition); yPosition += 7; doc.text(`ID de transaction: ${certificateData.metadata.commitmentId}`, 20, yPosition); yPosition += 7; doc.text(`Dernière modification: ${certificateData.metadata.updatedAt.toLocaleDateString('fr-FR')}`, 20, yPosition); yPosition += 7; doc.text(`Statut: ${certificateData.metadata.isDeleted ? 'Supprimé' : 'Actif'}`, 20, yPosition); yPosition += 15; // Document Hash Section doc.setFont('helvetica', 'bold'); doc.text('Intégrité du Document', 20, yPosition); yPosition += 10; doc.setFont('helvetica', 'normal'); doc.text(`Hash SHA256 du Document:`, 20, yPosition); yPosition += 7; // Split hash into multiple lines if needed const hash = certificateData.documentHash; const maxWidth = 170; // Available width for text const hashLines = this.splitTextToFit(doc, hash, maxWidth); for (const line of hashLines) { doc.text(line, 25, yPosition); yPosition += 7; } yPosition += 8; // Extra space after hash // Footer const pageCount = doc.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(10); doc.setFont('helvetica', 'italic'); doc.text(`Page ${i} sur ${pageCount}`, 105, 280, { align: 'center' }); doc.text(`Certificat Notarial - Généré le ${new Date().toLocaleString('fr-FR')}`, 105, 285, { align: 'center' }); } return doc.output('blob'); } catch (error) { console.error('Error generating notary certificate:', error); throw new Error('Failed to generate notary certificate'); } } /** * Download a notary certificate PDF * @param certificateData - Data needed for the certificate * @param filename - Optional filename for the download */ public async downloadNotaryCertificate(certificateData: CertificateData, filename?: string): Promise { try { const pdfBlob = await this.generateNotaryCertificate(certificateData); const defaultFilename = `notary_certificate_${certificateData.metadata.documentUid}_${new Date().toISOString().split('T')[0]}.pdf`; saveAs(pdfBlob, filename || defaultFilename); } catch (error) { console.error('Error downloading notary certificate:', error); throw error; } } }