Initial local backup snapshot

This commit is contained in:
Selecta Keke 2026-06-24 00:02:55 -03:00
commit acd9e14ba5
367 changed files with 118038 additions and 0 deletions

View file

@ -0,0 +1,238 @@
function formatDateFr(date: Date | string | null | undefined) {
if (!date) return "Non renseignée";
return new Date(date).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "long",
year: "numeric",
});
}
function escapeHtml(value: string) {
return value
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
export function generateMaterialReturnReminderEmail(input: {
associationName: string;
serviceLabel?: string | null;
restitutionDate: Date | string;
uploadLink: string;
requestTitle: string;
tone?: "assignment" | "scheduled" | "overdue" | "reactivation";
}) {
const restitutionLabel = formatDateFr(input.restitutionDate);
const serviceLabel = input.serviceLabel?.trim() || "service technique / logistique";
const tone = input.tone || "scheduled";
const toneContent = {
assignment: {
subject: `Mission de récupération assignée - ${input.associationName} - restitution prévue le ${restitutionLabel}`,
title: "Mission de récupération assignée",
intro: `Une mission de récupération du matériel a été attribuée à <strong>${escapeHtml(serviceLabel)}</strong> pour <strong>${escapeHtml(input.associationName)}</strong>.`,
actionTitle: "Action à préparer",
actionBody:
"La fiche d'état des lieux préremplie CCDS est jointe à cet email. Conservez ce lien terrain : il vous permettra de constater la restitution, signer sur smartphone et clôturer le dossier le moment venu.",
accent: "#0f2d63",
alertBg: "#f5f9ff",
alertBorder: "#cfe0ff",
alertTitleColor: "#0f2d63",
},
scheduled: {
subject: `Récupération matériel à organiser - ${input.associationName} - restitution prévue le ${restitutionLabel}`,
title: "Récupération du matériel à organiser",
intro: `La restitution du matériel pour <strong>${escapeHtml(input.associationName)}</strong> approche.`,
actionTitle: "Action attendue",
actionBody:
"La fiche d'état des lieux préremplie CCDS est jointe à cet email. Votre service dispose d'un délai maximal de <strong>3 jours</strong> pour effectuer la récupération, compléter la fiche contradictoire puis la déposer sur le portail.",
accent: "#0f2d63",
alertBg: "#fff8e6",
alertBorder: "#efb100",
alertTitleColor: "#7c5a00",
},
overdue: {
subject: `Retard de restitution - action requise - ${input.associationName} - ${restitutionLabel}`,
title: "Alerte retard de restitution",
intro: `La restitution du matériel pour <strong>${escapeHtml(input.associationName)}</strong> n'a pas encore été validée dans les délais.`,
actionTitle: "Relance automatique",
actionBody:
"Merci d'intervenir en priorité. Le délai de 3 jours après la date de restitution est dépassé. Utilisez le lien ci-dessous pour finaliser le constat terrain et débloquer la clôture du dossier.",
accent: "#b45309",
alertBg: "#fff7ed",
alertBorder: "#fdba74",
alertTitleColor: "#9a3412",
},
reactivation: {
subject: `Nouveau lien de restitution actif - ${input.associationName} - ${restitutionLabel}`,
title: "Lien de restitution réactivé",
intro: `Un nouveau lien terrain actif a été généré pour <strong>${escapeHtml(input.associationName)}</strong>.`,
actionTitle: "Action immédiate",
actionBody:
"Utilisez exclusivement le nouveau lien ci-dessous pour constater la restitution, signer sur smartphone et clôturer le dossier. Les anciens liens de restitution ne sont plus valides.",
accent: "#0f2d63",
alertBg: "#eef6ff",
alertBorder: "#93c5fd",
alertTitleColor: "#1d4ed8",
},
}[tone];
const subject = toneContent.subject;
const text = [
tone === "assignment"
? "Assignation logistique - récupération du matériel événementiel"
: tone === "overdue"
? "Alerte automatique - restitution du matériel en retard"
: "Rappel automatique - récupération du matériel événementiel",
"",
`Association : ${input.associationName}`,
`Demande : ${input.requestTitle}`,
`Service concerné : ${serviceLabel}`,
`Date de restitution prévue : ${restitutionLabel}`,
"",
"La fiche d'état des lieux préremplie CCDS est jointe à cet email.",
tone === "assignment"
? "Ce lien terrain devra être utilisé par l'agent mandaté pour effectuer le constat contradictoire, signer et clôturer la restitution."
: tone === "reactivation"
? "Un nouveau lien actif vient d'être généré. Les anciens liens de restitution ne sont plus valides."
: tone === "overdue"
? "Le délai de 3 jours après la restitution est dépassé. Merci de finaliser la récupération et de valider immédiatement la fiche sur le portail."
: "Vous disposez d'un délai maximal de 3 jours pour effectuer la récupération, compléter la fiche de manière contradictoire puis la téléverser sur le portail.",
"",
`Téléverser la fiche signée : ${input.uploadLink}`,
"",
"Ce message a été généré automatiquement par le Portail Associations.",
].join("\n");
const html = `
<div style="margin:0;padding:32px 0;background:#f4f7fb;font-family:Arial,Helvetica,sans-serif;color:#0f172a;">
<div style="max-width:720px;margin:0 auto;background:#ffffff;border:1px solid #dbe4f0;border-radius:20px;overflow:hidden;box-shadow:0 12px 30px rgba(15,23,42,0.08);">
<div style="padding:28px 32px;background:${tone === "overdue" ? "linear-gradient(135deg,#8a4b00 0%,#b45309 100%)" : "linear-gradient(135deg,#0f2d63 0%,#18438f 100%)"};color:#ffffff;">
<div style="font-size:12px;letter-spacing:0.12em;text-transform:uppercase;opacity:0.82;">Portail Associations</div>
<h1 style="margin:12px 0 6px 0;font-size:24px;line-height:1.2;">${toneContent.title}</h1>
<p style="margin:0;font-size:15px;line-height:1.6;opacity:0.9;">
${toneContent.intro}
</p>
</div>
<div style="padding:28px 32px;">
<div style="padding:18px 20px;border:1px solid #cfe0ff;border-radius:16px;background:#f5f9ff;">
<div style="display:grid;grid-template-columns:180px 1fr;gap:10px 18px;font-size:14px;line-height:1.5;">
<div style="color:#64748b;">Association</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(input.associationName)}</div>
<div style="color:#64748b;">Demande</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(input.requestTitle)}</div>
<div style="color:#64748b;">Service concerné</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(serviceLabel)}</div>
<div style="color:#64748b;">Restitution prévue</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(restitutionLabel)}</div>
</div>
</div>
<div style="margin-top:22px;padding:18px 20px;border-left:4px solid ${toneContent.alertBorder};background:${toneContent.alertBg};border-radius:12px;">
<p style="margin:0 0 8px 0;font-size:15px;font-weight:700;color:${toneContent.alertTitleColor};">${toneContent.actionTitle}</p>
<p style="margin:0;font-size:14px;line-height:1.7;color:#3f3f46;">
${toneContent.actionBody}
</p>
</div>
<div style="margin-top:26px;text-align:center;">
<a href="${escapeHtml(input.uploadLink)}" style="display:inline-block;padding:14px 22px;background:#0f2d63;color:#ffffff;text-decoration:none;border-radius:12px;font-weight:700;">
${tone === "reactivation" ? "Ouvrir le nouveau lien terrain" : "Téléverser la fiche signée"}
</a>
</div>
<div style="margin-top:16px;padding:14px 16px;border:1px dashed #cbd5e1;border-radius:12px;background:#f8fafc;">
<p style="margin:0 0 6px 0;font-size:12px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:0.08em;">
Lien terrain à utiliser
</p>
<p style="margin:0;font-size:13px;line-height:1.6;word-break:break-all;color:#0f172a;">
<a href="${escapeHtml(input.uploadLink)}" style="color:#0f2d63;text-decoration:underline;">${escapeHtml(input.uploadLink)}</a>
</p>
</div>
<p style="margin:26px 0 0 0;font-size:13px;line-height:1.6;color:#64748b;">
Ce message a é généré automatiquement par le Portail Associations afin de sécuriser le retour du matériel événementiel.
</p>
</div>
</div>
</div>
`;
return { subject, text, html };
}
export function generateMaterialReturnCompletionEmail(input: {
associationName: string;
requestTitle: string;
restitutionDate: Date | string;
serviceLabel?: string | null;
issueFlag: boolean;
compliance: "conforme" | "non_conforme";
discrepancyDetails?: string | null;
}) {
const restitutionLabel = formatDateFr(input.restitutionDate);
const serviceLabel = input.serviceLabel?.trim() || "service technique / logistique";
const statusLabel = input.issueFlag ? "Alerte dégradation / litige" : "Matériel récupéré / dossier clôturé";
const subject = `${input.issueFlag ? "Alerte restitution" : "Restitution clôturée"} - ${input.associationName} - ${restitutionLabel}`;
const text = [
"Clôture de restitution du matériel événementiel",
"",
`Association : ${input.associationName}`,
`Demande : ${input.requestTitle}`,
`Service concerné : ${serviceLabel}`,
`Restitution : ${restitutionLabel}`,
`Résultat : ${statusLabel}`,
`Conformité : ${input.compliance === "conforme" ? "Conforme" : "Non conforme / partielle"}`,
input.discrepancyDetails ? `Réserves : ${input.discrepancyDetails}` : "",
"",
"Le PDF officiel finalisé est joint à cet email pour archivage et suivi.",
"",
"Ce message a été généré automatiquement par le Portail Associations.",
]
.filter(Boolean)
.join("\n");
const html = `
<div style="margin:0;padding:32px 0;background:#f4f7fb;font-family:Arial,Helvetica,sans-serif;color:#0f172a;">
<div style="max-width:720px;margin:0 auto;background:#ffffff;border:1px solid #dbe4f0;border-radius:20px;overflow:hidden;box-shadow:0 12px 30px rgba(15,23,42,0.08);">
<div style="padding:28px 32px;background:${input.issueFlag ? "#8a4b00" : "#0f2d63"};color:#ffffff;">
<div style="font-size:12px;letter-spacing:0.12em;text-transform:uppercase;opacity:0.82;">Portail Associations</div>
<h1 style="margin:12px 0 6px 0;font-size:24px;line-height:1.2;">${input.issueFlag ? "Alerte restitution" : "Restitution clôturée"}</h1>
<p style="margin:0;font-size:15px;line-height:1.6;opacity:0.92;">
${input.issueFlag ? "Des réserves ont été constatées lors de la restitution." : "Le matériel a été récupéré et la fiche contradictoire est finalisée."}
</p>
</div>
<div style="padding:28px 32px;">
<div style="display:grid;grid-template-columns:180px 1fr;gap:10px 18px;font-size:14px;line-height:1.5;">
<div style="color:#64748b;">Association</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(input.associationName)}</div>
<div style="color:#64748b;">Demande</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(input.requestTitle)}</div>
<div style="color:#64748b;">Service concerné</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(serviceLabel)}</div>
<div style="color:#64748b;">Restitution</div>
<div style="font-weight:700;color:#0f172a;">${escapeHtml(restitutionLabel)}</div>
<div style="color:#64748b;">Résultat</div>
<div style="font-weight:700;color:${input.issueFlag ? "#9a3412" : "#166534"};">${escapeHtml(statusLabel)}</div>
</div>
<div style="margin-top:20px;padding:18px 20px;border-radius:14px;background:${input.issueFlag ? "#fff7ed" : "#ecfdf3"};border:1px solid ${input.issueFlag ? "#fdba74" : "#a7f3d0"};">
<p style="margin:0 0 8px 0;font-size:15px;font-weight:700;color:${input.issueFlag ? "#9a3412" : "#166534"};">
${input.compliance === "conforme" ? "Restitution conforme" : "Restitution non conforme / partielle"}
</p>
<p style="margin:0;font-size:14px;line-height:1.7;color:#3f3f46;">
${escapeHtml(input.discrepancyDetails || "Aucune réserve particulière n'a été déclarée.")}
</p>
</div>
<p style="margin:22px 0 0 0;font-size:13px;line-height:1.6;color:#64748b;">
Le PDF officiel finalisé est joint à cet email pour archivage et suivi.
</p>
</div>
</div>
</div>
`;
return { subject, text, html };
}