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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user