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

This commit is contained in:
2026-06-18 12:55:21 +00:00
parent 37bf403412
commit 0fb887756f
+188
View File
@@ -14989,6 +14989,194 @@ async function loadRecipeArchive() {
container.innerHTML = html;
}
// ===== RECIPE LIBRARY (recettes manuelles, ex: cocktails) =====
let _recipeLibraryCache = [];
async function loadRecipeLibrary() {
const container = document.getElementById('recipe-library-list');
if (!container) return;
container.innerHTML = `<p class="settings-hint">Chargement…</p>`;
try {
const result = await api('recipe_library_list', {}, 'GET');
if (!result.success) {
container.innerHTML = `<p class="settings-hint">Erreur de chargement.</p>`;
return;
}
_recipeLibraryCache = result.recipes;
renderRecipeLibraryList(result.recipes);
} catch (e) {
container.innerHTML = `<p class="settings-hint">Erreur de chargement.</p>`;
}
}
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>`;
return;
}
container.innerHTML = recipes.map(entry => {
const r = entry.recipe;
const favBadge = entry.is_favorite ? `<span class="recipe-fav-badge" title="Favori">★</span>` : '';
const ingCount = (r.ingredients || []).length;
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">
<span class="recipe-archive-meal">🍹</span>
<span class="recipe-archive-title">${escapeHtml(r.title)}</span>
${favBadge}
</div>
<div class="recipe-archive-card-meta">
<span>${ingCount} ingrédient${ingCount > 1 ? 's' : ''}</span>
</div>
</div>
`;
}).join('');
}
function openRecipeLibraryForm(id = null) {
const existing = id ? _recipeLibraryCache.find(e => e.id === id) : null;
const r = existing ? existing.recipe : { title: '', ingredients: [{ name: '', qty: '' }], steps: [''] };
document.getElementById('modal-content').innerHTML = `
<div class="modal-header">
<h3>${existing ? '✏️ Modifier la recette' : ' Nouvelle recette'}</h3>
<button class="modal-close" onclick="closeModal()"></button>
</div>
<form class="form" onsubmit="submitRecipeLibraryForm(event, ${id || 'null'})">
<div class="form-group">
<label>Titre</label>
<input type="text" id="rl-title" class="form-input" value="${escapeHtml(r.title || '')}" placeholder="Es: Mojito, Spritz..." required>
</div>
<div class="form-group">
<label>Ingrédients</label>
<div id="rl-ingredients-list">
${r.ingredients.map(ing => `
<div class="barcode-input-row" style="margin-bottom:6px">
<input type="text" class="form-input rl-ing-name" placeholder="Nom (ex: Rhum blanc)" value="${escapeHtml(ing.name || '')}">
<input type="text" class="form-input rl-ing-qty" style="max-width:90px" placeholder="Qté (ex: 5cl)" value="${escapeHtml(ing.qty || '')}">
<button type="button" class="btn btn-small btn-secondary" onclick="this.closest('.barcode-input-row').remove()">🗑</button>
</div>
`).join('')}
</div>
<button type="button" class="btn btn-small btn-secondary" onclick="addRecipeLibraryRow('rl-ingredients-list', 'ingredient')"> Ingrédient</button>
</div>
<div class="form-group">
<label>Étapes</label>
<div id="rl-steps-list">
${r.steps.map(step => `
<div class="barcode-input-row" style="margin-bottom:6px">
<input type="text" class="form-input rl-step-text" placeholder="Étape" value="${escapeHtml(step || '')}">
<button type="button" class="btn btn-small btn-secondary" onclick="this.closest('.barcode-input-row').remove()">🗑</button>
</div>
`).join('')}
</div>
<button type="button" class="btn btn-small btn-secondary" onclick="addRecipeLibraryRow('rl-steps-list', 'step')"> Étape</button>
</div>
<button type="submit" class="btn btn-large btn-primary full-width">💾 Enregistrer</button>
</form>
`;
document.getElementById('modal-overlay').style.display = 'flex';
}
function addRecipeLibraryRow(containerId, type) {
const container = document.getElementById(containerId);
if (!container) return;
const row = document.createElement('div');
row.className = 'barcode-input-row';
row.style.marginBottom = '6px';
if (type === 'ingredient') {
row.innerHTML = `
<input type="text" class="form-input rl-ing-name" placeholder="Nom (ex: Rhum blanc)">
<input type="text" class="form-input rl-ing-qty" style="max-width:90px" placeholder="Qté (ex: 5cl)">
<button type="button" class="btn btn-small btn-secondary" onclick="this.closest('.barcode-input-row').remove()">🗑</button>
`;
} else {
row.innerHTML = `
<input type="text" class="form-input rl-step-text" placeholder="Étape">
<button type="button" class="btn btn-small btn-secondary" onclick="this.closest('.barcode-input-row').remove()">🗑</button>
`;
}
container.appendChild(row);
}
async function submitRecipeLibraryForm(e, id) {
e.preventDefault();
const title = document.getElementById('rl-title').value.trim();
if (!title) { showToast('Le titre est obligatoire', 'warning'); return; }
const ingredients = Array.from(document.querySelectorAll('#rl-ingredients-list .barcode-input-row')).map(row => ({
name: row.querySelector('.rl-ing-name')?.value.trim() || '',
qty: row.querySelector('.rl-ing-qty')?.value.trim() || '',
})).filter(ing => ing.name);
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 };
showLoading(true);
try {
const result = await api('recipe_library_save', {}, 'POST', { id, recipe });
showLoading(false);
if (result.success) {
showToast('Recette enregistrée', 'success');
closeModal();
loadRecipeLibrary();
} else {
showToast(result.error || 'Erreur', 'error');
}
} catch (e) {
showLoading(false);
showToast('Erreur', 'error');
}
}
function viewRecipeLibraryItem(id) {
const entry = _recipeLibraryCache.find(e => e.id === id);
if (!entry) return;
const r = entry.recipe;
document.getElementById('modal-content').innerHTML = `
<div class="modal-header">
<h3>🍹 ${escapeHtml(r.title)}</h3>
<button class="modal-close" onclick="closeModal()"></button>
</div>
<div class="form-group">
<label>Ingrédients</label>
<ul style="margin:0;padding-left:20px">
${(r.ingredients || []).map(ing => `<li>${escapeHtml(ing.name)}${ing.qty ? ' — ' + escapeHtml(ing.qty) : ''}</li>`).join('')}
</ul>
</div>
<div class="form-group">
<label>Étapes</label>
<ol style="margin:0;padding-left:20px">
${(r.steps || []).map(s => `<li>${escapeHtml(s)}</li>`).join('')}
</ol>
</div>
<div style="display:flex;gap:8px;margin-top:10px">
<button class="btn btn-secondary flex-1" onclick="closeModal();openRecipeLibraryForm(${id})"> Modifier</button>
<button class="btn btn-secondary flex-1" onclick="toggleRecipeLibraryFavorite(${id})">${entry.is_favorite ? '★ Retirer favori' : '☆ Favori'}</button>
<button class="btn btn-secondary flex-1" onclick="deleteRecipeLibraryItem(${id})">🗑 Supprimer</button>
</div>
`;
document.getElementById('modal-overlay').style.display = 'flex';
}
async function toggleRecipeLibraryFavorite(id) {
await api('recipe_library_toggle_favorite', {}, 'POST', { id });
closeModal();
loadRecipeLibrary();
}
async function deleteRecipeLibraryItem(id) {
if (!confirm('Supprimer cette recette ?')) return;
await api('recipe_library_delete', {}, 'POST', { id });
closeModal();
showToast('Recette supprimée', 'success');
loadRecipeLibrary();
}
function viewArchivedRecipe(idx) {
const pick = _recipeArchiveEntries[idx];
if (!pick) return;