320 lines
13 KiB
JavaScript
320 lines
13 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
// --- Snowflakes Effect ---
|
|
function createSnowflakes() {
|
|
const snowContainer = document.getElementById('snow-container');
|
|
if (!snowContainer) return;
|
|
|
|
snowContainer.innerHTML = '';
|
|
|
|
const numberOfSnowflakes = 50;
|
|
|
|
for (let i = 0; i < numberOfSnowflakes; i++) {
|
|
const snowflake = document.createElement('div');
|
|
snowflake.className = 'snowflake';
|
|
|
|
const size = Math.random() * 4 + 2; // size from 2px to 6px
|
|
snowflake.style.width = `${size}px`;
|
|
snowflake.style.height = `${size}px`;
|
|
|
|
snowflake.style.left = Math.random() * 100 + 'vw';
|
|
|
|
const animationDuration = Math.random() * 5 + 5; // 5 to 10 seconds
|
|
snowflake.style.animationDuration = `${animationDuration}s`;
|
|
|
|
const animationDelay = Math.random() * 5; // 0 to 5 seconds
|
|
snowflake.style.animationDelay = `${animationDelay}s`;
|
|
|
|
snowflake.style.opacity = Math.random() * 0.7 + 0.3; // 0.3 to 1.0
|
|
|
|
snowContainer.appendChild(snowflake);
|
|
}
|
|
}
|
|
createSnowflakes();
|
|
|
|
// --- DOM Elements ---
|
|
const recipeNameInput = document.getElementById('recipeName');
|
|
const guestCountInput = document.getElementById('guestCount');
|
|
const ingredientsContainer = document.getElementById('ingredients-container');
|
|
const addIngredientBtn = document.getElementById('add-ingredient');
|
|
const calculateBtn = document.getElementById('calculate-btn');
|
|
const newRecipeBtn = document.getElementById('new-recipe-btn');
|
|
const shoppingListContainer = document.getElementById('shopping-list-container');
|
|
const recipeCardsContainer = document.getElementById('recipe-cards-container');
|
|
|
|
// --- Core Functions ---
|
|
|
|
async function loadRecipes() {
|
|
try {
|
|
const response = await fetch('api/get_recipes.php');
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
renderRecipeCards(data.recipes);
|
|
} else {
|
|
console.error('Failed to load recipes:', data.error);
|
|
recipeCardsContainer.innerHTML = '<div class="col-12"><p class="text-center text-danger">Error loading recipes.</p></div>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
recipeCardsContainer.innerHTML = '<div class="col-12"><p class="text-center text-danger">Could not connect to the server.</p></div>';
|
|
}
|
|
}
|
|
|
|
function addIngredientRow(ingredient = { name: '', quantity: '', unit: '' }) {
|
|
const row = document.createElement('div');
|
|
row.className = 'ingredient-row mb-2';
|
|
row.innerHTML = `
|
|
<input type="text" class="form-control" placeholder="Ingredient Name" aria-label="Ingredient Name" value="${ingredient.name}">
|
|
<input type="number" class="form-control" placeholder="Qty" aria-label="Quantity" min="0" step="any" value="${ingredient.quantity}">
|
|
<input type="text" class="form-control" placeholder="Unit (e.g., grams, ml)" aria-label="Unit" value="${ingredient.unit}">
|
|
<button type="button" class="btn btn-danger btn-sm remove-ingredient">×</button>
|
|
`;
|
|
ingredientsContainer.appendChild(row);
|
|
}
|
|
|
|
function clearForm() {
|
|
recipeNameInput.value = '';
|
|
guestCountInput.value = '1';
|
|
ingredientsContainer.innerHTML = '';
|
|
addIngredientRow();
|
|
shoppingListContainer.innerHTML = `
|
|
<div class="text-center text-muted p-5">
|
|
<h3 class="h4">Your Shopping List</h3>
|
|
<p>Your calculated list will appear here.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderRecipeCards(recipes) {
|
|
recipeCardsContainer.innerHTML = '';
|
|
if (!recipes || recipes.length === 0) {
|
|
recipeCardsContainer.innerHTML = '<div class="col-12"><p class="text-center text-muted">Здесь будут появляться ваши сохраненные рецепты.</p></div>';
|
|
return;
|
|
}
|
|
|
|
recipes.forEach(recipe => {
|
|
const cardCol = document.createElement('div');
|
|
cardCol.className = 'col-lg-4 col-md-6 mb-4';
|
|
cardCol.setAttribute('data-id', recipe.id);
|
|
|
|
const card = document.createElement('div');
|
|
card.className = 'card h-100';
|
|
|
|
const cardBody = document.createElement('div');
|
|
cardBody.className = 'card-body d-flex flex-column';
|
|
|
|
const title = document.createElement('h5');
|
|
title.className = 'card-title';
|
|
title.textContent = recipe.name;
|
|
|
|
const subtitle = document.createElement('h6');
|
|
subtitle.className = 'card-subtitle mb-2 text-muted';
|
|
subtitle.textContent = `${recipe.guests} guest(s)`;
|
|
|
|
const text = document.createElement('p');
|
|
text.className = 'card-text';
|
|
text.textContent = `${recipe.ingredients.length} ingredients`;
|
|
|
|
const buttonGroup = document.createElement('div');
|
|
buttonGroup.className = 'mt-auto';
|
|
buttonGroup.innerHTML = `
|
|
<button class="btn btn-sm btn-outline-primary edit-recipe-btn">Edit</button>
|
|
<button class="btn btn-sm btn-outline-danger delete-recipe-btn">Delete</button>
|
|
`;
|
|
|
|
cardBody.appendChild(title);
|
|
cardBody.appendChild(subtitle);
|
|
cardBody.appendChild(text);
|
|
cardBody.appendChild(buttonGroup);
|
|
card.appendChild(cardBody);
|
|
cardCol.appendChild(card);
|
|
recipeCardsContainer.appendChild(cardCol);
|
|
});
|
|
}
|
|
|
|
function renderShoppingList(list) {
|
|
let html = '<h3>Общий список покупок</h3><hr>';
|
|
if (list.length === 0) {
|
|
html += '<p>Нет ингредиентов для расчета.</p>';
|
|
} else {
|
|
html += '<ul class="list-group list-group-flush">';
|
|
list.forEach(item => {
|
|
const quantityStr = Number.isInteger(item.qty) ? item.qty : parseFloat(item.qty.toFixed(2));
|
|
html += `<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span>${item.name}</span>
|
|
<span class="badge bg-primary rounded-pill">${quantityStr} ${item.unit}</span>
|
|
</li>`;
|
|
});
|
|
html += '</ul>';
|
|
}
|
|
shoppingListContainer.innerHTML = html;
|
|
}
|
|
|
|
function getRecipeDataFromForm() {
|
|
const recipeName = recipeNameInput.value.trim();
|
|
const guests = parseInt(guestCountInput.value, 10) || 0;
|
|
|
|
const ingredients = [];
|
|
const rows = ingredientsContainer.querySelectorAll('.ingredient-row');
|
|
rows.forEach(row => {
|
|
const name = row.querySelector('input[placeholder="Ingredient Name"]').value.trim();
|
|
const qty = parseFloat(row.querySelector('input[placeholder="Qty"]').value);
|
|
const unit = row.querySelector('input[placeholder="Unit (e.g., grams, ml)"]').value.trim();
|
|
if (name && !isNaN(qty) && qty > 0) {
|
|
ingredients.push({ name, quantity: qty, unit });
|
|
}
|
|
});
|
|
|
|
if (recipeName && guests > 0 && ingredients.length > 0) {
|
|
return { name: recipeName, guests, ingredients };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// --- Event Listeners ---
|
|
|
|
addIngredientBtn.addEventListener('click', () => addIngredientRow());
|
|
|
|
ingredientsContainer.addEventListener('click', function(e) {
|
|
if (e.target.classList.contains('remove-ingredient')) {
|
|
e.target.closest('.ingredient-row').remove();
|
|
}
|
|
});
|
|
|
|
recipeCardsContainer.addEventListener('click', async function(e) {
|
|
const target = e.target;
|
|
const card = target.closest('.col-lg-4');
|
|
if (!card) return;
|
|
|
|
const recipeId = card.getAttribute('data-id');
|
|
|
|
if (target.classList.contains('delete-recipe-btn')) {
|
|
if (confirm('Are you sure you want to delete this recipe?')) {
|
|
try {
|
|
const response = await fetch('api/delete_recipe.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: recipeId })
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
card.remove();
|
|
} else {
|
|
alert('Failed to delete recipe: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
alert('Error: ' + error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (target.classList.contains('edit-recipe-btn')) {
|
|
// Find the recipe data from the currently rendered cards
|
|
const response = await fetch('api/get_recipes.php');
|
|
const data = await response.json();
|
|
if(!data.success) return;
|
|
const recipeToEdit = data.recipes.find(r => r.id == recipeId);
|
|
|
|
if (recipeToEdit) {
|
|
// Populate form
|
|
recipeNameInput.value = recipeToEdit.name;
|
|
guestCountInput.value = recipeToEdit.guests;
|
|
ingredientsContainer.innerHTML = '';
|
|
recipeToEdit.ingredients.forEach(ing => addIngredientRow(ing));
|
|
|
|
// Delete the old recipe from DB
|
|
try {
|
|
await fetch('api/delete_recipe.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: recipeId })
|
|
});
|
|
card.remove(); // Remove from UI immediately
|
|
} catch (error) {
|
|
alert('Error preparing for edit: ' + error.message);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
newRecipeBtn.addEventListener('click', async function() {
|
|
const recipeData = getRecipeDataFromForm();
|
|
if (recipeData) {
|
|
try {
|
|
const response = await fetch('api/save_recipe.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(recipeData)
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
await loadRecipes(); // Reload all recipes to show the new one
|
|
clearForm();
|
|
} else {
|
|
alert('Failed to save recipe: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
alert('Error: ' + error.message);
|
|
}
|
|
} else {
|
|
alert('Please fill out the recipe name, guests, and at least one ingredient before saving.');
|
|
}
|
|
});
|
|
|
|
calculateBtn.addEventListener('click', async function() {
|
|
try {
|
|
const response = await fetch('api/get_recipes.php');
|
|
const data = await response.json();
|
|
if (!data.success) {
|
|
alert('Could not get recipes for calculation.');
|
|
return;
|
|
}
|
|
|
|
const allRecipesToCalculate = data.recipes;
|
|
const currentRecipe = getRecipeDataFromForm();
|
|
|
|
if (currentRecipe) {
|
|
// Give it a temporary ID to avoid collisions
|
|
currentRecipe.id = 'current';
|
|
allRecipesToCalculate.push(currentRecipe);
|
|
}
|
|
|
|
if (allRecipesToCalculate.length === 0) {
|
|
alert('There are no recipes to calculate. Please fill out the form or save a recipe.');
|
|
return;
|
|
}
|
|
|
|
const combinedIngredients = new Map();
|
|
|
|
allRecipesToCalculate.forEach(recipe => {
|
|
const multiplier = recipe.guests;
|
|
recipe.ingredients.forEach(ing => {
|
|
const ingName = ing.name || '';
|
|
const ingUnit = ing.unit || '';
|
|
const key = `${ingName.trim().toLowerCase()}|${ingUnit.trim().toLowerCase()}`;
|
|
const existing = combinedIngredients.get(key);
|
|
|
|
if (existing) {
|
|
existing.qty += (ing.quantity || 0) * multiplier;
|
|
} else {
|
|
combinedIngredients.set(key, {
|
|
name: ing.name,
|
|
qty: (ing.quantity || 0) * multiplier,
|
|
unit: ing.unit
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
renderShoppingList(Array.from(combinedIngredients.values()));
|
|
|
|
} catch(error) {
|
|
alert('Calculation Error: ' + error.message);
|
|
}
|
|
});
|
|
|
|
// --- Initial State ---
|
|
addIngredientRow();
|
|
loadRecipes();
|
|
});
|