fix: Etiketten-Import zeigt konkreten Grund statt generischem Fehler

Eine KI-erzeugte JSON ohne Kenntnis des echten Schemas (z.B. eigene
Feldnamen wie xMm/yMm oder erfundene Elementtypen wie rectangle/svg)
scheiterte bisher mit "Datei enthält keine gültige Etikettenvorlage"
ohne Hinweis, was falsch ist. Jetzt wird pro Element benannt, was
fehlt oder welcher Typ nicht unterstützt wird.
This commit is contained in:
Nick Szaunig committed 2026-09-01 20:13:36 +02:00
1 parent c6941a797f
commit abe1f6e8cf
1 file changed
+36 -17
@@ -86,20 +86,35 @@ export interface LabelDesignerValue {
// Loose runtime check for imported JSON (own export, or hand/AI-authored) - // Loose runtime check for imported JSON (own export, or hand/AI-authored) -
// only verifies the shape is plausible enough to load into the canvas; // only verifies the shape is plausible enough to load into the canvas;
// the backend's own DTO validation is still the real boundary check on save. // the backend's own DTO validation is still the real boundary check on save.
function isLabelElementArray(value: unknown): value is LabelElement[] { // Returns a specific reason per bad element instead of a flat true/false, so
return ( // an AI-authored file that guessed the wrong schema tells the user exactly
Array.isArray(value) && // what to fix rather than a generic "invalid file".
value.every( function validateImportedElements(value: unknown): { elements: LabelElement[] } | { errors: string[] } {
(item) => if (!Array.isArray(value)) {
typeof item === "object" && return { errors: ['Die Datei enthält kein "elements"-Array.'] };
item !== null && }
"id" in item && const errors: string[] = [];
"type" in item && value.forEach((item, index) => {
"x" in item && const label = typeof item === "object" && item && "id" in item ? String((item as { id: unknown }).id) : `#${index + 1}`;
"y" in item && if (typeof item !== "object" || item === null) {
ELEMENT_TYPES.includes((item as { type: unknown }).type as (typeof ELEMENT_TYPES)[number]), errors.push(`Element ${label}: kein Objekt.`);
) return;
); }
if (!("x" in item) || !("y" in item)) {
errors.push(`Element ${label}: "x"/"y" fehlen (nicht "xMm"/"yMm").`);
return;
}
const type = (item as { type?: unknown }).type;
if (!ELEMENT_TYPES.includes(type as (typeof ELEMENT_TYPES)[number])) {
errors.push(
`Element ${label}: Typ "${String(type)}" wird nicht unterstützt (nur ${ELEMENT_TYPES.join(", ")}).`,
);
}
});
if (errors.length > 0) {
return { errors };
}
return { elements: value as LabelElement[] };
} }
function slugify(name: string): string { function slugify(name: string): string {
@@ -235,11 +250,15 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
const importedElements = Array.isArray(parsed) const importedElements = Array.isArray(parsed)
? parsed ? parsed
: (parsed as { elements?: unknown })?.elements; : (parsed as { elements?: unknown })?.elements;
if (!isLabelElementArray(importedElements)) { const result = validateImportedElements(importedElements);
toast.error("Datei enthält keine gültige Etikettenvorlage."); if ("errors" in result) {
toast.error("Vorlage konnte nicht importiert werden", {
description: result.errors.slice(0, 5).join(" · "),
duration: 15000,
});
return; return;
} }
setElements(importedElements.map((element) => ({ ...element, id: newId() }))); setElements(result.elements.map((element) => ({ ...element, id: newId() })));
if (!Array.isArray(parsed)) { if (!Array.isArray(parsed)) {
const value = parsed as Partial<LabelDesignerValue>; const value = parsed as Partial<LabelDesignerValue>;
if (typeof value.name === "string" && value.name && !name) setName(value.name); if (typeof value.name === "string" && value.name && !name) setName(value.name);