Web: Lagerort-Etiketten-CSV-Export (P-touch), wie bei Einzelstücken
Neuer Abschnitt in Import/Export: exportiert je Lagerort eine Zeile (QR-Inhalt = Link auf den Ort, ID, Name, Pfad "Hedingen → Keller") als CSV mit demselben wählbaren Trennzeichen wie der Einzelstück-Export und derselben Formel-Injektion-Härtung. Client-seitig aus den Lagerorten gebaut. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,36 @@ function buildLabelCsv(rows, origin, delim = ",") {
|
||||
return "" + lines.join("\r\n");
|
||||
}
|
||||
|
||||
// Etiketten-CSV für Lagerorte (QR-Inhalt = Link auf den Ort). Gleiches Prinzip
|
||||
// wie bei den Einzelstücken; „Pfad" nennt die Ober-Lagerorte (Hedingen → Keller).
|
||||
function buildLocationCsv(locations, origin, delim = ",") {
|
||||
const byId = Object.fromEntries(locations.map((l) => [l.id, l]));
|
||||
const pfad = (l) => {
|
||||
const teile = [l.name];
|
||||
const gesehen = new Set([l.id]);
|
||||
let cur = l.parent_id;
|
||||
while (cur != null && byId[cur] && !gesehen.has(cur)) {
|
||||
gesehen.add(cur);
|
||||
teile.unshift(byId[cur].name);
|
||||
cur = byId[cur].parent_id;
|
||||
}
|
||||
return teile.join(" → ");
|
||||
};
|
||||
const head = ["QR-Inhalt", "ID", "Lagerort", "Pfad"];
|
||||
const delimRe = delim === "\t" ? "\\t" : delim;
|
||||
const needsQuote = new RegExp(`[${delimRe}"\\n\\r]`);
|
||||
const esc = (v) => {
|
||||
let s = String(v ?? "");
|
||||
if (/^[=+\-@\t\r]/.test(s)) s = `'${s}`;
|
||||
return needsQuote.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
};
|
||||
const lines = [head.map(esc).join(delim)];
|
||||
for (const l of locations) {
|
||||
lines.push([`${origin}/l/${l.id}`, l.id, l.name, pfad(l)].map(esc).join(delim));
|
||||
}
|
||||
return "" + lines.join("\r\n");
|
||||
}
|
||||
|
||||
export default function Transfer() {
|
||||
const confirm = useConfirm();
|
||||
const fileRef = useRef(null);
|
||||
@@ -41,8 +71,43 @@ export default function Transfer() {
|
||||
const [labelInfo, setLabelInfo] = useState(null);
|
||||
// Gemerktes Trennzeichen (Standard Komma – P-touch mag das lieber).
|
||||
const [labelDelim, setLabelDelim] = useState(() => localStorage.getItem("label_delim") || ",");
|
||||
// Lagerort-Etiketten.
|
||||
const [locations, setLocations] = useState([]);
|
||||
const [locBusy, setLocBusy] = useState(false);
|
||||
const [locInfo, setLocInfo] = useState(null);
|
||||
|
||||
useEffect(() => { api.listCategories().then(setCategories).catch(() => {}); }, []);
|
||||
useEffect(() => {
|
||||
api.listCategories().then(setCategories).catch(() => {});
|
||||
api.listLocations().then(setLocations).catch(() => {});
|
||||
}, []);
|
||||
|
||||
function downloadCsv(csv, filename) {
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function exportLocationLabels() {
|
||||
setError(null);
|
||||
setLocInfo(null);
|
||||
setLocBusy(true);
|
||||
try {
|
||||
if (!locations.length) { setLocInfo("Keine Lagerorte vorhanden."); return; }
|
||||
downloadCsv(buildLocationCsv(locations, window.location.origin, labelDelim),
|
||||
"vorrania-lagerort-etiketten.csv");
|
||||
setLocInfo(`${locations.length} Lagerort-Etikett(en) exportiert.`);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLocBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
function changeDelim(v) {
|
||||
setLabelDelim(v);
|
||||
@@ -288,6 +353,25 @@ export default function Transfer() {
|
||||
{labelInfo && <p className="muted small">{labelInfo}</p>}
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<div className="card-head"><Icon name="location" /><h2>Lagerort-Etiketten-Export (P-touch & Co.)</h2></div>
|
||||
<p className="muted small mt-0">
|
||||
Je Lagerort eine Zeile: <strong>QR-Inhalt</strong>, ID, Lagerort und der{" "}
|
||||
<strong>Pfad</strong> (z.B. „Hedingen → Keller"). In der Etiketten-Software als{" "}
|
||||
<strong>Datenbank</strong> verknüpfen. Verwendet dasselbe Trennzeichen wie oben.
|
||||
Ein gescannter Lagerort-QR öffnet den Ort in der App/Weboberfläche.
|
||||
</p>
|
||||
<div className="field-inline">
|
||||
<button className="btn primary" disabled={locBusy || !locations.length} onClick={exportLocationLabels}>
|
||||
<Icon name="download" size={16} />{locBusy ? "Exportiere…" : "Lagerort-Etiketten-CSV herunterladen"}
|
||||
</button>
|
||||
<span className="muted small" style={{ alignSelf: "center" }}>
|
||||
{locations.length} Lagerort{locations.length === 1 ? "" : "e"}
|
||||
</span>
|
||||
</div>
|
||||
{locInfo && <p className="muted small">{locInfo}</p>}
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<div className="card-head"><Icon name="package" /><h2>CSV-Aufbau</h2></div>
|
||||
<div className="table-wrap">
|
||||
|
||||
Reference in New Issue
Block a user