Initial commit: repair cafe kanban board
A per-event queue board for a repair cafe. Customers sign items in on a shared tablet; volunteers drag repairs through Queue, In progress and Done on a dashboard, then export the day's records as CSV. Zero npm dependencies: node:http plus JSON files on disk, no build step. Categories and house rules are set in config.json. Events are deleted 7 days after creation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
const COLUMNS = [
|
||||
'event',
|
||||
'date/time added',
|
||||
'customer',
|
||||
'item',
|
||||
'category',
|
||||
'problem',
|
||||
'status',
|
||||
'repairer',
|
||||
'outcome',
|
||||
'tools/consumables',
|
||||
'notes',
|
||||
];
|
||||
|
||||
// Quote a field if it contains anything that would break the row, doubling any
|
||||
// quotes inside it. Everything else goes out bare.
|
||||
function escapeField(value) {
|
||||
const text = value === null || value === undefined ? '' : String(value);
|
||||
if (/[",\r\n]/.test(text)) return '"' + text.replace(/"/g, '""') + '"';
|
||||
return text;
|
||||
}
|
||||
|
||||
function row(fields) {
|
||||
return fields.map(escapeField).join(',');
|
||||
}
|
||||
|
||||
// "2026-08-15 17:14" in the server's timezone — spreadsheets read that as a
|
||||
// date, which a raw UTC ISO string does not.
|
||||
function localDateTime(iso) {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return iso;
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
return (
|
||||
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
|
||||
`${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
);
|
||||
}
|
||||
|
||||
function repairRows(event) {
|
||||
return event.repairs.map((r) =>
|
||||
row([
|
||||
event.name,
|
||||
localDateTime(r.createdAt),
|
||||
r.customer,
|
||||
r.item,
|
||||
r.category,
|
||||
r.problem,
|
||||
r.status,
|
||||
r.repairer,
|
||||
r.outcome,
|
||||
r.toolsUsed,
|
||||
(r.notes || []).map((n) => n.text).join(' | '),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// A leading BOM so Excel reads it as UTF-8 rather than mangling accents.
|
||||
function toCsv(events) {
|
||||
const lines = [row(COLUMNS)];
|
||||
for (const event of events) lines.push(...repairRows(event));
|
||||
return '' + lines.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
module.exports = { toCsv, escapeField, COLUMNS };
|
||||
Reference in New Issue
Block a user