Merge pull request #59 from baragoon/dev
Unify shopping category localization and limit meal ingredient categories to food-relevant options
This commit is contained in:
+17
-6
@@ -9,6 +9,7 @@ import { openModal as openSharedModal, closeModal as closeSharedModal, selectMod
|
|||||||
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';
|
||||||
|
import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js';
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// Konstanten
|
// Konstanten
|
||||||
@@ -26,6 +27,8 @@ const DAY_NAMES = () => [
|
|||||||
t('meals.dayFr'), t('meals.daySa'), t('meals.daySo'),
|
t('meals.dayFr'), t('meals.daySa'), t('meals.daySo'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const EXCLUDED_MEAL_CATEGORY_NAMES = new Set(['Haushalt', 'Drogerie']);
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// State
|
// State
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
@@ -73,6 +76,10 @@ function formatDayDate(dateStr) {
|
|||||||
return formatDate(dateStr);
|
return formatDate(dateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mealCategories() {
|
||||||
|
return state.categories.filter((c) => !EXCLUDED_MEAL_CATEGORY_NAMES.has(c.name));
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// API-Wrapper
|
// API-Wrapper
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
@@ -564,7 +571,7 @@ function buildModalContent({ mode, date, mealType, meal }) {
|
|||||||
: `<option value="" disabled>${t('meals.noShoppingLists')}</option>`;
|
: `<option value="" disabled>${t('meals.noShoppingLists')}</option>`;
|
||||||
|
|
||||||
const ingRows = isEdit && meal.ingredients?.length
|
const ingRows = isEdit && meal.ingredients?.length
|
||||||
? meal.ingredients.map((ing) => ingredientRowHTML(ing.name, ing.quantity ?? '', ing.id, ing.category ?? 'Sonstiges')).join('')
|
? meal.ingredients.map((ing) => ingredientRowHTML(ing.name, ing.quantity ?? '', ing.id, ing.category ?? DEFAULT_CATEGORY_NAME)).join('')
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const hasIngOpen = isEdit && meal.ingredients?.some((i) => !i.on_shopping_list);
|
const hasIngOpen = isEdit && meal.ingredients?.some((i) => !i.on_shopping_list);
|
||||||
@@ -630,10 +637,14 @@ function buildModalContent({ mode, date, mealType, meal }) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ingredientRowHTML(name, qty, id, category = 'Sonstiges') {
|
function ingredientRowHTML(name, qty, id, category = DEFAULT_CATEGORY_NAME) {
|
||||||
const catOptions = state.categories.length
|
const availableCategories = mealCategories();
|
||||||
? state.categories.map((c) => `<option value="${esc(c.name)}" ${c.name === category ? 'selected' : ''}>${esc(c.name)}</option>`).join('')
|
const resolvedCategory = availableCategories.some((c) => c.name === category)
|
||||||
: `<option value="Sonstiges" selected>${t('meals.ingredientCategoryDefault')}</option>`;
|
? category
|
||||||
|
: (availableCategories[0]?.name ?? DEFAULT_CATEGORY_NAME);
|
||||||
|
const catOptions = availableCategories.length
|
||||||
|
? availableCategories.map((c) => `<option value="${esc(c.name)}" ${c.name === resolvedCategory ? 'selected' : ''}>${esc(categoryLabel(c.name))}</option>`).join('')
|
||||||
|
: `<option value="${DEFAULT_CATEGORY_NAME}" selected>${t('meals.ingredientCategoryDefault')}</option>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="ingredient-row" data-ing-id="${id ?? ''}">
|
<div class="ingredient-row" data-ing-id="${id ?? ''}">
|
||||||
@@ -669,7 +680,7 @@ async function saveModal(overlay) {
|
|||||||
overlay.querySelectorAll('.ingredient-row').forEach((row) => {
|
overlay.querySelectorAll('.ingredient-row').forEach((row) => {
|
||||||
const name = row.querySelector('.ingredient-row__name').value.trim();
|
const name = row.querySelector('.ingredient-row__name').value.trim();
|
||||||
const qty = row.querySelector('.ingredient-row__qty').value.trim() || null;
|
const qty = row.querySelector('.ingredient-row__qty').value.trim() || null;
|
||||||
const category = row.querySelector('.ingredient-row__cat')?.value || 'Sonstiges';
|
const category = row.querySelector('.ingredient-row__cat')?.value || DEFAULT_CATEGORY_NAME;
|
||||||
if (name) ingredients.push({ name, quantity: qty, category, id: row.dataset.ingId || null });
|
if (name) ingredients.push({ name, quantity: qty, category, id: row.dataset.ingId || null });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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, confirmModal } from '/components/modal.js';
|
||||||
|
import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js';
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// Konstanten
|
// Konstanten
|
||||||
@@ -19,25 +20,6 @@ const SWIPE_THRESHOLD = 80; // px - Mindestweg für Aktion
|
|||||||
const SWIPE_MAX_VERT = 12; // px - vertikaler Toleranzbereich
|
const SWIPE_MAX_VERT = 12; // px - vertikaler Toleranzbereich
|
||||||
const SWIPE_LOCK_VERT = 30; // px - ab diesem Weg gilt es als Scroll
|
const SWIPE_LOCK_VERT = 30; // px - ab diesem Weg gilt es als Scroll
|
||||||
|
|
||||||
// Übersetzungs-Map für die Standard-Kategorien (DB-Name → i18n-Key)
|
|
||||||
const DEFAULT_CATEGORY_I18N = {
|
|
||||||
'Obst & Gemüse': 'shopping.catFruitVeg',
|
|
||||||
'Backwaren': 'shopping.catBakery',
|
|
||||||
'Milchprodukte': 'shopping.catDairy',
|
|
||||||
'Fleisch & Fisch': 'shopping.catMeatFish',
|
|
||||||
'Tiefkühl': 'shopping.catFrozen',
|
|
||||||
'Getränke': 'shopping.catDrinks',
|
|
||||||
'Haushalt': 'shopping.catHousehold',
|
|
||||||
'Drogerie': 'shopping.catDrugstore',
|
|
||||||
'Sonstiges': 'shopping.catMisc',
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Übersetzten Label für eine Kategorie zurückgeben. */
|
|
||||||
function catLabel(name) {
|
|
||||||
const key = DEFAULT_CATEGORY_I18N[name];
|
|
||||||
return key ? t(key) : name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Icon für eine Kategorie (aus state.categories, Fallback 'tag'). */
|
/** Icon für eine Kategorie (aus state.categories, Fallback 'tag'). */
|
||||||
function catIcon(name) {
|
function catIcon(name) {
|
||||||
return state.categories.find((c) => c.name === name)?.icon ?? 'tag';
|
return state.categories.find((c) => c.name === name)?.icon ?? 'tag';
|
||||||
@@ -67,7 +49,7 @@ const state = {
|
|||||||
function groupItemsByCategory(items) {
|
function groupItemsByCategory(items) {
|
||||||
const grouped = {};
|
const grouped = {};
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const cat = item.category || (state.categories[0]?.name ?? 'Sonstiges');
|
const cat = item.category || (state.categories[0]?.name ?? DEFAULT_CATEGORY_NAME);
|
||||||
(grouped[cat] = grouped[cat] || []).push(item);
|
(grouped[cat] = grouped[cat] || []).push(item);
|
||||||
}
|
}
|
||||||
// In DB-Reihenfolge zurückgeben; unbekannte Kategorien ans Ende
|
// In DB-Reihenfolge zurückgeben; unbekannte Kategorien ans Ende
|
||||||
@@ -157,7 +139,7 @@ function renderListContent(container) {
|
|||||||
<div class="autocomplete-dropdown" id="autocomplete-dropdown" hidden></div>
|
<div class="autocomplete-dropdown" id="autocomplete-dropdown" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
<select class="quick-add__cat" id="item-cat-select" aria-label="${t('shopping.categoryLabel')}">
|
<select class="quick-add__cat" id="item-cat-select" aria-label="${t('shopping.categoryLabel')}">
|
||||||
${state.categories.map((c) => `<option value="${esc(c.name)}">${esc(catLabel(c.name))}</option>`).join('')}
|
${state.categories.map((c) => `<option value="${esc(c.name)}">${esc(categoryLabel(c.name))}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
<button class="quick-add__btn" type="submit" aria-label="${t('shopping.addItemLabel')}">
|
<button class="quick-add__btn" type="submit" aria-label="${t('shopping.addItemLabel')}">
|
||||||
<i data-lucide="plus" style="width:20px;height:20px" aria-hidden="true"></i>
|
<i data-lucide="plus" style="width:20px;height:20px" aria-hidden="true"></i>
|
||||||
@@ -196,7 +178,7 @@ function renderItems() {
|
|||||||
<div class="item-category">
|
<div class="item-category">
|
||||||
<div class="item-category__header">
|
<div class="item-category__header">
|
||||||
<i data-lucide="${catIcon(cat)}" class="item-category__icon" aria-hidden="true"></i>
|
<i data-lucide="${catIcon(cat)}" class="item-category__icon" aria-hidden="true"></i>
|
||||||
${esc(catLabel(cat))}
|
${esc(categoryLabel(cat))}
|
||||||
</div>
|
</div>
|
||||||
${items.map(renderItem).join('')}
|
${items.map(renderItem).join('')}
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { t } from '/i18n.js';
|
||||||
|
|
||||||
|
export const DEFAULT_CATEGORY_NAME = 'Sonstiges';
|
||||||
|
|
||||||
|
export const DEFAULT_CATEGORY_I18N = {
|
||||||
|
'Obst & Gemüse': 'shopping.catFruitVeg',
|
||||||
|
'Backwaren': 'shopping.catBakery',
|
||||||
|
'Milchprodukte': 'shopping.catDairy',
|
||||||
|
'Fleisch & Fisch': 'shopping.catMeatFish',
|
||||||
|
'Tiefkühl': 'shopping.catFrozen',
|
||||||
|
'Getränke': 'shopping.catDrinks',
|
||||||
|
'Haushalt': 'shopping.catHousehold',
|
||||||
|
'Drogerie': 'shopping.catDrugstore',
|
||||||
|
'Sonstiges': 'shopping.catMisc',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function categoryLabel(name) {
|
||||||
|
const key = DEFAULT_CATEGORY_I18N[name];
|
||||||
|
return key ? t(key) : name;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user