f4ffc02028
LST-59 (3.1) front. Completes the Reporting module. - New frontend/modules/reporting/ layer (auto-detected): /reporting page (admin middleware) consuming the 4 read-only report endpoints. - Filters (period presets + custom dates, project, user). 4 sections (time per project, time per user, tasks by status, absences by type) each with a DataTable + a Chart.js chart (reused global registration). - Front-side CSV export per section (useCsvExport: BOM UTF-8, ; separator). - i18n keys (reporting.*, sidebar.admin.reporting). nuxt build passes; /reporting routed; no route regression.
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
export type CsvColumn<T> = {
|
|
header: string
|
|
value: (row: T) => string | number
|
|
}
|
|
|
|
/** Escapes a single CSV cell (quotes wrapping + doubled inner quotes). */
|
|
function escapeCell(value: string | number): string {
|
|
const str = String(value ?? '')
|
|
if (/[",\n;]/.test(str)) {
|
|
return `"${str.replace(/"/g, '""')}"`
|
|
}
|
|
return str
|
|
}
|
|
|
|
export function useCsvExport() {
|
|
/** Builds a CSV string from rows + column definitions (semicolon separator, FR-friendly). */
|
|
function toCsv<T>(rows: T[], columns: CsvColumn<T>[]): string {
|
|
const header = columns.map(c => escapeCell(c.header)).join(';')
|
|
const lines = rows.map(row =>
|
|
columns.map(c => escapeCell(c.value(row))).join(';'),
|
|
)
|
|
return [header, ...lines].join('\n')
|
|
}
|
|
|
|
/** Triggers a browser download of the given CSV content. */
|
|
function downloadCsv<T>(filename: string, rows: T[], columns: CsvColumn<T>[]): void {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
const csv = toCsv(rows, columns)
|
|
// Prepend BOM so Excel detects UTF-8.
|
|
const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' })
|
|
const url = URL.createObjectURL(blob)
|
|
const link = document.createElement('a')
|
|
link.href = url
|
|
link.download = filename.endsWith('.csv') ? filename : `${filename}.csv`
|
|
document.body.appendChild(link)
|
|
link.click()
|
|
document.body.removeChild(link)
|
|
URL.revokeObjectURL(url)
|
|
}
|
|
|
|
return { toCsv, downloadCsv }
|
|
}
|