chore: release v0.25.3

This commit is contained in:
Ulas Kalayci
2026-04-26 18:24:27 +02:00
parent 1cc1b63745
commit 9fba1d7ae4
26 changed files with 242 additions and 120 deletions
+5
View File
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.25.3] - 2026-04-26
### Changed
- Delete actions in all seven modules (tasks, notes, budget, calendar, contacts, meals, recipes) and shopping list deletion no longer show a confirmation dialog; instead the item is removed immediately and a toast with an Undo button gives a 5-second window to reverse the action before the API call is made
## [0.25.2] - 2026-04-26 ## [0.25.2] - 2026-04-26
### Changed ### Changed
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "oikos", "name": "oikos",
"version": "0.25.2", "version": "0.25.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "oikos", "name": "oikos",
"version": "0.25.2", "version": "0.25.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "oikos", "name": "oikos",
"version": "0.25.2", "version": "0.25.3",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js", "main": "server/index.js",
"type": "module", "type": "module",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "التقويم", "title": "التقويم",
+2 -1
View File
@@ -264,7 +264,8 @@
"savedRecipeLabel": "Gespeichertes Rezept", "savedRecipeLabel": "Gespeichertes Rezept",
"savedRecipePlaceholder": "Rezept auswählen", "savedRecipePlaceholder": "Rezept auswählen",
"saveAsRecipe": "Als Rezept speichern", "saveAsRecipe": "Als Rezept speichern",
"recipeScaleLabel": "Zutaten skalieren" "recipeScaleLabel": "Zutaten skalieren",
"deletedToast": "Mahlzeit gelöscht"
}, },
"calendar": { "calendar": {
"title": "Kalender", "title": "Kalender",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Ημερολόγιο", "title": "Ημερολόγιο",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Calendar", "title": "Calendar",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Calendario", "title": "Calendario",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Calendrier", "title": "Calendrier",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "कैलेंडर", "title": "कैलेंडर",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Calendario", "title": "Calendario",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "カレンダー", "title": "カレンダー",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Calendário", "title": "Calendário",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Календарь", "title": "Календарь",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Sparat recept", "savedRecipeLabel": "Sparat recept",
"savedRecipePlaceholder": "Välj recept", "savedRecipePlaceholder": "Välj recept",
"saveAsRecipe": "Spara som recept", "saveAsRecipe": "Spara som recept",
"recipeScaleLabel": "Skala ingredienser" "recipeScaleLabel": "Skala ingredienser",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Kalender", "title": "Kalender",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Takvim", "title": "Takvim",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "Календар", "title": "Календар",
+2 -1
View File
@@ -258,7 +258,8 @@
"savedRecipeLabel": "Saved recipe", "savedRecipeLabel": "Saved recipe",
"savedRecipePlaceholder": "Select recipe", "savedRecipePlaceholder": "Select recipe",
"saveAsRecipe": "Save as recipe", "saveAsRecipe": "Save as recipe",
"recipeScaleLabel": "Scale ingredients" "recipeScaleLabel": "Scale ingredients",
"deletedToast": "Meal deleted"
}, },
"calendar": { "calendar": {
"title": "日历", "title": "日历",
+23 -6
View File
@@ -6,7 +6,7 @@
*/ */
import { api } from '/api.js'; import { api } from '/api.js';
import { openModal as openSharedModal, closeModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js'; import { stagger, vibrate } from '/utils/ux.js';
import { t, formatDate, getLocale } from '/i18n.js'; import { t, formatDate, getLocale } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
@@ -660,18 +660,35 @@ function openBudgetModal({ mode, entry = null }) {
// -------------------------------------------------------- // --------------------------------------------------------
async function deleteEntry(id) { async function deleteEntry(id) {
if (!await confirmModal(t('budget.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return; const entry = state.entries.find((e) => e.id === id);
state.entries = state.entries.filter((e) => e.id !== id);
renderBody();
vibrate([30, 50, 30]);
let undone = false;
window.oikos?.showToast(t('budget.deletedToast'), 'default', 5000, () => {
undone = true;
if (entry) {
state.entries = [...state.entries, entry].sort((a, b) => new Date(b.date) - new Date(a.date));
renderBody();
}
});
setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/budget/${id}`); await api.delete(`/budget/${id}`);
state.entries = state.entries.filter((e) => e.id !== id);
const sumRes = await api.get(`/budget/summary?month=${state.month}`); const sumRes = await api.get(`/budget/summary?month=${state.month}`);
state.summary = sumRes.data; state.summary = sumRes.data;
renderBody(); renderBody();
vibrate([30, 50, 30]);
window.oikos?.showToast(t('budget.deletedToast'), 'success');
} catch (err) { } catch (err) {
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error'); if (entry) {
state.entries = [...state.entries, entry].sort((a, b) => new Date(b.date) - new Date(a.date));
renderBody();
} }
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
}
}, 5000);
} }
// -------------------------------------------------------- // --------------------------------------------------------
+22 -7
View File
@@ -6,7 +6,7 @@
import { api } from '/api.js'; import { api } from '/api.js';
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js'; import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
import { openModal as openSharedModal, closeModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js';
import { stagger } from '/utils/ux.js'; import { stagger } from '/utils/ux.js';
import { t, formatTime } from '/i18n.js'; import { t, formatTime } from '/i18n.js';
import { esc, fmtLocation } from '/utils/html.js'; import { esc, fmtLocation } from '/utils/html.js';
@@ -764,7 +764,6 @@ function showEventPopup(ev, anchor) {
}); });
popup.querySelector('#popup-delete').addEventListener('click', async () => { popup.querySelector('#popup-delete').addEventListener('click', async () => {
if (!await confirmModal(t('calendar.deleteConfirm', { title: ev.title }), { danger: true, confirmLabel: t('common.delete') })) return;
popup.remove(); popup.remove();
await deleteEvent(ev.id); await deleteEvent(ev.id);
}); });
@@ -868,7 +867,6 @@ function openEventModal({ mode, event = null, date = null, reminder = null }) {
panel.querySelector('#modal-cancel').addEventListener('click', closeModal); panel.querySelector('#modal-cancel').addEventListener('click', closeModal);
panel.querySelector('#modal-delete')?.addEventListener('click', async () => { panel.querySelector('#modal-delete')?.addEventListener('click', async () => {
if (!await confirmModal(t('calendar.deleteConfirm', { title: event.title }), { danger: true, confirmLabel: t('common.delete') })) return;
closeModal(); closeModal();
await deleteEvent(event.id); await deleteEvent(event.id);
}); });
@@ -1073,15 +1071,32 @@ async function saveEvent(overlay, mode, eventId, existingReminder = null) {
} }
async function deleteEvent(id) { async function deleteEvent(id) {
const event = state.events.find((e) => e.id === id);
state.events = state.events.filter((e) => e.id !== id);
renderView();
let undone = false;
window.oikos?.showToast(t('calendar.deletedToast'), 'default', 5000, () => {
undone = true;
if (event) {
state.events = [...state.events, event];
renderView();
}
});
setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/calendar/${id}`); await api.delete(`/calendar/${id}`);
api.delete(`/reminders?entity_type=event&entity_id=${id}`).catch(() => {}); api.delete(`/reminders?entity_type=event&entity_id=${id}`).catch(() => {});
refreshReminders(); refreshReminders();
state.events = state.events.filter((e) => e.id !== id);
renderView();
window.oikos?.showToast(t('calendar.deletedToast'), 'success');
} catch (err) { } catch (err) {
window.oikos?.showToast(err.data?.error ?? t('calendar.deleteError'), 'error'); if (event) {
state.events = [...state.events, event];
renderView();
} }
window.oikos?.showToast(err.data?.error ?? t('calendar.deleteError'), 'danger');
}
}, 5000);
} }
+23 -7
View File
@@ -5,7 +5,7 @@
*/ */
import { api } from '/api.js'; import { api } from '/api.js';
import { openModal as openSharedModal, closeModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js'; import { stagger, vibrate } from '/utils/ux.js';
import { t } from '/i18n.js'; import { t } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
@@ -350,16 +350,32 @@ function openContactModal({ mode, contact = null }) {
} }
async function deleteContact(id) { async function deleteContact(id) {
if (!await confirmModal(t('contacts.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return; const contact = state.contacts.find((c) => c.id === id);
try {
await api.delete(`/contacts/${id}`);
state.contacts = state.contacts.filter((c) => c.id !== id); state.contacts = state.contacts.filter((c) => c.id !== id);
renderList(); renderList();
vibrate([30, 50, 30]); vibrate([30, 50, 30]);
window.oikos?.showToast(t('contacts.deletedToast'), 'success');
} catch (err) { let undone = false;
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error'); window.oikos?.showToast(t('contacts.deletedToast'), 'default', 5000, () => {
undone = true;
if (contact) {
state.contacts = [...state.contacts, contact].sort((a, b) => a.name.localeCompare(b.name));
renderList();
} }
});
setTimeout(async () => {
if (undone) return;
try {
await api.delete(`/contacts/${id}`);
} catch (err) {
if (contact) {
state.contacts = [...state.contacts, contact].sort((a, b) => a.name.localeCompare(b.name));
renderList();
}
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
}
}, 5000);
} }
+16 -4
View File
@@ -5,7 +5,7 @@
*/ */
import { api } from '/api.js'; import { api } from '/api.js';
import { openModal as openSharedModal, closeModal as closeSharedModal, selectModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal as closeSharedModal, selectModal } from '/components/modal.js';
import { stagger } from '/utils/ux.js'; import { stagger } from '/utils/ux.js';
import { t, formatDate } from '/i18n.js'; import { t, formatDate } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
@@ -920,15 +920,27 @@ function collectModalIngredients(overlay) {
// -------------------------------------------------------- // --------------------------------------------------------
async function deleteMeal(mealId) { async function deleteMeal(mealId) {
if (!await confirmModal(t('meals.deleteMeal') + '?', { danger: true, confirmLabel: t('common.delete') })) return; const meal = state.meals.find((m) => m.id === mealId);
const itemEl = _container.querySelector(`.meal-slot--has-meal[data-meal-id="${mealId}"]`);
if (itemEl) itemEl.style.display = 'none';
let undone = false;
window.oikos?.showToast(t('meals.deletedToast'), 'default', 5000, () => {
undone = true;
if (itemEl) itemEl.style.display = '';
});
setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/meals/${mealId}`); await api.delete(`/meals/${mealId}`);
state.meals = state.meals.filter((m) => m.id !== mealId); state.meals = state.meals.filter((m) => m.id !== mealId);
renderWeekGrid(); renderWeekGrid();
window.oikos?.showToast(t('meals.deleteMeal'), 'success');
} catch (err) { } catch (err) {
window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error'); if (itemEl) itemEl.style.display = '';
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
} }
}, 5000);
} }
// -------------------------------------------------------- // --------------------------------------------------------
+24 -7
View File
@@ -5,7 +5,7 @@
*/ */
import { api } from '/api.js'; import { api } from '/api.js';
import { openModal as openSharedModal, closeModal, btnError, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal, btnError } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js'; import { stagger, vibrate } from '/utils/ux.js';
import { t } from '/i18n.js'; import { t } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
@@ -476,16 +476,33 @@ async function togglePin(id) {
} }
async function deleteNote(id) { async function deleteNote(id) {
if (!await confirmModal(t('notes.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return; closeModal();
try { const note = state.notes.find((n) => n.id === id);
await api.delete(`/notes/${id}`);
state.notes = state.notes.filter((n) => n.id !== id); state.notes = state.notes.filter((n) => n.id !== id);
renderGrid(); renderGrid();
vibrate([30, 50, 30]); vibrate([30, 50, 30]);
window.oikos?.showToast(t('notes.deletedToast'), 'success');
} catch (err) { let undone = false;
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error'); window.oikos?.showToast(t('notes.deletedToast'), 'default', 5000, () => {
undone = true;
if (note) {
state.notes = [...state.notes, note].sort((a, b) => b.pinned - a.pinned);
renderGrid();
} }
});
setTimeout(async () => {
if (undone) return;
try {
await api.delete(`/notes/${id}`);
} catch (err) {
if (note) {
state.notes = [...state.notes, note].sort((a, b) => b.pinned - a.pinned);
renderGrid();
}
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
}
}, 5000);
} }
// -------------------------------------------------------- // --------------------------------------------------------
+14 -8
View File
@@ -5,7 +5,7 @@
import { api } from '/api.js'; import { api } from '/api.js';
import { t } from '/i18n.js'; import { t } from '/i18n.js';
import { openModal as openSharedModal, closeModal as closeSharedModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal as closeSharedModal } from '/components/modal.js';
import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js';
let _container = null; let _container = null;
@@ -134,6 +134,7 @@ function renderRecipeList() {
for (const recipe of state.recipes) { for (const recipe of state.recipes) {
const card = document.createElement('article'); const card = document.createElement('article');
card.className = 'recipe-card'; card.className = 'recipe-card';
card.dataset.id = String(recipe.id);
const h = document.createElement('h2'); const h = document.createElement('h2');
h.className = 'recipe-card__title'; h.className = 'recipe-card__title';
@@ -370,21 +371,26 @@ async function saveRecipe(panel, mode, recipe) {
} }
async function removeRecipe(recipe) { async function removeRecipe(recipe) {
const ok = await confirmModal(t('recipes.deleteConfirm', { title: recipe.title }), { const itemEl = _container.querySelector(`.recipe-card[data-id="${recipe.id}"]`);
danger: true, if (itemEl) itemEl.style.display = 'none';
confirmLabel: t('common.delete'),
let undone = false;
window.oikos?.showToast(t('recipes.deleted'), 'default', 5000, () => {
undone = true;
if (itemEl) itemEl.style.display = '';
}); });
if (!ok) return; setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/recipes/${recipe.id}`); await api.delete(`/recipes/${recipe.id}`);
state.recipes = state.recipes.filter((r) => r.id !== recipe.id); state.recipes = state.recipes.filter((r) => r.id !== recipe.id);
renderRecipeList(); renderRecipeList();
window.oikos?.showToast(t('recipes.deleted'), 'success');
} catch (err) { } catch (err) {
window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error'); if (itemEl) itemEl.style.display = '';
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
} }
}, 5000);
} }
async function duplicateRecipe(recipe) { async function duplicateRecipe(recipe) {
+14 -6
View File
@@ -8,7 +8,7 @@ import { api } from '/api.js';
import { stagger, vibrate } from '/utils/ux.js'; import { stagger, vibrate } from '/utils/ux.js';
import { t } from '/i18n.js'; import { t } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
import { promptModal, confirmModal } from '/components/modal.js'; import { promptModal } from '/components/modal.js';
import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js';
// -------------------------------------------------------- // --------------------------------------------------------
@@ -781,10 +781,18 @@ function wireListContentEvents(container) {
// ---- Liste löschen ---- // ---- Liste löschen ----
if (action === 'delete-list') { if (action === 'delete-list') {
if (!await confirmModal(t('shopping.deleteListConfirm', { name: state.activeList?.name }), { danger: true, confirmLabel: t('common.delete') })) return; const deletedListId = state.activeListId;
let undone = false;
window.oikos.showToast(t('shopping.deletedListToast'), 'default', 5000, () => {
undone = true;
});
setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/shopping/${state.activeListId}`); await api.delete(`/shopping/${deletedListId}`);
state.lists = state.lists.filter((l) => l.id !== state.activeListId); state.lists = state.lists.filter((l) => l.id !== deletedListId);
state.activeListId = state.lists[0]?.id ?? null; state.activeListId = state.lists[0]?.id ?? null;
if (state.activeListId) { if (state.activeListId) {
await switchList(state.activeListId, container); await switchList(state.activeListId, container);
@@ -794,10 +802,10 @@ function wireListContentEvents(container) {
renderTabs(container); renderTabs(container);
renderListContent(container); renderListContent(container);
} }
window.oikos.showToast(t('shopping.deletedListToast'));
} catch (err) { } catch (err) {
window.oikos.showToast(err.message, 'danger'); window.oikos.showToast(err.message ?? t('common.unknownError'), 'danger');
} }
}, 5000);
} }
}); });
+16 -5
View File
@@ -6,7 +6,7 @@
import { api } from '/api.js'; import { api } from '/api.js';
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js'; import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js'; import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js'; import { stagger, vibrate } from '/utils/ux.js';
import { t, formatDate, formatTime } from '/i18n.js'; import { t, formatDate, formatTime } from '/i18n.js';
import { esc } from '/utils/html.js'; import { esc } from '/utils/html.js';
@@ -582,18 +582,29 @@ async function handleFormSubmit(e, container) {
} }
async function handleDeleteTask(id, container) { async function handleDeleteTask(id, container) {
if (!await confirmModal(t('tasks.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return; closeModal();
const itemEl = container.querySelector(`[data-task-id="${id}"]`);
if (itemEl) itemEl.style.display = 'none';
let undone = false;
window.oikos.showToast(t('tasks.deletedToast'), 'default', 5000, () => {
undone = true;
if (itemEl) itemEl.style.display = '';
});
setTimeout(async () => {
if (undone) return;
try { try {
await api.delete(`/tasks/${id}`); await api.delete(`/tasks/${id}`);
// Erinnerungen für diese Aufgabe ebenfalls entfernen // Erinnerungen für diese Aufgabe ebenfalls entfernen
api.delete(`/reminders?entity_type=task&entity_id=${id}`).catch(() => {}); api.delete(`/reminders?entity_type=task&entity_id=${id}`).catch(() => {});
refreshReminders(); refreshReminders();
closeModal();
window.oikos.showToast(t('tasks.deletedToast'), 'default');
await loadTasks(container); await loadTasks(container);
} catch (err) { } catch (err) {
window.oikos.showToast(err.message, 'danger'); if (itemEl) itemEl.style.display = '';
window.oikos.showToast(err.message ?? t('common.unknownError'), 'danger');
} }
}, 5000);
} }
async function handleAddSubtask(parentId, container) { async function handleAddSubtask(parentId, container) {