chore: release v0.25.3
This commit is contained in:
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [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
|
||||
|
||||
### Changed
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "oikos",
|
||||
"version": "0.25.2",
|
||||
"version": "0.25.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "oikos",
|
||||
"version": "0.25.2",
|
||||
"version": "0.25.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "server/index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "التقويم",
|
||||
|
||||
@@ -264,7 +264,8 @@
|
||||
"savedRecipeLabel": "Gespeichertes Rezept",
|
||||
"savedRecipePlaceholder": "Rezept auswählen",
|
||||
"saveAsRecipe": "Als Rezept speichern",
|
||||
"recipeScaleLabel": "Zutaten skalieren"
|
||||
"recipeScaleLabel": "Zutaten skalieren",
|
||||
"deletedToast": "Mahlzeit gelöscht"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalender",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Ημερολόγιο",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendar",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendario",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendrier",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "कैलेंडर",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendario",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "カレンダー",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendário",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Календарь",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Sparat recept",
|
||||
"savedRecipePlaceholder": "Välj recept",
|
||||
"saveAsRecipe": "Spara som recept",
|
||||
"recipeScaleLabel": "Skala ingredienser"
|
||||
"recipeScaleLabel": "Skala ingredienser",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalender",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Takvim",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Календар",
|
||||
|
||||
@@ -258,7 +258,8 @@
|
||||
"savedRecipeLabel": "Saved recipe",
|
||||
"savedRecipePlaceholder": "Select recipe",
|
||||
"saveAsRecipe": "Save as recipe",
|
||||
"recipeScaleLabel": "Scale ingredients"
|
||||
"recipeScaleLabel": "Scale ingredients",
|
||||
"deletedToast": "Meal deleted"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "日历",
|
||||
|
||||
+30
-13
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
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 { t, formatDate, getLocale } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
@@ -660,18 +660,35 @@ function openBudgetModal({ mode, entry = null }) {
|
||||
// --------------------------------------------------------
|
||||
|
||||
async function deleteEntry(id) {
|
||||
if (!await confirmModal(t('budget.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/budget/${id}`);
|
||||
state.entries = state.entries.filter((e) => e.id !== id);
|
||||
const sumRes = await api.get(`/budget/summary?month=${state.month}`);
|
||||
state.summary = sumRes.data;
|
||||
renderBody();
|
||||
vibrate([30, 50, 30]);
|
||||
window.oikos?.showToast(t('budget.deletedToast'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error');
|
||||
}
|
||||
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 {
|
||||
await api.delete(`/budget/${id}`);
|
||||
const sumRes = await api.get(`/budget/summary?month=${state.month}`);
|
||||
state.summary = sumRes.data;
|
||||
renderBody();
|
||||
} catch (err) {
|
||||
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);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
+28
-13
@@ -6,7 +6,7 @@
|
||||
|
||||
import { api } from '/api.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 { t, formatTime } from '/i18n.js';
|
||||
import { esc, fmtLocation } from '/utils/html.js';
|
||||
@@ -764,7 +764,6 @@ function showEventPopup(ev, anchor) {
|
||||
});
|
||||
|
||||
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();
|
||||
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-delete')?.addEventListener('click', async () => {
|
||||
if (!await confirmModal(t('calendar.deleteConfirm', { title: event.title }), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
closeModal();
|
||||
await deleteEvent(event.id);
|
||||
});
|
||||
@@ -1073,15 +1071,32 @@ async function saveEvent(overlay, mode, eventId, existingReminder = null) {
|
||||
}
|
||||
|
||||
async function deleteEvent(id) {
|
||||
try {
|
||||
await api.delete(`/calendar/${id}`);
|
||||
api.delete(`/reminders?entity_type=event&entity_id=${id}`).catch(() => {});
|
||||
refreshReminders();
|
||||
state.events = state.events.filter((e) => e.id !== id);
|
||||
renderView();
|
||||
window.oikos?.showToast(t('calendar.deletedToast'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('calendar.deleteError'), 'error');
|
||||
}
|
||||
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 {
|
||||
await api.delete(`/calendar/${id}`);
|
||||
api.delete(`/reminders?entity_type=event&entity_id=${id}`).catch(() => {});
|
||||
refreshReminders();
|
||||
} catch (err) {
|
||||
if (event) {
|
||||
state.events = [...state.events, event];
|
||||
renderView();
|
||||
}
|
||||
window.oikos?.showToast(err.data?.error ?? t('calendar.deleteError'), 'danger');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
||||
+27
-11
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
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 { t } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
@@ -350,16 +350,32 @@ function openContactModal({ mode, contact = null }) {
|
||||
}
|
||||
|
||||
async function deleteContact(id) {
|
||||
if (!await confirmModal(t('contacts.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/contacts/${id}`);
|
||||
state.contacts = state.contacts.filter((c) => c.id !== id);
|
||||
renderList();
|
||||
vibrate([30, 50, 30]);
|
||||
window.oikos?.showToast(t('contacts.deletedToast'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error');
|
||||
}
|
||||
const contact = state.contacts.find((c) => c.id === id);
|
||||
state.contacts = state.contacts.filter((c) => c.id !== id);
|
||||
renderList();
|
||||
vibrate([30, 50, 30]);
|
||||
|
||||
let undone = false;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+22
-10
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
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 { t, formatDate } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
@@ -920,15 +920,27 @@ function collectModalIngredients(overlay) {
|
||||
// --------------------------------------------------------
|
||||
|
||||
async function deleteMeal(mealId) {
|
||||
if (!await confirmModal(t('meals.deleteMeal') + '?', { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/meals/${mealId}`);
|
||||
state.meals = state.meals.filter((m) => m.id !== mealId);
|
||||
renderWeekGrid();
|
||||
window.oikos?.showToast(t('meals.deleteMeal'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error');
|
||||
}
|
||||
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 {
|
||||
await api.delete(`/meals/${mealId}`);
|
||||
state.meals = state.meals.filter((m) => m.id !== mealId);
|
||||
renderWeekGrid();
|
||||
} catch (err) {
|
||||
if (itemEl) itemEl.style.display = '';
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
+28
-11
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
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 { t } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
@@ -476,16 +476,33 @@ async function togglePin(id) {
|
||||
}
|
||||
|
||||
async function deleteNote(id) {
|
||||
if (!await confirmModal(t('notes.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/notes/${id}`);
|
||||
state.notes = state.notes.filter((n) => n.id !== id);
|
||||
renderGrid();
|
||||
vibrate([30, 50, 30]);
|
||||
window.oikos?.showToast(t('notes.deletedToast'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'error');
|
||||
}
|
||||
closeModal();
|
||||
const note = state.notes.find((n) => n.id === id);
|
||||
state.notes = state.notes.filter((n) => n.id !== id);
|
||||
renderGrid();
|
||||
vibrate([30, 50, 30]);
|
||||
|
||||
let undone = false;
|
||||
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);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
+20
-14
@@ -5,7 +5,7 @@
|
||||
|
||||
import { api } from '/api.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';
|
||||
|
||||
let _container = null;
|
||||
@@ -134,6 +134,7 @@ function renderRecipeList() {
|
||||
for (const recipe of state.recipes) {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'recipe-card';
|
||||
card.dataset.id = String(recipe.id);
|
||||
|
||||
const h = document.createElement('h2');
|
||||
h.className = 'recipe-card__title';
|
||||
@@ -370,21 +371,26 @@ async function saveRecipe(panel, mode, recipe) {
|
||||
}
|
||||
|
||||
async function removeRecipe(recipe) {
|
||||
const ok = await confirmModal(t('recipes.deleteConfirm', { title: recipe.title }), {
|
||||
danger: true,
|
||||
confirmLabel: t('common.delete'),
|
||||
const itemEl = _container.querySelector(`.recipe-card[data-id="${recipe.id}"]`);
|
||||
if (itemEl) itemEl.style.display = 'none';
|
||||
|
||||
let undone = false;
|
||||
window.oikos?.showToast(t('recipes.deleted'), 'default', 5000, () => {
|
||||
undone = true;
|
||||
if (itemEl) itemEl.style.display = '';
|
||||
});
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
try {
|
||||
await api.delete(`/recipes/${recipe.id}`);
|
||||
state.recipes = state.recipes.filter((r) => r.id !== recipe.id);
|
||||
renderRecipeList();
|
||||
window.oikos?.showToast(t('recipes.deleted'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error');
|
||||
}
|
||||
setTimeout(async () => {
|
||||
if (undone) return;
|
||||
try {
|
||||
await api.delete(`/recipes/${recipe.id}`);
|
||||
state.recipes = state.recipes.filter((r) => r.id !== recipe.id);
|
||||
renderRecipeList();
|
||||
} catch (err) {
|
||||
if (itemEl) itemEl.style.display = '';
|
||||
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async function duplicateRecipe(recipe) {
|
||||
|
||||
+25
-17
@@ -8,7 +8,7 @@ import { api } from '/api.js';
|
||||
import { stagger, vibrate } from '/utils/ux.js';
|
||||
import { t } from '/i18n.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';
|
||||
|
||||
// --------------------------------------------------------
|
||||
@@ -781,23 +781,31 @@ function wireListContentEvents(container) {
|
||||
|
||||
// ---- Liste löschen ----
|
||||
if (action === 'delete-list') {
|
||||
if (!await confirmModal(t('shopping.deleteListConfirm', { name: state.activeList?.name }), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/shopping/${state.activeListId}`);
|
||||
state.lists = state.lists.filter((l) => l.id !== state.activeListId);
|
||||
state.activeListId = state.lists[0]?.id ?? null;
|
||||
if (state.activeListId) {
|
||||
await switchList(state.activeListId, container);
|
||||
} else {
|
||||
state.items = [];
|
||||
state.activeList = null;
|
||||
renderTabs(container);
|
||||
renderListContent(container);
|
||||
const deletedListId = state.activeListId;
|
||||
|
||||
let undone = false;
|
||||
window.oikos.showToast(t('shopping.deletedListToast'), 'default', 5000, () => {
|
||||
undone = true;
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
if (undone) return;
|
||||
try {
|
||||
await api.delete(`/shopping/${deletedListId}`);
|
||||
state.lists = state.lists.filter((l) => l.id !== deletedListId);
|
||||
state.activeListId = state.lists[0]?.id ?? null;
|
||||
if (state.activeListId) {
|
||||
await switchList(state.activeListId, container);
|
||||
} else {
|
||||
state.items = [];
|
||||
state.activeList = null;
|
||||
renderTabs(container);
|
||||
renderListContent(container);
|
||||
}
|
||||
} catch (err) {
|
||||
window.oikos.showToast(err.message ?? t('common.unknownError'), 'danger');
|
||||
}
|
||||
window.oikos.showToast(t('shopping.deletedListToast'));
|
||||
} catch (err) {
|
||||
window.oikos.showToast(err.message, 'danger');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+24
-13
@@ -6,7 +6,7 @@
|
||||
|
||||
import { api } from '/api.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 { t, formatDate, formatTime } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
@@ -582,18 +582,29 @@ async function handleFormSubmit(e, container) {
|
||||
}
|
||||
|
||||
async function handleDeleteTask(id, container) {
|
||||
if (!await confirmModal(t('tasks.deleteConfirm'), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||
try {
|
||||
await api.delete(`/tasks/${id}`);
|
||||
// Erinnerungen für diese Aufgabe ebenfalls entfernen
|
||||
api.delete(`/reminders?entity_type=task&entity_id=${id}`).catch(() => {});
|
||||
refreshReminders();
|
||||
closeModal();
|
||||
window.oikos.showToast(t('tasks.deletedToast'), 'default');
|
||||
await loadTasks(container);
|
||||
} catch (err) {
|
||||
window.oikos.showToast(err.message, 'danger');
|
||||
}
|
||||
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 {
|
||||
await api.delete(`/tasks/${id}`);
|
||||
// Erinnerungen für diese Aufgabe ebenfalls entfernen
|
||||
api.delete(`/reminders?entity_type=task&entity_id=${id}`).catch(() => {});
|
||||
refreshReminders();
|
||||
await loadTasks(container);
|
||||
} catch (err) {
|
||||
if (itemEl) itemEl.style.display = '';
|
||||
window.oikos.showToast(err.message ?? t('common.unknownError'), 'danger');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async function handleAddSubtask(parentId, container) {
|
||||
|
||||
Reference in New Issue
Block a user