101 lines
4.1 KiB
JavaScript
101 lines
4.1 KiB
JavaScript
/* ============================================================
|
|
exportXlsx.js — brand-styled spreadsheet exports.
|
|
|
|
One shared exporter so every "Export" button produces the same
|
|
dashboard-flavoured file: TalentFlow green title band, mint meta row,
|
|
ink-teal header, zebra data rows, frozen header + autofilter.
|
|
|
|
exceljs is ~1MB, so it is imported dynamically — Vite splits it into
|
|
its own chunk that only ever downloads when an export is clicked.
|
|
============================================================ */
|
|
|
|
/** Utopia Brands palette (ARGB, from styles.css brand constants). */
|
|
const BRAND = {
|
|
green: 'FF004D43', // deep green — title band, like the sidebar/action colour
|
|
ink: 'FF1A3134', // ink teal — header row
|
|
lime: 'FFCEFF71', // signature lime — title text accent
|
|
mint: 'FFEAFFF4', // mint white — meta row fill
|
|
zebra: 'FFF1F7F4', // app background — alternating data rows
|
|
border: 'FFDBE8E2', // hairline borders
|
|
text: 'FF10231F',
|
|
sub: 'FF4A625C',
|
|
white: 'FFFFFFFF',
|
|
}
|
|
|
|
const thin = { style: 'thin', color: { argb: BRAND.border } }
|
|
|
|
/**
|
|
* Build and download a styled .xlsx.
|
|
*
|
|
* @param {object} opts
|
|
* @param {string} opts.filename without extension
|
|
* @param {string} opts.title big brand-band line, e.g. "Recruitment Inbox"
|
|
* @param {string} opts.subtitle meta line, e.g. "All Applications · 512 rows · 02/09/2026"
|
|
* @param {Array<{header: string, key: string, width?: number}>} opts.columns
|
|
* @param {Array<object>} opts.rows keyed by columns[].key; null/undefined print blank
|
|
*/
|
|
export async function exportStyledXlsx({ filename, title, subtitle, columns, rows }) {
|
|
const ExcelJS = (await import('exceljs')).default
|
|
const wb = new ExcelJS.Workbook()
|
|
wb.creator = 'TalentFlow ATS'
|
|
wb.created = new Date()
|
|
|
|
const ws = wb.addWorksheet(title.slice(0, 31) || 'Export', {
|
|
views: [{ state: 'frozen', ySplit: 3 }],
|
|
})
|
|
|
|
ws.columns = columns.map((c) => ({ key: c.key, width: c.width ?? 18 }))
|
|
const span = columns.length
|
|
|
|
// Row 1 — brand title band
|
|
const titleRow = ws.addRow([title])
|
|
ws.mergeCells(1, 1, 1, span)
|
|
titleRow.height = 30
|
|
const titleCell = ws.getCell(1, 1)
|
|
titleCell.font = { name: 'Calibri', size: 14, bold: true, color: { argb: BRAND.lime } }
|
|
titleCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: BRAND.green } }
|
|
titleCell.alignment = { vertical: 'middle', indent: 1 }
|
|
|
|
// Row 2 — meta line
|
|
const metaRow = ws.addRow([subtitle])
|
|
ws.mergeCells(2, 1, 2, span)
|
|
metaRow.height = 20
|
|
const metaCell = ws.getCell(2, 1)
|
|
metaCell.font = { name: 'Calibri', size: 10, color: { argb: BRAND.sub } }
|
|
metaCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: BRAND.mint } }
|
|
metaCell.alignment = { vertical: 'middle', indent: 1 }
|
|
|
|
// Row 3 — column headers
|
|
const headRow = ws.addRow(columns.map((c) => c.header))
|
|
headRow.height = 22
|
|
headRow.eachCell((cell) => {
|
|
cell.font = { name: 'Calibri', size: 10, bold: true, color: { argb: BRAND.white } }
|
|
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: BRAND.ink } }
|
|
cell.alignment = { vertical: 'middle' }
|
|
cell.border = { bottom: { style: 'medium', color: { argb: BRAND.lime } } }
|
|
})
|
|
|
|
// Data — zebra rows with hairline borders
|
|
for (const [i, r] of rows.entries()) {
|
|
const row = ws.addRow(columns.map((c) => r[c.key] ?? ''))
|
|
row.eachCell({ includeEmpty: true }, (cell) => {
|
|
cell.font = { name: 'Calibri', size: 10, color: { argb: BRAND.text } }
|
|
if (i % 2 === 1) cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: BRAND.zebra } }
|
|
cell.border = { bottom: thin, right: thin }
|
|
cell.alignment = { vertical: 'middle', wrapText: false }
|
|
})
|
|
}
|
|
|
|
ws.autoFilter = { from: { row: 3, column: 1 }, to: { row: 3, column: span } }
|
|
|
|
const buf = await wb.xlsx.writeBuffer()
|
|
const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
|
const a = document.createElement('a')
|
|
a.href = URL.createObjectURL(blob)
|
|
a.download = `${filename}.xlsx`
|
|
document.body.appendChild(a)
|
|
a.click()
|
|
a.remove()
|
|
URL.revokeObjectURL(a.href)
|
|
}
|