feat(ux): zentrales deleteWithUndo + Undo-Toast in Birthdays

deleteWithUndo in ux.js: onDelete ausführen, Undo-Toast anzeigen.
Birthdays migriert; Contacts/Notes/Meals haben bereits optimistische Undo-Logik.
This commit is contained in:
Ulas Kalayci
2026-04-27 22:26:46 +02:00
parent 048e31e933
commit a66bd2b05c
3 changed files with 68 additions and 10 deletions
+18 -7
View File
@@ -1,6 +1,6 @@
import { api } from '/api.js';
import { openModal as openSharedModal, closeModal, confirmModal } from '/components/modal.js';
import { stagger } from '/utils/ux.js';
import { stagger, deleteWithUndo } from '/utils/ux.js';
import { t, formatDate } from '/i18n.js';
import { esc } from '/utils/html.js';
@@ -378,15 +378,26 @@ function openBirthdayModal({ mode, birthday = null }) {
async function deleteBirthday(id, name) {
if (!await confirmModal(t('birthdays.deleteConfirm', { name }), { danger: true, confirmLabel: t('common.delete') })) return;
await api.delete(`/birthdays/${id}`);
state.birthdays = state.birthdays
.filter((birthday) => birthday.id !== id)
.sort((a, b) => a.name.localeCompare(b.name));
state.upcoming = state.upcoming.filter((birthday) => birthday.id !== id);
const birthday = state.birthdays.find((b) => b.id === id);
state.birthdays = state.birthdays.filter((b) => b.id !== id).sort((a, b) => a.name.localeCompare(b.name));
state.upcoming = state.upcoming.filter((b) => b.id !== id);
renderUpcoming();
renderSuggestions();
renderList();
window.oikos?.showToast(t('birthdays.deletedToast'), 'success');
await deleteWithUndo({
onDelete: async () => { await api.delete(`/birthdays/${id}`); },
onUndo: async () => {
if (birthday) {
state.birthdays = [...state.birthdays, birthday].sort((a, b) => a.name.localeCompare(b.name));
state.upcoming = [...state.upcoming, birthday];
renderUpcoming();
renderSuggestions();
renderList();
}
},
toastMessage: t('birthdays.deletedToast'),
toastType: 'success',
});
}
export async function render(container) {
+21
View File
@@ -40,3 +40,24 @@ export function vibrate(pattern) {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
navigator.vibrate(pattern);
}
/**
* Führt eine DELETE-Aktion aus und zeigt einen Undo-Toast.
*
* @param {Object} opts
* @param {() => Promise<void>} opts.onDelete - Async-Funktion die DELETE ausführt
* @param {() => Promise<void>} [opts.onUndo] - Async-Funktion die die Aktion rückgängig macht
* @param {string} opts.toastMessage - Text für den Toast
* @param {'success'|'danger'} [opts.toastType] - Toast-Typ, default 'success'
*/
export async function deleteWithUndo({ onDelete, onUndo, toastMessage, toastType = 'success' }) {
await onDelete();
if (window.oikos?.showToast) {
window.oikos.showToast(
toastMessage,
toastType,
onUndo ? 4000 : 2000,
onUndo ?? null,
);
}
}