feat(naa): Amélioration UX modal EditNAA - replier/déplier
- Tous les clients repliés par défaut à l'ouverture du modal - Boutons 'Tout replier' / 'Tout déplier' pour gérer tous les clients - Section factures repliable avec bouton Afficher/Masquer - Affichage résumé facture sélectionnée quand section repliée - Nouveau client déplié automatiquement pour faciliter la saisie - Améliore la lisibilité pour NAA avec nombreux clients
This commit is contained in:
parent
35d5283434
commit
6485db4a75
18 changed files with 3290 additions and 51 deletions
286
PDF/CDDU.html
Normal file
286
PDF/CDDU.html
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<div style="display: flex; justify-content: center;">
|
||||
<img src="{{imageUrl}}" style="width: 180px;" />
|
||||
</div>
|
||||
|
||||
{% assign cachet_representation = "cachet_representation" %}
|
||||
|
||||
<h1>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" %}<br>ARTISTE CADRE{% else %}ARTISTE CADRE{% endif %}</h1>
|
||||
|
||||
<p class="bold">Entre les {% if employee_civ == "Monsieur" %}soussignés{% else %}soussignées{% endif %} :</p>
|
||||
<ul>
|
||||
<li class="bold">{{structure_name}}</li>
|
||||
<li>{{forme_juridique}}</li>
|
||||
<li>{{structure_adresse}}</li>
|
||||
<li>{{structure_cpville}} {{structure_ville}}</li>
|
||||
<li>SIRET : {{structure_siret}}</li>
|
||||
{% if structure_licence != "n/a" %}<li>Licence d'entrepreneur de spectacles : {{structure_licence}}</li>{% endif %}
|
||||
<li>représentée par {{structure_signataire}}, en sa qualité {% if structure_signatairequalite == "Administrateur" %}d'{% else %}de {% endif %}{{structure_signatairequalite}}{% if delegation == "Oui" %}, pour le représentant légal et par délégation.{% else %}.{% endif %}</li>
|
||||
</ul>
|
||||
|
||||
<p class="bold">d'une part,</p>
|
||||
|
||||
<p class="bold">et :</p>
|
||||
|
||||
<ul>
|
||||
<li class="bold">{{employee_civ}} {{employee_firstname}} {{employee_lastname}}{% if employee_birthname != employee_lastname %}{% if employee_civ == "Monsieur" %}, né {{employee_birthname}}{% elsif employee_civ == "Madame" %}, née {{employee_birthname}}{% endif %}{% endif %}{% if employee_pseudo != "n/a" %}, {% if employee_civ == "Monsieur" %}dit{% else %}dite{% endif %} "{{employee_pseudo}}"{% endif %}</li>
|
||||
{% assign cob_sans_le = employee_cob | remove_first: "Le " %}
|
||||
<li>
|
||||
{% if employee_civ == "Monsieur" %}
|
||||
né
|
||||
{% else %}
|
||||
née
|
||||
{% endif %}
|
||||
le {{ employee_dob }}
|
||||
{% if employee_cob contains "Le " %}
|
||||
au {{ cob_sans_le }}
|
||||
{% else %}
|
||||
à {{ employee_cob }}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>demeurant {{employee_address}}</li>
|
||||
{% if employee_ss == 0 or employee_ss == '' or employee_ss == nil %}
|
||||
<li>Le numéro de Sécurité Sociale du salarié est en cours d'attribution.</li>
|
||||
{% else %}
|
||||
<li>N° de Sécurité Sociale : {{ employee_ss }}</li>
|
||||
{% endif %}
|
||||
<li>N° Congés Spectacles : {{employee_cs}}</li>
|
||||
{% if mineur1618 == "Oui" %}<li>dont {% if representant_civ == "Monsieur" %}le représentant légal{% elsif representant_civ == "Madame" %}la représentante légale{% endif %} est {{representant_civ}} {{representant_nom}}, {% if representant_civ == "Monsieur" %}né{% elsif representant_civ == "Madame" %}née{% endif %} le {{representant_dob}} à {{representant_cob}}, demeurant {{representant_adresse}}.</li>{% endif %}
|
||||
</ul>
|
||||
|
||||
<p class="bold">d'autre part.</p>
|
||||
|
||||
<p>
|
||||
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 %}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Il a été convenu et arrêté ce qui suit :
|
||||
</p>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">OBJET</h2>
|
||||
{{employee_civ}} {{employee_firstname}} {{employee_lastname}} est {% if employee_civ == "Monsieur" %}engagé{% else %}engagée{% endif %} selon l'objet suivant :
|
||||
<ul>
|
||||
<li><b>Profession</b> : {{employee_profession}}</li>
|
||||
<li><b>Code emploi</b> : {{employee_codeprofession}}</li>
|
||||
<li>
|
||||
{% if structure_spectacle == "Oui" and type_numobjet != "Administratif" %}
|
||||
<b>Spectacle</b> : {{ spectacle }}
|
||||
{% elsif CCN contains "Convention Collective Nationale de la Production Audiovisuelle" or CCN contains "Convention Collective Nationale de l'Édition" %}
|
||||
<b>Production</b> : {{ spectacle }}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>{% if numobjet != empty %}<b>Numéro d'objet</b> : {{numobjet}}
|
||||
{% elsif numobjet == empty %}Le <b>numéro d'objet</b> de cette production est en cours d'attribution.
|
||||
{% endif %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section-objet">
|
||||
<h2 class="section-title">DURÉE DE L'ENGAGEMENT</h2>
|
||||
<p>
|
||||
{% 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 %}
|
||||
<ul>
|
||||
{% assign dates = dates_travaillees | split: ";" %}
|
||||
{% for date in dates %}
|
||||
<li>
|
||||
- {{ date | strip }}
|
||||
{% if forloop.last %}
|
||||
|
||||
{% else %}
|
||||
;
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
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 %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% 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 %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
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.
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL</h2>
|
||||
<p>
|
||||
{{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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">RÉMUNÉRATION</h2>
|
||||
<p>
|
||||
Il sera alloué à {{employee_firstname}} {{employee_lastname}} à titre de salaire la somme de {{salaire_brut}} euros bruts.
|
||||
</p>
|
||||
{% if precisions_salaire != blank %}<p>
|
||||
À titre informatif, la répartition de ce salaire brut est la suivante : {{precisions_salaire}}.
|
||||
</p>{% endif %}
|
||||
{% if panierrepas != blank and hebergement != blank %}
|
||||
<p>{{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 %}</p>
|
||||
{% endif %}
|
||||
{% if autreprecision != blank %}<p>{{autreprecision}}</p>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">RETRAITE ET CONGÉS PAYÉS</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">ABSENCE-MALADIE</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ</h2>
|
||||
<p>
|
||||
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}}.
|
||||
</p>
|
||||
<p>
|
||||
{{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 %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">MÉDECINE DU TRAVAIL</h2>
|
||||
<p>
|
||||
{{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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">ASSURANCES</h2>
|
||||
<p>
|
||||
{{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 %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">LITIGES</h2>
|
||||
<p>
|
||||
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).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">PROTECTION DES DONNÉES PERSONNELLES</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
La signature du présent contrat vaut autorisation pour la société de collecter, d'enregistrer et de stocker les données nécessaires.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Ces informations sont réservées à l'usage des services concernés et ne peuvent être communiquées qu'à ces destinataires.
|
||||
</p>
|
||||
<p>
|
||||
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}}.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<p>
|
||||
Fait en double exemplaire,
|
||||
</p>
|
||||
{% assign ville_sans_le = structure_ville | remove_first: "Le " %}
|
||||
<p>
|
||||
{% if structure_ville contains "Le " %}
|
||||
Au {{ ville_sans_le }}, le {{ date_signature }}.
|
||||
{% else %}
|
||||
À {{ structure_ville }}, le {{ date_signature }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-paragraph">
|
||||
<div class="info-row">
|
||||
<div class="info-label">{% if employee_civ == "Monsieur" %}Le salarié :{% else %}La salariée :{% endif %}</div>
|
||||
<div class="info-value">{{employee_civ}} {{employee_firstname}} {{employee_lastname}}</div>
|
||||
<br>
|
||||
<div>{% raw %}{{Signature Employé;role=Salarié;type=signature;height=60;width=150}}{% endraw %}</div>
|
||||
<br><br><br><br>
|
||||
</div>
|
||||
{% if mineur1618 == "Oui" %}
|
||||
<div class="info-paragraph">
|
||||
<div class="info-row">
|
||||
<div class="info-label">{% 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 %}</div>
|
||||
<div class="info-value">{{representant_civ}} {{representant_nom}}</div>
|
||||
<div class="info-signature">[[s|1]]</div>
|
||||
<br><br>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="info-row">
|
||||
<div class="info-label">L'employeur:</div>
|
||||
<div class="info-value">Pour {{structure_name}},</div>
|
||||
{% if delegation == "Oui" %}<div class="info-delegation">Pour le représentant légal et par délégation,</div>{% endif %}
|
||||
<div class="info-value">{{structure_signataire}},</div>
|
||||
<div class="info-value">{{structure_signatairequalite}}.</div>
|
||||
<br>
|
||||
<div>{% raw %}{{Signature Employeur;role=Employeur;type=signature;height=60;width=150}}{% endraw %}</div>
|
||||
</div>
|
||||
</div>
|
||||
70
PDF/CSS_PDF.css
Normal file
70
PDF/CSS_PDF.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
60
PDF/donnees_test.json
Normal file
60
PDF/donnees_test.json
Normal file
|
|
@ -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": ""
|
||||
}
|
||||
|
|
@ -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<string | null>(null);
|
||||
const [loadingPdf, setLoadingPdf] = useState<string | null>(null);
|
||||
const [regenerating, setRegenerating] = useState<string | null>(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() {
|
|||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<button
|
||||
onClick={() => setEditingNaaId(naa.id)}
|
||||
disabled={loadingPdf === naa.id || regenerating === naa.id}
|
||||
className="p-1.5 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Modifier la NAA"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
{naa.pdf_url && (
|
||||
<>
|
||||
<button
|
||||
|
|
@ -333,6 +348,14 @@ export default function NAAPage() {
|
|||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editingNaaId && (
|
||||
<EditNAAModal
|
||||
naaId={editingNaaId}
|
||||
onClose={() => setEditingNaaId(null)}
|
||||
onSuccess={handleEditSuccess}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
163
app/api/contrats/[id]/generate-pdf-test/route.tsx
Normal file
163
app/api/contrats/[id]/generate-pdf-test/route.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import React from 'react';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { generateContractPdf } from '@/lib/pdf/generateContract';
|
||||
import { ContratCDDUData } from '@/lib/pdf/types';
|
||||
|
||||
/**
|
||||
* Route API pour tester la génération de PDF d'un contrat existant
|
||||
*
|
||||
* URL: GET /api/contrats/[id]/generate-pdf-test
|
||||
*
|
||||
* Cette route récupère un contrat depuis Supabase et génère son PDF
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const contractId = params.id;
|
||||
console.log('🧪 [Test PDF Generation] Génération du PDF pour le contrat:', contractId);
|
||||
|
||||
// 1. Récupérer les données du contrat depuis Supabase (avec service_role pour bypass RLS)
|
||||
const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { data: contract, error: contractError } = await supabase
|
||||
.from('cddu_contracts')
|
||||
.select('*')
|
||||
.eq('id', contractId)
|
||||
.single();
|
||||
|
||||
if (contractError || !contract) {
|
||||
console.error('❌ [Test PDF Generation] Contrat non trouvé:', contractError);
|
||||
return NextResponse.json(
|
||||
{ error: 'Contrat non trouvé', details: contractError?.message },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('✅ [Test PDF Generation] Contrat récupéré:', {
|
||||
id: contract.id,
|
||||
employee: contract.employee_name,
|
||||
organization: contract.structure,
|
||||
contract_number: contract.contract_number,
|
||||
});
|
||||
|
||||
// 2. Transformer les données au format attendu par le template PDF
|
||||
// La table cddu_contracts contient déjà toutes les données nécessaires
|
||||
const pdfData: ContratCDDUData = {
|
||||
// Structure employeur
|
||||
structure_name: contract.structure || '',
|
||||
structure_adresse: contract.structure_address || '',
|
||||
structure_cpville: contract.structure_postal_code || '',
|
||||
structure_ville: contract.structure_city || '',
|
||||
structure_siret: contract.structure_siret || '',
|
||||
structure_licence: contract.structure_license || '',
|
||||
structure_signataire: contract.structure_signatory || '',
|
||||
structure_signatairequalite: contract.structure_signatory_title || '',
|
||||
structure_spectacle: contract.is_spectacle ? 'Oui' : 'Non',
|
||||
delegation: contract.delegation || '',
|
||||
forme_juridique: contract.legal_form || '',
|
||||
|
||||
// Représentant légal (mineur)
|
||||
mineur1618: contract.is_minor ? 'Oui' : 'Non',
|
||||
representant_civ: contract.guardian_civility || '',
|
||||
representant_nom: contract.guardian_name || '',
|
||||
representant_dob: contract.guardian_birth_date || '',
|
||||
representant_cob: contract.guardian_birth_place || '',
|
||||
representant_adresse: contract.guardian_address || '',
|
||||
|
||||
// Salarié - utiliser les champs qui existent vraiment dans cddu_contracts
|
||||
employee_civ: contract.employee_civility || '',
|
||||
employee_firstname: contract.employee_firstname || contract.prenom || '',
|
||||
employee_lastname: contract.employee_lastname || contract.nom || '',
|
||||
employee_birthname: contract.employee_birthname || contract.nomnaiss || '',
|
||||
employee_dob: contract.employee_birth_date || contract.datedenaissancemois || '',
|
||||
employee_cob: contract.employee_birth_place || contract.communedenaiss || '',
|
||||
employee_address: contract.employee_address || contract.adressepostale || '',
|
||||
employee_ss: contract.employee_ss_number || contract.secu || '',
|
||||
employee_cs: contract.employee_classification || contract.cs || '',
|
||||
employee_profession: contract.employee_profession || contract.profession || '',
|
||||
employee_codeprofession: contract.employee_profession_code || contract.codeprofession || '',
|
||||
employee_catpro: contract.employee_category || contract.catpro || '',
|
||||
employee_pseudo: contract.employee_artistic_name || contract.pseudo || '',
|
||||
|
||||
// Spectacle/Production
|
||||
spectacle: contract.production_name || contract.spectacle || '',
|
||||
numobjet: contract.object_number || contract.numobjet || '',
|
||||
type_numobjet: contract.object_type || contract.typedenumobjet || '',
|
||||
|
||||
// Dates et durée
|
||||
date_debut: contract.start_date || contract.datedebutcontrat || '',
|
||||
date_fin: contract.end_date || contract.datefincontrat || '',
|
||||
dates_travaillees: contract.working_dates || contract.datestravaillees || '',
|
||||
date_signature: contract.signature_date || new Date().toISOString().split('T')[0],
|
||||
|
||||
// Rémunération
|
||||
salaire_brut: contract.gross_salary?.toString() || contract.brut || '0',
|
||||
precisions_salaire: contract.salary_details || contract.precisionssalaire || '',
|
||||
panierrepas: contract.meal_allowance || contract.panierrepas || '',
|
||||
panierrepasccn: contract.meal_allowance_ccn ? 'Oui' : 'Non',
|
||||
montantpanierrepas: contract.meal_allowance_amount?.toString() || contract.montantpanierrepas || '',
|
||||
hebergement: contract.accommodation || contract.hebergement || '',
|
||||
hebergementccn: contract.accommodation_ccn ? 'Oui' : 'Non',
|
||||
montanthebergement: contract.accommodation_amount?.toString() || contract.montanthebergement || '',
|
||||
|
||||
// Cachets
|
||||
cachets: {
|
||||
representations: contract.cachets_representations || contract.representations || 0,
|
||||
repetitions: contract.cachets_repetitions || contract.repetitions || 0,
|
||||
heures: contract.cachets_hours || contract.heures || 0,
|
||||
heuresparjour: contract.cachets_hours_per_day || contract.heuresparjour || 0,
|
||||
},
|
||||
|
||||
// Convention collective
|
||||
CCN: contract.collective_agreement || contract.ccn || contract.conventioncollective || '',
|
||||
|
||||
// Autres
|
||||
autreprecision: contract.other_details || contract.autreprecision || '',
|
||||
nom_responsable_traitement: contract.data_controller_name || '',
|
||||
qualite_responsable_traitement: contract.data_controller_title || '',
|
||||
email_responsable_traitement: contract.data_controller_email || '',
|
||||
imageUrl: '', // Pas d'image pour le test
|
||||
};
|
||||
|
||||
console.log('📄 [Test PDF Generation] Données transformées, génération du PDF...');
|
||||
|
||||
// 3. Générer le PDF
|
||||
const pdfBuffer = await generateContractPdf(pdfData);
|
||||
|
||||
console.log(`✅ [Test PDF Generation] PDF généré avec succès (${pdfBuffer.byteLength} bytes)`);
|
||||
|
||||
// 4. Retourner le PDF
|
||||
return new NextResponse(new Uint8Array(pdfBuffer), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `inline; filename="contrat_${contractId}.pdf"`,
|
||||
'Content-Length': pdfBuffer.byteLength.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Test PDF Generation] Erreur:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Erreur lors de la génération du PDF',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +79,32 @@ export async function POST(
|
|||
|
||||
console.log(`[NAA Regenerate] Starting regeneration for NAA ID: ${params.id}`);
|
||||
|
||||
// Lire le body (optionnel) pour récupérer les factures explicitement incluses
|
||||
let includedInvoiceIds: string[] = [];
|
||||
try {
|
||||
const reqBody = await request.json();
|
||||
includedInvoiceIds = Array.isArray(reqBody?.included_invoices) ? reqBody.included_invoices : [];
|
||||
} catch (e) {
|
||||
// pas de body
|
||||
includedInvoiceIds = [];
|
||||
}
|
||||
|
||||
// Si des invoice IDs sont fournis, précharger ces factures
|
||||
let includedInvoicesMap: Record<string, any> = {};
|
||||
if (includedInvoiceIds && includedInvoiceIds.length > 0) {
|
||||
const { data: includedInvoices } = await supabase
|
||||
.from('invoices')
|
||||
.select('id, amount_ht, org_id, created_at, period_label')
|
||||
.in('id', includedInvoiceIds as string[]);
|
||||
|
||||
if (includedInvoices && includedInvoices.length > 0) {
|
||||
for (const inv of includedInvoices) {
|
||||
includedInvoicesMap[inv.id] = inv;
|
||||
}
|
||||
}
|
||||
console.log(`[NAA Regenerate] ${Object.keys(includedInvoicesMap).length} included invoices preloaded`);
|
||||
}
|
||||
|
||||
// Récupérer le document NAA avec l'apporteur
|
||||
const { data: naaDoc, error: naaError } = await supabase
|
||||
.from("naa_documents")
|
||||
|
|
@ -93,23 +119,217 @@ export async function POST(
|
|||
return NextResponse.json({ error: "NAA non trouvée" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Récupérer les prestations
|
||||
// Récupérer les prestations actuelles
|
||||
const { data: prestations } = await supabase
|
||||
.from("naa_prestations")
|
||||
.select("*")
|
||||
.eq("naa_id", params.id)
|
||||
.order("created_at");
|
||||
|
||||
// Récupérer les line items (commissions)
|
||||
const { data: lineItems } = await supabase
|
||||
.from("naa_line_items")
|
||||
.select("*")
|
||||
.eq("naa_id", params.id)
|
||||
.order("created_at");
|
||||
|
||||
const referrer = Array.isArray(naaDoc.referrers) ? naaDoc.referrers[0] : naaDoc.referrers;
|
||||
|
||||
// Préparer le payload pour PDFMonkey
|
||||
// ===== RECALCUL COMPLET DES COMMISSIONS =====
|
||||
console.log("[NAA Regenerate] Recalculating commissions from scratch...");
|
||||
|
||||
// Extraire le mois et l'année de la période (format : "Janvier 2025")
|
||||
const periodeParts = naaDoc.periode.split(" ");
|
||||
const monthNames = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"];
|
||||
const periodMonth = monthNames.indexOf(periodeParts[0]) + 1;
|
||||
const periodYear = parseInt(periodeParts[1]);
|
||||
const month = periodMonth.toString().padStart(2, '0');
|
||||
|
||||
// Calcul du mois d'émission (mois suivant)
|
||||
let emissionMonth = periodMonth + 1;
|
||||
let emissionYear = periodYear;
|
||||
|
||||
if (emissionMonth > 12) {
|
||||
emissionMonth = 1;
|
||||
emissionYear += 1;
|
||||
}
|
||||
|
||||
const emissionMonthStr = emissionMonth.toString().padStart(2, '0');
|
||||
const emissionStartDate = `${emissionYear}-${emissionMonthStr}-01`;
|
||||
const emissionEndDate = `${emissionYear}-${emissionMonthStr}-20`;
|
||||
|
||||
// Récupérer la liste actuelle des clients apportés (peut avoir changé)
|
||||
const { data: referredClients, error: clientsError } = await supabase
|
||||
.from("organization_details")
|
||||
.select(`
|
||||
org_id,
|
||||
referrer_code,
|
||||
commission_rate,
|
||||
code_employeur,
|
||||
organizations!organization_details_org_id_fkey (
|
||||
id,
|
||||
name
|
||||
)
|
||||
`)
|
||||
.eq("is_referred", true)
|
||||
.eq("referrer_code", naaDoc.referrer_code);
|
||||
|
||||
if (clientsError) {
|
||||
console.error("[NAA Regenerate] Erreur récupération clients apportés:", clientsError);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des clients" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[NAA Regenerate] Trouvé ${referredClients?.length || 0} clients apportés actuellement`);
|
||||
|
||||
// Recalculer les line items pour chaque client apporté
|
||||
const newLineItems = [];
|
||||
let totalCommission = 0;
|
||||
|
||||
for (const client of referredClients || []) {
|
||||
const org = Array.isArray(client.organizations) ? client.organizations[0] : client.organizations;
|
||||
const clientCode = client.code_employeur;
|
||||
const clientName = org?.name || 'N/A';
|
||||
|
||||
console.log(`[NAA Regenerate] Recherche facture pour client ${clientName} (${clientCode})`);
|
||||
|
||||
// Si l'utilisateur a fourni des invoices incluses, vérifier s'il y en a pour ce client
|
||||
let invoice: any | null = null;
|
||||
if (includedInvoiceIds && includedInvoiceIds.length > 0) {
|
||||
const matching = Object.values(includedInvoicesMap).filter((inv: any) => String(inv.org_id) === String(client.org_id));
|
||||
if (matching && matching.length > 0) {
|
||||
// prendre la plus récente
|
||||
invoice = matching.sort((a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
||||
console.log(`[NAA Regenerate] ✅ Utilisation d'une facture explicitement incluse: ${invoice.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Stratégie 1 : Recherche exacte par period_label (si pas de facture fournie)
|
||||
if (!invoice) {
|
||||
invoice = await supabase
|
||||
.from("invoices")
|
||||
.select("id, amount_ht, created_at, period_label")
|
||||
.eq("org_id", client.org_id)
|
||||
.eq("period_label", naaDoc.periode)
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle()
|
||||
.then(res => res.data);
|
||||
}
|
||||
|
||||
// Stratégie 2 : Si pas trouvé, chercher par date de création
|
||||
if (!invoice) {
|
||||
invoice = await supabase
|
||||
.from("invoices")
|
||||
.select("id, amount_ht, created_at, period_label")
|
||||
.eq("org_id", client.org_id)
|
||||
.gte("created_at", emissionStartDate)
|
||||
.lte("created_at", emissionEndDate)
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle()
|
||||
.then(res => res.data);
|
||||
}
|
||||
|
||||
// Stratégie 3 : Chercher dans une plage élargie
|
||||
if (!invoice) {
|
||||
const { data: invoices } = await supabase
|
||||
.from("invoices")
|
||||
.select("id, amount_ht, created_at, period_label")
|
||||
.eq("org_id", client.org_id)
|
||||
.gte("created_at", `${periodYear}-${month}-01`)
|
||||
.lte("created_at", `${emissionYear}-${emissionMonthStr}-31`)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (invoices && invoices.length > 0) {
|
||||
invoice = invoices[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice && invoice.amount_ht > 0) {
|
||||
const caHT = parseFloat(invoice.amount_ht);
|
||||
const commissionRate = client.commission_rate || 0;
|
||||
const commission = caHT * commissionRate;
|
||||
totalCommission += commission;
|
||||
|
||||
console.log(`[NAA Regenerate] ✅ Commission: ${caHT}€ × ${(commissionRate * 100).toFixed(2)}% = ${commission.toFixed(2)}€`);
|
||||
|
||||
newLineItems.push({
|
||||
naa_id: params.id,
|
||||
organization_id: client.org_id,
|
||||
client_name: clientName,
|
||||
client_code: clientCode,
|
||||
invoice_id: invoice.id || null,
|
||||
commission_rate: commissionRate,
|
||||
ca_ht: caHT,
|
||||
commission: commission
|
||||
});
|
||||
} else {
|
||||
console.log(`[NAA Regenerate] ⚠️ Aucune facture trouvée ou montant HT = 0`);
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer les anciens line items et insérer les nouveaux
|
||||
await supabase
|
||||
.from("naa_line_items")
|
||||
.delete()
|
||||
.eq("naa_id", params.id);
|
||||
|
||||
if (newLineItems.length > 0) {
|
||||
await supabase
|
||||
.from("naa_line_items")
|
||||
.insert(newLineItems);
|
||||
}
|
||||
|
||||
// Recalculer les totaux
|
||||
const nbreClients = newLineItems.length;
|
||||
const nbrePrestations = (prestations || []).length;
|
||||
const totalFacture = totalCommission - (naaDoc.deposit || 0) + (naaDoc.solde_compte_apporteur || 0);
|
||||
|
||||
console.log(`[NAA Regenerate] Totaux recalculés:`);
|
||||
console.log(` - Nombre de clients: ${nbreClients}`);
|
||||
console.log(` - Nombre de prestations: ${nbrePrestations}`);
|
||||
console.log(` - Total commission: ${totalCommission.toFixed(2)}€`);
|
||||
console.log(` - Total facture: ${totalFacture.toFixed(2)}€`);
|
||||
|
||||
// Mettre à jour le document NAA avec les nouveaux totaux
|
||||
await supabase
|
||||
.from("naa_documents")
|
||||
.update({
|
||||
total_commission: totalCommission,
|
||||
total_facture: totalFacture,
|
||||
nbre_clients: nbreClients,
|
||||
nbre_prestations: nbrePrestations,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq("id", params.id);
|
||||
|
||||
// Trier les line items par ordre alphabétique de code client
|
||||
const sortedLineItems = (newLineItems || [])
|
||||
.map((item: any) => ({
|
||||
id: item.client_code || '',
|
||||
client: item.client_name || '',
|
||||
code: item.client_code || '',
|
||||
comactuelle: item.commission_rate || 0,
|
||||
caht: item.ca_ht || 0,
|
||||
commission: item.commission || 0
|
||||
}))
|
||||
.sort((a: any, b: any) => {
|
||||
const codeA = a.code.toLowerCase();
|
||||
const codeB = b.code.toLowerCase();
|
||||
return codeA.localeCompare(codeB);
|
||||
}); // Trier les prestations par ordre alphabétique du code client
|
||||
const sortedPrestations = (prestations || [])
|
||||
.map(p => ({
|
||||
client: p.client_name,
|
||||
code: p.client_code,
|
||||
type_prestation: p.type_prestation,
|
||||
quantite: p.quantite,
|
||||
tarif: p.tarif,
|
||||
total: p.total
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
const codeA = (a.code || "").toUpperCase();
|
||||
const codeB = (b.code || "").toUpperCase();
|
||||
return codeA.localeCompare(codeB);
|
||||
});
|
||||
|
||||
// Préparer le payload pour PDFMonkey avec les totaux recalculés
|
||||
const pdfMonkeyPayload = {
|
||||
apporteur_address: referrer?.address || "",
|
||||
apporteur_cp: referrer?.postal_code || "",
|
||||
|
|
@ -121,33 +341,20 @@ export async function POST(
|
|||
limit_date: naaDoc.limit_date ? new Date(naaDoc.limit_date).toLocaleDateString("fr-FR") : "",
|
||||
callsheet_number: naaDoc.naa_number,
|
||||
periode: naaDoc.periode,
|
||||
total_commission: naaDoc.total_commission || 0,
|
||||
total_commission: totalCommission,
|
||||
solde_compte_apporteur: naaDoc.solde_compte_apporteur || 0,
|
||||
total_facture: naaDoc.total_facture || 0,
|
||||
total_facture: totalFacture,
|
||||
deposit: naaDoc.deposit || 0,
|
||||
nbre_clients: naaDoc.nbre_clients || 0,
|
||||
nbre_prestations: naaDoc.nbre_prestations || 0,
|
||||
nbre_clients: nbreClients,
|
||||
nbre_prestations: nbrePrestations,
|
||||
transfer_reference: naaDoc.transfer_reference || "",
|
||||
logo_odentas: "",
|
||||
lineItems: (lineItems || []).map(item => ({
|
||||
id: item.client_code,
|
||||
client: item.client_name,
|
||||
code: item.client_code,
|
||||
comactuelle: item.commission_rate,
|
||||
caht: item.ca_ht,
|
||||
commission: item.commission
|
||||
})),
|
||||
prestations: (prestations || []).map(p => ({
|
||||
client: p.client_name,
|
||||
code: p.client_code,
|
||||
type_prestation: p.type_prestation,
|
||||
quantite: p.quantite,
|
||||
tarif: p.tarif,
|
||||
total: p.total
|
||||
}))
|
||||
lineItems: sortedLineItems,
|
||||
prestations: sortedPrestations
|
||||
};
|
||||
|
||||
console.log("[NAA Regenerate] Calling PDFMonkey API...");
|
||||
console.log("[NAA Regenerate] Payload lineItems:", JSON.stringify(sortedLineItems, null, 2));
|
||||
|
||||
const pdfMonkeyApiKey = process.env.PDFMONKEY_API_KEY;
|
||||
const pdfMonkeyUrl = "https://api.pdfmonkey.io/api/v1/documents";
|
||||
|
|
@ -227,13 +434,13 @@ export async function POST(
|
|||
});
|
||||
const presignedUrl = await getSignedUrl(s3Client, getObjectCommand, { expiresIn: 3600 });
|
||||
|
||||
// Mettre à jour le document NAA
|
||||
// Mettre à jour le document NAA avec l'URL du PDF (les totaux ont déjà été mis à jour plus haut)
|
||||
await supabase
|
||||
.from("naa_documents")
|
||||
.update({
|
||||
pdf_url: s3Url,
|
||||
s3_key: s3Key,
|
||||
updated_at: new Date().toISOString()
|
||||
status: "sent"
|
||||
})
|
||||
.eq("id", params.id);
|
||||
|
||||
|
|
|
|||
|
|
@ -111,3 +111,156 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT - Mettre à jour une NAA et ses prestations
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const cookieStore = cookies();
|
||||
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
||||
|
||||
// Vérifier l'authentification staff
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { data: staffUser } = await supabase
|
||||
.from("staff_users")
|
||||
.select("user_id")
|
||||
.eq("user_id", user.id)
|
||||
.single();
|
||||
|
||||
if (!staffUser) {
|
||||
return NextResponse.json({ error: "Accès non autorisé" }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const {
|
||||
prestations,
|
||||
solde_compte_apporteur,
|
||||
deposit,
|
||||
included_invoices
|
||||
} = body;
|
||||
|
||||
// Vérifier que la NAA existe
|
||||
const { data: naaDoc, error: naaCheckError } = await supabase
|
||||
.from("naa_documents")
|
||||
.select("*")
|
||||
.eq("id", params.id)
|
||||
.single();
|
||||
|
||||
if (naaCheckError || !naaDoc) {
|
||||
return NextResponse.json({ error: "NAA non trouvée" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 1. Récupérer les IDs des prestations existantes
|
||||
const { data: existingPrestations } = await supabase
|
||||
.from("naa_prestations")
|
||||
.select("id")
|
||||
.eq("naa_id", params.id);
|
||||
|
||||
const existingIds = existingPrestations?.map(p => p.id) || [];
|
||||
|
||||
// 2. Identifier les prestations à conserver (celles qui ont un ID existant)
|
||||
const prestationsWithIds = prestations.filter((p: any) => p.id && existingIds.includes(p.id));
|
||||
const prestationIdsToKeep = prestationsWithIds.map((p: any) => p.id);
|
||||
|
||||
// 3. Supprimer les prestations qui ne sont plus dans la liste
|
||||
const idsToDelete = existingIds.filter(id => !prestationIdsToKeep.includes(id));
|
||||
|
||||
if (idsToDelete.length > 0) {
|
||||
await supabase
|
||||
.from("naa_prestations")
|
||||
.delete()
|
||||
.in("id", idsToDelete);
|
||||
}
|
||||
|
||||
// 4. Mettre à jour les prestations existantes qui ont changé
|
||||
for (const prest of prestationsWithIds) {
|
||||
await supabase
|
||||
.from("naa_prestations")
|
||||
.update({
|
||||
client_name: prest.client,
|
||||
client_code: prest.code,
|
||||
type_prestation: prest.type_prestation,
|
||||
quantite: prest.quantite,
|
||||
tarif: prest.tarif,
|
||||
total: prest.total
|
||||
})
|
||||
.eq("id", prest.id);
|
||||
}
|
||||
|
||||
// 5. Insérer les nouvelles prestations (celles sans ID)
|
||||
const newPrestations = prestations.filter((p: any) => !p.id);
|
||||
|
||||
if (newPrestations.length > 0) {
|
||||
const prestationsToInsert = newPrestations.map((p: any) => ({
|
||||
naa_id: params.id,
|
||||
client_name: p.client,
|
||||
client_code: p.code,
|
||||
type_prestation: p.type_prestation,
|
||||
quantite: p.quantite,
|
||||
tarif: p.tarif,
|
||||
total: p.total
|
||||
}));
|
||||
|
||||
await supabase.from("naa_prestations").insert(prestationsToInsert);
|
||||
}
|
||||
|
||||
// 6. Calculer les nouveaux totaux
|
||||
const nbrePrestations = prestations.length;
|
||||
const uniqueClients = [...new Set(prestations.map((p: any) => p.code))];
|
||||
const nbreClients = uniqueClients.length;
|
||||
|
||||
// 7. Mettre à jour le document NAA avec les nouveaux totaux
|
||||
await supabase
|
||||
.from("naa_documents")
|
||||
.update({
|
||||
nbre_prestations: nbrePrestations,
|
||||
nbre_clients: nbreClients,
|
||||
solde_compte_apporteur: solde_compte_apporteur || 0,
|
||||
deposit: deposit || 0,
|
||||
updated_at: new Date().toISOString(),
|
||||
status: "draft" // Remettre en draft car le PDF doit être régénéré
|
||||
})
|
||||
.eq("id", params.id);
|
||||
|
||||
// 8. Régénérer le PDF
|
||||
const regenerateRes = await fetch(
|
||||
`${request.nextUrl.origin}/api/staff/naa/${params.id}/regenerate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
cookie: request.headers.get("cookie") || ""
|
||||
},
|
||||
body: JSON.stringify({ included_invoices })
|
||||
}
|
||||
);
|
||||
|
||||
if (!regenerateRes.ok) {
|
||||
console.error("Erreur lors de la régénération du PDF");
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
warning: "NAA mise à jour mais erreur lors de la régénération du PDF"
|
||||
});
|
||||
}
|
||||
|
||||
const regenerateData = await regenerateRes.json();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
presigned_url: regenerateData.presigned_url
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error PUT /api/staff/naa/[id]:", error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,9 +147,9 @@ export async function POST(req: NextRequest) {
|
|||
|
||||
const emissionMonthStr = emissionMonth.toString().padStart(2, '0');
|
||||
|
||||
// Date de début et fin du mois d'émission (1er au 10 du mois suivant généralement)
|
||||
// Date de début et fin du mois d'émission (1er au 20 du mois suivant)
|
||||
const emissionStartDate = `${emissionYear}-${emissionMonthStr}-01`;
|
||||
const emissionEndDate = `${emissionYear}-${emissionMonthStr}-15`; // Buffer de 15 jours
|
||||
const emissionEndDate = `${emissionYear}-${emissionMonthStr}-20`;
|
||||
|
||||
// Récupérer les clients apportés
|
||||
const { data: referredClients, error: clientsError } = await supabase
|
||||
|
|
@ -346,6 +346,29 @@ export async function POST(req: NextRequest) {
|
|||
await supabase.from("naa_prestations").insert(prestationsToInsert);
|
||||
}
|
||||
|
||||
// Trier les lineItems par ordre alphabétique du code client
|
||||
const sortedLineItems = [...lineItems].sort((a, b) => {
|
||||
const codeA = (a.code || "").toUpperCase();
|
||||
const codeB = (b.code || "").toUpperCase();
|
||||
return codeA.localeCompare(codeB);
|
||||
});
|
||||
|
||||
// Trier les prestations par ordre alphabétique du code client
|
||||
const sortedPrestations = [...prestations]
|
||||
.map(p => ({
|
||||
client: p.client,
|
||||
code: p.code,
|
||||
type_prestation: p.type_prestation,
|
||||
quantite: p.quantite,
|
||||
tarif: p.tarif,
|
||||
total: p.total
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
const codeA = (a.code || "").toUpperCase();
|
||||
const codeB = (b.code || "").toUpperCase();
|
||||
return codeA.localeCompare(codeB);
|
||||
});
|
||||
|
||||
// Préparer les données pour PDFMonkey
|
||||
const pdfMonkeyPayload = {
|
||||
apporteur_address: referrer.address,
|
||||
|
|
@ -366,15 +389,8 @@ export async function POST(req: NextRequest) {
|
|||
nbre_prestations: nbrePrestations,
|
||||
transfer_reference: transfer_reference || "",
|
||||
logo_odentas: "",
|
||||
lineItems,
|
||||
prestations: prestations.map(p => ({
|
||||
client: p.client,
|
||||
code: p.code,
|
||||
type_prestation: p.type_prestation,
|
||||
quantite: p.quantite,
|
||||
tarif: p.tarif,
|
||||
total: p.total
|
||||
}))
|
||||
lineItems: sortedLineItems,
|
||||
prestations: sortedPrestations
|
||||
};
|
||||
|
||||
// Envoyer à PDFMonkey pour générer le PDF
|
||||
|
|
|
|||
80
app/api/staff/organizations/[orgId]/invoices/route.ts
Normal file
80
app/api/staff/organizations/[orgId]/invoices/route.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { orgId: string } }
|
||||
) {
|
||||
try {
|
||||
const supabase = createRouteHandlerClient({ cookies });
|
||||
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Vérifier que l'utilisateur est staff
|
||||
const { data: staffUser } = await supabase
|
||||
.from("staff_users")
|
||||
.select("is_staff")
|
||||
.eq("user_id", session.user.id)
|
||||
.single();
|
||||
|
||||
if (!staffUser || !staffUser.is_staff) {
|
||||
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
|
||||
}
|
||||
|
||||
const periode = req.nextUrl.searchParams.get("periode") || undefined;
|
||||
|
||||
// Si une période est fournie, on va élargir la recherche sur 2 mois autour
|
||||
let startDate: string | undefined;
|
||||
let endDate: string | undefined;
|
||||
|
||||
if (periode) {
|
||||
const parts = periode.split(" ");
|
||||
const monthNames = ["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"];
|
||||
const periodMonth = monthNames.indexOf(parts[0]) + 1;
|
||||
const periodYear = parseInt(parts[1]);
|
||||
const month = periodMonth.toString().padStart(2, '0');
|
||||
|
||||
let emissionMonth = periodMonth + 1;
|
||||
let emissionYear = periodYear;
|
||||
if (emissionMonth > 12) { emissionMonth = 1; emissionYear += 1; }
|
||||
const emissionMonthStr = String(emissionMonth).padStart(2, '0');
|
||||
|
||||
startDate = `${periodYear}-${month}-01`;
|
||||
endDate = `${emissionYear}-${emissionMonthStr}-20`;
|
||||
}
|
||||
|
||||
const orgId = params.orgId;
|
||||
|
||||
let query = supabase
|
||||
.from("invoices")
|
||||
.select("id, amount_ht, created_at, period_label, org_id")
|
||||
.eq("org_id", orgId)
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(50);
|
||||
|
||||
if (startDate && endDate) {
|
||||
query = query.gte("created_at", startDate).lte("created_at", endDate) as any;
|
||||
}
|
||||
|
||||
const { data: invoices, error } = await query;
|
||||
|
||||
if (error) {
|
||||
console.error("Erreur GET invoices for org:", error);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json(invoices || []);
|
||||
} catch (error: any) {
|
||||
console.error("Erreur GET /api/staff/organizations/[orgId]/invoices:", error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
58
app/api/test-pdf/route.tsx
Normal file
58
app/api/test-pdf/route.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import React from 'react';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import ReactPDF from '@react-pdf/renderer';
|
||||
import { ContratCDDU } from '@/lib/pdf/templates/ContratCDDU';
|
||||
import testData from '@/PDF/donnees_test.json';
|
||||
import { ContratCDDUData } from '@/lib/pdf/types';
|
||||
|
||||
/**
|
||||
* Route API de test pour la génération de PDF avec @react-pdf/renderer
|
||||
*
|
||||
* URL: GET /api/test-pdf
|
||||
*
|
||||
* Cette route génère un PDF de contrat CDDU en utilisant les données de test
|
||||
* et retourne le PDF directement dans le navigateur.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🧪 [test-pdf] Début de la génération du PDF de test');
|
||||
|
||||
// Cast des données de test
|
||||
const data = testData as unknown as ContratCDDUData;
|
||||
|
||||
// Génération du PDF à partir du composant React
|
||||
console.log('📄 [test-pdf] Rendu du composant ContratCDDU...');
|
||||
|
||||
// Créer le composant et le rendre en PDF
|
||||
const doc = <ContratCDDU data={data} />;
|
||||
const pdfBlob = await ReactPDF.pdf(doc).toBlob();
|
||||
|
||||
// Convertir le Blob en ArrayBuffer puis en Buffer
|
||||
const arrayBuffer = await pdfBlob.arrayBuffer();
|
||||
const pdfBuffer = Buffer.from(arrayBuffer);
|
||||
|
||||
console.log(`✅ [test-pdf] PDF généré avec succès (${pdfBuffer.byteLength} bytes)`);
|
||||
|
||||
// Retour du PDF avec les bons headers
|
||||
return new NextResponse(pdfBuffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': 'inline; filename="contrat_cddu_test.pdf"',
|
||||
'Content-Length': pdfBuffer.byteLength.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [test-pdf] Erreur lors de la génération du PDF:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Erreur lors de la génération du PDF',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
712
components/staff/EditNAAModal.tsx
Normal file
712
components/staff/EditNAAModal.tsx
Normal file
|
|
@ -0,0 +1,712 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { X, Plus, Trash2, ChevronDown, ChevronRight, Loader2 } from "lucide-react";
|
||||
|
||||
type PrestationLine = {
|
||||
id?: string; // ID de la prestation existante (pour la suppression)
|
||||
type_prestation: string;
|
||||
quantite: number;
|
||||
tarif: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
type ClientPrestations = {
|
||||
client: string;
|
||||
code: string;
|
||||
expanded: boolean;
|
||||
lines: PrestationLine[];
|
||||
};
|
||||
|
||||
type ReferredClient = {
|
||||
org_id: string;
|
||||
code_employeur: string;
|
||||
organizations: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Invoice = {
|
||||
id: string;
|
||||
amount_ht: string;
|
||||
created_at: string;
|
||||
period_label?: string;
|
||||
org_id: string;
|
||||
};
|
||||
|
||||
type EditNAAModalProps = {
|
||||
naaId: string;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
};
|
||||
|
||||
export default function EditNAAModal({ naaId, onClose, onSuccess }: EditNAAModalProps) {
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Formulaire
|
||||
const [referrerCode, setReferrerCode] = useState("");
|
||||
const [periode, setPeriode] = useState("");
|
||||
const [callsheetDate, setCallsheetDate] = useState("");
|
||||
const [limitDate, setLimitDate] = useState("");
|
||||
const [transferReference, setTransferReference] = useState("");
|
||||
const [soldeCompte, setSoldeCompte] = useState("0");
|
||||
const [deposit, setDeposit] = useState("0");
|
||||
|
||||
// Prestations groupées par client
|
||||
const [clientsPrestations, setClientsPrestations] = useState<ClientPrestations[]>([]);
|
||||
// Factures détectées par organisation
|
||||
const [clientInvoices, setClientInvoices] = useState<Record<string, Invoice[]>>({});
|
||||
// Ensemble des invoices sélectionnées (IDs)
|
||||
const [selectedInvoiceIds, setSelectedInvoiceIds] = useState<Record<string, string[]>>({});
|
||||
// État d'expansion des sections factures par org_id
|
||||
const [invoicesExpanded, setInvoicesExpanded] = useState<Record<string, boolean>>({});
|
||||
|
||||
const typesPrestation = [
|
||||
"Ouverture de compte",
|
||||
"Abonnement",
|
||||
"Paies CDDU",
|
||||
"Paies RG",
|
||||
"Avenants",
|
||||
"Autres"
|
||||
];
|
||||
|
||||
// Charger les données de la NAA existante
|
||||
const { data: naaData, isLoading: isLoadingNaa } = useQuery({
|
||||
queryKey: ["naa-detail", naaId],
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/staff/naa/${naaId}`, {
|
||||
credentials: "include"
|
||||
});
|
||||
if (!res.ok) throw new Error("Impossible de charger la NAA");
|
||||
return res.json();
|
||||
},
|
||||
});
|
||||
|
||||
// Charger les clients apportés
|
||||
const { data: referredClients = [] } = useQuery<ReferredClient[]>({
|
||||
queryKey: ["referred-clients", referrerCode],
|
||||
queryFn: async () => {
|
||||
if (!referrerCode) return [];
|
||||
const res = await fetch(`/api/staff/referrers/${referrerCode}/clients`, {
|
||||
credentials: "include"
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
},
|
||||
enabled: !!referrerCode,
|
||||
});
|
||||
|
||||
// Initialiser le formulaire avec les données existantes
|
||||
useEffect(() => {
|
||||
if (naaData) {
|
||||
setReferrerCode(naaData.referrer_code || "");
|
||||
setPeriode(naaData.periode || "");
|
||||
setCallsheetDate(naaData.callsheet_date || "");
|
||||
setLimitDate(naaData.limit_date || "");
|
||||
setTransferReference(naaData.transfer_reference || "");
|
||||
setSoldeCompte(String(naaData.solde_compte_apporteur || 0));
|
||||
setDeposit(String(naaData.deposit || 0));
|
||||
|
||||
// Regrouper les prestations par client
|
||||
if (naaData.prestations && naaData.prestations.length > 0) {
|
||||
const grouped = naaData.prestations.reduce((acc: any, prest: any) => {
|
||||
const key = `${prest.client_code}_${prest.client_name}`;
|
||||
if (!acc[key]) {
|
||||
acc[key] = {
|
||||
client: prest.client_name,
|
||||
code: prest.client_code,
|
||||
expanded: false, // Replié par défaut
|
||||
lines: []
|
||||
};
|
||||
}
|
||||
acc[key].lines.push({
|
||||
id: prest.id,
|
||||
type_prestation: prest.type_prestation,
|
||||
quantite: prest.quantite,
|
||||
tarif: prest.tarif,
|
||||
total: prest.total
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
setClientsPrestations(Object.values(grouped));
|
||||
}
|
||||
}
|
||||
}, [naaData]);
|
||||
|
||||
// Si des clients sont déjà présents (à l'ouverture), précharger leurs factures
|
||||
useEffect(() => {
|
||||
const preload = async () => {
|
||||
for (const client of clientsPrestations) {
|
||||
if (client.code) {
|
||||
const rc = referredClients.find(rc => rc.code_employeur === client.code);
|
||||
if (rc && rc.org_id && !(clientInvoices[rc.org_id] || []).length) {
|
||||
try {
|
||||
const res = await fetch(`/api/staff/organizations/${rc.org_id}/invoices?periode=${encodeURIComponent(periode)}`, { credentials: 'include' });
|
||||
if (!res.ok) continue;
|
||||
const invoices = await res.json();
|
||||
setClientInvoices(prev => ({ ...prev, [rc.org_id]: invoices }));
|
||||
if (invoices && invoices.length > 0) {
|
||||
setSelectedInvoiceIds(prev => ({ ...prev, [rc.org_id]: [invoices[0].id] }));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (clientsPrestations.length > 0 && referredClients.length > 0) {
|
||||
preload();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clientsPrestations, referredClients]);
|
||||
|
||||
// Ajouter un nouveau client
|
||||
const addClient = () => {
|
||||
setClientsPrestations([...clientsPrestations, {
|
||||
client: "",
|
||||
code: "",
|
||||
expanded: true, // Nouveau client déplié pour faciliter la saisie
|
||||
lines: [{
|
||||
type_prestation: "",
|
||||
quantite: 1,
|
||||
tarif: 0,
|
||||
total: 0
|
||||
}]
|
||||
}]);
|
||||
};
|
||||
|
||||
// Supprimer un client et toutes ses prestations
|
||||
const removeClient = (clientIndex: number) => {
|
||||
setClientsPrestations(clientsPrestations.filter((_, i) => i !== clientIndex));
|
||||
};
|
||||
|
||||
// Ajouter une ligne de prestation pour un client
|
||||
const addLineToClient = (clientIndex: number) => {
|
||||
const newClients = [...clientsPrestations];
|
||||
newClients[clientIndex].lines.push({
|
||||
type_prestation: "",
|
||||
quantite: 1,
|
||||
tarif: 0,
|
||||
total: 0
|
||||
});
|
||||
setClientsPrestations(newClients);
|
||||
};
|
||||
|
||||
// Supprimer une ligne de prestation
|
||||
const removeLine = (clientIndex: number, lineIndex: number) => {
|
||||
const newClients = [...clientsPrestations];
|
||||
newClients[clientIndex].lines = newClients[clientIndex].lines.filter((_, i) => i !== lineIndex);
|
||||
setClientsPrestations(newClients);
|
||||
};
|
||||
|
||||
// Mettre à jour le client
|
||||
const updateClient = (clientIndex: number, clientName: string) => {
|
||||
const newClients = [...clientsPrestations];
|
||||
const selectedClient = referredClients.find(c => c.organizations.name === clientName);
|
||||
newClients[clientIndex].client = clientName;
|
||||
newClients[clientIndex].code = selectedClient?.code_employeur || "";
|
||||
setClientsPrestations(newClients);
|
||||
|
||||
// Si on a trouvé l'org_id, récupérer les factures candidates
|
||||
if (selectedClient?.org_id) {
|
||||
fetch(`/api/staff/organizations/${selectedClient.org_id}/invoices?periode=${encodeURIComponent(periode)}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then(res => res.ok ? res.json() : [])
|
||||
.then((invoices: Invoice[]) => {
|
||||
setClientInvoices(prev => ({ ...prev, [selectedClient.org_id]: invoices }));
|
||||
// Par défaut, sélectionner la facture la plus récente (la première)
|
||||
if (invoices && invoices.length > 0) {
|
||||
setSelectedInvoiceIds(prev => ({ ...prev, [selectedClient.org_id]: [invoices[0].id] }));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleInvoiceSelection = (orgId: string, invoiceId: string) => {
|
||||
setSelectedInvoiceIds(prev => {
|
||||
const current = prev[orgId] || [];
|
||||
// Si cette facture est déjà la seule sélectionnée, on la désélectionne
|
||||
if (current.length === 1 && current[0] === invoiceId) {
|
||||
return { ...prev, [orgId]: [] };
|
||||
}
|
||||
// Sinon, on remplace la sélection par cette facture uniquement (mode radio)
|
||||
return { ...prev, [orgId]: [invoiceId] };
|
||||
});
|
||||
};
|
||||
|
||||
// Mettre à jour une ligne de prestation
|
||||
const updateLine = (clientIndex: number, lineIndex: number, field: keyof PrestationLine, value: any) => {
|
||||
const newClients = [...clientsPrestations];
|
||||
newClients[clientIndex].lines[lineIndex] = {
|
||||
...newClients[clientIndex].lines[lineIndex],
|
||||
[field]: value
|
||||
};
|
||||
|
||||
// Calculer le total automatiquement
|
||||
if (field === "quantite" || field === "tarif") {
|
||||
const line = newClients[clientIndex].lines[lineIndex];
|
||||
const quantite = field === "quantite" ? parseFloat(value) || 0 : line.quantite;
|
||||
const tarif = field === "tarif" ? parseFloat(value) || 0 : line.tarif;
|
||||
newClients[clientIndex].lines[lineIndex].total = quantite * tarif;
|
||||
}
|
||||
|
||||
setClientsPrestations(newClients);
|
||||
};
|
||||
|
||||
// Basculer l'état expanded d'un client
|
||||
const toggleClientExpanded = (clientIndex: number) => {
|
||||
const newClients = [...clientsPrestations];
|
||||
newClients[clientIndex].expanded = !newClients[clientIndex].expanded;
|
||||
setClientsPrestations(newClients);
|
||||
};
|
||||
|
||||
// Tout replier
|
||||
const collapseAll = () => {
|
||||
setClientsPrestations(clientsPrestations.map(c => ({ ...c, expanded: false })));
|
||||
};
|
||||
|
||||
// Tout déplier
|
||||
const expandAll = () => {
|
||||
setClientsPrestations(clientsPrestations.map(c => ({ ...c, expanded: true })));
|
||||
};
|
||||
|
||||
// Basculer l'expansion de la section factures
|
||||
const toggleInvoicesExpanded = (orgId: string) => {
|
||||
setInvoicesExpanded(prev => ({ ...prev, [orgId]: !prev[orgId] }));
|
||||
};
|
||||
|
||||
// Calculer le total pour un client
|
||||
const getClientTotal = (client: ClientPrestations) => {
|
||||
return client.lines.reduce((sum, line) => sum + line.total, 0);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
if (!referrerCode || !periode || !callsheetDate) {
|
||||
setError("Veuillez remplir tous les champs obligatoires");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
// Convertir clientsPrestations en format plat pour l'API
|
||||
const prestations = clientsPrestations.flatMap(client =>
|
||||
client.lines.map(line => ({
|
||||
id: line.id, // Inclure l'ID pour identifier les prestations existantes
|
||||
client: client.client,
|
||||
code: client.code,
|
||||
type_prestation: line.type_prestation,
|
||||
quantite: line.quantite,
|
||||
tarif: line.tarif,
|
||||
total: line.total
|
||||
}))
|
||||
);
|
||||
|
||||
const payload = {
|
||||
referrer_code: referrerCode,
|
||||
periode,
|
||||
callsheet_date: callsheetDate,
|
||||
limit_date: limitDate || undefined,
|
||||
transfer_reference: transferReference || undefined,
|
||||
solde_compte_apporteur: parseFloat(soldeCompte) || 0,
|
||||
deposit: parseFloat(deposit) || 0,
|
||||
prestations,
|
||||
// Liste des factures explicitement incluses par l'utilisateur (IDs)
|
||||
included_invoices: Object.values(selectedInvoiceIds).flat()
|
||||
};
|
||||
|
||||
const res = await fetch(`/api/staff/naa/${naaId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
throw new Error(errorData.error || "Erreur lors de la mise à jour de la NAA");
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
setIsUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoadingNaa) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-xl p-8 flex items-center gap-3">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-indigo-600" />
|
||||
<span className="text-slate-700">Chargement de la NAA...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="px-6 py-4 border-b flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-slate-800">
|
||||
Modifier la NAA
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
{naaData?.naa_number} - {naaData?.periode}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
disabled={isUpdating}
|
||||
className="text-slate-400 hover:text-slate-600 transition"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto">
|
||||
<div className="p-6 space-y-6">
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-xl">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info: Champs de base non modifiables */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
|
||||
<p className="text-sm text-blue-800">
|
||||
<strong>Note :</strong> Les champs de base (apporteur, période, dates) ne sont pas modifiables.
|
||||
Vous pouvez uniquement ajouter ou supprimer des prestations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Champs de base (lecture seule) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Apporteur d'affaires
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={`${referrerCode} - ${naaData?.referrer_name || ""}`}
|
||||
disabled
|
||||
className="w-full px-3 py-2 bg-slate-100 border rounded-lg text-slate-500 cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Période
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={periode}
|
||||
disabled
|
||||
className="w-full px-3 py-2 bg-slate-100 border rounded-lg text-slate-500 cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Prestations par client */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">
|
||||
Prestations
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{clientsPrestations.length > 0 && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={collapseAll}
|
||||
disabled={isUpdating}
|
||||
className="px-3 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded-lg transition disabled:opacity-50"
|
||||
>
|
||||
Tout replier
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={expandAll}
|
||||
disabled={isUpdating}
|
||||
className="px-3 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded-lg transition disabled:opacity-50"
|
||||
>
|
||||
Tout déplier
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={addClient}
|
||||
disabled={isUpdating}
|
||||
className="px-4 py-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 transition disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Ajouter un client
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{clientsPrestations.length === 0 ? (
|
||||
<div className="text-center py-8 bg-slate-50 rounded-xl border border-dashed">
|
||||
<p className="text-slate-500">Aucune prestation. Cliquez sur "Ajouter un client" pour commencer.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{clientsPrestations.map((client, clientIndex) => (
|
||||
<div key={clientIndex} className="border rounded-xl overflow-hidden bg-white">
|
||||
{/* En-tête du client */}
|
||||
<div className="bg-slate-50 px-4 py-3 flex items-center justify-between border-b">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleClientExpanded(clientIndex)}
|
||||
className="flex items-center gap-2 flex-1 text-left font-medium text-slate-700 hover:text-indigo-600 transition"
|
||||
>
|
||||
{client.expanded ? (
|
||||
<ChevronDown className="w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
)}
|
||||
<span>
|
||||
{client.client || "Client non défini"} {client.code && `(${client.code})`}
|
||||
</span>
|
||||
<span className="ml-auto text-sm text-slate-500">
|
||||
{client.lines.length} prestation{client.lines.length > 1 ? "s" : ""} • Total: {getClientTotal(client).toFixed(2)} €
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeClient(clientIndex)}
|
||||
disabled={isUpdating}
|
||||
className="ml-2 p-2 text-red-600 hover:bg-red-50 rounded-lg transition disabled:opacity-50"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Contenu du client */}
|
||||
{client.expanded && (
|
||||
<div className="p-4 space-y-4">
|
||||
{/* Sélection du client */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Nom du client
|
||||
</label>
|
||||
<select
|
||||
value={client.client}
|
||||
onChange={(e) => updateClient(clientIndex, e.target.value)}
|
||||
disabled={isUpdating}
|
||||
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50"
|
||||
>
|
||||
<option value="">Sélectionner un client</option>
|
||||
{referredClients.map((rc: any) => (
|
||||
<option key={rc.org_id} value={rc.organizations.name}>
|
||||
{rc.organizations.name} ({rc.code_employeur})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Lignes de prestations */}
|
||||
{/* Factures détectées pour inclusion/exclusion */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="block text-sm font-medium text-slate-700">Factures détectées</label>
|
||||
{(() => {
|
||||
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 (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleInvoicesExpanded(orgId)}
|
||||
className="text-xs text-slate-500 hover:text-slate-700 flex items-center gap-1"
|
||||
>
|
||||
{invoicesExpanded[orgId] ? (
|
||||
<>
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
Masquer
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
Afficher
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})()}
|
||||
</div>
|
||||
<div className="bg-white border rounded-lg p-2">
|
||||
{(() => {
|
||||
const orgId = client.code ? referredClients.find(rc => rc.code_employeur === client.code)?.org_id : undefined;
|
||||
const invoices = orgId ? clientInvoices[orgId] || [] : [];
|
||||
if (!orgId) return <div className="text-sm text-slate-500">Sélectionnez un client pour voir les factures.</div>;
|
||||
if (invoices.length === 0) return <div className="text-sm text-slate-500">Aucune facture détectée pour cette période.</div>;
|
||||
|
||||
// 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 (
|
||||
<div className="text-sm text-slate-700">
|
||||
Facture sélectionnée : {selectedInvoice.period_label || new Date(selectedInvoice.created_at).toLocaleDateString("fr-FR")} — {parseFloat(selectedInvoice.amount_ht).toFixed(2)} €
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div className="text-sm text-slate-500">Aucune facture sélectionnée</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{invoices.map(inv => (
|
||||
<label key={inv.id} className="flex items-center gap-3 text-sm cursor-pointer hover:bg-slate-50 p-2 rounded-lg transition">
|
||||
<input
|
||||
type="radio"
|
||||
name={`invoice-${orgId}`}
|
||||
checked={(selectedInvoiceIds[inv.org_id] || []).includes(inv.id)}
|
||||
onChange={() => toggleInvoiceSelection(inv.org_id, inv.id)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="flex-1">{inv.period_label || new Date(inv.created_at).toLocaleDateString("fr-FR")} — {parseFloat(inv.amount_ht).toFixed(2)} €</span>
|
||||
<span className="text-slate-400 text-xs">#{inv.id.substring(0, 8)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{client.lines.map((line, lineIndex) => (
|
||||
<div key={lineIndex} className="grid grid-cols-12 gap-2 items-start p-3 bg-slate-50 rounded-lg">
|
||||
<div className="col-span-4">
|
||||
<label className="block text-xs font-medium text-slate-600 mb-1">
|
||||
Type de prestation
|
||||
</label>
|
||||
<select
|
||||
value={line.type_prestation}
|
||||
onChange={(e) => updateLine(clientIndex, lineIndex, "type_prestation", 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"
|
||||
>
|
||||
<option value="">Type</option>
|
||||
{typesPrestation.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-xs font-medium text-slate-600 mb-1">
|
||||
Quantité
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={line.quantite}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-xs font-medium text-slate-600 mb-1">
|
||||
Tarif (€)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={line.tarif}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<label className="block text-xs font-medium text-slate-600 mb-1">
|
||||
Total (€)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={line.total.toFixed(2)}
|
||||
readOnly
|
||||
className="w-full px-2 py-1.5 text-sm border rounded-lg bg-slate-100 text-slate-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex items-end justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeLine(clientIndex, lineIndex)}
|
||||
disabled={isUpdating || client.lines.length === 1}
|
||||
className="p-1.5 text-red-600 hover:bg-red-50 rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
title={client.lines.length === 1 ? "Impossible de supprimer la dernière prestation" : "Supprimer cette prestation"}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bouton ajouter prestation */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addLineToClient(clientIndex)}
|
||||
disabled={isUpdating}
|
||||
className="w-full py-2 text-sm text-indigo-600 hover:text-indigo-700 hover:bg-indigo-50 rounded-lg transition flex items-center justify-center gap-1 disabled:opacity-50"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Ajouter une prestation pour ce client
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-4 border-t flex items-center justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
disabled={isUpdating}
|
||||
className="px-4 py-2 text-slate-700 hover:bg-slate-100 rounded-xl transition disabled:opacity-50"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isUpdating}
|
||||
className="px-6 py-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
{isUpdating && <Loader2 className="w-4 h-4 animate-spin" />}
|
||||
{isUpdating ? "Mise à jour en cours..." : "Mettre à jour et régénérer le PDF"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
118
lib/pdf/generateContract.tsx
Normal file
118
lib/pdf/generateContract.tsx
Normal file
|
|
@ -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<GenerateContractResult> {
|
||||
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 = <ContratCDDU data={data} />;
|
||||
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<Buffer> {
|
||||
console.log('📄 [Contract Generation] Génération du PDF uniquement...');
|
||||
|
||||
const doc = <ContratCDDU data={data} />;
|
||||
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;
|
||||
}
|
||||
42
lib/pdf/index.ts
Normal file
42
lib/pdf/index.ts
Normal file
|
|
@ -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';
|
||||
592
lib/pdf/templates/ContratCDDU.tsx
Normal file
592
lib/pdf/templates/ContratCDDU.tsx
Normal file
|
|
@ -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<ContratCDDUProps> = ({ 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 (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
{/* Logo */}
|
||||
{data.imageUrl && (
|
||||
<View style={styles.logoContainer}>
|
||||
<Image src={data.imageUrl} style={styles.logo} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Titre */}
|
||||
<Text style={styles.title}>
|
||||
CONTRAT D'ENGAGEMENT {getTitreContrat()}
|
||||
</Text>
|
||||
|
||||
{/* Entre les soussignés */}
|
||||
<Text style={[styles.paragraph, styles.bold]}>
|
||||
Entre les {isMonsieur ? 'soussignés' : 'soussignées'} :
|
||||
</Text>
|
||||
|
||||
<View style={styles.list}>
|
||||
<Text style={[styles.listItem, styles.bold]}>{data.structure_name}</Text>
|
||||
<Text style={styles.listItem}>{data.forme_juridique}</Text>
|
||||
<Text style={styles.listItem}>{data.structure_adresse}</Text>
|
||||
<Text style={styles.listItem}>{data.structure_cpville} {data.structure_ville}</Text>
|
||||
<Text style={styles.listItem}>SIRET : {data.structure_siret}</Text>
|
||||
{data.structure_licence !== 'n/a' && (
|
||||
<Text style={styles.listItem}>
|
||||
Licence d'entrepreneur de spectacles : {data.structure_licence}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.listItem}>
|
||||
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.'
|
||||
: '.'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.paragraph, styles.bold]}>d'une part,</Text>
|
||||
<Text style={[styles.paragraph, styles.bold]}>et :</Text>
|
||||
|
||||
{/* Salarié */}
|
||||
<View style={styles.list}>
|
||||
<Text style={[styles.listItem, styles.bold]}>
|
||||
{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}"
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.listItem}>
|
||||
{isMonsieur ? 'né' : 'née'} le {data.employee_dob} {cobData.prefix} {cobData.ville}
|
||||
</Text>
|
||||
<Text style={styles.listItem}>demeurant {data.employee_address}</Text>
|
||||
{(!data.employee_ss || data.employee_ss === 0 || data.employee_ss === '') ? (
|
||||
<Text style={styles.listItem}>
|
||||
Le numéro de Sécurité Sociale du salarié est en cours d'attribution.
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={styles.listItem}>
|
||||
N° de Sécurité Sociale : {data.employee_ss}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.listItem}>N° Congés Spectacles : {data.employee_cs}</Text>
|
||||
|
||||
{/* Représentant légal si mineur */}
|
||||
{data.mineur1618 === 'Oui' && (
|
||||
<Text style={styles.listItem}>
|
||||
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}.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Text style={[styles.paragraph, styles.bold]}>d'autre part.</Text>
|
||||
|
||||
{/* Préambule */}
|
||||
<Text style={styles.paragraph}>
|
||||
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'}.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.paragraph}>Il a été convenu et arrêté ce qui suit :</Text>
|
||||
|
||||
{/* Section OBJET */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>OBJET</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{data.employee_civ} {data.employee_firstname} {data.employee_lastname} est{' '}
|
||||
{isMonsieur ? 'engagé' : 'engagée'} selon l'objet suivant :
|
||||
</Text>
|
||||
<View style={styles.list}>
|
||||
<Text style={styles.listItem}><Text style={styles.bold}>Profession</Text> : {data.employee_profession}</Text>
|
||||
<Text style={styles.listItem}><Text style={styles.bold}>Code emploi</Text> : {data.employee_codeprofession}</Text>
|
||||
{(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') ? (
|
||||
<Text style={styles.listItem}>
|
||||
<Text style={styles.bold}>
|
||||
{data.structure_spectacle === 'Oui' && data.type_numobjet !== 'Administratif'
|
||||
? 'Spectacle'
|
||||
: 'Production'}
|
||||
</Text> : {data.spectacle}
|
||||
</Text>
|
||||
) : null}
|
||||
{data.numobjet ? (
|
||||
<Text style={styles.listItem}>
|
||||
<Text style={styles.bold}>Numéro d'objet</Text> : {data.numobjet}
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={styles.listItem}>
|
||||
Le <Text style={styles.bold}>numéro d'objet</Text> de cette production est en cours d'attribution.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Section DURÉE DE L'ENGAGEMENT - Partie 1 */}
|
||||
<View style={styles.sectionObjet}>
|
||||
<Text style={styles.sectionTitle}>DURÉE DE L'ENGAGEMENT</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{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}.</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{/* Dates travaillées */}
|
||||
{datesArray && (
|
||||
<View style={styles.list}>
|
||||
{datesArray.map((date, index) => (
|
||||
<Text key={index} style={styles.listItem}>
|
||||
- {date}{index < datesArray.length - 1 ? ' ;' : ''}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Suite selon catégorie professionnelle */}
|
||||
{data.date_debut !== data.date_fin && <Text style={styles.paragraph}>Pour </Text>}
|
||||
|
||||
{/* Artiste */}
|
||||
{isArtiste && (
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Technicien */}
|
||||
{isTechnicien && (
|
||||
<Text style={styles.paragraph}>
|
||||
un total de {data.cachets.heures} heures de travail
|
||||
{data.cachets.heuresparjour === 0 ? '.' : `, à raison de ${data.cachets.heuresparjour} heures par jour de travail.`}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Metteur en scène */}
|
||||
{isMetteurEnScene && (
|
||||
<Text style={styles.paragraph}>
|
||||
{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.</>
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Durée répétitions */}
|
||||
{data.cachets.repetitions >= 1 && (
|
||||
<Text style={styles.paragraph}>
|
||||
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.`}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{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.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* RÉMUNÉRATION */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>RÉMUNÉRATION</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Il sera alloué à {data.employee_firstname} {data.employee_lastname} à titre de salaire la somme de {data.salaire_brut} euros bruts.
|
||||
</Text>
|
||||
{data.precisions_salaire && (
|
||||
<Text style={styles.paragraph}>
|
||||
À titre informatif, la répartition de ce salaire brut est la suivante : {data.precisions_salaire}.
|
||||
</Text>
|
||||
)}
|
||||
{data.panierrepas && data.hebergement && (
|
||||
<Text style={styles.paragraph}>
|
||||
{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.
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
{data.autreprecision && (
|
||||
<Text style={styles.paragraph}>{data.autreprecision}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* RETRAITE ET CONGÉS PAYÉS */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>RETRAITE ET CONGÉS PAYÉS</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* ABSENCE-MALADIE */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>ABSENCE-MALADIE</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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}.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{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.</>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* MÉDECINE DU TRAVAIL */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>MÉDECINE DU TRAVAIL</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{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.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* ASSURANCES */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>ASSURANCES</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{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.</>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* LITIGES */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>LITIGES</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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).
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* PROTECTION DES DONNÉES PERSONNELLES */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>PROTECTION DES DONNÉES PERSONNELLES</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
La signature du présent contrat vaut autorisation pour la société de collecter, d'enregistrer et de stocker les données nécessaires.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Ces informations sont réservées à l'usage des services concernés et ne peuvent être communiquées qu'à ces destinataires.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
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}.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Fait à / Date signature */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.paragraph}>Fait en double exemplaire,</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{villeSignature.prefix} {villeSignature.ville}, le {data.date_signature}.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Signatures */}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.signatureSpace}>
|
||||
<Text style={styles.infoLabel}>
|
||||
{isMonsieur ? 'Le salarié :' : 'La salariée :'}
|
||||
</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{data.employee_civ} {data.employee_firstname} {data.employee_lastname}
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>(Signature électronique via DocuSeal)</Text>
|
||||
</View>
|
||||
|
||||
{/* Signature représentant légal si mineur */}
|
||||
{data.mineur1618 === 'Oui' && (
|
||||
<View style={styles.signatureSpace}>
|
||||
<Text style={styles.infoLabel}>
|
||||
{data.representant_civ === 'Monsieur' ? 'Le représentant légal' : 'La représentante légale'}
|
||||
{isMadame ? ' de la salariée :' : ' du salarié :'}
|
||||
</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{data.representant_civ} {data.representant_nom}
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>(Signature électronique via DocuSeal)</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Signature employeur */}
|
||||
<View style={styles.signatureSpace}>
|
||||
<Text style={styles.infoLabel}>L'employeur:</Text>
|
||||
<Text style={styles.infoValue}>Pour {data.structure_name},</Text>
|
||||
{data.delegation === 'Oui' && (
|
||||
<Text style={styles.infoDelegation}>
|
||||
Pour le représentant légal et par délégation,
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.infoValue}>{data.structure_signataire},</Text>
|
||||
<Text style={styles.infoValue}>{data.structure_signatairequalite}.</Text>
|
||||
<Text style={styles.paragraph}>(Signature électronique via DocuSeal)</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
82
lib/pdf/types.ts
Normal file
82
lib/pdf/types.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
125
lib/pdf/uploadPdf.ts
Normal file
125
lib/pdf/uploadPdf.ts
Normal file
|
|
@ -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<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string> {
|
||||
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`;
|
||||
}
|
||||
467
package-lock.json
generated
467
package-lock.json
generated
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue