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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user