Files
travandClaude Opus 5 d1cb472696 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>
2026-08-15 15:02:45 -04:00

97 lines
2.9 KiB
JavaScript

'use strict';
const slug = location.pathname.split('/').filter(Boolean)[0];
const form = document.getElementById('form');
const customer = document.getElementById('customer');
const item = document.getElementById('item');
const category = document.getElementById('category');
const problem = document.getElementById('problem');
const agree = document.getElementById('agree');
const submitBtn = document.getElementById('submit');
const savedBox = document.getElementById('saved');
const errorBox = document.getElementById('error');
const rules = document.getElementById('rules');
let categories = [];
let savedTimer = null;
document.getElementById('dashboard-link').href = `/${slug}/dashboard`;
async function init() {
const [config, event] = await Promise.all([
fetch('/api/config').then((r) => r.json()),
fetch(`/api/events/${slug}`).then((r) => r.json()),
]);
document.title = `Sign in a repair — ${event.name}`;
document.getElementById('event-name').textContent = event.name;
categories = config.categories.map((c) => c.name);
const list = document.getElementById('categories');
for (const name of categories) {
const option = document.createElement('option');
option.value = name;
list.append(option);
}
document.getElementById('rules-text').textContent = config.houseRules;
customer.focus();
}
/* house rules popup */
document.getElementById('rules-link').addEventListener('click', (e) => {
e.preventDefault();
rules.showModal();
});
document.getElementById('rules-ok').addEventListener('click', () => rules.close());
/* submit */
function showError(message) {
errorBox.textContent = message;
errorBox.hidden = false;
savedBox.hidden = true;
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
errorBox.hidden = true;
const chosen = category.value.trim().toLowerCase();
if (!categories.includes(chosen)) {
showError(`Please pick a category from the list: ${categories.join(', ')}.`);
category.focus();
return;
}
submitBtn.disabled = true;
try {
const res = await fetch(`/api/events/${slug}/repairs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customer: customer.value,
item: item.value,
category: chosen,
problem: problem.value,
agreedRules: agree.checked,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Could not submit.');
// Clear everything and hand the tablet back, ready for the next person.
form.reset();
customer.focus();
savedBox.hidden = false;
clearTimeout(savedTimer);
savedTimer = setTimeout(() => { savedBox.hidden = true; }, 6000);
} catch (err) {
showError(err.message);
} finally {
submitBtn.disabled = false;
}
});
init().catch(() => showError('Could not load this event. Try reloading the page.'));