improv
This commit is contained in:
parent
66c4c1fe4f
commit
2c2e34b71e
42 changed files with 910 additions and 939 deletions
|
@ -13,785 +13,115 @@ document.addEventListener('mousemove', function (e) {
|
|||
}, 1000);
|
||||
});
|
||||
|
||||
// Dashboard, Order Management, Inventory Management, Settings, and Drink Type functionality
|
||||
// Function to initialize number input buttons
|
||||
function initNumberInputs(container = document) {
|
||||
container.querySelectorAll('.number-input-wrapper').forEach(function(wrapper) {
|
||||
const input = wrapper.querySelector('input[type="number"]');
|
||||
const decreaseBtn = wrapper.querySelector('[data-action="decrease"]');
|
||||
const increaseBtn = wrapper.querySelector('[data-action="increase"]');
|
||||
|
||||
if (!input || !decreaseBtn || !increaseBtn) return;
|
||||
|
||||
// Skip if already initialized
|
||||
if (decreaseBtn.hasAttribute('data-initialized')) return;
|
||||
|
||||
const step = parseFloat(input.getAttribute('step')) || 1;
|
||||
const min = 0;
|
||||
const max = input.getAttribute('max') ? parseFloat(input.getAttribute('max')) : null;
|
||||
|
||||
decreaseBtn.addEventListener('click', function() {
|
||||
const currentValue = parseFloat(input.value) || 0;
|
||||
const newValue = currentValue - step;
|
||||
|
||||
if (min === null || newValue >= min) {
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
|
||||
increaseBtn.addEventListener('click', function() {
|
||||
const currentValue = parseFloat(input.value) || 0;
|
||||
const newValue = currentValue + step;
|
||||
|
||||
if (max === null || newValue <= max) {
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
|
||||
// Validate input on change
|
||||
input.addEventListener('input', function() {
|
||||
const value = parseFloat(this.value);
|
||||
|
||||
if (min !== null && value < min) {
|
||||
this.value = min;
|
||||
}
|
||||
if (max !== null && value > max) {
|
||||
this.value = max;
|
||||
}
|
||||
});
|
||||
|
||||
// Mark as initialized
|
||||
decreaseBtn.setAttribute('data-initialized', 'true');
|
||||
increaseBtn.setAttribute('data-initialized', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Stock update form handling
|
||||
initStockUpdateForms();
|
||||
// Bootstrap Modal handling
|
||||
const htmxModal = document.getElementById('htmxModal');
|
||||
if (htmxModal) {
|
||||
htmxModal.addEventListener('show.bs.modal', function(event) {
|
||||
// Get the button that triggered the modal
|
||||
const button = event.relatedTarget;
|
||||
// Extract the drink name from data-* attributes
|
||||
const drinkName = button.getAttribute('data-drink-name');
|
||||
// Update the modal title
|
||||
if (drinkName) {
|
||||
const modalTitle = htmxModal.querySelector('.modal-title');
|
||||
if (modalTitle) {
|
||||
modalTitle.textContent = 'Update Stock for ' + drinkName;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Edit button handling
|
||||
initEditButtons();
|
||||
// HTMX Modal handling
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
// If the target is the modal body, initialize any form elements inside it
|
||||
if (event.detail.target.id === 'htmxModalBody') {
|
||||
// The modal content has been loaded
|
||||
console.log('Modal content loaded');
|
||||
// Initialize number inputs in the modal
|
||||
initNumberInputs(event.detail.target);
|
||||
}
|
||||
});
|
||||
|
||||
// Order form handling
|
||||
initOrderForms();
|
||||
// Handle form submissions in the modal
|
||||
document.body.addEventListener('htmx:beforeSend', function(event) {
|
||||
// If the event is from a form inside the modal
|
||||
if (event.detail.elt.closest('#htmxModalBody')) {
|
||||
// Add the HX-Request header to the request
|
||||
event.detail.requestConfig.headers['HX-Request'] = 'true';
|
||||
}
|
||||
});
|
||||
|
||||
// Order status form handling
|
||||
initOrderStatusForms();
|
||||
// Handle redirects from HTMX
|
||||
document.body.addEventListener('htmx:responseError', function(event) {
|
||||
if (event.detail.xhr.status === 303) {
|
||||
const redirectUrl = event.detail.xhr.getResponseHeader('HX-Redirect');
|
||||
if (redirectUrl) {
|
||||
// Close the modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('htmxModal'));
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
}
|
||||
// Redirect to the specified URL
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inventory form handling
|
||||
initInventoryForm();
|
||||
|
||||
// Settings form handling
|
||||
initSettingsForm();
|
||||
|
||||
// Drink Type form handling
|
||||
initDrinkTypeForm();
|
||||
// Initialize number inputs on page load
|
||||
initNumberInputs();
|
||||
});
|
||||
|
||||
/**
|
||||
* 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}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
`;
|
||||
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 = `
|
||||
<td>${drinkTypeName}</td>
|
||||
<td>
|
||||
<input type="hidden" name="items[${rowCount}][drinkTypeId]" value="${drinkTypeId}">
|
||||
<input type="number" name="items[${rowCount}][quantity]"
|
||||
class="form-control form-control-sm order-quantity"
|
||||
value="1" min="0">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-danger remove-order-item">Remove</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// 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 = '<h6>The following items were updated:</h6><ul>';
|
||||
data.updatedItems.forEach(item => {
|
||||
resultsHtml += `<li>${item.name}: ${item.oldQuantity} → ${item.newQuantity}</li>`;
|
||||
});
|
||||
resultsHtml += '</ul>';
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue