From 69e424519d523aaae9641464c45e62cb509c4dc9 Mon Sep 17 00:00:00 2001 From: Ferdinand Urban Date: Tue, 28 Apr 2026 11:03:05 +0000 Subject: [PATCH] feat(excel): group entries by date, join names with ' & ' --- js/excel.js | 29 ++++++++++++++----- package.json | 2 +- tests/excel.test.mjs | 67 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 tests/excel.test.mjs diff --git a/js/excel.js b/js/excel.js index 36f6e78..d93c2b0 100644 --- a/js/excel.js +++ b/js/excel.js @@ -8,16 +8,31 @@ export function readEmployeeNames(arrayBuffer) { .filter(name => name.length > 0); } +// Pure helper: returns { names: [...], dates: [...] } columns for a schedule. +// Multiple employees per date are joined alphabetically with " & ". +export function buildScheduleColumns(assignments, employees) { + const nameById = Object.fromEntries(employees.map(e => [e.id, e.name])); + const byDate = new Map(); + for (const a of assignments) { + if (!byDate.has(a.date)) byDate.set(a.date, []); + byDate.get(a.date).push(nameById[a.employeeId] ?? ''); + } + const sortedDates = [...byDate.keys()].sort(); + const names = sortedDates.map(d => + byDate.get(d).slice().sort((x, y) => x.localeCompare(y, 'de')).join(' & ') + ); + const dates = sortedDates.map(d => { + const [y, m, day] = d.split('-'); + return day + '.' + m + '.' + y; + }); + return { names, dates }; +} + // Exports schedule as xlsx blob -// assignments: [{ date: "2026-04-01", employeeId }] +// assignments: [{ date: "2026-04-01", employeeId }] — multiple entries per date allowed // employees: [{ id, name }] export function exportSchedule(assignments, employees) { - const nameById = Object.fromEntries(employees.map(e => [e.id, e.name])); - const names = assignments.map(a => nameById[a.employeeId] ?? ''); - const dates = assignments.map(a => { - const [y, m, d] = a.date.split('-'); - return d + '.' + m + '.' + y; - }); + const { names, dates } = buildScheduleColumns(assignments, employees); const ws = XLSX.utils.aoa_to_sheet([names, dates]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Moderationsplan'); diff --git a/package.json b/package.json index db770df..c4a0e0d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,6 @@ "type": "module", "scripts": { "start": "npx serve . -p 3001", - "test": "node tests/algorithm.test.mjs && node tests/data.test.mjs" + "test": "node tests/algorithm.test.mjs && node tests/data.test.mjs && node tests/excel.test.mjs" } } diff --git a/tests/excel.test.mjs b/tests/excel.test.mjs new file mode 100644 index 0000000..01c5806 --- /dev/null +++ b/tests/excel.test.mjs @@ -0,0 +1,67 @@ +import assert from 'node:assert/strict'; +import { buildScheduleColumns } from '../js/excel.js'; + +const employees = [ + { id: 'a1', name: 'Anna Müller' }, + { id: 'b1', name: 'Bernd Schulz' }, + { id: 'c1', name: 'Carla Weber' } +]; + +// Single employee per day +{ + const assignments = [ + { date: '2026-04-01', employeeId: 'a1' }, + { date: '2026-04-02', employeeId: 'b1' } + ]; + const cols = buildScheduleColumns(assignments, employees); + assert.deepEqual(cols.names, ['Anna Müller', 'Bernd Schulz'], 'names per single-entry day'); + assert.deepEqual(cols.dates, ['01.04.2026', '02.04.2026'], 'dates formatted DD.MM.YYYY'); +} + +// Two employees per day → joined with " & ", alphabetical +{ + const assignments = [ + { date: '2026-04-01', employeeId: 'b1' }, // Bernd inserted first + { date: '2026-04-01', employeeId: 'a1' } // Anna inserted second + ]; + const cols = buildScheduleColumns(assignments, employees); + assert.equal(cols.names.length, 1, 'one column per date'); + assert.equal(cols.names[0], 'Anna Müller & Bernd Schulz', 'names alphabetical, joined with " & "'); + assert.equal(cols.dates[0], '01.04.2026', 'one date entry'); +} + +// Three employees per day +{ + const assignments = [ + { date: '2026-04-01', employeeId: 'c1' }, + { date: '2026-04-01', employeeId: 'a1' }, + { date: '2026-04-01', employeeId: 'b1' } + ]; + const cols = buildScheduleColumns(assignments, employees); + assert.equal(cols.names[0], 'Anna Müller & Bernd Schulz & Carla Weber', 'three names joined alphabetical'); +} + +// Multiple dates with multiple employees, dates ascending +{ + const assignments = [ + { date: '2026-04-02', employeeId: 'a1' }, + { date: '2026-04-01', employeeId: 'b1' }, + { date: '2026-04-01', employeeId: 'a1' } + ]; + const cols = buildScheduleColumns(assignments, employees); + assert.deepEqual(cols.dates, ['01.04.2026', '02.04.2026'], 'dates sorted ascending'); + assert.deepEqual(cols.names, ['Anna Müller & Bernd Schulz', 'Anna Müller'], 'names per column in date order'); +} + +// Unknown employeeId: empty string slot, sorted with other names +{ + const assignments = [ + { date: '2026-04-01', employeeId: 'a1' }, + { date: '2026-04-01', employeeId: 'unknown' } + ]; + const cols = buildScheduleColumns(assignments, employees); + // Empty-string sorts before 'Anna Müller' + assert.equal(cols.names[0], ' & Anna Müller', 'unknown employee → empty string in join'); +} + +console.log('All excel tests passed.');