diff --git a/PDF/CDDU.html b/PDF/CDDU.html new file mode 100644 index 0000000..1fb23fe --- /dev/null +++ b/PDF/CDDU.html @@ -0,0 +1,286 @@ +
+ +
+ + {% assign cachet_representation = "cachet_representation" %} + +

CONTRAT D'ENGAGEMENT {% if employee_catpro == "Artiste" %}ARTISTE{% elsif employee_civ == "Madame" and employee_catpro == "Technicien" %}TECHNICIENNE + {% elsif employee_civ == "Monsieur" and employee_catpro == "Technicien" %}TECHNICIEN{% elsif employee_civ == "Madame" and employee_catpro == "Metteur·se en scène" %}
ARTISTE CADRE{% else %}ARTISTE CADRE{% endif %}

+ +

Entre les {% if employee_civ == "Monsieur" %}soussignés{% else %}soussignées{% endif %} :

+ + +

d'une part,

+ +

et :

+ + + +

d'autre part.

+ +

+Le présent contrat est conclu dans le cadre de la législation du travail, des usages en vigueur dans la +profession, de l’article L. 1242-2° du Code du travail et de l’accord interbranche sur le recours au +contrat à durée déterminée d’usage dans le spectacle du 12/10/1998. Il est, en outre, régi par les +dispositions de la {{ CCN | join: ', ' }}{% if CCN contains "Convention Collective Nationale de l'Édition" %} et de ses annexes afférentes à l'Édition Phonographique{% endif %}. +

+ +

+ Il a été convenu et arrêté ce qui suit : +

+ +
+

OBJET

+ {{employee_civ}} {{employee_firstname}} {{employee_lastname}} est {% if employee_civ == "Monsieur" %}engagé{% else %}engagée{% endif %} selon l'objet suivant : + +
+ +
+

DURÉE DE L'ENGAGEMENT

+

+ {% if date_debut == date_fin %} + Le présent engagement couvre la journée du {{ date_debut }}, pour +{% else %} + {% if dates_travaillees != empty %} + Le présent engagement couvre la période du {{ date_debut }} au {{ date_fin }} pour les dates travaillées suivantes : + {% else %} + Le présent engagement couvre la période du {{ date_debut }} au {{ date_fin }}. + {% endif %} +

+ Pour {% endif %} + {% if employee_catpro == "Artiste" %} + un total de {% if cachets.representations >= 1 and cachets.repetitions >= 1 %} {{cachets.representations}} {% if cachets.representations == 1 %}cachet{% else %}cachets{% endif %} de représentation et {{cachets.repetitions}} {% if cachets.repetitions == 1 %}service{% else %}services{% endif %} de répétition.{% endif %} + {% if cachets.representations >= 1 and cachets.repetitions == 0 %} {{cachets.representations}} {% if cachets.representations == 1 %}cachet{% else %}cachets{% endif %}{% if CCN contains "Convention Collective Nationale de la Production Audiovisuelle" or CCN contains "Convention Collective Nationale de l'Édition" %} d'enregistrement.{% endif %}{% unless CCN contains "Convention Collective Nationale de la Production Audiovisuelle" + or CCN contains "Convention Collective Nationale de l'Édition" %} + de représentation.{% endunless %}{% endif %} + {% if cachets.representations == 0 and cachets.repetitions >= 1 %} + {{cachets.repetitions}} {% if cachets.repetitions == 1 %}service{% else %}services{% endif %} de répétition. + {% endif %} + {% endif %} + {% if employee_catpro == "Technicien" %} + un total de {{cachets.heures}} heures de travail{% if cachets.heuresparjour == 0 %}.{% endif %}{% if cachets.heuresparjour >= 1 %}, à raison de {{cachets.heuresparjour}} heures par jour de travail.{% endif %} + {% endif %} + {% if employee_catpro == "Metteur en scène" and cachets.representations >= 1 and cachets.heures > 0 %} + un total de {{cachets.representations}} {% if cachets.representations == 1 %}cachet{% else %}cachets{% endif %} de représentation et {{cachets.heures}} heures de travail. + {% endif %} + {% if employee_catpro == "Metteur en scène" and cachets.representations == 0 %} + un total de {{cachets.heures}} heures de travail. + {% endif %} + {% if employee_catpro == "Metteur en scène" and cachets.representations >= 1 and cachets.heures == 0 %} + un total de {{cachets.representations}} {% if cachets.representations == 1 %}cachet{% else %}cachets{% endif %} de représentation. + {% endif %} +

+ +

+ {% if cachets.repetitions >= 1 %}La durée totale des répétitions sera de {{cachets.heures}} heures{% endif %}{% if cachets.repetitions >= 1 and cachets.heuresparjour == 0 %}.{% endif %}{% if cachets.repetitions >= 1 and cachets.heuresparjour >= 1 %}, à raison de {{cachets.heuresparjour}} heures par journée de répétition.{% endif %} +

+
+
+ Il ne nous sera, en aucun cas, fait obligation de proroger le présent engagement à expiration. La fin de la période d'engagement prévue + aux présentes, prorogée éventuellement de la durée de dépassement, en constitue le terme. Il n'y a lieu à aucun préavis. +
+ +
+

LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL

+

+ {{structure_name}} communiquera à {{employee_firstname}} {{employee_lastname}} les lieux {% if CCN contains "Convention Collective Nationale de la Production Audiovisuelle" or CCN contains "Convention Collective Nationale de l'Édition" %}{% elsif cachets.representations >=1 and cachets.repetitions == 0 %}des représentations{% elsif cachets.representations == 0 and cachets.repetitions >= 1 %}des répétitions{% elsif employee_catpro == "Technicien" %}d'engagement{% elsif cachets.representations >= 1 and cachets.repetitions >= 1 %}des répétitions et des représentations{% endif %}{% if employee_catpro == "Metteur en scène" and cachets.representations == 0 %}d'exercice de sa fonction{% endif %}, ainsi que ses horaires de travail. +

+
+ +
+

RÉMUNÉRATION

+

+ Il sera alloué à {{employee_firstname}} {{employee_lastname}} à titre de salaire la somme de {{salaire_brut}} euros bruts. +

+ {% if precisions_salaire != blank %}

+ À titre informatif, la répartition de ce salaire brut est la suivante : {{precisions_salaire}}. +

{% endif %} + {% if panierrepas != blank and hebergement != blank %} +

{{employee_firstname}} {{employee_lastname}} percevra {{panierrepas}} + {% if panierrepas == "1" %}panier repas principal {% else %}paniers repas principaux, {% endif %}et {{hebergement}} {% if hebergement == "1" %}indemnité{% else %}indemnités{% endif %} d'hébergement et petit-déjeuner, + {% if panierrepasccn == "Oui" and hebergementccn == "Oui" %} selon les conditions prévues par la Convention Collective. + {% elsif panierrepasccn == "Non" and hebergementccn == "Oui" %} à hauteur de {{montantpanierrepas}} euros par panier repas principal, et selon les conditions prévues par la Convention Collective pour l'indemnité hébergement et petit-déjeuner. + {% elsif panierrepasccn == "Oui" and hebergementccn == "Non" %} selon les conditions prévues par la Convention Collective pour les paniers repas principaux, et à hauteur de {{montanthebergement}} euros par indemnité hébergement et petit-déjeuner. + {% elsif panierrepasccn == "Non" and hebergementccn == "Non" %} à hauteur de {{montantpanierrepas}} euros par panier repas principal et à hauteur de {{montanthebergement}} euros par indemnité hébergement et petit-déjeuner. + {% endif %}

+ {% endif %} + {% if autreprecision != blank %}

{{autreprecision}}

{% endif %} +
+ +
+

RETRAITE ET CONGÉS PAYÉS

+

+ Les cotisations de retraite seront versées à AUDIENS - 7 rue Jean Bleuzen - 92177 VANVES Cedex. L'employeur acquittera ses + contributions à la caisse des Congés Spectacles conformément à la législation et dans la limite des plafonds applicables en vigueur. +

+
+ +
+

ABSENCE-MALADIE

+

+ En cas de maladie ou d'empêchement d'assurer {% if employee_catpro == "Metteur en scène" %}ses missions de mise en scène,{% elsif CCN contains "Convention Collective Nationale de la Production Audiovisuelle" %}ses missions de {{employee_profession}},{% elsif CCN contains "Convention Collective Nationale de l'Édition" %}un enregistrement,{% else %}une répétition ou une représentation,{% endif %} {{employee_firstname}} {{employee_lastname}} sera {% if employee_civ == "Monsieur" %}tenu{% else %}tenue{% endif %} + d'en aviser {{structure_name}} dans un délai de 24 heures en précisant la durée probable de son absence. En cas de prolongation d'arrêt de travail, + {{employee_firstname}} {{employee_lastname}} devra transmettre à {{structure_name}}, dans les plus brefs délais, le certificat médical + justifiant de cette prolongation. En tout état de cause, les parties conviennent expressément qu'en cas de maladie de {{employee_firstname}} {{employee_lastname}}, + le présent contrat pourra être résilié de plein droit par {{structure_name}} et ce, dans le respect des dispositions de la convention collective applicable. +

+
+ +
+

DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ

+

+ Le présent contrat donne à {{structure_name}} une priorité absolue sur tous les autres engagements que pourrait conclure par ailleurs {{employee_firstname}} {{employee_lastname}}, sur la période de l'engagement. + La dérogation éventuelle à cette clause devra faire l'objet d'un accord écrit de {{structure_name}}. +

+

+ {{employee_firstname}} {{employee_lastname}} ne pourra en aucun cas refuser sa présence {% if employee_catpro == "Metteur en scène" %}sur ses lieux de travail et aux répétitions{% elsif CCN contains "Convention Collective Nationale de la Production Audiovisuelle" %}sur les lieux de production{% elsif CCN contains "Convention Collective Nationale de l'Édition" %}sur les lieux d'enregistrement{% else %} à une répétition ou à une représentation{% endif %} pour cause + d'engagement extérieur, à quelque moment qu'il·elle ait été prévenu {% if employee_catpro == "Metteur en scène" %}de ses horaires et jours de travail et de l'existence de répétitons.{% elsif CCN contains "Convention Collective Nationale de la Production Audiovisuelle" %}de ses horaires, jours et lieux de travail.{% elsif CCN contains "Convention Collective Nationale de l'Édition" %}de cet session d'enregistrement.{% else %}de l'existence de cette répétition ou représentation.{% endif %} +

+
+ +
+

MÉDECINE DU TRAVAIL

+

+ {{employee_firstname}} {{employee_lastname}} déclare avoir satisfait aux obligations relatives à la Médecine du travail et communiquera + à {{structure_name}} l'attestation annuelle qui lui a été délivrée par cet organisme. +

+
+ +
+

ASSURANCES

+

+ {{employee_firstname}} {{employee_lastname}} est {% if employee_civ == "Monsieur" %}tenu{% else %}tenue{% endif %} d'assurer contre tous les risques tous les objets lui appartenant. {{structure_name}} + déclare avoir souscrit les assurances nécessaires à la couverture des risques liés{% if CCN contains "Convention Collective Nationale de la Production Audiovisuelle" %} à la production audiovisuelle.{% elsif CCN contains "Convention Collective Nationale de l'Édition" %} à l'édition phonographique.{% else %} aux représentations du spectacle.{% endif %} +

+
+ +
+

LITIGES

+

+ En cas de litige portant sur l'interprétation ou l'application du présent contrat, les parties conviennent de s'en remettre à l'appréciation des + tribunaux compétents, mais seulement après épuisement des voies amiables (conciliation, arbitrage). +

+
+ +
+

PROTECTION DES DONNÉES PERSONNELLES

+

+ Aux fins de gestion du personnel et de traitement des rémunérations, nous sommes amenés à solliciter des données personnelles vous concernant + à l'occasion de la conclusion, l'exécution et le cas échéant, la rupture de votre contrat de travail. +

+

+ La signature du présent contrat vaut autorisation pour la société de collecter, d'enregistrer et de stocker les données nécessaires. +

+

+ Outre les services internes de {{structure_name}}, les destinataires de ces données sont, à ce jour, les organismes de sécurité sociale, + les caisses de retraite et de prévoyance, la mutuelle, France Travail Spectacle, les services des impôts, le service de médecine du travail, les organismes conventionnels et la société + Odentas Media SAS, notre prestataire de gestion de la paie. +

+

+ Ces informations sont réservées à l'usage des services concernés et ne peuvent être communiquées qu'à ces destinataires. +

+

+ Vous bénéficiez notamment d'un droit d'accès, de rectification et d'effacement des informations vous concernant, que vous pouvez exercer + en adressant directement une demande au responsable de ces traitements : {{nom_responsable_traitement}}, {{qualite_responsable_traitement}}, {{email_responsable_traitement}}. +

+
+ +
+

+ Fait en double exemplaire, +

+{% assign ville_sans_le = structure_ville | remove_first: "Le " %} +

+ {% if structure_ville contains "Le " %} + Au {{ ville_sans_le }}, le {{ date_signature }}. + {% else %} + À {{ structure_ville }}, le {{ date_signature }}. + {% endif %} +

+

+
+ +
+
+
{% if employee_civ == "Monsieur" %}Le salarié :{% else %}La salariée :{% endif %}
+
{{employee_civ}} {{employee_firstname}} {{employee_lastname}}
+
+
{% raw %}{{Signature Employé;role=Salarié;type=signature;height=60;width=150}}{% endraw %}
+



+
+ {% if mineur1618 == "Oui" %} +
+
+
{% if representant_civ == "Monsieur" %}Le représentant légal{% elsif representant_civ == "Madame" %}La représentante légale{% endif %}{% if employee_civ == "Madame" %} de la salariée :{% elsif employee_civ == "Monsieur" %} du salarié :{% endif %}
+
{{representant_civ}} {{representant_nom}}
+
[[s|1]]
+

+
+ {% endif %} +
+
L'employeur:
+
Pour {{structure_name}},
+ {% if delegation == "Oui" %}
Pour le représentant légal et par délégation,
{% endif %} +
{{structure_signataire}},
+
{{structure_signatairequalite}}.
+
+
{% raw %}{{Signature Employeur;role=Employeur;type=signature;height=60;width=150}}{% endraw %}
+
+
\ No newline at end of file diff --git a/PDF/CSS_PDF.css b/PDF/CSS_PDF.css new file mode 100644 index 0000000..4dbf755 --- /dev/null +++ b/PDF/CSS_PDF.css @@ -0,0 +1,70 @@ +body { + font-family: Verdana, sans-serif; + font-size: 12px; + padding: 0 50px; +} + +h1 { + text-align: center; + margin-bottom: 30px; +} + +p { + margin-bottom: 0; + text-align: justify; +} + +.bold { + font-weight: bold; +} + +.section { + margin-bottom: 30px; + page-break-inside: avoid; +} + +.section-objet { + margin-bottom: 20px; +} + +.section_protection { + margin-bottom: 30px; +} + +.section-title { + font-weight: bold; + margin-bottom: 10px; +} + +ul { + list-style-type: none; +} + + .info-paragraph { + display: flex; + flex-direction: column; + } + + .info-signature { + font-size: 80px; + color: white; + } + + .info-row { + display: flex; + flex-direction: column; + margin-bottom: 10px; + } + + .info-label { + font-weight: bold; + } + + .info-value { + text-align: left; + } + + .info-delegation { + text-align: left; + font-style: italic; + } \ No newline at end of file diff --git a/PDF/donnees_test.json b/PDF/donnees_test.json new file mode 100644 index 0000000..f0ceec8 --- /dev/null +++ b/PDF/donnees_test.json @@ -0,0 +1,60 @@ +{ + "structure_name": "Association Compagnie Lazara", + "structure_adresse": "1495 Chemin de Fenestrelle", + "structure_cpville": "13400", + "structure_ville": "Le Kremlin-Bicêtre", + "structure_siret": "824 938 161 00026", + "structure_licence": "2022-999999", + "structure_signataire": "Renaud BREVIERE-ABRAHAM", + "structure_signatairequalite": "Administrateur", + "structure_spectacle": "Non", + "forme_juridique": "Association", + "delegation": "Oui", + "mineur1618": "Non", + "representant_civ": "Monsieur", + "representant_nom": "Jacques DELRUE", + "representant_dob": "27/01/1989", + "representant_cob": "Nantes (44)", + "representant_adresse": "15 rue du Château, 44000 Nantes", + "employee_civ": "Monsieur", + "employee_firstname": "Jean", + "employee_lastname": "GOLTIER", + "employee_birthname": "BALTAZAR", + "employee_dob": "06/07/1992", + "employee_cob": "Le Kremlin-Bicêtre", + "employee_address": "48 Boulevard André Aune, 13006 MARSEILLE", + "employee_ss": 0, + "employee_cs": "F574430", + "employee_profession": "Batteur", + "employee_codeprofession": "BAT010", + "employee_catpro": "Metteur en scène", + "employee_pseudo": "n/a", + "spectacle": "Mamés", + "numobjet": "6Z000000000", + "type_numobjet": "Spectacle", + "date_debut": "01/07/2023", + "date_fin": "28/08/2023", + "dates_travaillees": "00", + "details_cachets": "", + "salaire_brut": "2764,80", + "date_signature": "01/07/2023", + "CCN": "Convention Collective Nationale de l'Édition", + "precisions_salaire": "", + "panierrepas": "", + "panierrepasccn": "Non", + "montantpanierrepas": "", + "hebergement": "", + "hebergementccn": "Non", + "montanthebergement": "", + "autreprecision": "Il est entendu entre GADMER Sylvie et Balumina Films SAS que toutes les éventuelles futures embauches de la salariée dans le cadre de la production de 'L'Europe s'est arrêtée à Istanbul', en tant que cheffe monteuse, se feront sur la base d'un salaire brut de 1500,00 euros par semaine de 39 heures travaillées.", + "nom_responsable_traitement": "Renaud BREVIERE-ABRAHAM", + "qualite_responsable_traitement": "Administrateur", + "email_responsable_traitement": "contact@compagnie-lazara.fr", + "cachets": { + "representations": 2, + "repetitions": 10, + "heures": 0, + "heuresparjour": 0 + }, + "imageUrl": "" +} \ No newline at end of file diff --git a/app/(app)/staff/naa/page.tsx b/app/(app)/staff/naa/page.tsx index ee725c1..6dd5e65 100644 --- a/app/(app)/staff/naa/page.tsx +++ b/app/(app)/staff/naa/page.tsx @@ -2,8 +2,9 @@ import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { Plus, FileText, Download, Eye, Calendar, RefreshCw, Trash2 } from "lucide-react"; +import { Plus, FileText, Download, Eye, Calendar, RefreshCw, Trash2, Edit } from "lucide-react"; import CreateNAAModal from "@/components/staff/CreateNAAModal"; +import EditNAAModal from "@/components/staff/EditNAAModal"; type NAADocument = { id: string; @@ -38,6 +39,7 @@ function formatCurrency(amount: number) { export default function NAAPage() { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [editingNaaId, setEditingNaaId] = useState(null); const [loadingPdf, setLoadingPdf] = useState(null); const [regenerating, setRegenerating] = useState(null); const queryClient = useQueryClient(); @@ -166,6 +168,11 @@ export default function NAAPage() { setIsCreateModalOpen(false); }; + const handleEditSuccess = () => { + queryClient.invalidateQueries({ queryKey: ["staff-naa-list"] }); + setEditingNaaId(null); + }; + const getStatusBadge = (status: string) => { const badges = { draft: "bg-slate-100 text-slate-700", @@ -282,6 +289,14 @@ export default function NAAPage() {
+ {naa.pdf_url && ( <> +
+ + {/* Body */} +
+
+ {error && ( +
+ {error} +
+ )} + + {/* Info: Champs de base non modifiables */} +
+

+ Note : Les champs de base (apporteur, période, dates) ne sont pas modifiables. + Vous pouvez uniquement ajouter ou supprimer des prestations. +

+
+ + {/* Champs de base (lecture seule) */} +
+
+ + +
+ +
+ + +
+
+ + {/* Prestations par client */} +
+
+

+ Prestations +

+
+ {clientsPrestations.length > 0 && ( + <> + + + + )} + +
+
+ + {clientsPrestations.length === 0 ? ( +
+

Aucune prestation. Cliquez sur "Ajouter un client" pour commencer.

+
+ ) : ( +
+ {clientsPrestations.map((client, clientIndex) => ( +
+ {/* En-tête du client */} +
+ + +
+ + {/* Contenu du client */} + {client.expanded && ( +
+ {/* Sélection du client */} +
+ + +
+ + {/* Lignes de prestations */} + {/* Factures détectées pour inclusion/exclusion */} +
+
+ + {(() => { + const orgId = client.code ? referredClients.find(rc => rc.code_employeur === client.code)?.org_id : undefined; + const invoices = orgId ? clientInvoices[orgId] || [] : []; + if (orgId && invoices.length > 0) { + return ( + + ); + } + return null; + })()} +
+
+ {(() => { + const orgId = client.code ? referredClients.find(rc => rc.code_employeur === client.code)?.org_id : undefined; + const invoices = orgId ? clientInvoices[orgId] || [] : []; + if (!orgId) return
Sélectionnez un client pour voir les factures.
; + if (invoices.length === 0) return
Aucune facture détectée pour cette période.
; + + // Si replié, afficher juste la facture sélectionnée + if (!invoicesExpanded[orgId]) { + const selectedId = (selectedInvoiceIds[orgId] || [])[0]; + const selectedInvoice = invoices.find(inv => inv.id === selectedId); + if (selectedInvoice) { + return ( +
+ Facture sélectionnée : {selectedInvoice.period_label || new Date(selectedInvoice.created_at).toLocaleDateString("fr-FR")} — {parseFloat(selectedInvoice.amount_ht).toFixed(2)} € +
+ ); + } + return
Aucune facture sélectionnée
; + } + + return ( +
+ {invoices.map(inv => ( + + ))} +
+ ); + })()} +
+
+
+ {client.lines.map((line, lineIndex) => ( +
+
+ + +
+
+ + updateLine(clientIndex, lineIndex, "quantite", e.target.value)} + disabled={isUpdating} + className="w-full px-2 py-1.5 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50" + min="0" + step="1" + /> +
+
+ + updateLine(clientIndex, lineIndex, "tarif", e.target.value)} + disabled={isUpdating} + className="w-full px-2 py-1.5 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50" + min="0" + step="0.01" + /> +
+
+ + +
+
+ +
+
+ ))} +
+ + {/* Bouton ajouter prestation */} + +
+ )} +
+ ))} +
+ )} +
+
+ + {/* Footer */} +
+ + +
+
+
+ + ); +} diff --git a/lib/pdf/generateContract.tsx b/lib/pdf/generateContract.tsx new file mode 100644 index 0000000..21deb7d --- /dev/null +++ b/lib/pdf/generateContract.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import ReactPDF from '@react-pdf/renderer'; +import { ContratCDDU } from './templates/ContratCDDU'; +import { ContratCDDUData } from './types'; +import { uploadPdfToS3, generateContractS3Key } from './uploadPdf'; + +export interface GenerateContractResult { + /** Clé S3 du fichier uploadé */ + s3Key: string; + /** URL complète S3 (non signée) */ + s3Url: string; + /** Taille du PDF en bytes */ + size: number; +} + +/** + * Génère un contrat CDDU en PDF et l'upload sur S3 + * + * @param data - Données du contrat + * @param organizationId - ID de l'organisation + * @param contractId - ID du contrat + * @returns Informations sur le fichier uploadé + * + * @example + * ```typescript + * const result = await generateAndUploadContract( + * contractData, + * 'org-123', + * 'contract-456' + * ); + * + * console.log('PDF uploadé:', result.s3Key); + * // Enregistrer result.s3Key dans Supabase + * ``` + */ +export async function generateAndUploadContract( + data: ContratCDDUData, + organizationId: string, + contractId: string +): Promise { + console.log('🚀 [Contract Generation] Début de la génération du contrat:', { + organizationId, + contractId, + }); + + try { + // 1. Générer le PDF + console.log('📄 [Contract Generation] Génération du PDF...'); + const doc = ; + const pdfBlob = await ReactPDF.pdf(doc).toBlob(); + + // Convertir le Blob en Buffer + const arrayBuffer = await pdfBlob.arrayBuffer(); + const pdfBuffer = Buffer.from(arrayBuffer); + + console.log(`✅ [Contract Generation] PDF généré (${pdfBuffer.byteLength} bytes)`); + + // 2. Générer la clé S3 + const year = new Date(data.date_debut).getFullYear(); + const s3Key = generateContractS3Key(organizationId, contractId, year); + + // 3. Upload sur S3 + console.log('📤 [Contract Generation] Upload sur S3...'); + await uploadPdfToS3({ + pdfBuffer, + key: s3Key, + metadata: { + contractId, + organizationId, + employeeName: `${data.employee_firstname} ${data.employee_lastname}`, + contractType: 'CDDU', + generatedAt: new Date().toISOString(), + }, + }); + + // 4. Construire l'URL S3 + const region = process.env.AWS_REGION || 'eu-west-3'; + const bucket = process.env.AWS_S3_BUCKET || 'odentas-docs'; + const s3Url = `https://${bucket}.s3.${region}.amazonaws.com/${s3Key}`; + + console.log('✅ [Contract Generation] Contrat généré et uploadé avec succès:', { + s3Key, + s3Url, + size: pdfBuffer.byteLength, + }); + + return { + s3Key, + s3Url, + size: pdfBuffer.byteLength, + }; + } catch (error) { + console.error('❌ [Contract Generation] Erreur lors de la génération du contrat:', error); + throw new Error( + `Échec de la génération du contrat: ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +/** + * Génère uniquement le PDF sans l'uploader (utile pour les tests) + * + * @param data - Données du contrat + * @returns Buffer du PDF + */ +export async function generateContractPdf(data: ContratCDDUData): Promise { + console.log('📄 [Contract Generation] Génération du PDF uniquement...'); + + const doc = ; + const pdfBlob = await ReactPDF.pdf(doc).toBlob(); + + const arrayBuffer = await pdfBlob.arrayBuffer(); + const pdfBuffer = Buffer.from(arrayBuffer); + + console.log(`✅ [Contract Generation] PDF généré (${pdfBuffer.byteLength} bytes)`); + + return pdfBuffer; +} diff --git a/lib/pdf/index.ts b/lib/pdf/index.ts new file mode 100644 index 0000000..19a79c1 --- /dev/null +++ b/lib/pdf/index.ts @@ -0,0 +1,42 @@ +/** + * Module de génération de PDFs avec @react-pdf/renderer + * + * Ce module remplace progressivement PDFMonkey pour la génération de PDFs. + * + * @example + * ```typescript + * import { generateAndUploadContract } from '@/lib/pdf'; + * + * const result = await generateAndUploadContract( + * contractData, + * organizationId, + * contractId + * ); + * + * // Enregistrer result.s3Key dans Supabase + * await supabase + * .from('contracts') + * .update({ pdf_url: result.s3Key }) + * .eq('id', contractId); + * ``` + */ + +// Types +export type { ContratCDDUData, CachetsData } from './types'; +export type { GenerateContractResult } from './generateContract'; +export type { UploadPdfOptions } from './uploadPdf'; + +// Fonctions principales +export { + generateAndUploadContract, + generateContractPdf +} from './generateContract'; + +export { + uploadPdfToS3, + generateContractS3Key, + generatePayslipS3Key +} from './uploadPdf'; + +// Composants (si besoin d'être utilisés directement) +export { ContratCDDU } from './templates/ContratCDDU'; diff --git a/lib/pdf/templates/ContratCDDU.tsx b/lib/pdf/templates/ContratCDDU.tsx new file mode 100644 index 0000000..d6b52a3 --- /dev/null +++ b/lib/pdf/templates/ContratCDDU.tsx @@ -0,0 +1,592 @@ +import React from 'react'; +import { Document, Page, Text, View, Image, StyleSheet, Font } from '@react-pdf/renderer'; +import { ContratCDDUData } from '../types'; + +// Styles du document (conversion du CSS) +const styles = StyleSheet.create({ + page: { + fontFamily: 'Helvetica', + fontSize: 12, + paddingHorizontal: 50, + paddingVertical: 40, + }, + logoContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }, + logo: { + width: 180, + }, + title: { + textAlign: 'center', + marginBottom: 30, + fontSize: 14, + fontWeight: 'bold', + }, + bold: { + fontWeight: 'bold', + }, + section: { + marginBottom: 30, + }, + sectionObjet: { + marginBottom: 20, + }, + sectionTitle: { + fontWeight: 'bold', + marginBottom: 10, + fontSize: 12, + }, + paragraph: { + marginBottom: 0, + textAlign: 'justify', + }, + list: { + marginLeft: 0, + }, + listItem: { + marginBottom: 5, + }, + infoLabel: { + fontWeight: 'bold', + marginBottom: 5, + }, + infoValue: { + textAlign: 'left', + marginBottom: 5, + }, + infoDelegation: { + textAlign: 'left', + fontStyle: 'italic', + marginBottom: 5, + }, + signatureSpace: { + marginTop: 20, + marginBottom: 60, + }, +}); + +interface ContratCDDUProps { + data: ContratCDDUData; +} + +export const ContratCDDU: React.FC = ({ data }) => { + // Helpers pour la logique conditionnelle + const isMadame = data.employee_civ === 'Madame'; + const isMonsieur = data.employee_civ === 'Monsieur'; + const isArtiste = data.employee_catpro === 'Artiste'; + const isTechnicien = data.employee_catpro === 'Technicien'; + const isMetteurEnScene = data.employee_catpro === 'Metteur en scène'; + + // Titre du contrat selon la catégorie + const getTitreContrat = () => { + if (isArtiste) return 'ARTISTE'; + if (isMadame && isTechnicien) return 'TECHNICIENNE'; + if (isMonsieur && isTechnicien) return 'TECHNICIEN'; + if (isMadame && isMetteurEnScene) return '\nARTISTE CADRE'; + return 'ARTISTE CADRE'; + }; + + // Formatage des dates travaillées + const getDatesFormatted = () => { + if (!data.dates_travaillees || data.dates_travaillees === '00') return null; + return data.dates_travaillees.split(';').map(d => d.trim()); + }; + + // Manipulation du lieu de naissance (retirer "Le ") + const getCobFormatted = () => { + const cob = data.employee_cob; + if (cob.startsWith('Le ')) { + return { prefix: 'au', ville: cob.replace(/^Le /, '') }; + } + return { prefix: 'à', ville: cob }; + }; + + // Manipulation de la ville (pour signature) + const getVilleSignature = () => { + const ville = data.structure_ville; + if (ville.includes('Le ')) { + return { prefix: 'Au', ville: ville.replace(/^Le /, '') }; + } + return { prefix: 'À', ville }; + }; + + // Convention collective formatée + const getCCNFormatted = () => { + if (Array.isArray(data.CCN)) { + return data.CCN.join(', '); + } + return data.CCN; + }; + + const cobData = getCobFormatted(); + const villeSignature = getVilleSignature(); + const datesArray = getDatesFormatted(); + const ccnFormatted = getCCNFormatted(); + + return ( + + + {/* Logo */} + {data.imageUrl && ( + + + + )} + + {/* Titre */} + + CONTRAT D'ENGAGEMENT {getTitreContrat()} + + + {/* Entre les soussignés */} + + Entre les {isMonsieur ? 'soussignés' : 'soussignées'} : + + + + {data.structure_name} + {data.forme_juridique} + {data.structure_adresse} + {data.structure_cpville} {data.structure_ville} + SIRET : {data.structure_siret} + {data.structure_licence !== 'n/a' && ( + + Licence d'entrepreneur de spectacles : {data.structure_licence} + + )} + + représentée par {data.structure_signataire}, en sa qualité{' '} + {data.structure_signatairequalite === 'Administrateur' ? "d'" : 'de '} + {data.structure_signatairequalite} + {data.delegation === 'Oui' + ? ', pour le représentant légal et par délégation.' + : '.'} + + + + d'une part, + et : + + {/* Salarié */} + + + {data.employee_civ} {data.employee_firstname} {data.employee_lastname} + {data.employee_birthname !== data.employee_lastname && ( + <> + {isMonsieur ? ', né ' : ', née '} + {data.employee_birthname} + + )} + {data.employee_pseudo !== 'n/a' && ( + <> + , {isMonsieur ? 'dit' : 'dite'} "{data.employee_pseudo}" + + )} + + + {isMonsieur ? 'né' : 'née'} le {data.employee_dob} {cobData.prefix} {cobData.ville} + + demeurant {data.employee_address} + {(!data.employee_ss || data.employee_ss === 0 || data.employee_ss === '') ? ( + + Le numéro de Sécurité Sociale du salarié est en cours d'attribution. + + ) : ( + + N° de Sécurité Sociale : {data.employee_ss} + + )} + N° Congés Spectacles : {data.employee_cs} + + {/* Représentant légal si mineur */} + {data.mineur1618 === 'Oui' && ( + + dont {data.representant_civ === 'Monsieur' ? 'le représentant légal' : 'la représentante légale'} est{' '} + {data.representant_civ} {data.representant_nom},{' '} + {data.representant_civ === 'Monsieur' ? 'né' : 'née'} le {data.representant_dob} à{' '} + {data.representant_cob}, demeurant {data.representant_adresse}. + + )} + + + d'autre part. + + {/* Préambule */} + + Le présent contrat est conclu dans le cadre de la législation du travail, des usages en vigueur dans la + profession, de l'article L. 1242-2° du Code du travail et de l'accord interbranche sur le recours au + contrat à durée déterminée d'usage dans le spectacle du 12/10/1998. Il est, en outre, régi par les + dispositions de la {ccnFormatted} + {ccnFormatted.includes('Convention Collective Nationale de l\'Édition') && + ' et de ses annexes afférentes à l\'Édition Phonographique'}. + + + Il a été convenu et arrêté ce qui suit : + + {/* Section OBJET */} + + OBJET + + {data.employee_civ} {data.employee_firstname} {data.employee_lastname} est{' '} + {isMonsieur ? 'engagé' : 'engagée'} selon l'objet suivant : + + + Profession : {data.employee_profession} + Code emploi : {data.employee_codeprofession} + {(data.structure_spectacle === 'Oui' && data.type_numobjet !== 'Administratif') || + ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') || + ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + + + {data.structure_spectacle === 'Oui' && data.type_numobjet !== 'Administratif' + ? 'Spectacle' + : 'Production'} + : {data.spectacle} + + ) : null} + {data.numobjet ? ( + + Numéro d'objet : {data.numobjet} + + ) : ( + + Le numéro d'objet de cette production est en cours d'attribution. + + )} + + + + {/* Section DURÉE DE L'ENGAGEMENT - Partie 1 */} + + DURÉE DE L'ENGAGEMENT + + {data.date_debut === data.date_fin ? ( + <>Le présent engagement couvre la journée du {data.date_debut}, pour + ) : ( + <> + {datesArray ? ( + <>Le présent engagement couvre la période du {data.date_debut} au {data.date_fin} pour les dates travaillées suivantes : + ) : ( + <>Le présent engagement couvre la période du {data.date_debut} au {data.date_fin}. + )} + + )} + + + {/* Dates travaillées */} + {datesArray && ( + + {datesArray.map((date, index) => ( + + - {date}{index < datesArray.length - 1 ? ' ;' : ''} + + ))} + + )} + + {/* Suite selon catégorie professionnelle */} + {data.date_debut !== data.date_fin && Pour } + + {/* Artiste */} + {isArtiste && ( + + un total de{' '} + {data.cachets.representations >= 1 && data.cachets.repetitions >= 1 && ( + <> + {data.cachets.representations} {data.cachets.representations === 1 ? 'cachet' : 'cachets'} de représentation et{' '} + {data.cachets.repetitions} {data.cachets.repetitions === 1 ? 'service' : 'services'} de répétition. + + )} + {data.cachets.representations >= 1 && data.cachets.repetitions === 0 && ( + <> + {data.cachets.representations} {data.cachets.representations === 1 ? 'cachet' : 'cachets'} + {ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') || + ccnFormatted.includes('Convention Collective Nationale de l\'Édition') + ? ' d\'enregistrement.' + : ' de représentation.'} + + )} + {data.cachets.representations === 0 && data.cachets.repetitions >= 1 && ( + <> + {data.cachets.repetitions} {data.cachets.repetitions === 1 ? 'service' : 'services'} de répétition. + + )} + + )} + + {/* Technicien */} + {isTechnicien && ( + + un total de {data.cachets.heures} heures de travail + {data.cachets.heuresparjour === 0 ? '.' : `, à raison de ${data.cachets.heuresparjour} heures par jour de travail.`} + + )} + + {/* Metteur en scène */} + {isMetteurEnScene && ( + + {data.cachets.representations >= 1 && data.cachets.heures > 0 ? ( + <> + un total de {data.cachets.representations} {data.cachets.representations === 1 ? 'cachet' : 'cachets'} de représentation et{' '} + {data.cachets.heures} heures de travail. + + ) : data.cachets.representations === 0 ? ( + <>un total de {data.cachets.heures} heures de travail. + ) : ( + <>un total de {data.cachets.representations} {data.cachets.representations === 1 ? 'cachet' : 'cachets'} de représentation. + )} + + )} + + {/* Durée répétitions */} + {data.cachets.repetitions >= 1 && ( + + La durée totale des répétitions sera de {data.cachets.heures} heures + {data.cachets.heuresparjour === 0 + ? '.' + : `, à raison de ${data.cachets.heuresparjour} heures par journée de répétition.`} + + )} + + + + + Il ne nous sera, en aucun cas, fait obligation de proroger le présent engagement à expiration. La fin de la période d'engagement prévue + aux présentes, prorogée éventuellement de la durée de dépassement, en constitue le terme. Il n'y a lieu à aucun préavis. + + + + {/* LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL */} + + LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL + + {data.structure_name} communiquera à {data.employee_firstname} {data.employee_lastname} les lieux{' '} + {ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') || + ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + <> + ) : data.cachets.representations >= 1 && data.cachets.repetitions === 0 ? ( + <>des représentations + ) : data.cachets.representations === 0 && data.cachets.repetitions >= 1 ? ( + <>des répétitions + ) : isTechnicien ? ( + <>d'engagement + ) : data.cachets.representations >= 1 && data.cachets.repetitions >= 1 ? ( + <>des répétitions et des représentations + ) : null} + {isMetteurEnScene && data.cachets.representations === 0 && <>d'exercice de sa fonction} + , ainsi que ses horaires de travail. + + + + {/* RÉMUNÉRATION */} + + RÉMUNÉRATION + + Il sera alloué à {data.employee_firstname} {data.employee_lastname} à titre de salaire la somme de {data.salaire_brut} euros bruts. + + {data.precisions_salaire && ( + + À titre informatif, la répartition de ce salaire brut est la suivante : {data.precisions_salaire}. + + )} + {data.panierrepas && data.hebergement && ( + + {data.employee_firstname} {data.employee_lastname} percevra {data.panierrepas}{' '} + {data.panierrepas === '1' ? 'panier repas principal ' : 'paniers repas principaux, '} + et {data.hebergement} {data.hebergement === '1' ? 'indemnité' : 'indemnités'} d'hébergement et petit-déjeuner,{' '} + {data.panierrepasccn === 'Oui' && data.hebergementccn === 'Oui' ? ( + <>selon les conditions prévues par la Convention Collective. + ) : data.panierrepasccn === 'Non' && data.hebergementccn === 'Oui' ? ( + <> + à hauteur de {data.montantpanierrepas} euros par panier repas principal, et selon les conditions prévues par la Convention Collective pour l'indemnité hébergement et petit-déjeuner. + + ) : data.panierrepasccn === 'Oui' && data.hebergementccn === 'Non' ? ( + <> + selon les conditions prévues par la Convention Collective pour les paniers repas principaux, et à hauteur de {data.montanthebergement} euros par indemnité hébergement et petit-déjeuner. + + ) : ( + <> + à hauteur de {data.montantpanierrepas} euros par panier repas principal et à hauteur de {data.montanthebergement} euros par indemnité hébergement et petit-déjeuner. + + )} + + )} + {data.autreprecision && ( + {data.autreprecision} + )} + + + {/* RETRAITE ET CONGÉS PAYÉS */} + + RETRAITE ET CONGÉS PAYÉS + + Les cotisations de retraite seront versées à AUDIENS - 7 rue Jean Bleuzen - 92177 VANVES Cedex. L'employeur acquittera ses + contributions à la caisse des Congés Spectacles conformément à la législation et dans la limite des plafonds applicables en vigueur. + + + + {/* ABSENCE-MALADIE */} + + ABSENCE-MALADIE + + En cas de maladie ou d'empêchement d'assurer{' '} + {isMetteurEnScene ? ( + <>ses missions de mise en scène, + ) : ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') ? ( + <>ses missions de {data.employee_profession}, + ) : ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + <>un enregistrement, + ) : ( + <>une répétition ou une représentation, + )}{' '} + {data.employee_firstname} {data.employee_lastname} sera {isMonsieur ? 'tenu' : 'tenue'} d'en aviser {data.structure_name} dans un délai de 24 heures en précisant la durée probable de son absence. En cas de prolongation d'arrêt de travail, + {' '}{data.employee_firstname} {data.employee_lastname} devra transmettre à {data.structure_name}, dans les plus brefs délais, le certificat médical + justifiant de cette prolongation. En tout état de cause, les parties conviennent expressément qu'en cas de maladie de {data.employee_firstname} {data.employee_lastname}, + le présent contrat pourra être résilié de plein droit par {data.structure_name} et ce, dans le respect des dispositions de la convention collective applicable. + + + + {/* DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ */} + + DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ + + Le présent contrat donne à {data.structure_name} une priorité absolue sur tous les autres engagements que pourrait conclure par ailleurs {data.employee_firstname} {data.employee_lastname}, sur la période de l'engagement. + La dérogation éventuelle à cette clause devra faire l'objet d'un accord écrit de {data.structure_name}. + + + {data.employee_firstname} {data.employee_lastname} ne pourra en aucun cas refuser sa présence{' '} + {isMetteurEnScene ? ( + <>sur ses lieux de travail et aux répétitions + ) : ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') ? ( + <>sur les lieux de production + ) : ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + <>sur les lieux d'enregistrement + ) : ( + <>à une répétition ou à une représentation + )}{' '} + pour cause d'engagement extérieur, à quelque moment qu'il·elle ait été prévenu{' '} + {isMetteurEnScene ? ( + <>de ses horaires et jours de travail et de l'existence de répétitons. + ) : ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') ? ( + <>de ses horaires, jours et lieux de travail. + ) : ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + <>de cet session d'enregistrement. + ) : ( + <>de l'existence de cette répétition ou représentation. + )} + + + + {/* MÉDECINE DU TRAVAIL */} + + MÉDECINE DU TRAVAIL + + {data.employee_firstname} {data.employee_lastname} déclare avoir satisfait aux obligations relatives à la Médecine du travail et communiquera + à {data.structure_name} l'attestation annuelle qui lui a été délivrée par cet organisme. + + + + {/* ASSURANCES */} + + ASSURANCES + + {data.employee_firstname} {data.employee_lastname} est {isMonsieur ? 'tenu' : 'tenue'} d'assurer contre tous les risques tous les objets lui appartenant. {data.structure_name} + déclare avoir souscrit les assurances nécessaires à la couverture des risques liés + {ccnFormatted.includes('Convention Collective Nationale de la Production Audiovisuelle') ? ( + <> à la production audiovisuelle. + ) : ccnFormatted.includes('Convention Collective Nationale de l\'Édition') ? ( + <> à l'édition phonographique. + ) : ( + <> aux représentations du spectacle. + )} + + + + {/* LITIGES */} + + LITIGES + + En cas de litige portant sur l'interprétation ou l'application du présent contrat, les parties conviennent de s'en remettre à l'appréciation des + tribunaux compétents, mais seulement après épuisement des voies amiables (conciliation, arbitrage). + + + + {/* PROTECTION DES DONNÉES PERSONNELLES */} + + PROTECTION DES DONNÉES PERSONNELLES + + Aux fins de gestion du personnel et de traitement des rémunérations, nous sommes amenés à solliciter des données personnelles vous concernant + à l'occasion de la conclusion, l'exécution et le cas échéant, la rupture de votre contrat de travail. + + + La signature du présent contrat vaut autorisation pour la société de collecter, d'enregistrer et de stocker les données nécessaires. + + + Outre les services internes de {data.structure_name}, les destinataires de ces données sont, à ce jour, les organismes de sécurité sociale, + les caisses de retraite et de prévoyance, la mutuelle, France Travail Spectacle, les services des impôts, le service de médecine du travail, les organismes conventionnels et la société + Odentas Media SAS, notre prestataire de gestion de la paie. + + + Ces informations sont réservées à l'usage des services concernés et ne peuvent être communiquées qu'à ces destinataires. + + + Vous bénéficiez notamment d'un droit d'accès, de rectification et d'effacement des informations vous concernant, que vous pouvez exercer + en adressant directement une demande au responsable de ces traitements : {data.nom_responsable_traitement}, {data.qualite_responsable_traitement}, {data.email_responsable_traitement}. + + + + {/* Fait à / Date signature */} + + Fait en double exemplaire, + + {villeSignature.prefix} {villeSignature.ville}, le {data.date_signature}. + + + + {/* Signatures */} + + + + {isMonsieur ? 'Le salarié :' : 'La salariée :'} + + + {data.employee_civ} {data.employee_firstname} {data.employee_lastname} + + (Signature électronique via DocuSeal) + + + {/* Signature représentant légal si mineur */} + {data.mineur1618 === 'Oui' && ( + + + {data.representant_civ === 'Monsieur' ? 'Le représentant légal' : 'La représentante légale'} + {isMadame ? ' de la salariée :' : ' du salarié :'} + + + {data.representant_civ} {data.representant_nom} + + (Signature électronique via DocuSeal) + + )} + + {/* Signature employeur */} + + L'employeur: + Pour {data.structure_name}, + {data.delegation === 'Oui' && ( + + Pour le représentant légal et par délégation, + + )} + {data.structure_signataire}, + {data.structure_signatairequalite}. + (Signature électronique via DocuSeal) + + + + + ); +}; diff --git a/lib/pdf/types.ts b/lib/pdf/types.ts new file mode 100644 index 0000000..94c58f5 --- /dev/null +++ b/lib/pdf/types.ts @@ -0,0 +1,82 @@ +/** + * Types pour la génération de PDF de contrats CDDU + */ + +export interface CachetsData { + representations: number; + repetitions: number; + heures: number; + heuresparjour: number; +} + +export interface ContratCDDUData { + // Structure employeur + structure_name: string; + structure_adresse: string; + structure_cpville: string; + structure_ville: string; + structure_siret: string; + structure_licence: string; + structure_signataire: string; + structure_signatairequalite: string; + structure_spectacle: string; + delegation: string; + forme_juridique: string; + + // Représentant légal (mineur) + mineur1618: string; + representant_civ: string; + representant_nom: string; + representant_dob: string; + representant_cob: string; + representant_adresse: string; + + // Salarié + employee_civ: string; + employee_firstname: string; + employee_lastname: string; + employee_birthname: string; + employee_dob: string; + employee_cob: string; + employee_address: string; + employee_ss: number | string; + employee_cs: string; + employee_profession: string; + employee_codeprofession: string; + employee_catpro: string; + employee_pseudo: string; + + // Spectacle/Production + spectacle: string; + numobjet: string; + type_numobjet: string; + + // Dates et durée + date_debut: string; + date_fin: string; + dates_travaillees: string; + date_signature: string; + + // Rémunération + salaire_brut: string; + precisions_salaire: string; + panierrepas: string; + panierrepasccn: string; + montantpanierrepas: string; + hebergement: string; + hebergementccn: string; + montanthebergement: string; + autreprecision: string; + cachets: CachetsData; + + // Convention collective + CCN: string | string[]; + + // Protection des données + nom_responsable_traitement: string; + qualite_responsable_traitement: string; + email_responsable_traitement: string; + + // Logo + imageUrl?: string; +} diff --git a/lib/pdf/uploadPdf.ts b/lib/pdf/uploadPdf.ts new file mode 100644 index 0000000..435e2a7 --- /dev/null +++ b/lib/pdf/uploadPdf.ts @@ -0,0 +1,125 @@ +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; + +const REGION = process.env.AWS_REGION || 'eu-west-3'; +const BUCKET = (process.env.AWS_S3_BUCKET || 'odentas-docs').trim(); + +const s3Client = new S3Client({ + region: REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, +}); + +export interface UploadPdfOptions { + /** Buffer du PDF à uploader */ + pdfBuffer: Buffer; + /** Clé S3 (chemin + nom du fichier), ex: 'contrats/2025/contrat-123.pdf' */ + key: string; + /** Type de contenu (par défaut: application/pdf) */ + contentType?: string; + /** Métadonnées supplémentaires */ + metadata?: Record; +} + +/** + * Upload un PDF généré sur S3 + * + * @param options - Options d'upload + * @returns La clé S3 du fichier uploadé + * + * @example + * ```typescript + * const pdfBuffer = await generateContratPdf(data); + * const s3Key = await uploadPdfToS3({ + * pdfBuffer, + * key: `contrats/${organizationId}/${contractId}.pdf`, + * metadata: { + * contractId: contractId, + * organizationId: organizationId, + * generatedAt: new Date().toISOString(), + * } + * }); + * ``` + */ +export async function uploadPdfToS3(options: UploadPdfOptions): Promise { + const { pdfBuffer, key, contentType = 'application/pdf', metadata = {} } = options; + + console.log('📤 [S3 Upload] Début de l\'upload du PDF:', { + key, + bucket: BUCKET, + region: REGION, + size: pdfBuffer.byteLength, + metadata, + }); + + try { + const command = new PutObjectCommand({ + Bucket: BUCKET, + Key: key, + Body: pdfBuffer, + ContentType: contentType, + Metadata: metadata, + }); + + await s3Client.send(command); + + console.log('✅ [S3 Upload] PDF uploadé avec succès:', { + key, + bucket: BUCKET, + size: pdfBuffer.byteLength, + }); + + return key; + } catch (error) { + console.error('❌ [S3 Upload] Erreur lors de l\'upload du PDF:', error); + throw new Error(`Échec de l'upload du PDF sur S3: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * Génère une clé S3 pour un contrat CDDU + * + * @param organizationId - ID de l'organisation + * @param contractId - ID du contrat + * @param year - Année du contrat (par défaut: année courante) + * @returns Clé S3 formatée + * + * @example + * ```typescript + * const key = generateContractS3Key('org-123', 'contract-456', 2025); + * // Retourne: 'contrats/org-123/2025/contract-456.pdf' + * ``` + */ +export function generateContractS3Key( + organizationId: string, + contractId: string, + year: number = new Date().getFullYear() +): string { + return `contrats/${organizationId}/${year}/${contractId}.pdf`; +} + +/** + * Génère une clé S3 pour une fiche de paie + * + * @param organizationId - ID de l'organisation + * @param payslipId - ID de la fiche de paie + * @param year - Année de la fiche de paie + * @param month - Mois de la fiche de paie (1-12) + * @returns Clé S3 formatée + * + * @example + * ```typescript + * const key = generatePayslipS3Key('org-123', 'payslip-456', 2025, 10); + * // Retourne: 'fiches-paie/org-123/2025/10/payslip-456.pdf' + * ``` + */ +export function generatePayslipS3Key( + organizationId: string, + payslipId: string, + year: number, + month: number +): string { + const monthPadded = String(month).padStart(2, '0'); + return `fiches-paie/${organizationId}/${year}/${monthPadded}/${payslipId}.pdf`; +} diff --git a/package-lock.json b/package-lock.json index 10719a8..388e69b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@react-pdf-viewer/core": "^3.12.0", "@react-pdf-viewer/default-layout": "^3.12.0", + "@react-pdf/renderer": "^4.3.1", "@supabase/auth-helpers-nextjs": "^0.10.0", "@supabase/ssr": "^0.7.0", "@supabase/supabase-js": "^2.57.4", @@ -3384,6 +3385,189 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@react-pdf/fns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", + "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==", + "license": "MIT" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.3.tgz", + "integrity": "sha512-N1qQDZr6phXYQOp033Hvm2nkUkx2LkszjGPbmRavs9VOYzi4sp31MaccMKptL24ii6UhBh/z9yPUhnuNe/qHwA==", + "license": "MIT", + "dependencies": { + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/types": "^2.9.1", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz", + "integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.1.tgz", + "integrity": "sha512-GVzdlWoZWldRDzlWj3SttRXmVDxg7YfraAohwy+o9gb9hrbDJaaAV6jV3pc630Evd3K46OAzk8EFu8EgPDuVuA==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/image": "^3.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "emoji-regex-xs": "^1.0.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/pdfkit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.4.tgz", + "integrity": "sha512-/nITLggsPlB66bVLnm0X7MNdKQxXelLGZG6zB5acF5cCgkFwmXHnLNyxYOUD4GMOMg1HOPShXDKWrwk2ZeHsvw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "crypto-js": "^4.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", + "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==", + "license": "MIT" + }, + "node_modules/@react-pdf/reconciler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz", + "integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/reconciler/node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" + }, + "node_modules/@react-pdf/render": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.1.tgz", + "integrity": "sha512-v1WAaAhQShQZGcBxfjkEThGCHVH9CSuitrZ1bIOLvB5iBKM14abYK5D6djKhWCwF6FTzYeT2WRjRMVgze/ND2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.1.tgz", + "integrity": "sha512-dPKHiwGTaOsKqNWCHPYYrx8CDfAGsUnV4tvRsEu0VPGxuot1AOq/M+YgfN/Pb+MeXCTe2/lv6NvA8haUtj3tsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/font": "^4.0.3", + "@react-pdf/layout": "^4.4.1", + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/reconciler": "^1.1.4", + "@react-pdf/render": "^4.3.1", + "@react-pdf/types": "^2.9.1", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/renderer/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.1.tgz", + "integrity": "sha512-Iyw0A3wRIeQLN4EkaKf8yF9MvdMxiZ8JjoyzLzDHSxnKYoOA4UGu84veCb8dT9N8MxY5x7a0BUv/avTe586Plg==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/types": "^2.9.1", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz", + "integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.1.tgz", + "integrity": "sha512-5GoCgG0G5NMgpPuHbKG2xcVRQt7+E5pg3IyzVIIozKG3nLcnsXW4zy25vG1ZBQA0jmo39q34au/sOnL/0d1A4w==", + "license": "MIT", + "dependencies": { + "@react-pdf/font": "^4.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4995,6 +5179,12 @@ "optional": true, "peer": true }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -5510,6 +5700,15 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5553,6 +5752,30 @@ "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserify-zlib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/browserslist": { "version": "4.26.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", @@ -5813,6 +6036,15 @@ "wrap-ansi": "^6.2.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/cloudinary": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz", @@ -5892,6 +6124,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -5974,6 +6216,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -6174,6 +6422,12 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -6273,6 +6527,12 @@ "dev": true, "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -6930,7 +7190,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7107,6 +7366,32 @@ } } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/fontkit/node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7650,6 +7935,21 @@ "node": ">= 0.4" } }, + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", + "license": "MIT", + "dependencies": { + "hsl-to-rgb-for-reals": "^1.1.0" + } + }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", + "license": "ISC" + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -7678,6 +7978,12 @@ "node": ">= 6" } }, + "node_modules/hyphen": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", + "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==", + "license": "ISC" + }, "node_modules/ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -7794,6 +8100,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -8160,6 +8472,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -8252,6 +8570,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "license": "MIT", + "dependencies": { + "restructure": "^3.0.0" + } + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -8481,6 +8808,25 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8642,6 +8988,12 @@ "node": ">= 0.4" } }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", + "license": "MIT" + }, "node_modules/merge-refs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz", @@ -9022,6 +9374,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -9047,7 +9408,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9295,6 +9655,12 @@ "node": ">=6" } }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9753,7 +10119,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/posthog-js": { @@ -9817,7 +10182,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9878,6 +10242,15 @@ "node": ">=0.4.x" } }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9954,7 +10327,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-pdf": { @@ -10166,6 +10538,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -10213,6 +10594,12 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10624,6 +11011,15 @@ "optional": true, "peer": true }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -10697,8 +11093,6 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -11039,6 +11433,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" + }, "node_modules/svg-pathdata": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", @@ -11207,6 +11607,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -11476,6 +11882,32 @@ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "license": "MIT" }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -11640,7 +12072,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, "license": "MIT" }, "node_modules/utrie": { @@ -11661,6 +12092,20 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -12032,6 +12477,12 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" } } } diff --git a/package.json b/package.json index e959cde..b7dea89 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@react-pdf-viewer/core": "^3.12.0", "@react-pdf-viewer/default-layout": "^3.12.0", + "@react-pdf/renderer": "^4.3.1", "@supabase/auth-helpers-nextjs": "^0.10.0", "@supabase/ssr": "^0.7.0", "@supabase/supabase-js": "^2.57.4",