summaryrefslogtreecommitdiff
path: root/static/recipe.html
diff options
context:
space:
mode:
Diffstat (limited to 'static/recipe.html')
-rw-r--r--static/recipe.html217
1 files changed, 183 insertions, 34 deletions
diff --git a/static/recipe.html b/static/recipe.html
index 2a5614f..34b0669 100644
--- a/static/recipe.html
+++ b/static/recipe.html
@@ -18,46 +18,195 @@
<script>
const params = new URLSearchParams(location.search);
const id = params.get('id');
+ let currentRecipe = null;
+ let isEditMode = false;
+
+ function parseIngredientsFromText(text) {
+ return text.split('\n')
+ .map(line => line.trim())
+ .filter(line => line.length > 0)
+ .map(line => {
+ const parts = line.split(/\s+/);
+ if (parts.length >= 3) {
+ const quantity = parseFloat(parts[0]);
+ if (!isNaN(quantity)) {
+ const unit = parts[1];
+ const name = parts.slice(2).join(' ');
+ return { quantity, unit, name };
+ }
+ }
+ return { name: line, quantity: 1, unit: '' };
+ });
+ }
+
+ function ingredientsToText(ingredients) {
+ if (!Array.isArray(ingredients)) return '';
+ return ingredients.map(i => {
+ if (typeof i === 'string') return i;
+ return [i.quantity, i.unit, i.name].filter(Boolean).join(' ');
+ }).join('\n');
+ }
+
+ function renderViewMode(recipe) {
+ let ingredients = [];
+ try { ingredients = JSON.parse(recipe.ingredients); } catch {}
+
+ const mealType = recipe.meal_type || 'dinner';
+ const typeDisplay = mealType.charAt(0).toUpperCase() + mealType.slice(1);
+ const time = recipe.estimated_time_minutes;
+ const serves = recipe.servings;
+ const meta = [time ? `${time} min` : '', serves ? `serves ${serves}` : ''].filter(Boolean).join(' · ');
+
+ const ingredientList = Array.isArray(ingredients)
+ ? ingredients.map(i => {
+ if (typeof i === 'string') return `<li>${i}</li>`;
+ return `<li>${[i.quantity, i.unit, i.name].filter(Boolean).join(' ')}</li>`;
+ }).join('')
+ : '';
+
+ const instructionSteps = (recipe.instructions || '')
+ .split(/\n+| (?=\d+\. )/)
+ .map(s => s.trim())
+ .filter(Boolean)
+ .map(s => `<p>${s}</p>`)
+ .join('');
+
+ document.title = `${recipe.name} — Commis`;
+ document.getElementById('recipe-content').innerHTML = `
+ <div class="recipe-card-header" style="margin-bottom:0.5rem;display:flex;justify-content:space-between;align-items:start;gap:1rem;">
+ <div>
+ <h1 style="font-size:1.6rem;margin:0;">${recipe.name}</h1>
+ <span class="recipe-type-badge ${mealType}">${typeDisplay}</span>
+ </div>
+ <button class="btn btn-secondary" style="white-space:nowrap;margin-top:0.25rem;" onclick="enterEditMode()">Edit</button>
+ </div>
+ ${recipe.description ? `<p style="color:var(--text-muted);margin:0.5rem 0 1rem;">${recipe.description}</p>` : ''}
+ ${meta ? `<div class="recipe-card-meta" style="margin-bottom:1.5rem;">${meta}</div>` : ''}
+ ${ingredientList ? `<h2 style="font-size:1.1rem;margin-bottom:0.5rem;">Ingredients</h2><ul style="padding-left:1.25rem;line-height:1.8;">${ingredientList}</ul>` : ''}
+ ${instructionSteps ? `<h2 style="font-size:1.1rem;margin:1.5rem 0 0.5rem;">Instructions</h2><div style="line-height:1.7;">${instructionSteps}</div>` : ''}
+ `;
+ isEditMode = false;
+ }
+
+ function renderEditMode(recipe) {
+ let ingredients = [];
+ try { ingredients = JSON.parse(recipe.ingredients); } catch {}
+ const ingredientText = ingredientsToText(ingredients);
+
+ document.getElementById('recipe-content').innerHTML = `
+ <div style="margin-bottom:1.5rem;">
+ <button class="btn btn-secondary" style="margin-bottom:1rem;" onclick="exitEditMode()">← Back</button>
+ <h2 style="font-size:1.4rem;margin:0 0 1.5rem;">Edit Recipe</h2>
+
+ <form id="edit-form" style="display:flex;flex-direction:column;gap:1.5rem;">
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Name</label>
+ <input type="text" id="edit-name" value="${recipe.name}" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;">
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Meal Type</label>
+ <select id="edit-meal-type" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;">
+ <option value="breakfast" ${recipe.meal_type === 'breakfast' ? 'selected' : ''}>Breakfast</option>
+ <option value="lunch" ${recipe.meal_type === 'lunch' ? 'selected' : ''}>Lunch</option>
+ <option value="dinner" ${recipe.meal_type === 'dinner' ? 'selected' : ''}>Dinner</option>
+ </select>
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Description (optional)</label>
+ <textarea id="edit-description" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;min-height:80px;font-family:inherit;" placeholder="Brief description of the recipe">${recipe.description || ''}</textarea>
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Time (minutes, optional)</label>
+ <input type="number" id="edit-time" value="${recipe.estimated_time_minutes || ''}" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;">
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Servings</label>
+ <input type="number" id="edit-servings" value="${recipe.servings}" min="1" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;">
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Ingredients</label>
+ <textarea id="edit-ingredients" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;min-height:150px;font-family:monospace;" placeholder="One ingredient per line (e.g., 1 cup flour)">${ingredientText}</textarea>
+ <p style="color:var(--text-muted);font-size:0.9rem;margin-top:0.25rem;">Format: quantity unit name (e.g., 1 cup flour)</p>
+ </div>
+
+ <div>
+ <label style="display:block;margin-bottom:0.5rem;font-weight:500;">Instructions</label>
+ <textarea id="edit-instructions" style="width:100%;padding:0.5rem;border:1px solid var(--border-color);border-radius:6px;font-size:1rem;box-sizing:border-box;min-height:150px;font-family:inherit;" placeholder="Step-by-step instructions">${recipe.instructions || ''}</textarea>
+ </div>
+
+ <div style="display:flex;gap:1rem;">
+ <button type="button" class="btn btn-primary" onclick="saveRecipe()" style="flex:1;">Save</button>
+ <button type="button" class="btn btn-secondary" onclick="exitEditMode()" style="flex:1;">Cancel</button>
+ </div>
+ </form>
+ </div>
+ `;
+ isEditMode = true;
+ }
+
+ function enterEditMode() {
+ if (currentRecipe) renderEditMode(currentRecipe);
+ }
+
+ function exitEditMode() {
+ if (currentRecipe) renderViewMode(currentRecipe);
+ }
+
+ async function saveRecipe() {
+ const name = document.getElementById('edit-name').value.trim();
+ const meal_type = document.getElementById('edit-meal-type').value;
+ const description = document.getElementById('edit-description').value.trim();
+ const time_minutes = document.getElementById('edit-time').value;
+ const servings = parseInt(document.getElementById('edit-servings').value, 10) || 2;
+ const ingredientText = document.getElementById('edit-ingredients').value.trim();
+ const instructions = document.getElementById('edit-instructions').value.trim();
+
+ if (!name) {
+ alert('Recipe name is required.');
+ return;
+ }
+
+ const ingredients = parseIngredientsFromText(ingredientText);
+ const payload = {
+ name,
+ meal_type,
+ description: description || null,
+ estimated_time_minutes: time_minutes ? parseInt(time_minutes, 10) : null,
+ servings,
+ ingredients: JSON.stringify(ingredients),
+ instructions: instructions || null,
+ };
+
+ try {
+ const response = await fetch(`/api/recipes/${id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+ const updated = await response.json();
+ currentRecipe = updated;
+ renderViewMode(currentRecipe);
+ } catch (err) {
+ alert(`Failed to save recipe: ${err.message}`);
+ }
+ }
+
if (!id) {
document.getElementById('recipe-content').innerHTML = '<p>No recipe ID provided.</p>';
} else {
fetch(`/api/recipes/${id}`)
.then(r => r.ok ? r.json() : Promise.reject(r.statusText))
.then(recipe => {
- let ingredients = [];
- try { ingredients = JSON.parse(recipe.ingredients); } catch {}
-
- const mealType = recipe.meal_type || 'dinner';
- const typeDisplay = mealType.charAt(0).toUpperCase() + mealType.slice(1);
- const time = recipe.estimated_time_minutes;
- const serves = recipe.servings;
- const meta = [time ? `${time} min` : '', serves ? `serves ${serves}` : ''].filter(Boolean).join(' · ');
-
- const ingredientList = Array.isArray(ingredients)
- ? ingredients.map(i => {
- if (typeof i === 'string') return `<li>${i}</li>`;
- return `<li>${[i.quantity, i.unit, i.name].filter(Boolean).join(' ')}</li>`;
- }).join('')
- : '';
-
- const instructionSteps = (recipe.instructions || '')
- .split(/\n+| (?=\d+\. )/)
- .map(s => s.trim())
- .filter(Boolean)
- .map(s => `<p>${s}</p>`)
- .join('');
-
- document.title = `${recipe.name} — Commis`;
- document.getElementById('recipe-content').innerHTML = `
- <div class="recipe-card-header" style="margin-bottom:0.5rem;">
- <h1 style="font-size:1.6rem;margin:0;">${recipe.name}</h1>
- <span class="recipe-type-badge ${mealType}">${typeDisplay}</span>
- </div>
- ${recipe.description ? `<p style="color:var(--text-muted);margin:0.5rem 0 1rem;">${recipe.description}</p>` : ''}
- ${meta ? `<div class="recipe-card-meta" style="margin-bottom:1.5rem;">${meta}</div>` : ''}
- ${ingredientList ? `<h2 style="font-size:1.1rem;margin-bottom:0.5rem;">Ingredients</h2><ul style="padding-left:1.25rem;line-height:1.8;">${ingredientList}</ul>` : ''}
- ${instructionSteps ? `<h2 style="font-size:1.1rem;margin:1.5rem 0 0.5rem;">Instructions</h2><div style="line-height:1.7;">${instructionSteps}</div>` : ''}
- `;
+ currentRecipe = recipe;
+ renderViewMode(recipe);
})
.catch(err => {
document.getElementById('recipe-content').innerHTML = `<p>Failed to load recipe: ${err}</p>`;