diff --git a/web/src/pages/Transfer.jsx b/web/src/pages/Transfer.jsx index a967c10..42e5661 100644 --- a/web/src/pages/Transfer.jsx +++ b/web/src/pages/Transfer.jsx @@ -4,17 +4,20 @@ import { useConfirm } from "../confirm"; import Icon from "../components/Icon"; import { asTree } from "../categoryTree"; -// Baut die Etiketten-CSV (Semikolon, BOM für Excel). QR-Inhalt = Link aufs Stück. -function buildLabelCsv(rows, origin) { +// Baut die Etiketten-CSV mit wählbarem Trennzeichen (BOM für Excel-Umlaute). +// QR-Inhalt = Link aufs Stück. +function buildLabelCsv(rows, origin, delim = ",") { const head = ["QR-Inhalt", "UID", "Produkt", "Marke", "Kategorie", "Lagerort"]; + const delimRe = delim === "\t" ? "\\t" : delim; + const needsQuote = new RegExp(`[${delimRe}"\\n\\r]`); const esc = (v) => { const s = String(v ?? ""); - return /[";\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; + return needsQuote.test(s) ? `"${s.replace(/"/g, '""')}"` : s; }; - const lines = [head.join(";")]; + const lines = [head.map(esc).join(delim)]; for (const r of rows) { lines.push([`${origin}/i/${r.uid}`, r.uid, r.product, r.brand, r.category, r.location] - .map(esc).join(";")); + .map(esc).join(delim)); } return "" + lines.join("\r\n"); } @@ -32,9 +35,16 @@ export default function Transfer() { const [selCats, setSelCats] = useState(() => new Set()); const [labelBusy, setLabelBusy] = useState(false); const [labelInfo, setLabelInfo] = useState(null); + // Gemerktes Trennzeichen (Standard Komma – P-touch mag das lieber). + const [labelDelim, setLabelDelim] = useState(() => localStorage.getItem("label_delim") || ","); useEffect(() => { api.listCategories().then(setCategories).catch(() => {}); }, []); + function changeDelim(v) { + setLabelDelim(v); + localStorage.setItem("label_delim", v); + } + function toggleCat(id, on) { setSelCats((prev) => { const neu = new Set(prev); @@ -50,7 +60,7 @@ export default function Transfer() { try { const rows = await api.labelRows([...selCats]); if (!rows.length) { setLabelInfo("Keine Einzelstücke in der Auswahl."); return; } - const csv = buildLabelCsv(rows, window.location.origin); + const csv = buildLabelCsv(rows, window.location.origin, labelDelim); const blob = new Blob([csv], { type: "text/csv;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); @@ -206,6 +216,14 @@ export default function Transfer() { verknüpfen – sie erzeugt QR-Code und Text automatisch. Kategorien wählen (Unterkategorien sind enthalten); ohne Auswahl kommen alle Einzelstücke.

+
{asTree(categories).filter((c) => c.tracking === "object").map((c) => (