feat(ui): undo for deleted label elements, warn before leaving unsaved changes

The Delete key removed a label element for good; it now shows a toast
with "Rückgängig" that puts it back in place. Closing or reloading the
designer with unsaved changes asks first.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-01 12:17:36 +02:00
1 parent b4c8d80c33
commit d57162e709
1 file changed
+37 -1
@@ -184,9 +184,25 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
);
}
// Deleting is one key press away, so it is always undoable.
function deleteElement(id: string) {
const index = elements.findIndex((element) => element.id === id);
const removed = elements[index];
if (!removed) return;
setElements((current) => current.filter((element) => element.id !== id));
setSelectedId((current) => (current === id ? null : current));
toast("Element gelöscht", {
action: {
label: "Rückgängig",
onClick: () =>
setElements((current) => [
...current.slice(0, index),
removed,
...current.slice(index),
]),
},
duration: 8000,
});
}
function duplicateElement(id: string) {
@@ -210,8 +226,28 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
}
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
// deleteElement only reads `elements`, which is listed.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedId, elements]);
// Leaving the page (tab close, reload) with unsaved changes asks first.
const currentSnapshot = JSON.stringify({ name, elements, widthMm, heightMm });
const initialSnapshot = JSON.stringify({
name: initial?.name ?? "",
elements: initial?.elements ?? [],
widthMm: initial?.widthMm ?? 90,
heightMm: initial?.heightMm ?? 35,
});
const hasUnsavedChanges = currentSnapshot !== initialSnapshot;
useEffect(() => {
if (!hasUnsavedChanges || isSubmitting) return;
function onBeforeUnload(event: BeforeUnloadEvent) {
event.preventDefault();
}
window.addEventListener("beforeunload", onBeforeUnload);
return () => window.removeEventListener("beforeunload", onBeforeUnload);
}, [hasUnsavedChanges, isSubmitting]);
function handlePlaceholderInsert(key: PlaceholderKey) {
addElement(newTextElement({ placeholderKey: key, text: "" }));
}
@@ -640,7 +676,7 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
) : (
<p className="text-sm text-muted-foreground">
Element im Etikett anklicken, um es zu bearbeiten. Entf/Backspace löscht das
ausgewählte Element.
ausgewählte Element – „Rückgängig“ im Hinweis holt es zurück.
</p>
)}
</CardContent>