Web: Etiketten-Export signalisiert mitexportierte Unterkategorien

Waehlt man beim P-touch-Export eine Kategorie, schliesst der Server ihre
Unterkategorien mit ein – das war im UI nicht sichtbar. Jetzt erscheinen die
Unterkategorien einer gewaehlten Kategorie als „enthalten" (angehakt,
deaktiviert, gedimmt); gewaehlte Oberkategorien mit Unterkategorien tragen
„inkl. Unterkategorien", nicht gewaehlte einen „+ Unterkategorien"-Hinweis. Der
Einleitungstext erklaert es zusaetzlich.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-26 21:32:41 +02:00
parent 3befaad850
commit 3453471716
2 changed files with 47 additions and 10 deletions

View File

@@ -129,6 +129,23 @@ export default function Transfer() {
} }
} }
// Gegenstands-Kategorien als Baum; für die Anzeige, welche Unterkategorien
// durch eine gewählte Oberkategorie automatisch mitkommen.
const objTree = asTree(categories).filter((c) => c.tracking === "object");
const parentOf = Object.fromEntries(categories.map((c) => [c.id, c.parent_id]));
const nameById = Object.fromEntries(categories.map((c) => [c.id, c.name]));
const hatKinder = (id) => objTree.some((o) => o.parent_id === id);
// Name der nächsten ausgewählten Oberkategorie (oder null) dann ist diese
// Kategorie „über die Oberkategorie enthalten".
function abgedecktVon(id) {
let p = parentOf[id];
while (p != null) {
if (selCats.has(p)) return nameById[p];
p = parentOf[p];
}
return null;
}
return ( return (
<div> <div>
<div className="page-head"> <div className="page-head">
@@ -217,8 +234,9 @@ export default function Transfer() {
<p className="muted small mt-0"> <p className="muted small mt-0">
Je Einzelstück eine Zeile: <strong>QR-Inhalt</strong>, UID, Produkt, Marke, Kategorie, Je Einzelstück eine Zeile: <strong>QR-Inhalt</strong>, UID, Produkt, Marke, Kategorie,
Lagerort. In der Etiketten-Software (z.B. P-touch) als <strong>Datenbank</strong>{" "} Lagerort. In der Etiketten-Software (z.B. P-touch) als <strong>Datenbank</strong>{" "}
verknüpfen sie erzeugt QR-Code und Text automatisch. Kategorien wählen verknüpfen sie erzeugt QR-Code und Text automatisch. Ohne Auswahl kommen alle
(Unterkategorien sind enthalten); ohne Auswahl kommen alle Einzelstücke. Einzelstücke. Wählst du eine Kategorie, kommen ihre <strong>Unterkategorien
automatisch mit</strong> sie erscheinen unten als enthalten".
</p> </p>
<label style={{ maxWidth: 340 }}> <label style={{ maxWidth: 340 }}>
Trennzeichen (für die Etiketten-Software) Trennzeichen (für die Etiketten-Software)
@@ -229,14 +247,31 @@ export default function Transfer() {
</select> </select>
</label> </label>
<div className="kat-check-liste"> <div className="kat-check-liste">
{asTree(categories).filter((c) => c.tracking === "object").map((c) => ( {objTree.map((c) => {
<label key={c.id} className="check-inline" style={{ paddingLeft: c.depth * 18 }}> const enthalten = abgedecktVon(c.id);
<input type="checkbox" checked={selCats.has(c.id)} const direkt = selCats.has(c.id);
return (
<label key={c.id}
className={`check-inline ${enthalten ? "kat-enthalten" : ""}`}
style={{ paddingLeft: c.depth * 18 }}
title={enthalten ? `über „${enthalten}" enthalten` : undefined}>
<input type="checkbox" checked={direkt || enthalten != null}
disabled={enthalten != null}
onChange={(e) => toggleCat(c.id, e.target.checked)} /> onChange={(e) => toggleCat(c.id, e.target.checked)} />
<span>{c.name}</span> <span>{c.name}</span>
{enthalten != null && (
<span className="badge nowrap">enthalten</span>
)}
{enthalten == null && direkt && hatKinder(c.id) && (
<span className="badge nowrap">inkl. Unterkategorien</span>
)}
{enthalten == null && !direkt && hatKinder(c.id) && (
<span className="muted small nowrap">+ Unterkategorien</span>
)}
</label> </label>
))} );
{asTree(categories).filter((c) => c.tracking === "object").length === 0 && ( })}
{objTree.length === 0 && (
<span className="muted small">Noch keine Gegenstands-Kategorien.</span> <span className="muted small">Noch keine Gegenstands-Kategorien.</span>
)} )}
</div> </div>

View File

@@ -891,6 +891,8 @@ select.zeitraum:hover { color: var(--text); }
.clean-list li { display: flex; align-items: center; gap: var(--sp-2); padding: 6px 8px; background: var(--surface-2); border-radius: 8px; } .clean-list li { display: flex; align-items: center; gap: var(--sp-2); padding: 6px 8px; background: var(--surface-2); border-radius: 8px; }
.check-inline { display: flex; align-items: center; gap: var(--sp-2); } .check-inline { display: flex; align-items: center; gap: var(--sp-2); }
.check-inline input { width: auto; margin: 0; } .check-inline input { width: auto; margin: 0; }
/* Über eine gewählte Oberkategorie automatisch enthaltene Unterkategorie. */
.kat-enthalten { opacity: 0.6; }
.feldkopf { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; } .feldkopf { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
tr.row-active { background: var(--surface-2); } tr.row-active { background: var(--surface-2); }