/** * services/export-xlsx.js * Génération XLSX avec ExcelJS (SuperAdmin uniquement) */ const ExcelJS = require('exceljs'); const HEADER_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF002D62' } }; const HEADER_FONT = { color: { argb: 'FFFFFFFF' }, bold: true, size: 10 }; const ALT_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF1F5F9' } }; function styleHeader(row) { row.eachCell(cell => { cell.fill = HEADER_FILL; cell.font = HEADER_FONT; cell.alignment = { vertical: 'middle', horizontal: 'center' }; cell.border = { bottom: { style: 'thin', color: { argb: 'FFE2E8F0' } } }; }); row.height = 22; } function styleDataRow(row, alt) { if (alt) { row.eachCell(cell => { cell.fill = ALT_FILL; }); } row.height = 16; } async function generateXlsx(view, data) { const wb = new ExcelJS.Workbook(); wb.creator = 'RLA API'; wb.created = new Date(); const titles = { synthese: 'Synthèse Globale', alertes: 'Alertes Délais', 'en-service': 'Marchés en Service', 'en-cours': 'Marchés en Cours', 'par-region': 'Par Région', clotures: 'Marchés Clôturés', pilotage: 'Pilotage Proactif', 'matrice-risque': 'Matrice de Risque', }; const title = titles[view] || view; const ws = wb.addWorksheet(title.slice(0, 31)); const items = data.items || data.regions || []; if (!items.length) { ws.addRow(['Aucune donnée disponible.']); return wb.xlsx.writeBuffer(); } const sample = items[0]; const keys = Object.keys(sample).filter(k => !k.endsWith('_raw') && k !== 'id' && typeof sample[k] !== 'object'); // En-tête ws.columns = keys.map(k => ({ header: k, key: k, width: 20 })); styleHeader(ws.getRow(1)); // Données items.forEach((item, i) => { const row = ws.addRow(keys.map(k => item[k] ?? '')); styleDataRow(row, i % 2 === 1); }); // Freeze header ws.views = [{ state: 'frozen', ySplit: 1 }]; // Onglet résumé si synthèse if (view === 'synthese' && data.par_statut) { const ws2 = wb.addWorksheet('Par Statut'); ws2.columns = [{ header: 'Statut', key: 'statut', width: 30 }, { header: 'Nombre', key: 'nb', width: 15 }]; styleHeader(ws2.getRow(1)); Object.entries(data.par_statut).forEach(([s, n], i) => { const row = ws2.addRow({ statut: s, nb: n }); styleDataRow(row, i % 2 === 1); }); } return wb.xlsx.writeBuffer(); } module.exports = { generateXlsx };