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,231 @@
import PDFDocument from "pdfkit";
import * as XLSX from "xlsx";
type AnalyticsPayload = {
period: string;
periodLabel: string;
comparisonLabel: string;
startDate: string;
endDate: string;
summary: {
totalReservations: number;
salleReservations: number;
materialReservations: number;
occupancyRate: number;
activeAssociations: number;
totalMaterialUnits: number;
averageReservationsPerAssociation: number;
};
associationUsage: Array<{
associationName: string;
thematics: string[];
totalReservations: number;
salleReservations: number;
materialReservations: number;
lastReservationAt: string;
}>;
roomOccupancy: Array<{
roomName: string;
bookedDays: number;
occupancyRate: number;
}>;
materialUsage: Array<{
label: string;
quantity: number;
requests: number;
}>;
history: Array<{
type: string;
associationName: string;
title: string;
startDate: string;
endDate: string;
resources: string;
status: string;
createdAt: Date;
}>;
};
const requestTypeLabels: Record<string, string> = {
demande_salle: "Demande salle",
demande_materiel_evenementiel: "Matériel événementiel",
};
const statusLabels: Record<string, string> = {
brouillon: "Brouillon",
soumise: "Soumise",
en_cours_traitement: "En cours",
information_complementaire: "Info requise",
validee: "Validée",
refusee: "Refusée",
annulee: "Annulée",
};
function formatDateFr(value: string | Date | null | undefined) {
if (!value) return "-";
try {
return new Date(value).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
} catch {
return String(value);
}
}
function buildFileBaseName(data: AnalyticsPayload) {
return `rapport-statistiques-ccds-${data.period}-${data.endDate}`;
}
export async function generateReservationAnalyticsPdf(data: AnalyticsPayload) {
const doc = new PDFDocument({
size: "A4",
margin: 40,
info: {
Title: `Rapport statistiques CCDS - ${data.periodLabel}`,
Author: "Portail Associations CCDS",
},
});
const chunks: Buffer[] = [];
doc.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
const done = new Promise<Buffer>((resolve) => {
doc.on("end", () => resolve(Buffer.concat(chunks)));
});
doc.fontSize(20).fillColor("#000091").text("Rapport statistiques CCDS");
doc.moveDown(0.3);
doc.fontSize(11).fillColor("#475569").text(`${data.periodLabel} - ${formatDateFr(data.startDate)} au ${formatDateFr(data.endDate)}`);
doc.moveDown(1);
doc.fontSize(14).fillColor("#0f172a").text("Synthèse");
doc.moveDown(0.5);
[
["Réservations totales", String(data.summary.totalReservations)],
["Réservations de salles", String(data.summary.salleReservations)],
["Réservations de matériel", String(data.summary.materialReservations)],
["Taux d'occupation des salles", `${data.summary.occupancyRate}%`],
["Associations utilisatrices", String(data.summary.activeAssociations)],
["Unités matérielles demandées", String(data.summary.totalMaterialUnits)],
["Fréquence moyenne par association", String(data.summary.averageReservationsPerAssociation)],
].forEach(([label, value]) => {
doc.font("Helvetica-Bold").fontSize(10).fillColor("#0f172a").text(`${label} : `, { continued: true });
doc.font("Helvetica").text(value);
});
doc.moveDown(1);
doc.font("Helvetica-Bold").fontSize(14).text("Top associations");
doc.moveDown(0.5);
data.associationUsage.slice(0, 10).forEach((entry) => {
doc.fontSize(10).font("Helvetica-Bold").text(entry.associationName);
doc.font("Helvetica").text(
`Catégories: ${entry.thematics.join(" / ") || "Non renseignée"} • Total: ${entry.totalReservations} • Salles: ${entry.salleReservations} • Matériel: ${entry.materialReservations} • Dernière réservation: ${formatDateFr(entry.lastReservationAt)}`
);
doc.moveDown(0.4);
});
doc.addPage();
doc.font("Helvetica-Bold").fontSize(14).text("Occupation des salles");
doc.moveDown(0.5);
data.roomOccupancy.forEach((entry) => {
doc.fontSize(10).font("Helvetica-Bold").text(entry.roomName, { continued: true });
doc.font("Helvetica").text(` - ${entry.bookedDays} jour(s) réservé(s), ${entry.occupancyRate}% d'occupation`);
});
doc.moveDown(1);
doc.font("Helvetica-Bold").fontSize(14).text("Matériels les plus demandés");
doc.moveDown(0.5);
data.materialUsage.forEach((entry) => {
doc.fontSize(10).font("Helvetica-Bold").text(entry.label, { continued: true });
doc.font("Helvetica").text(` - Quantité: ${entry.quantity} • Dossiers: ${entry.requests}`);
});
doc.moveDown(1);
doc.font("Helvetica-Bold").fontSize(14).text("Historique récent");
doc.moveDown(0.5);
data.history.slice(0, 20).forEach((entry) => {
const period = entry.startDate === entry.endDate
? formatDateFr(entry.startDate)
: `${formatDateFr(entry.startDate)} au ${formatDateFr(entry.endDate)}`;
doc.fontSize(10).font("Helvetica-Bold").text(`${requestTypeLabels[entry.type] || entry.type} - ${entry.associationName}`);
doc.font("Helvetica").text(`${entry.title}${period}${entry.resources || "-"}${statusLabels[entry.status] || entry.status}`);
doc.moveDown(0.35);
});
doc.end();
const buffer = await done;
return {
buffer,
fileName: `${buildFileBaseName(data)}.pdf`,
contentType: "application/pdf",
};
}
export function generateReservationAnalyticsExcel(data: AnalyticsPayload) {
const workbook = XLSX.utils.book_new();
const summaryRows = [
["Rapport statistiques CCDS", ""],
["Période", `${data.periodLabel} (${data.startDate} au ${data.endDate})`],
["Comparaison", data.comparisonLabel],
[],
["Indicateur", "Valeur"],
["Réservations totales", data.summary.totalReservations],
["Réservations de salles", data.summary.salleReservations],
["Réservations de matériel", data.summary.materialReservations],
["Taux d'occupation des salles", `${data.summary.occupancyRate}%`],
["Associations utilisatrices", data.summary.activeAssociations],
["Unités matérielles demandées", data.summary.totalMaterialUnits],
["Fréquence moyenne par association", data.summary.averageReservationsPerAssociation],
];
const associationRows = [
["Association", "Catégories", "Total", "Salles", "Matériel", "Dernière réservation"],
...data.associationUsage.map((item) => [
item.associationName,
item.thematics.join(" / "),
item.totalReservations,
item.salleReservations,
item.materialReservations,
item.lastReservationAt,
]),
];
const roomRows = [
["Salle", "Jours réservés", "Taux d'occupation"],
...data.roomOccupancy.map((item) => [item.roomName, item.bookedDays, `${item.occupancyRate}%`]),
];
const materialRows = [
["Matériel", "Quantité", "Dossiers"],
...data.materialUsage.map((item) => [item.label, item.quantity, item.requests]),
];
const historyRows = [
["Type", "Association", "Dossier", "Début", "Fin", "Ressources", "Statut"],
...data.history.map((item) => [
requestTypeLabels[item.type] || item.type,
item.associationName,
item.title,
item.startDate,
item.endDate,
item.resources,
statusLabels[item.status] || item.status,
]),
];
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(summaryRows), "Synthèse");
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(associationRows), "Associations");
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(roomRows), "Salles");
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(materialRows), "Matériel");
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(historyRows), "Historique");
const buffer = XLSX.write(workbook, { type: "buffer", bookType: "xlsx" }) as Buffer;
return {
buffer,
fileName: `${buildFileBaseName(data)}.xlsx`,
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
};
}