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.
103 lines
3.9 KiB
JavaScript
103 lines
3.9 KiB
JavaScript
// 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);
|
|
}
|