Fermentation Recipe Card & Log
Ingredients
Instructions
Fermentation Log
${escapeHTML(entry.notes)}
`; const deleteBtn = document.createElement("button"); deleteBtn.type = "button"; deleteBtn.className = "frc-log-delete-btn"; deleteBtn.textContent = "Delete"; deleteBtn.dataset.id = entry.id; deleteBtn.addEventListener("click", () => handleDeleteLogEntry(entry.id)); item.appendChild(content); item.appendChild(deleteBtn); logEntriesList.appendChild(item); }); } /** * Populates the config form with current recipe data. */ function renderConfigForm() { if (!recipeTitleInput || !recipeIngredientsInput || !recipeInstructionsInput) return; recipeTitleInput.value = recipeData.title || ""; recipeIngredientsInput.value = (recipeData.ingredients || []).join("\n"); recipeInstructionsInput.value = recipeData.instructions || ""; } /** * Handles saving the recipe from the config form. */ function handleSaveRecipe(e) { e.preventDefault(); recipeData.title = recipeTitleInput.value; recipeData.instructions = recipeInstructionsInput.value; recipeData.ingredients = recipeIngredientsInput.value .split('\n') .filter(line => line.trim() !== ""); // Filter out empty lines renderRecipeDisplay(); alert("Recipe Saved!"); showTab(1); // Per spec: move to next logical tab (dashboard) } /** * Handles adding a new log entry from the dashboard. */ function handleAddLogEntry() { const date = logDateInput.value.trim(); const notes = logNotesInput.value.trim(); if (!date || !notes) { alert("Please enter both a date/day and notes."); return; } const newEntry = { id: Date.now(), date: date, notes: notes }; logEntries.push(newEntry); renderLogEntries(); // Clear inputs logDateInput.value = ""; logNotesInput.value = ""; logDateInput.focus(); } /** * Handles deleting a log entry. * @param {number} id - The ID of the log entry to delete. */ function handleDeleteLogEntry(id) { if (confirm("Are you sure you want to delete this log entry?")) { logEntries = logEntries.filter(entry => entry.id != id); renderLogEntries(); } } /** * Generates and downloads a PDF of the recipe and log. */ function downloadPDF() { if (typeof jsPDF === "undefined") { alert("Error: PDF generation library failed to load."); return; } try { const doc = new jsPDF(); const pageMargin = 20; const pageWidth = doc.internal.pageSize.getWidth() - pageMargin * 2; let yPos = 25; // --- 1. Recipe Title --- doc.setFont("helvetica", "bold"); doc.setFontSize(20); doc.setTextColor("#388e3c"); // Theme color doc.text(recipeData.title, pageMargin, yPos); yPos += 15; // --- 2. Ingredients --- doc.setFont("helvetica", "bold"); doc.setFontSize(14); doc.setTextColor("#333333"); doc.text("Ingredients", pageMargin, yPos); yPos += 8; doc.setFont("helvetica", "normal"); doc.setFontSize(11); recipeData.ingredients.forEach(item => { doc.text(`• ${item}`, pageMargin + 5, yPos); yPos += 7; }); yPos += 5; // Extra space // --- 3. Instructions --- doc.setFont("helvetica", "bold"); doc.setFontSize(14); doc.text("Instructions", pageMargin, yPos); yPos += 8; doc.setFont("helvetica", "normal"); const instructionLines = doc.splitTextToSize(recipeData.instructions, pageWidth); doc.text(instructionLines, pageMargin, yPos); yPos += (instructionLines.length * 4.5) + 10; // --- 4. Log --- if (logEntries.length > 0) { // Check for page break if (yPos > 260) { doc.addPage(); yPos = 20; } doc.setFont("helvetica", "bold"); doc.setFontSize(16); doc.setTextColor("#333333"); doc.text("Fermentation Log", pageMargin, yPos); yPos += 10; logEntries.forEach(entry => { if (yPos > 270) { // Check for break before each entry doc.addPage(); yPos = 20; } doc.setFont("helvetica", "bold"); doc.setFontSize(11); doc.setTextColor("#388e3c"); doc.text(entry.date, pageMargin, yPos); yPos += 6; doc.setFont("helvetica", "normal"); doc.setTextColor("#333333"); const noteLines = doc.splitTextToSize(entry.notes, pageWidth - 5); doc.text(noteLines, pageMargin + 5, yPos); yPos += (noteLines.length * 4.5) + 5; }); } doc.save(`${recipeData.title.replace(/ /g, '_')}-Log.pdf`); } catch (error) { console.error("Failed to generate PDF:", error); alert("An error occurred while generating the PDF."); } } /** * Utility to escape HTML. */ function escapeHTML(str) { if (!str) return ""; return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // --- Event Listeners --- if (tabButtons[1]) tabButtons[1].onclick = () => showTab(1); if (tabButtons[2]) tabButtons[2].onclick = () => showTab(2); if (prevBtn) prevBtn.onclick = () => showTab(currentTab - 1); if (nextBtn) nextBtn.onclick = () => showTab(currentTab + 1); // Tab 1 if (addLogBtn) addLogBtn.addEventListener("click", handleAddLogEntry); if (downloadPdfBtn) downloadPdfBtn.addEventListener("click", downloadPDF); // Tab 2 if (recipeForm) recipeForm.addEventListener("submit", handleSaveRecipe); // --- Initialization --- function init() { loadSampleData(); renderRecipeDisplay(); renderLogEntries(); renderConfigForm(); showTab(1); // Set initial tab state } init(); });