From 81716725f20ba6e765d76477f281da0c86c1cf5f Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 3 Apr 2026 14:50:32 +0200 Subject: [PATCH] feat(manacore/web): add undo toasts for delete and tag removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend toast system with action buttons and toastStore.undo() helper. After deleting a task/event/contact or removing a tag, a toast with "Rückgängig" button appears for 5 seconds. Clicking it restores the item (clears deletedAt) or re-adds the tag. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../modules/calendar/views/DetailView.svelte | 16 ++++++---- .../modules/contacts/views/DetailView.svelte | 19 ++++++++---- .../lib/modules/todo/views/DetailView.svelte | 16 ++++++---- .../shared-ui/src/toast/ToastContainer.svelte | 28 ++++++++++++++++++ packages/shared-ui/src/toast/index.ts | 2 +- packages/shared-ui/src/toast/toast.svelte.ts | 29 +++++++++++++++++-- 6 files changed, 92 insertions(+), 18 deletions(-) diff --git a/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte index f458d19ad..b1bba8511 100644 --- a/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte @@ -11,6 +11,7 @@ import type { LocalEvent } from '../types'; import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; import LinkedItems from '$lib/components/links/LinkedItems.svelte'; + import { toastStore } from '@manacore/shared-ui/toast'; let { navigate, goBack, params }: ViewProps = $props(); let eventId = $derived(params.eventId as string); @@ -34,10 +35,11 @@ async function removeTag(tagId: string) { const current = event?.tagIds ?? []; - await eventsStore.updateTagIds( - eventId, - current.filter((id) => id !== tagId) - ); + const removed = current.filter((id) => id !== tagId); + await eventsStore.updateTagIds(eventId, removed); + toastStore.undo('Tag entfernt', () => { + eventsStore.updateTagIds(eventId, current); + }); } $effect(() => { @@ -84,8 +86,12 @@ } async function deleteEvent() { - await eventsStore.deleteEvent(eventId); + const id = eventId; + await eventsStore.deleteEvent(id); goBack(); + toastStore.undo('Termin gelöscht', () => { + db.table('events').update(id, { deletedAt: undefined, updatedAt: new Date().toISOString() }); + }); } diff --git a/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte index 952c07df1..5585c62d5 100644 --- a/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte @@ -20,6 +20,7 @@ import type { LocalContact } from '../types'; import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; import LinkedItems from '$lib/components/links/LinkedItems.svelte'; + import { toastStore } from '@manacore/shared-ui/toast'; let { navigate, goBack, params }: ViewProps = $props(); let contactId = $derived(params.contactId as string); @@ -49,10 +50,11 @@ async function removeTag(tagId: string) { const current = contact?.tagIds ?? []; - await contactsStore.updateTagIds( - contactId, - current.filter((id) => id !== tagId) - ); + const removed = current.filter((id) => id !== tagId); + await contactsStore.updateTagIds(contactId, removed); + toastStore.undo('Tag entfernt', () => { + contactsStore.updateTagIds(contactId, current); + }); } $effect(() => { @@ -119,8 +121,15 @@ } async function deleteContact() { - await contactsStore.deleteContact(contactId); + const id = contactId; + await contactsStore.deleteContact(id); goBack(); + toastStore.undo('Kontakt gelöscht', () => { + db.table('contacts').update(id, { + deletedAt: undefined, + updatedAt: new Date().toISOString(), + }); + }); } diff --git a/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte index ab5ba3f15..3971eb81f 100644 --- a/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte @@ -11,6 +11,7 @@ import type { LocalTask, TaskPriority } from '../types'; import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; import LinkedItems from '$lib/components/links/LinkedItems.svelte'; + import { toastStore } from '@manacore/shared-ui/toast'; let { navigate, goBack, params }: ViewProps = $props(); let taskId = $derived(params.taskId as string); @@ -38,10 +39,11 @@ async function removeTag(tagId: string) { const current = getTaskTagIds(); - await tasksStore.updateLabels( - taskId, - current.filter((id) => id !== tagId) - ); + const removed = current.filter((id) => id !== tagId); + await tasksStore.updateLabels(taskId, removed); + toastStore.undo('Tag entfernt', () => { + tasksStore.updateLabels(taskId, current); + }); } $effect(() => { @@ -96,8 +98,12 @@ } async function deleteTask() { - await tasksStore.deleteTask(taskId); + const id = taskId; + await tasksStore.deleteTask(id); goBack(); + toastStore.undo('Aufgabe gelöscht', () => { + db.table('tasks').update(id, { deletedAt: undefined, updatedAt: new Date().toISOString() }); + }); } const priorityLabels: Record = { diff --git a/packages/shared-ui/src/toast/ToastContainer.svelte b/packages/shared-ui/src/toast/ToastContainer.svelte index d59fda4ef..d4a6257be 100644 --- a/packages/shared-ui/src/toast/ToastContainer.svelte +++ b/packages/shared-ui/src/toast/ToastContainer.svelte @@ -31,6 +31,17 @@