Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
This commit is contained in:
+169
-5
@@ -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 = `<p class="settings-hint">Chargement…</p>`;
|
||||
try {
|
||||
const result = await api('recipe_tags_list', {}, 'GET');
|
||||
if (!result.success) {
|
||||
container.innerHTML = `<p class="settings-hint">Erreur de chargement.</p>`;
|
||||
return;
|
||||
}
|
||||
RECIPE_TAGS = result.tags;
|
||||
renderRecipeTagsConfigList(result.tags);
|
||||
} catch (e) {
|
||||
container.innerHTML = `<p class="settings-hint">Erreur de chargement.</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderRecipeTagsConfigList(tags) {
|
||||
const container = document.getElementById('recipe-tags-list-container');
|
||||
if (!container) return;
|
||||
if (!tags || tags.length === 0) {
|
||||
container.innerHTML = `<p class="settings-hint">Aucun tag.</p>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = tags.map(tag => `
|
||||
<div class="loc-row" style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--border,#e2e8f0)">
|
||||
<input type="text" class="form-input" style="max-width:60px;text-align:center" value="${escapeHtml(tag.icon)}" id="rtag-icon-${tag.key}" maxlength="4">
|
||||
<input type="text" class="form-input" style="flex:1" value="${escapeHtml(tag.label)}" id="rtag-label-${tag.key}">
|
||||
<button class="btn btn-small btn-primary" onclick="updateRecipeTagConfig('${tag.key}')" title="Enregistrer">💾</button>
|
||||
<button class="btn btn-small btn-secondary" onclick="removeRecipeTagConfig('${tag.key}')" title="Supprimer">🗑️</button>
|
||||
</div>
|
||||
`).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 = `<p class="settings-hint">Erreur de chargement.</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
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 `<button type="button" class="btn btn-small ${active ? 'btn-primary' : 'btn-secondary'}" onclick="setRecipeLibraryTagFilter('${tag.key}')">${tag.icon} ${escapeHtml(tag.label)}</button>`;
|
||||
}).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 = `<p class="settings-hint">Aucune recette pour l'instant.</p>`;
|
||||
const filtered = _recipeLibraryActiveTagFilter
|
||||
? recipes.filter(entry => (entry.recipe.tags || []).includes(_recipeLibraryActiveTagFilter))
|
||||
: recipes;
|
||||
if (!filtered || filtered.length === 0) {
|
||||
container.innerHTML = `<p class="settings-hint">Aucune recette${_recipeLibraryActiveTagFilter ? ' avec ce tag' : ' pour l\\'instant'}.</p>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = recipes.map(entry => {
|
||||
container.innerHTML = filtered.map(entry => {
|
||||
const r = entry.recipe;
|
||||
const favBadge = entry.is_favorite ? `<span class="recipe-fav-badge" title="Favori">★</span>` : '';
|
||||
const ingCount = (r.ingredients || []).length;
|
||||
const tagBadges = (r.tags || []).map(k => {
|
||||
const tag = RECIPE_TAGS.find(t => t.key === k);
|
||||
return tag ? `<span class="inv-badge badge-subcategory">${tag.icon} ${escapeHtml(tag.label)}</span>` : '';
|
||||
}).join('');
|
||||
return `
|
||||
<div class="recipe-archive-card${entry.is_favorite ? ' recipe-archive-card-fav' : ''}" style="cursor:pointer" onclick="viewRecipeLibraryItem(${entry.id})">
|
||||
<div class="recipe-archive-card-header">
|
||||
@@ -15031,6 +15180,7 @@ function renderRecipeLibraryList(recipes) {
|
||||
<div class="recipe-archive-card-meta">
|
||||
<span>${ingCount} ingrédient${ingCount > 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
${tagBadges ? `<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px">${tagBadges}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
@@ -15075,6 +15225,15 @@ function openRecipeLibraryForm(id = null) {
|
||||
</div>
|
||||
<button type="button" class="btn btn-small btn-secondary" onclick="addRecipeLibraryRow('rl-steps-list', 'step')">➕ Étape</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div id="rl-tags-picker" style="display:flex;flex-wrap:wrap;gap:6px">
|
||||
${RECIPE_TAGS.map(tag => {
|
||||
const selected = (r.tags || []).includes(tag.key);
|
||||
return `<button type="button" class="btn btn-small ${selected ? 'btn-primary' : 'btn-secondary'} rl-tag-chip" data-tag="${tag.key}" onclick="this.classList.toggle('btn-primary');this.classList.toggle('btn-secondary')">${tag.icon} ${escapeHtml(tag.label)}</button>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-large btn-primary full-width">💾 Enregistrer</button>
|
||||
</form>
|
||||
`;
|
||||
@@ -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 {
|
||||
@@ -20267,6 +20428,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');
|
||||
const resumeData = localStorage.getItem('evershelf_setup_data');
|
||||
|
||||
Reference in New Issue
Block a user