diff --git a/assets/js/app.js b/assets/js/app.js index c6e479b..cb4f6cb 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1397,6 +1397,114 @@ async function _loadConfigPage() { } await _loadCategoriesConfigSection(); await _loadSubcategoryConfigSection(); + await _loadRecipeTagsConfigSection(); +} + +async function _loadRecipeTagsConfigSection() { + const container = document.getElementById('recipe-tags-list-container'); + if (!container) return; + container.innerHTML = `

Chargement…

`; + try { + const result = await api('recipe_tags_list', {}, 'GET'); + if (!result.success) { + container.innerHTML = `

Erreur de chargement.

`; + return; + } + RECIPE_TAGS = result.tags; + renderRecipeTagsConfigList(result.tags); + } catch (e) { + container.innerHTML = `

Erreur de chargement.

`; + } +} + +function renderRecipeTagsConfigList(tags) { + const container = document.getElementById('recipe-tags-list-container'); + if (!container) return; + if (!tags || tags.length === 0) { + container.innerHTML = `

Aucun tag.

`; + return; + } + container.innerHTML = tags.map(tag => ` +
+ + + + +
+ `).join(''); +} + +async function addRecipeTagConfig() { + const iconInput = document.getElementById('new-recipe-tag-icon'); + const labelInput = document.getElementById('new-recipe-tag-label'); + const icon = iconInput.value.trim() || '🏷️'; + const label = labelInput.value.trim(); + + if (!label) { + showToast('Indique un nom pour le nouveau tag', 'warning'); + return; + } + + showLoading(true); + try { + const result = await api('recipe_tags_add', {}, 'POST', { label, icon }); + showLoading(false); + if (result.success) { + showToast(`Tag "${label}" ajouté`, 'success'); + iconInput.value = ''; + labelInput.value = ''; + _loadRecipeTagsConfigSection(); + } else { + showToast(result.error || 'Erreur lors de l\\'ajout', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur lors de l\\'ajout', 'error'); + } +} + +async function updateRecipeTagConfig(key) { + const icon = document.getElementById(`rtag-icon-${key}`).value.trim() || '🏷️'; + const label = document.getElementById(`rtag-label-${key}`).value.trim(); + + if (!label) { + showToast('Le nom ne peut pas être vide', 'warning'); + return; + } + + showLoading(true); + try { + const result = await api('recipe_tags_update', {}, 'POST', { key, label, icon }); + showLoading(false); + if (result.success) { + showToast('Tag mis à jour', 'success'); + _loadRecipeTagsConfigSection(); + } else { + showToast(result.error || 'Erreur', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur', 'error'); + } +} + +async function removeRecipeTagConfig(key) { + if (!confirm(`Supprimer ce tag ?`)) return; + + showLoading(true); + try { + const result = await api('recipe_tags_remove', {}, 'POST', { key }); + showLoading(false); + if (result.success) { + showToast('Tag supprimé', 'success'); + _loadRecipeTagsConfigSection(); + } else { + showToast(result.error || 'Impossible de supprimer', 'error'); + } + } catch (e) { + showLoading(false); + showToast('Erreur lors de la suppression', 'error'); + } } async function _loadCategoriesConfigSection() { @@ -14991,6 +15099,20 @@ async function loadRecipeArchive() { // ===== RECIPE LIBRARY (recettes manuelles, ex: cocktails) ===== +let RECIPE_TAGS = []; +let _recipeLibraryActiveTagFilter = null; + +async function loadRecipeTags() { + try { + const result = await api('recipe_tags_list', {}, 'GET'); + if (result.success && Array.isArray(result.tags)) { + RECIPE_TAGS = result.tags; + } + } catch (e) { + console.warn('[EverShelf] Could not load recipe tags:', e); + } +} + let _recipeLibraryCache = []; async function loadRecipeLibrary() { @@ -15004,23 +15126,50 @@ async function loadRecipeLibrary() { return; } _recipeLibraryCache = result.recipes; - renderRecipeLibraryList(result.recipes); + renderRecipeLibraryTagFilterBar(); + renderRecipeLibraryList(_recipeLibraryCache); } catch (e) { container.innerHTML = `

Erreur de chargement.

`; } } +function renderRecipeLibraryTagFilterBar() { + const container = document.getElementById('recipe-library-tag-filter'); + if (!container) return; + const usedKeys = new Set(); + _recipeLibraryCache.forEach(entry => (entry.recipe.tags || []).forEach(k => usedKeys.add(k))); + const usedTags = RECIPE_TAGS.filter(t => usedKeys.has(t.key)); + if (usedTags.length === 0) { container.innerHTML = ''; return; } + container.innerHTML = usedTags.map(tag => { + const active = _recipeLibraryActiveTagFilter === tag.key; + return ``; + }).join(''); +} + +function setRecipeLibraryTagFilter(key) { + _recipeLibraryActiveTagFilter = (_recipeLibraryActiveTagFilter === key) ? null : key; + renderRecipeLibraryTagFilterBar(); + renderRecipeLibraryList(_recipeLibraryCache); +} + function renderRecipeLibraryList(recipes) { const container = document.getElementById('recipe-library-list'); if (!container) return; - if (!recipes || recipes.length === 0) { - container.innerHTML = `

Aucune recette pour l'instant.

`; + const filtered = _recipeLibraryActiveTagFilter + ? recipes.filter(entry => (entry.recipe.tags || []).includes(_recipeLibraryActiveTagFilter)) + : recipes; + if (!filtered || filtered.length === 0) { + container.innerHTML = `

Aucune recette${_recipeLibraryActiveTagFilter ? ' avec ce tag' : ' pour l\\'instant'}.

`; return; } - container.innerHTML = recipes.map(entry => { + container.innerHTML = filtered.map(entry => { const r = entry.recipe; const favBadge = entry.is_favorite ? `` : ''; const ingCount = (r.ingredients || []).length; + const tagBadges = (r.tags || []).map(k => { + const tag = RECIPE_TAGS.find(t => t.key === k); + return tag ? `${tag.icon} ${escapeHtml(tag.label)}` : ''; + }).join(''); return `
@@ -15031,6 +15180,7 @@ function renderRecipeLibraryList(recipes) {
${ingCount} ingrédient${ingCount > 1 ? 's' : ''}
+ ${tagBadges ? `
${tagBadges}
` : ''}
`; }).join(''); @@ -15075,6 +15225,15 @@ function openRecipeLibraryForm(id = null) {
+
+ +
+ ${RECIPE_TAGS.map(tag => { + const selected = (r.tags || []).includes(tag.key); + return ``; + }).join('')} +
+
`; @@ -15114,7 +15273,9 @@ async function submitRecipeLibraryForm(e, id) { const steps = Array.from(document.querySelectorAll('#rl-steps-list .rl-step-text')).map(input => input.value.trim()).filter(Boolean); - const recipe = { title, ingredients, steps, persons: 1 }; + const tags = Array.from(document.querySelectorAll('#rl-tags-picker .rl-tag-chip.btn-primary')).map(b => b.dataset.tag); + + const recipe = { title, ingredients, steps, tags, persons: 1 }; showLoading(true); try { @@ -20266,6 +20427,9 @@ async function _initApp() { // Load custom subcategories (merges into SUBCATEGORIES_BY_CATEGORY before any UI renders) await loadCustomSubcategories(); + + // Load recipe tags (used by the "Mes recettes" form and filter bar) + await loadRecipeTags(); // Check for setup wizard resume (after language change) const resumeStep = localStorage.getItem('evershelf_setup_step');