feat: Sitzungsliste und Verdrahtung zur vollstaendigen App

Ersetzt das Sichtpruefungs-Geruest in main.js durch die vollstaendige
Verdrahtung aus Kamera, Barcode/OCR-Erkennung, Sitzungsverwaltung,
Speicherung und Oberflaeche. Neu: session-list.js fuer die
Sitzungsliste zum Umsortieren/Entfernen/Beenden.

Gegenueber dem urspruenglichen Plan an den seither geaenderten
Schnittstellen ausgerichtet: commitAssignment erhaelt die tatsaechliche
Erkennungsquelle (source) statt der Konfidenz als drittes Argument, und
die OCR-Verfuegbarkeitsanzeige ueberschreibt keine noch zu lesende
Fehlermeldung mehr. Bezeichner in beiden Dateien auf Englisch
umgestellt, damit sie der bereits etablierten Projektkonvention
entsprechen.
This commit is contained in:
vchuser
2026-07-28 17:28:15 +02:00
parent 0c96743211
commit c4ad0416ab
2 changed files with 269 additions and 12 deletions
+102
View File
@@ -0,0 +1,102 @@
// Rein darstellend: Vollbild-Liste aller Stapel der laufenden Sitzung, zum
// Umsortieren und Entfernen einzelner Eintraege sowie zum Beenden der
// Sitzung. Kennt weder Kamera noch Erkennung - alles kommt ueber die
// Rueckrufe, genau wie bei scan-view.js und ambiguous-dialog.js.
/** Kurzbeschreibung eines Specs fuer Stapel-Kopfzeilen und Eintrags-Zeilen ohne Teilenummer. */
function describeSpec(spec) {
const parts = [
spec.capacityGb ? `${spec.capacityGb}GB` : '?',
spec.rank ?? '?',
spec.speed ?? '?',
spec.formFactor ?? '?',
];
return parts.join(' ');
}
/**
* Vollbild-Liste aller Stapel der laufenden Sitzung.
* @param {HTMLElement} root
* @param {{stacks: Array<{id: string, count: number, spec: object}>, entries: Array<{entryId: number, spec: object, stackId: string}>}} session
* @param {{onMove: (entryId: number, stackId: string) => void, onRemove: (entryId: number) => void, onEndSession: () => void, onClose: () => void}} callbacks
*/
export function renderSessionList(root, session, { onMove, onRemove, onEndSession, onClose }) {
const overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.style.background = 'var(--bg)';
overlay.style.justifyContent = 'flex-start';
overlay.style.overflowY = 'auto';
overlay.innerHTML = `
<div class="stack-id" style="font-size:26px">Sitzung</div>
<div class="choices" id="body" style="max-width:520px"></div>
<div class="choices" style="max-width:520px">
<button class="action" id="close">zurueck zum Scannen</button>
<button class="action secondary" id="end">Sitzung beenden</button>
</div>
`;
const body = overlay.querySelector('#body');
for (const stack of session.stacks) {
const header = document.createElement('div');
header.className = 'detail';
header.style.textAlign = 'left';
header.style.marginTop = '12px';
header.textContent = `Stapel ${stack.id}${stack.count}x ${describeSpec(stack.spec)}`;
body.appendChild(header);
for (const entry of session.entries.filter((candidate) => candidate.stackId === stack.id)) {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.gap = '8px';
row.style.alignItems = 'center';
const label = document.createElement('span');
label.style.flex = '1';
label.style.fontSize = '14px';
label.style.textAlign = 'left';
label.textContent = entry.spec.partNumber ?? describeSpec(entry.spec);
row.appendChild(label);
// Bedienflaechen muessen mindestens 56px hoch sein - auch die Auswahl,
// nicht nur die Schaltflaechen.
const select = document.createElement('select');
select.style.minHeight = '56px';
select.setAttribute('aria-label', `Stapel fuer ${label.textContent}`);
for (const target of session.stacks) {
const option = document.createElement('option');
option.value = target.id;
option.textContent = target.id;
option.selected = target.id === entry.stackId;
select.appendChild(option);
}
select.addEventListener('change', () => onMove(entry.entryId, select.value));
row.appendChild(select);
const removeButton = document.createElement('button');
removeButton.className = 'action secondary';
removeButton.style.minWidth = '56px';
removeButton.textContent = '✕';
removeButton.setAttribute('aria-label', `${label.textContent} entfernen`);
removeButton.addEventListener('click', () => onRemove(entry.entryId));
row.appendChild(removeButton);
body.appendChild(row);
}
}
if (session.stacks.length === 0) {
body.textContent = 'Noch nichts erfasst.';
}
overlay.querySelector('#close').addEventListener('click', () => {
overlay.remove();
onClose();
});
overlay.querySelector('#end').addEventListener('click', () => {
overlay.remove();
onEndSession();
});
root.appendChild(overlay);
}