26
This commit is contained in:
parent
e97488f9dc
commit
4ea0efca37
@ -2,6 +2,17 @@
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
function get_ingredient_category($name) {
|
||||||
|
$name = strtolower($name);
|
||||||
|
$drink_keywords = ["water", "juice", "milk", "wine", "beer", "soda", "spirit", "vodka", "gin", "rum", "tequila", "whiskey", "liqueur", "coke", "pepsi", "tea", "coffee"];
|
||||||
|
foreach ($drink_keywords as $keyword) {
|
||||||
|
if (strpos($name, $keyword) !== false) {
|
||||||
|
return 'drink';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'food';
|
||||||
|
}
|
||||||
|
|
||||||
// The request is now multipart/form-data, so we use $_POST and $_FILES
|
// The request is now multipart/form-data, so we use $_POST and $_FILES
|
||||||
$data = $_POST;
|
$data = $_POST;
|
||||||
$files = $_FILES;
|
$files = $_FILES;
|
||||||
@ -68,9 +79,10 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert ingredients
|
// Insert ingredients
|
||||||
$stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit) VALUES (?, ?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit, category) VALUES (?, ?, ?, ?, ?)");
|
||||||
foreach ($ingredients as $ing) {
|
foreach ($ingredients as $ing) {
|
||||||
$stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit']]);
|
$ingredientCategory = get_ingredient_category($ing['name']);
|
||||||
|
$stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit'], $ingredientCategory]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|||||||
@ -328,26 +328,29 @@ footer.bg-light {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
#recipe-form-modal .modal-content, #add-product-modal .modal-content, #confirmRemoveModal .modal-content {
|
#recipe-form-modal .modal-content, #add-product-modal .modal-content, #confirmRemoveModal .modal-content, #view-recipe-modal .modal-content {
|
||||||
background-color: #142E35;
|
background-color: #142E35;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#recipe-form-modal .modal-header,
|
#recipe-form-modal .modal-header,
|
||||||
#add-product-modal .modal-header,
|
#add-product-modal .modal-header,
|
||||||
#confirmRemoveModal .modal-header {
|
#confirmRemoveModal .modal-header,
|
||||||
|
#view-recipe-modal .modal-header {
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#recipe-form-modal .modal-footer,
|
#recipe-form-modal .modal-footer,
|
||||||
#add-product-modal .modal-footer,
|
#add-product-modal .modal-footer,
|
||||||
#confirmRemoveModal .modal-footer {
|
#confirmRemoveModal .modal-footer,
|
||||||
|
#view-recipe-modal .modal-footer {
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#recipe-form-modal .btn-close,
|
#recipe-form-modal .btn-close,
|
||||||
#add-product-modal .btn-close,
|
#add-product-modal .btn-close,
|
||||||
#confirmRemoveModal .btn-close {
|
#confirmRemoveModal .btn-close,
|
||||||
|
#view-recipe-modal .btn-close {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,21 +378,16 @@ footer.bg-light {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Recipe Card Category Label */
|
/* Recipe Card Category Label */
|
||||||
.card {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-category-label {
|
.recipe-category-label {
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
background-color: #142E35;
|
background-color: #142E35;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
z-index: 1;
|
margin-left: 10px; /* Add some space between title and label */
|
||||||
|
white-space: nowrap; /* Prevent the label itself from wrapping */
|
||||||
|
align-self: flex-start; /* Align to the top of the flex container */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shopping List Quantity Controls */
|
/* Shopping List Quantity Controls */
|
||||||
@ -432,13 +430,7 @@ footer.bg-light {
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay category label */
|
|
||||||
.card .recipe-category-label {
|
|
||||||
top: 15px;
|
|
||||||
right: 15px;
|
|
||||||
background-color: rgba(20, 46, 53, 0.8);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Recipe Card Animation */
|
/* Recipe Card Animation */
|
||||||
.recipe-card-enter {
|
.recipe-card-enter {
|
||||||
@ -561,3 +553,21 @@ footer.bg-light {
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view-recipe-ingredients {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2); /* Darker shade */
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view-recipe-ingredients .list-group-item {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ const app = {
|
|||||||
state: {
|
state: {
|
||||||
recipes: [],
|
recipes: [],
|
||||||
confirmedRecipeProducts: [],
|
confirmedRecipeProducts: [],
|
||||||
|
checkedItems: [],
|
||||||
|
additionalProducts: []
|
||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
async getRecipes() {
|
async getRecipes() {
|
||||||
@ -77,23 +79,40 @@ const app = {
|
|||||||
card.appendChild(img);
|
card.appendChild(img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cardBody = document.createElement('div');
|
||||||
|
cardBody.className = 'card-body d-flex flex-column';
|
||||||
|
|
||||||
|
const titleWrapper = document.createElement('div');
|
||||||
|
titleWrapper.className = 'd-flex justify-content-between align-items-start mb-2 gap-2';
|
||||||
|
|
||||||
|
const title = document.createElement('h5');
|
||||||
|
title.className = 'card-title mb-0';
|
||||||
|
title.textContent = recipe.name;
|
||||||
|
titleWrapper.appendChild(title);
|
||||||
|
|
||||||
if (recipe.category) {
|
if (recipe.category) {
|
||||||
const categoryLabel = document.createElement('div');
|
const categoryLabel = document.createElement('div');
|
||||||
categoryLabel.className = 'recipe-category-label';
|
categoryLabel.className = 'recipe-category-label';
|
||||||
categoryLabel.textContent = recipe.category;
|
categoryLabel.textContent = recipe.category;
|
||||||
card.appendChild(categoryLabel);
|
titleWrapper.appendChild(categoryLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 text = document.createElement('p');
|
const text = document.createElement('p');
|
||||||
text.className = 'card-text text-muted';
|
text.className = 'card-text text-muted';
|
||||||
text.textContent = `${recipe.ingredients.length} ingredients`;
|
text.textContent = `Serves ${recipe.guests} | ${recipe.ingredients.length} ingredients`;
|
||||||
|
|
||||||
|
const guestPortionControls = document.createElement('div');
|
||||||
|
guestPortionControls.className = 'row g-2 mb-3';
|
||||||
|
guestPortionControls.innerHTML = `
|
||||||
|
<div class="col">
|
||||||
|
<label for="recipe-guests-${recipe.id}" class="form-label form-label-sm">Guests</label>
|
||||||
|
<input type="number" id="recipe-guests-${recipe.id}" class="form-control form-control-sm recipe-guests-input" value="${recipe.guests}" min="1" data-id="${recipe.id}">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="recipe-portions-${recipe.id}" class="form-label form-label-sm">Portions</label>
|
||||||
|
<input type="number" id="recipe-portions-${recipe.id}" class="form-control form-control-sm recipe-portions-input" value="1" min="1" data-id="${recipe.id}">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
const buttonGroup = document.createElement('div');
|
const buttonGroup = document.createElement('div');
|
||||||
buttonGroup.className = 'mt-auto pt-2';
|
buttonGroup.className = 'mt-auto pt-2';
|
||||||
@ -103,8 +122,9 @@ const app = {
|
|||||||
<button class="btn btn-danger btn-sm delete-recipe" title="Delete"><i class="bi bi-trash"></i></button>
|
<button class="btn btn-danger btn-sm delete-recipe" title="Delete"><i class="bi bi-trash"></i></button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
cardBody.appendChild(title);
|
cardBody.appendChild(titleWrapper);
|
||||||
cardBody.appendChild(text);
|
cardBody.appendChild(text);
|
||||||
|
cardBody.appendChild(guestPortionControls);
|
||||||
cardBody.appendChild(buttonGroup);
|
cardBody.appendChild(buttonGroup);
|
||||||
card.appendChild(cardBody);
|
card.appendChild(cardBody);
|
||||||
cardCol.appendChild(card);
|
cardCol.appendChild(card);
|
||||||
@ -112,14 +132,23 @@ const app = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateShoppingList() {
|
updateShoppingList() {
|
||||||
const guestCount = parseInt(app.dom.guestCountInput.value, 10) || 1;
|
|
||||||
const portionsPerGuest = parseInt(app.dom.portionsPerGuestInput.value, 10) || 1;
|
|
||||||
const totalMultiplier = guestCount * portionsPerGuest;
|
|
||||||
|
|
||||||
const combinedIngredients = new Map();
|
const combinedIngredients = new Map();
|
||||||
|
|
||||||
// 1. Process recipe ingredients
|
// 1. Process recipe ingredients and calculate total based on per-recipe inputs
|
||||||
app.state.recipes.forEach(recipe => {
|
app.state.recipes.forEach(recipe => {
|
||||||
|
const card = app.dom.recipeCardsContainer.querySelector(`[data-id="${recipe.id}"]`);
|
||||||
|
if (!card) return;
|
||||||
|
|
||||||
|
const guestsInput = card.querySelector('.recipe-guests-input');
|
||||||
|
const portionsInput = card.querySelector('.recipe-portions-input');
|
||||||
|
|
||||||
|
const targetGuests = guestsInput ? parseInt(guestsInput.value, 10) : recipe.guests;
|
||||||
|
const targetPortions = portionsInput ? parseInt(portionsInput.value, 10) : 1;
|
||||||
|
|
||||||
|
if (isNaN(targetGuests) || targetGuests <= 0) return;
|
||||||
|
|
||||||
|
const baseGuests = parseInt(recipe.guests, 10) || 1;
|
||||||
|
|
||||||
if (recipe.ingredients) {
|
if (recipe.ingredients) {
|
||||||
recipe.ingredients.forEach(ing => {
|
recipe.ingredients.forEach(ing => {
|
||||||
const name = ing.name.trim();
|
const name = ing.name.trim();
|
||||||
@ -134,11 +163,12 @@ const app = {
|
|||||||
recipeQty: 0,
|
recipeQty: 0,
|
||||||
additionalQty: 0,
|
additionalQty: 0,
|
||||||
sources: [],
|
sources: [],
|
||||||
category: null // For 'pack' items
|
category: ing.category
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const item = combinedIngredients.get(key);
|
const item = combinedIngredients.get(key);
|
||||||
item.recipeQty += (ing.quantity || 0);
|
item.recipeQty += (ing.quantity || 0) * targetGuests * targetPortions;
|
||||||
|
|
||||||
if (!item.sources.includes(recipe.name)) {
|
if (!item.sources.includes(recipe.name)) {
|
||||||
item.sources.push(recipe.name);
|
item.sources.push(recipe.name);
|
||||||
}
|
}
|
||||||
@ -161,7 +191,7 @@ const app = {
|
|||||||
recipeQty: 0,
|
recipeQty: 0,
|
||||||
additionalQty: 0,
|
additionalQty: 0,
|
||||||
sources: [],
|
sources: [],
|
||||||
category: null
|
category: prod.category
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const item = combinedIngredients.get(key);
|
const item = combinedIngredients.get(key);
|
||||||
@ -170,7 +200,7 @@ const app = {
|
|||||||
if (!item.sources.includes(source)) {
|
if (!item.sources.includes(source)) {
|
||||||
item.sources.push(source);
|
item.sources.push(source);
|
||||||
}
|
}
|
||||||
if (prod.unit === 'pack' && prod.category) {
|
if (prod.category && !item.category) {
|
||||||
item.category = prod.category;
|
item.category = prod.category;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -178,29 +208,24 @@ const app = {
|
|||||||
|
|
||||||
// 3. Group for display
|
// 3. Group for display
|
||||||
const groups = {
|
const groups = {
|
||||||
Food: { units: ['g', 'kg'], ingredients: [] },
|
'Food': { ingredients: [] },
|
||||||
Drinks: { units: ['ml', 'l'], ingredients: [] },
|
'Drinks': { ingredients: [] },
|
||||||
Count: { units: ['piece'], ingredients: [] },
|
'Cooking and serving': { ingredients: [] },
|
||||||
"Tableware and consumables": { units: [], ingredients: [] },
|
'Tableware and consumables': { ingredients: [] }
|
||||||
"Cooking and serving": { units: [], ingredients: [] },
|
|
||||||
Other: { units: [], ingredients: [] }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
combinedIngredients.forEach((item, key) => {
|
combinedIngredients.forEach((item, key) => {
|
||||||
let groupName = 'Other';
|
let groupName = 'Food'; // Default to Food
|
||||||
if (item.unit === 'pack' && item.category) {
|
|
||||||
|
if (item.category) {
|
||||||
|
const normalizedCategory = item.category.toLowerCase();
|
||||||
|
if (groups.hasOwnProperty(item.category)) {
|
||||||
groupName = item.category;
|
groupName = item.category;
|
||||||
} else {
|
} else if (normalizedCategory === 'drinks' || normalizedCategory === 'drink') {
|
||||||
for (const name in groups) {
|
groupName = 'Drinks';
|
||||||
if (groups[name].units.includes(item.unit)) {
|
|
||||||
groupName = name;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!groups[groupName]) { // handle dynamic categories from 'pack'
|
|
||||||
groups[groupName] = { units: [], ingredients: [] };
|
|
||||||
}
|
|
||||||
groups[groupName].ingredients.push(item);
|
groups[groupName].ingredients.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -215,17 +240,18 @@ const app = {
|
|||||||
html += `<h4 class="mt-3">${groupName}</h4>`;
|
html += `<h4 class="mt-3">${groupName}</h4>`;
|
||||||
html += '<ul class="list-group list-group-flush">';
|
html += '<ul class="list-group list-group-flush">';
|
||||||
group.ingredients.forEach((item, index) => {
|
group.ingredients.forEach((item, index) => {
|
||||||
const totalQty = (item.recipeQty * totalMultiplier) + item.additionalQty;
|
const totalQty = item.recipeQty + item.additionalQty;
|
||||||
if (totalQty === 0) return;
|
if (totalQty <= 0) return;
|
||||||
|
|
||||||
const quantityStr = Number.isInteger(totalQty) ? totalQty : parseFloat(totalQty.toFixed(2));
|
const quantityStr = Number.isInteger(totalQty) ? totalQty : parseFloat(totalQty.toFixed(2));
|
||||||
const uniqueId = `shopping-item-${groupName.replace(/\s/g, '-')}-${index}`;
|
const uniqueId = `shopping-item-${groupName.replace(/\s/g, '-')}-${index}`;
|
||||||
const tooltipText = item.sources.join(', ');
|
const tooltipText = item.sources.join(', ');
|
||||||
const itemKey = `${item.name.toLowerCase()}|${item.unit.toLowerCase()}`;
|
const itemKey = `${item.name.toLowerCase()}|${item.unit.toLowerCase()}`;
|
||||||
|
const isChecked = app.state.checkedItems.includes(itemKey);
|
||||||
|
|
||||||
html += `<li class="list-group-item d-flex justify-content-between align-items-center">
|
html += `<li class="list-group-item d-flex justify-content-between align-items-center ${isChecked ? 'checked' : ''}">
|
||||||
<div class="form-check d-flex align-items-center">
|
<div class="form-check d-flex align-items-center">
|
||||||
<input class="form-check-input" type="checkbox" id="${uniqueId}">
|
<input class="form-check-input" type="checkbox" id="${uniqueId}" data-key="${itemKey}" ${isChecked ? 'checked' : ''}>
|
||||||
<label class="form-check-label ms-2" for="${uniqueId}">
|
<label class="form-check-label ms-2" for="${uniqueId}">
|
||||||
${item.name}
|
${item.name}
|
||||||
</label>
|
</label>
|
||||||
@ -391,6 +417,15 @@ const app = {
|
|||||||
snowflake.style.opacity = Math.random() * 0.7 + 0.3;
|
snowflake.style.opacity = Math.random() * 0.7 + 0.3;
|
||||||
snowContainer.appendChild(snowflake);
|
snowContainer.appendChild(snowflake);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
loadCheckedItems() {
|
||||||
|
const checkedItems = localStorage.getItem('checkedItems');
|
||||||
|
if (checkedItems) {
|
||||||
|
app.state.checkedItems = JSON.parse(checkedItems);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveCheckedItems() {
|
||||||
|
localStorage.setItem('checkedItems', JSON.stringify(app.state.checkedItems));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
@ -457,32 +492,50 @@ const app = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.dom.recipeCardsContainer.addEventListener('click', function(e) {
|
app.dom.recipeCardsContainer.addEventListener('click', function(e) {
|
||||||
const target = e.target;
|
const target = e.target.closest('button');
|
||||||
const card = target.closest('.col-12[data-id]');
|
const card = e.target.closest('.col-12[data-id]');
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
|
||||||
const recipeId = card.getAttribute('data-id');
|
const recipeId = card.getAttribute('data-id');
|
||||||
|
|
||||||
if (target.classList.contains('delete-recipe')) {
|
if (target && target.classList.contains('delete-recipe')) {
|
||||||
if (confirm('Are you sure you want to delete this recipe?')) {
|
if (confirm('Are you sure you want to delete this recipe?')) {
|
||||||
app.api.deleteRecipe(recipeId);
|
app.api.deleteRecipe(recipeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.classList.contains('edit-recipe')) {
|
if (target && target.classList.contains('edit-recipe')) {
|
||||||
app.ui.populateFormForEdit(recipeId);
|
app.ui.populateFormForEdit(recipeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.classList.contains('view-recipe')) {
|
if (target && target.classList.contains('view-recipe')) {
|
||||||
app.ui.populateViewModal(recipeId);
|
app.ui.populateViewModal(recipeId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.dom.recipeCardsContainer.addEventListener('input', function(e) {
|
||||||
|
if (e.target.classList.contains('recipe-guests-input') || e.target.classList.contains('recipe-portions-input')) {
|
||||||
|
app.ui.updateShoppingList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.dom.shoppingListContainer.addEventListener('click', function(e) {
|
app.dom.shoppingListContainer.addEventListener('click', function(e) {
|
||||||
if (e.target.matches('.form-check-input')) {
|
if (e.target.matches('.form-check-input')) {
|
||||||
const listItem = e.target.closest('.list-group-item');
|
const listItem = e.target.closest('.list-group-item');
|
||||||
if (listItem) {
|
if (listItem) {
|
||||||
listItem.classList.toggle('checked', e.target.checked);
|
listItem.classList.toggle('checked', e.target.checked);
|
||||||
|
const itemKey = e.target.dataset.key;
|
||||||
|
if (e.target.checked) {
|
||||||
|
if (!app.state.checkedItems.includes(itemKey)) {
|
||||||
|
app.state.checkedItems.push(itemKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = app.state.checkedItems.indexOf(itemKey);
|
||||||
|
if (index > -1) {
|
||||||
|
app.state.checkedItems.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.ui.saveCheckedItems();
|
||||||
}
|
}
|
||||||
} else if (e.target.matches('.increment-item')) {
|
} else if (e.target.matches('.increment-item')) {
|
||||||
const key = e.target.dataset.key;
|
const key = e.target.dataset.key;
|
||||||
@ -569,8 +622,7 @@ const app = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.dom.guestCountInput.addEventListener('input', app.ui.updateShoppingList);
|
|
||||||
app.dom.portionsPerGuestInput.addEventListener('input', app.ui.updateShoppingList);
|
|
||||||
|
|
||||||
app.dom.cancelEditBtn.addEventListener('click', function() {
|
app.dom.cancelEditBtn.addEventListener('click', function() {
|
||||||
app.ui.clearForm();
|
app.ui.clearForm();
|
||||||
@ -624,12 +676,6 @@ const app = {
|
|||||||
});
|
});
|
||||||
e.target.classList.remove('btn-outline-secondary');
|
e.target.classList.remove('btn-outline-secondary');
|
||||||
e.target.classList.add('btn-secondary');
|
e.target.classList.add('btn-secondary');
|
||||||
|
|
||||||
if (e.target.textContent.trim() === 'pack') {
|
|
||||||
app.dom.productCategoryWrapper.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
app.dom.productCategoryWrapper.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -647,11 +693,6 @@ const app = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit === 'pack' && category === 'Choose...') {
|
|
||||||
alert('Please select a category for products in packs.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.state.additionalProducts) {
|
if (!app.state.additionalProducts) {
|
||||||
app.state.additionalProducts = [];
|
app.state.additionalProducts = [];
|
||||||
}
|
}
|
||||||
@ -662,10 +703,7 @@ const app = {
|
|||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
existingProduct.quantity += quantity;
|
existingProduct.quantity += quantity;
|
||||||
} else {
|
} else {
|
||||||
const newProduct = { name, quantity, unit, source: 'Additional product' };
|
const newProduct = { name, quantity, unit, source: 'Additional product', category: category };
|
||||||
if (unit === 'pack') {
|
|
||||||
newProduct.category = category;
|
|
||||||
}
|
|
||||||
app.state.additionalProducts.push(newProduct);
|
app.state.additionalProducts.push(newProduct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,8 +712,7 @@ const app = {
|
|||||||
// Reset form
|
// Reset form
|
||||||
app.dom.productNameInput.value = '';
|
app.dom.productNameInput.value = '';
|
||||||
app.dom.productQuantityInput.value = '1';
|
app.dom.productQuantityInput.value = '1';
|
||||||
app.dom.productCategory.value = 'Choose...';
|
app.dom.productCategory.value = 'Food';
|
||||||
app.dom.productCategoryWrapper.style.display = 'none';
|
|
||||||
|
|
||||||
const unitButtons = app.dom.addProductModal._element.querySelectorAll('.unit-selector .unit-btn');
|
const unitButtons = app.dom.addProductModal._element.querySelectorAll('.unit-selector .unit-btn');
|
||||||
unitButtons.forEach((btn, index) => {
|
unitButtons.forEach((btn, index) => {
|
||||||
@ -691,7 +728,15 @@ const app = {
|
|||||||
app.dom.addProductModal.hide();
|
app.dom.addProductModal.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.dom.musicToggle.addEventListener('click', function() {
|
||||||
|
if (app.dom.christmasMusic.paused) {
|
||||||
|
app.dom.christmasMusic.play();
|
||||||
|
app.dom.musicToggle.innerHTML = '<i class="bi bi-pause-fill" style="font-size: 1.5rem;"></i>';
|
||||||
|
} else {
|
||||||
|
app.dom.christmasMusic.pause();
|
||||||
|
app.dom.musicToggle.innerHTML = '<i class="bi bi-play-fill" style="font-size: 1.5rem;"></i>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('recipe-form-modal').addEventListener('show.bs.modal', function () {
|
document.getElementById('recipe-form-modal').addEventListener('show.bs.modal', function () {
|
||||||
if (!app.dom.recipeIdInput.value) {
|
if (!app.dom.recipeIdInput.value) {
|
||||||
@ -726,10 +771,13 @@ const app = {
|
|||||||
productQuantityInput: document.getElementById('productQuantity'),
|
productQuantityInput: document.getElementById('productQuantity'),
|
||||||
productCategoryWrapper: document.getElementById('product-category-wrapper'),
|
productCategoryWrapper: document.getElementById('product-category-wrapper'),
|
||||||
productCategory: document.getElementById('productCategory'),
|
productCategory: document.getElementById('productCategory'),
|
||||||
|
christmasMusic: document.getElementById('christmas-music'),
|
||||||
|
musicToggle: document.getElementById('music-toggle'),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app.ui.createSnowflakes();
|
app.ui.createSnowflakes();
|
||||||
|
app.ui.loadCheckedItems();
|
||||||
app.events.attachEventListeners();
|
app.events.attachEventListeners();
|
||||||
app.dom.cancelEditBtn.style.display = 'none';
|
app.dom.cancelEditBtn.style.display = 'none';
|
||||||
app.ui.addIngredientRow();
|
app.ui.addIngredientRow();
|
||||||
|
|||||||
1
db/migrations/004_add_category_to_ingredients.sql
Normal file
1
db/migrations/004_add_category_to_ingredients.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `ingredients` ADD `category` VARCHAR(50) NOT NULL DEFAULT 'food';
|
||||||
15
index.php
15
index.php
@ -45,6 +45,7 @@
|
|||||||
<div class="text-center mb-5" style="padding-top: 20px;">
|
<div class="text-center mb-5" style="padding-top: 20px;">
|
||||||
<h1 class="display-4 mt-4">Hey, it's Christmas time!</h1>
|
<h1 class="display-4 mt-4">Hey, it's Christmas time!</h1>
|
||||||
<p class="lead">Let's get your holiday recipes sorted.</p>
|
<p class="lead">Let's get your holiday recipes sorted.</p>
|
||||||
|
<div id="christmas-countdown" class="lead"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
@ -197,12 +198,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" id="product-category-wrapper" style="display: none;">
|
<div class="mb-3" id="product-category-wrapper">
|
||||||
<label for="productCategory" class="form-label">Category</label>
|
<label for="productCategory" class="form-label">Category</label>
|
||||||
<select class="form-select" id="productCategory">
|
<select class="form-select" id="productCategory">
|
||||||
<option selected>Choose...</option>
|
<option value="Food" selected>Food</option>
|
||||||
<option value="Tableware and consumables">Tableware and consumables</option>
|
<option value="Drinks">Drinks</option>
|
||||||
<option value="Cooking and serving">Cooking and serving</option>
|
<option value="Cooking and serving">Cooking and serving</option>
|
||||||
|
<option value="Tableware and consumables">Tableware and consumables</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid gap-2 mt-4">
|
<div class="d-grid gap-2 mt-4">
|
||||||
@ -243,6 +245,13 @@
|
|||||||
<p class="mb-0">© <?php echo date("Y"); ?> Christmas Recipe Calculator. Happy Holidays!</p>
|
<p class="mb-0">© <?php echo date("Y"); ?> Christmas Recipe Calculator. Happy Holidays!</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<audio id="christmas-music" loop>
|
||||||
|
<source src="https://www.fesliyanstudios.com/download-link.php?i=230" type="audio/mpeg">
|
||||||
|
</audio>
|
||||||
|
<button id="music-toggle" class="btn btn-light" style="position: fixed; bottom: 20px; right: 20px; border-radius: 50%; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<i class="bi bi-play-fill" style="font-size: 1.5rem;"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="assets/js/main.js?v=1700253313"></script>
|
<script src="assets/js/main.js?v=1700253313"></script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user