refactor(api): extract buildSessionsCsv shared by worker + admin exports

This commit is contained in:
Bas van Rossem
2026-06-24 16:22:06 +02:00
parent eac1eed71a
commit 01fa18f401
3 changed files with 106 additions and 40 deletions

View File

@@ -11,3 +11,62 @@ export function formatDuration(totalSeconds: number): string {
const sec = s % 60;
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
}
// One completed session, flattened for CSV. Times accept Date | number | string.
export interface SessionCsvRow {
id: number;
activityName: string | null;
userName?: string | null;
insoleType: string | null;
pairCount: number;
startTime: Date | number | string;
endTime: Date | number | string | null;
durationSeconds: number | null;
pausedSeconds: number | null;
}
// Build the CSV body shared by the worker self-export and the admin all-users export.
// includeWorker prepends a Worker column; everything else matches the legacy format byte-for-byte.
export function buildSessionsCsv(
rows: SessionCsvRow[],
opts: { includeWorker?: boolean } = {},
): string {
const includeWorker = opts.includeWorker ?? false;
const header = [
...(includeWorker ? ['Worker'] : []),
'ID',
'Task',
'Insole Type',
'No. of Insoles',
'Date',
'Total Duration',
'Paused Duration',
'Start Time',
'End Time',
]
.map(quote)
.join(',');
const dataLines = rows.map((row) => {
const start = new Date(row.startTime);
const end = row.endTime ? new Date(row.endTime) : null;
return [
...(includeWorker ? [row.userName ?? ''] : []),
row.id,
row.activityName ?? '',
row.insoleType ?? 'Kurk',
row.pairCount ?? 2,
start.toLocaleDateString('nl-BE', { day: '2-digit', month: '2-digit', year: 'numeric' }),
formatDuration(row.durationSeconds ?? 0),
formatDuration(row.pausedSeconds ?? 0),
start.toLocaleTimeString('nl-BE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
end
? end.toLocaleTimeString('nl-BE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
: '',
]
.map(quote)
.join(',');
});
return [header, ...dataLines].join('\n');
}

View File

@@ -5,7 +5,7 @@ import { db } from '../db/client';
import { activities, workSessions } from '../db/schema';
import { getSessionUser } from '../lib/require-user';
import { toWorkSession } from '../lib/work-session';
import { quote, formatDuration } from '../lib/csv';
import { buildSessionsCsv } from '../lib/csv';
export const sessionsRoutes = new Hono();
@@ -20,45 +20,18 @@ sessionsRoutes.get('/api/export', async (c) => {
.where(and(eq(workSessions.userId, sessionUser.id), eq(workSessions.status, 'completed')))
.orderBy(asc(workSessions.startTime));
const header = [
'ID',
'Task',
'Insole Type',
'No. of Insoles',
'Date',
'Total Duration',
'Paused Duration',
'Start Time',
'End Time',
]
.map(quote)
.join(',');
const dataLines = rows.map(({ session, activityName }) => {
const start = new Date(session.startTime);
const end = session.endTime ? new Date(session.endTime) : null;
return [
session.id,
activityName ?? '',
session.insoleType ?? 'Kurk',
session.pairCount ?? 2,
start.toLocaleDateString('nl-BE', { day: '2-digit', month: '2-digit', year: 'numeric' }),
formatDuration(session.durationSeconds ?? 0),
formatDuration(session.pausedSeconds ?? 0),
start.toLocaleTimeString('nl-BE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}),
end
? end.toLocaleTimeString('nl-BE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
: '',
]
.map(quote)
.join(',');
});
const csv = [header, ...dataLines].join('\n');
const csv = buildSessionsCsv(
rows.map(({ session, activityName }) => ({
id: session.id,
activityName,
insoleType: session.insoleType,
pairCount: session.pairCount,
startTime: session.startTime,
endTime: session.endTime,
durationSeconds: session.durationSeconds,
pausedSeconds: session.pausedSeconds,
})),
);
return c.body(csv, 200, {
'Content-Type': 'text/csv; charset=utf-8',