// Sparkle effect on mouse move document.addEventListener('mousemove', function (e) { const emojis = ['✨', '💖', '🌟', '💅', '🦄', '🎉', '🌈']; const sparkle = document.createElement('div'); sparkle.className = 'emoji-footprint'; sparkle.textContent = emojis[Math.floor(Math.random() * emojis.length)]; sparkle.style.left = e.pageX + 'px'; sparkle.style.top = e.pageY + 'px'; document.body.appendChild(sparkle); setTimeout(() => { sparkle.remove(); }, 1000); }); // Dashboard, Order Management, Inventory Management, Settings, and Drink Type functionality document.addEventListener('DOMContentLoaded', function() { // Stock update form handling initStockUpdateForms(); // Edit button handling initEditButtons(); // Order form handling initOrderForms(); // Order status form handling initOrderStatusForms(); // Inventory form handling initInventoryForm(); // Settings form handling initSettingsForm(); // Drink Type form handling initDrinkTypeForm(); }); /** * Initialize stock update forms with AJAX submission and validation */ function initStockUpdateForms() { // Get all stock update forms const stockForms = document.querySelectorAll('.stock-update-form'); // Add event listener to each form stockForms.forEach(form => { // Add validation to quantity input const quantityInput = form.querySelector('input[name="quantity"]'); if (quantityInput) { quantityInput.addEventListener('input', function() { validateQuantityInput(this); }); } // Add submit handler form.addEventListener('submit', function(e) { e.preventDefault(); // Validate form before submission if (!validateStockForm(form)) { return; } // Get form data const formData = new FormData(form); // Show loading state const submitButton = form.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; submitButton.textContent = 'Updating...'; submitButton.disabled = true; // Send AJAX request fetch(form.action, { method: 'POST', body: formData }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; if (data.success) { // Show success message showAlert(form.parentNode, 'success', 'Stock updated successfully.'); } else { // Show error message showAlert(form.parentNode, 'danger', `Error updating stock: ${data.message}`); } }) .catch(error => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; // Show error message showAlert(form.parentNode, 'danger', `Error: ${error.message}`); console.error('Error:', error); }); }); }); } /** * Initialize edit buttons */ function initEditButtons() { const editButtons = document.querySelectorAll('.btn-edit-drink-type'); editButtons.forEach(button => { button.addEventListener('click', function() { const drinkTypeId = this.dataset.drinkTypeId; window.location.href = `/drink-types/edit/${drinkTypeId}`; }); }); } /** * Validate a stock update form * @param {HTMLFormElement} form - The form to validate * @returns {boolean} - Whether the form is valid */ function validateStockForm(form) { const quantityInput = form.querySelector('input[name="quantity"]'); return validateQuantityInput(quantityInput); } /** * Validate a quantity input * @param {HTMLInputElement} input - The input to validate * @returns {boolean} - Whether the input is valid */ function validateQuantityInput(input) { const value = parseInt(input.value); // Check if value is a number if (isNaN(value)) { input.classList.add('is-invalid'); showInputError(input, 'Please enter a valid number'); return false; } // Check if value is non-negative if (value < 0) { input.classList.add('is-invalid'); showInputError(input, 'Quantity cannot be negative'); return false; } // Input is valid input.classList.remove('is-invalid'); input.classList.add('is-valid'); return true; } /** * Show an error message for an input * @param {HTMLInputElement} input - The input with the error * @param {string} message - The error message */ function showInputError(input, message) { // Remove any existing error message const existingError = input.parentNode.querySelector('.invalid-feedback'); if (existingError) { existingError.remove(); } // Create and add new error message const errorDiv = document.createElement('div'); errorDiv.className = 'invalid-feedback'; errorDiv.textContent = message; input.parentNode.appendChild(errorDiv); } /** * Show an alert message * @param {HTMLElement} container - The container to add the alert to * @param {string} type - The type of alert (success, danger, warning, info) * @param {string} message - The alert message */ function showAlert(container, type, message) { // Remove any existing alerts const existingAlerts = container.querySelectorAll('.alert'); existingAlerts.forEach(alert => alert.remove()); // Create and add new alert const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible fade show mt-3`; alert.innerHTML = ` ${message} `; container.appendChild(alert); // Remove alert after 3 seconds setTimeout(() => { alert.remove(); }, 3000); } /** * Initialize order forms with validation and dynamic item handling */ function initOrderForms() { const orderForm = document.getElementById('orderForm'); if (!orderForm) return; // Validate all quantity inputs const quantityInputs = orderForm.querySelectorAll('input[type="number"]'); quantityInputs.forEach(input => { input.addEventListener('input', function() { validateQuantityInput(this); }); }); // Add form submission handler orderForm.addEventListener('submit', function(e) { // Validate all inputs before submission let isValid = true; quantityInputs.forEach(input => { if (!validateQuantityInput(input)) { isValid = false; } }); if (!isValid) { e.preventDefault(); showAlert(orderForm, 'danger', 'Please correct the errors in the form before submitting.'); } }); // Add "Add Item" button functionality if it exists const addItemButton = document.getElementById('addOrderItem'); if (addItemButton) { addItemButton.addEventListener('click', function() { addOrderItem(); }); } // Add "Remove Item" button functionality const removeButtons = orderForm.querySelectorAll('.remove-order-item'); removeButtons.forEach(button => { button.addEventListener('click', function() { removeOrderItem(this); }); }); } /** * Add a new order item row to the order form */ function addOrderItem() { const orderItemsTable = document.querySelector('#orderForm table tbody'); const drinkTypeSelect = document.getElementById('drinkTypeSelect'); if (!orderItemsTable || !drinkTypeSelect) return; // Get the selected drink type const selectedOption = drinkTypeSelect.options[drinkTypeSelect.selectedIndex]; const drinkTypeId = selectedOption.value; const drinkTypeName = selectedOption.text; // Check if this drink type is already in the table const existingRow = orderItemsTable.querySelector(`input[value="${drinkTypeId}"]`); if (existingRow) { showAlert(orderItemsTable.parentNode, 'warning', `${drinkTypeName} is already in the order.`); return; } // Create a new row const newRow = document.createElement('tr'); const rowCount = orderItemsTable.querySelectorAll('tr').length; newRow.innerHTML = ` ${drinkTypeName} `; // Add event listeners to the new row const quantityInput = newRow.querySelector('input[type="number"]'); quantityInput.addEventListener('input', function() { validateQuantityInput(this); }); const removeButton = newRow.querySelector('.remove-order-item'); removeButton.addEventListener('click', function() { removeOrderItem(this); }); // Add the row to the table orderItemsTable.appendChild(newRow); } /** * Remove an order item row from the order form * @param {HTMLElement} button - The remove button that was clicked */ function removeOrderItem(button) { const row = button.closest('tr'); if (row) { row.remove(); } } /** * Initialize order status forms with AJAX submission */ function initOrderStatusForms() { const statusForm = document.querySelector('form[action^="/orders/update-status/"]'); if (!statusForm) return; statusForm.addEventListener('submit', function(e) { e.preventDefault(); // Get form data const formData = new FormData(statusForm); // Show loading state const submitButton = statusForm.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; submitButton.textContent = 'Updating...'; submitButton.disabled = true; // Send AJAX request fetch(statusForm.action, { method: 'POST', body: formData }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; if (data.success) { // Show success message showAlert(statusForm.parentNode, 'success', 'Order status updated successfully.'); // Update the status badge const statusBadge = document.querySelector('.badge'); if (statusBadge) { // Remove old status classes statusBadge.classList.remove('bg-primary', 'bg-warning', 'bg-info', 'bg-success'); // Add new status class const newStatus = formData.get('status'); if (newStatus === 'new') { statusBadge.classList.add('bg-primary'); } else if (newStatus === 'in_work') { statusBadge.classList.add('bg-warning'); } else if (newStatus === 'ordered') { statusBadge.classList.add('bg-info'); } else if (newStatus === 'fulfilled') { statusBadge.classList.add('bg-success'); } // Update the text statusBadge.textContent = newStatus.charAt(0).toUpperCase() + newStatus.slice(1); } } else { // Show error message showAlert(statusForm.parentNode, 'danger', `Error updating status: ${data.message}`); } }) .catch(error => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; // Show error message showAlert(statusForm.parentNode, 'danger', `Error: ${error.message}`); console.error('Error:', error); }); }); } /** * Initialize inventory form with validation, highlighting for changed values, and AJAX submission */ function initInventoryForm() { const inventoryForm = document.getElementById('inventoryForm'); if (!inventoryForm) return; // Get all quantity inputs const quantityInputs = inventoryForm.querySelectorAll('.inventory-quantity'); // Add event listener to each input for validation and highlighting quantityInputs.forEach(input => { // Store original value for comparison const originalValue = parseInt(input.dataset.originalValue); // Add input event listener for validation input.addEventListener('input', function() { // Validate input validateQuantityInput(this); // Highlight changed values const currentValue = parseInt(this.value); if (currentValue !== originalValue) { this.classList.add('bg-warning', 'text-dark'); } else { this.classList.remove('bg-warning', 'text-dark'); } }); }); // Add submit handler inventoryForm.addEventListener('submit', function(e) { e.preventDefault(); // Validate all inputs before submission let isValid = true; quantityInputs.forEach(input => { if (!validateQuantityInput(input)) { isValid = false; } }); if (!isValid) { showAlert(inventoryForm, 'danger', 'Please correct the errors in the form before submitting.'); return; } // Check if any values have changed let hasChanges = false; quantityInputs.forEach(input => { const originalValue = parseInt(input.dataset.originalValue); const currentValue = parseInt(input.value); if (currentValue !== originalValue) { hasChanges = true; } }); if (!hasChanges) { showAlert(inventoryForm, 'warning', 'No changes detected. Nothing to update.'); return; } // Get form data const formData = new FormData(inventoryForm); // Add X-Requested-With header to indicate AJAX request const headers = new Headers({ 'X-Requested-With': 'XMLHttpRequest' }); // Show loading state const submitButton = inventoryForm.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; submitButton.textContent = 'Updating...'; submitButton.disabled = true; // Send AJAX request fetch(inventoryForm.action, { method: 'POST', headers: headers, body: formData }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; if (data.success) { // Show success message showAlert(inventoryForm, 'success', 'Inventory updated successfully.'); // Update original values and remove highlighting quantityInputs.forEach(input => { const drinkTypeId = input.previousElementSibling.value; const updatedItem = data.updatedItems.find(item => item.drinkTypeId == drinkTypeId); if (updatedItem) { input.dataset.originalValue = updatedItem.newQuantity; input.classList.remove('bg-warning', 'text-dark'); } }); // Show update results const updateResults = document.getElementById('updateResults'); const updateResultsContent = document.getElementById('updateResultsContent'); if (updateResults && updateResultsContent) { updateResults.classList.remove('d-none'); let resultsHtml = '
The following items were updated:
'; updateResultsContent.innerHTML = resultsHtml; } } else { // Show error message let errorMessage = 'Error updating inventory.'; if (data.errors && data.errors.length > 0) { errorMessage += ' ' + data.errors.join(' '); } showAlert(inventoryForm, 'danger', errorMessage); } }) .catch(error => { // Reset button state submitButton.textContent = originalText; submitButton.disabled = false; // Show error message showAlert(inventoryForm, 'danger', `Error: ${error.message}`); console.error('Error:', error); }); }); } /** * Initialize settings form with validation and AJAX submission */ function initSettingsForm() { const settingsForm = document.getElementById('settings-form'); if (!settingsForm) return; // Get all inputs const textInputs = settingsForm.querySelectorAll('input[type="text"]'); const numberInputs = settingsForm.querySelectorAll('input[type="number"]'); // Add event listeners for validation textInputs.forEach(input => { input.addEventListener('input', function() { validateSettingTextInput(this); }); }); numberInputs.forEach(input => { input.addEventListener('input', function() { validateSettingNumberInput(this); }); }); // Add submit handler settingsForm.addEventListener('submit', function(e) { // Validate all inputs before submission let isValid = true; textInputs.forEach(input => { if (!validateSettingTextInput(input)) { isValid = false; } }); numberInputs.forEach(input => { if (!validateSettingNumberInput(input)) { isValid = false; } }); if (!isValid) { e.preventDefault(); showAlert(settingsForm, 'danger', 'Please correct the errors in the form before submitting.'); } }); // Reset to defaults button const resetButton = document.getElementById('reset-defaults'); if (resetButton) { resetButton.addEventListener('click', function() { if (confirm('Are you sure you want to reset all settings to their default values?')) { window.location.href = '/settings/reset'; } }); } } /** * Validate a text input for settings * @param {HTMLInputElement} input - The input to validate * @returns {boolean} - Whether the input is valid */ function validateSettingTextInput(input) { // Check if value is empty if (input.value.trim() === '') { input.classList.add('is-invalid'); showInputError(input, 'This field cannot be empty'); return false; } // Input is valid input.classList.remove('is-invalid'); input.classList.add('is-valid'); return true; } /** * Validate a number input for settings * @param {HTMLInputElement} input - The input to validate * @returns {boolean} - Whether the input is valid */ function validateSettingNumberInput(input) { const value = parseInt(input.value); // Check if value is a number if (isNaN(value)) { input.classList.add('is-invalid'); showInputError(input, 'Please enter a valid number'); return false; } // Check if value is positive if (value < 1) { input.classList.add('is-invalid'); showInputError(input, 'Please enter a positive number'); return false; } // Input is valid input.classList.remove('is-invalid'); input.classList.add('is-valid'); return true; } /** * Initialize drink type form with validation and AJAX name uniqueness check */ function initDrinkTypeForm() { const drinkTypeForm = document.getElementById('drink-type-form'); if (!drinkTypeForm) return; // Get form inputs const nameInput = drinkTypeForm.querySelector('input[name="name"]'); const descriptionInput = drinkTypeForm.querySelector('textarea[name="description"]'); const desiredStockInput = drinkTypeForm.querySelector('input[name="desired_stock"]'); // Store original name for edit mode const originalName = nameInput ? nameInput.value : ''; // Add validation for name input if (nameInput) { nameInput.addEventListener('input', function() { validateDrinkTypeName(this, originalName); }); // Check name uniqueness on blur nameInput.addEventListener('blur', function() { checkDrinkTypeNameUniqueness(this, originalName); }); } // Add validation for desired stock input if (desiredStockInput) { desiredStockInput.addEventListener('input', function() { validateDesiredStock(this); }); } // Add form submission handler drinkTypeForm.addEventListener('submit', function(e) { let isValid = true; // Validate name if (nameInput && !validateDrinkTypeName(nameInput, originalName)) { isValid = false; } // Validate desired stock if (desiredStockInput && !validateDesiredStock(desiredStockInput)) { isValid = false; } if (!isValid) { e.preventDefault(); showAlert(drinkTypeForm, 'danger', 'Please correct the errors in the form before submitting.'); } }); } /** * Validate a drink type name * @param {HTMLInputElement} input - The input to validate * @param {string} originalName - The original name (for edit mode) * @returns {boolean} - Whether the input is valid */ function validateDrinkTypeName(input, originalName) { const value = input.value.trim(); // Check if value is empty if (value === '') { input.classList.add('is-invalid'); showInputError(input, 'Name is required'); return false; } // Check if value is too long if (value.length > 255) { input.classList.add('is-invalid'); showInputError(input, 'Name cannot be longer than 255 characters'); return false; } // Input is valid input.classList.remove('is-invalid'); input.classList.add('is-valid'); return true; } /** * Check if a drink type name is unique * @param {HTMLInputElement} input - The input to check * @param {string} originalName - The original name (for edit mode) */ function checkDrinkTypeNameUniqueness(input, originalName) { const value = input.value.trim(); // Skip check if name hasn't changed or is empty if (value === '' || value === originalName) { return; } // Show loading state input.classList.add('is-loading'); // Check if name already exists fetch(`/api/drink-types/check-name?name=${encodeURIComponent(value)}`) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Remove loading state input.classList.remove('is-loading'); if (data.exists) { input.classList.add('is-invalid'); showInputError(input, 'A drink type with this name already exists'); } }) .catch(error => { // Remove loading state input.classList.remove('is-loading'); console.error('Error checking name uniqueness:', error); }); } /** * Validate a desired stock input * @param {HTMLInputElement} input - The input to validate * @returns {boolean} - Whether the input is valid */ function validateDesiredStock(input) { const value = parseInt(input.value); // Check if value is a number if (isNaN(value)) { input.classList.add('is-invalid'); showInputError(input, 'Desired stock must be a number'); return false; } // Check if value is non-negative if (value < 0) { input.classList.add('is-invalid'); showInputError(input, 'Desired stock cannot be negative'); return false; } // Input is valid input.classList.remove('is-invalid'); input.classList.add('is-valid'); return true; }