formulaires et modifs

This commit is contained in:
2026-06-30 22:39:13 +02:00
parent 83f509d2bc
commit 543f7c8fcd
9 changed files with 818 additions and 275 deletions

View File

@@ -1,8 +1,7 @@
import fs from "node:fs"
import path from "node:path"
import process from "node:process"
import { PassThrough } from "node:stream"
import archiver from "archiver"
import ExcelJS from "exceljs"
import mysql from "mysql2/promise"
import nodemailer from "nodemailer"
@@ -180,134 +179,49 @@ async function trySendExportReport(report) {
}
}
function escapeXml(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;")
}
function columnName(index) {
let name = ""
let number = index + 1
while (number > 0) {
const modulo = (number - 1) % 26
name = String.fromCharCode(65 + modulo) + name
number = Math.floor((number - modulo) / 26)
}
return name
}
function formatCellValue(value) {
if (value === null || value === undefined) {
return ""
}
if (value instanceof Date) {
return value.toISOString().replace("T", " ").slice(0, 19)
}
return String(value)
}
function buildSheetXml(rows) {
const allRows = [columns.map(([, label]) => label), ...rows.map((row) => columns.map(([key]) => formatCellValue(row[key])))]
const sheetRows = allRows
.map((row, rowIndex) => {
const rowNumber = rowIndex + 1
const cells = row
.map((value, columnIndex) => {
const reference = `${columnName(columnIndex)}${rowNumber}`
return `<c r="${reference}" t="inlineStr"><is><t>${escapeXml(value)}</t></is></c>`
})
.join("")
return `<row r="${rowNumber}">${cells}</row>`
})
.join("")
const lastColumn = columnName(columns.length - 1)
const lastRow = Math.max(allRows.length, 1)
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<dimension ref="A1:${lastColumn}${lastRow}"/>
<sheetViews><sheetView workbookViewId="0"/></sheetViews>
<sheetFormatPr defaultRowHeight="15"/>
<cols>${columns.map((_, index) => `<col min="${index + 1}" max="${index + 1}" width="22" customWidth="1"/>`).join("")}</cols>
<sheetData>${sheetRows}</sheetData>
<autoFilter ref="A1:${lastColumn}${lastRow}"/>
</worksheet>`
}
async function createXlsxBuffer(rows) {
const archive = archiver("zip", { zlib: { level: 9 } })
const stream = new PassThrough()
const chunks = []
stream.on("data", (chunk) => chunks.push(chunk))
const completed = new Promise((resolve, reject) => {
stream.on("end", () => resolve(Buffer.concat(chunks)))
stream.on("error", reject)
archive.on("error", reject)
const workbook = new ExcelJS.Workbook()
workbook.creator = "wondif_vue"
workbook.lastModifiedBy = "wondif_vue"
workbook.created = new Date()
workbook.modified = new Date()
const worksheet = workbook.addWorksheet("Inscriptions", {
views: [{ state: "frozen", ySplit: 1 }],
})
archive.pipe(stream)
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
</Types>`, { name: "[Content_Types].xml" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>`, { name: "_rels/.rels" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets><sheet name="Inscriptions" sheetId="1" r:id="rId1"/></sheets>
</workbook>`, { name: "xl/workbook.xml" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>`, { name: "xl/_rels/workbook.xml.rels" })
archive.append(buildSheetXml(rows), { name: "xl/worksheets/sheet1.xml" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>
<fills count="1"><fill><patternFill patternType="none"/></fill></fills>
<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
<cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>
<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
</styleSheet>`, { name: "xl/styles.xml" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Application>wondif_vue</Application>
</Properties>`, { name: "docProps/app.xml" })
archive.append(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>Inscriptions Académie d'Orchestre</dc:title>
<dc:creator>wondif_vue</dc:creator>
<cp:lastModifiedBy>wondif_vue</cp:lastModifiedBy>
<dcterms:created xsi:type="dcterms:W3CDTF">${new Date().toISOString()}</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">${new Date().toISOString()}</dcterms:modified>
</cp:coreProperties>`, { name: "docProps/core.xml" })
await archive.finalize()
return completed
const tableRows = rows.map((row) =>
columns.map(([key]) => {
const value = row[key]
if (value === null || value === undefined) {
return ""
}
if (value instanceof Date) {
return value
}
return value
})
)
worksheet.addTable({
name: "TableInscriptions",
ref: "A1",
headerRow: true,
totalsRow: false,
style: {
theme: "TableStyleMedium2",
showRowStripes: true,
},
columns: columns.map(([, label]) => ({
name: label,
filterButton: true,
})),
rows: tableRows,
})
worksheet.columns.forEach((column) => {
column.width = 22
})
worksheet.getRow(1).font = {
bold: true,
}
return Buffer.from(await workbook.xlsx.writeBuffer())
}
async function getRows(db, mode) {