Added persistent cart for chechout
This commit is contained in:
@@ -342,16 +342,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="number" id="weight-input" class="form-control form-control-lg" step="1"
|
<input type="number" id="weight-input" class="form-control form-control-lg" step="1"
|
||||||
placeholder="Ej: 400">
|
placeholder="Ej: 500" onkeydown="if(event.key === 'Enter') confirmWeight()">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer d-flex">
|
||||||
<button class="btn btn-primary w-100" onclick="confirmWeight()">Agregar</button>
|
<button class="btn btn-secondary flex-grow-1" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button class="btn btn-accent flex-grow-1" onclick="confirmWeight()">Agregar</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
let editingCartIndex = null;
|
||||||
|
let itemIndexToRemove = null;
|
||||||
const socket = io();
|
const socket = io();
|
||||||
let cart = [];
|
let cart = [];
|
||||||
let pendingProduct = null;
|
let pendingProduct = null;
|
||||||
@@ -386,41 +389,47 @@
|
|||||||
cart.forEach((item, index) => {
|
cart.forEach((item, index) => {
|
||||||
total += item.subtotal;
|
total += item.subtotal;
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
let qtyControls;
|
||||||
|
if (item.unit === 'kg') {
|
||||||
|
qtyControls = `
|
||||||
|
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="editWeight(${index})">
|
||||||
|
${item.qty.toFixed(3)} kg <i class="bi bi-pencil ms-1" style="font-size: 0.7rem;"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
qtyControls = `
|
||||||
|
<div class="d-flex align-items-center gap-1">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, -1)">-</button>
|
||||||
|
<input type="number" class="form-control form-control-sm text-center p-0"
|
||||||
|
style="width: 50px;" value="${item.qty}"
|
||||||
|
onchange="manualQty(${index}, this.value)">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, 1)">+</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${item.name}</td>
|
<td class="font-monospace small text-muted">${item.barcode}</td>
|
||||||
<td>${clp.format(item.price)}</td>
|
<td>${item.name}</td>
|
||||||
<td>${item.qty} ${item.unit === 'kg' ? 'kg' : 'u'}</td>
|
<td>${clp.format(item.price)}</td>
|
||||||
<td>${clp.format(item.subtotal)}</td>
|
<td>${qtyControls}</td>
|
||||||
<td>
|
<td>${clp.format(item.subtotal)}</td>
|
||||||
<button class="btn-danger-discord btn-sm" onclick="removeItem(${index}, '${item.name}')">
|
<td>
|
||||||
<i class="bi bi-trash"></i>
|
<button class="btn btn-danger-discord btn-sm" onclick="removeItem(${index})">
|
||||||
</button>
|
<i class="bi bi-trash"></i>
|
||||||
</td>
|
</button>
|
||||||
`;
|
</td>
|
||||||
|
`;
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('grand-total').innerText = clp.format(total);
|
document.getElementById('grand-total').innerText = clp.format(total);
|
||||||
|
|
||||||
|
// This will actually run now
|
||||||
|
saveCart();
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemIndexToRemove = null;
|
|
||||||
|
|
||||||
function removeItem(idx, name) {
|
|
||||||
itemIndexToRemove = idx;
|
|
||||||
document.getElementById('removeItemName').innerText = name;
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('removeConfirmModal'));
|
|
||||||
modal.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach listener to the confirm button in the modal
|
|
||||||
document.getElementById('btn-confirm-remove').addEventListener('click', () => {
|
|
||||||
if (itemIndexToRemove !== null) {
|
|
||||||
cart.splice(itemIndexToRemove, 1);
|
|
||||||
renderCart();
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('removeConfirmModal')).hide();
|
|
||||||
itemIndexToRemove = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function clearCart() {
|
function clearCart() {
|
||||||
if (cart.length === 0) return;
|
if (cart.length === 0) return;
|
||||||
const modal = new bootstrap.Modal(document.getElementById('clearCartModal'));
|
const modal = new bootstrap.Modal(document.getElementById('clearCartModal'));
|
||||||
@@ -468,20 +477,26 @@
|
|||||||
btn.innerHTML = '<i class="bi bi-cash-coin"></i> COBRAR';
|
btn.innerHTML = '<i class="bi bi-cash-coin"></i> COBRAR';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmWeight() {
|
function confirmWeight() {
|
||||||
const weightInput = document.getElementById('weight-input');
|
const weightInput = document.getElementById('weight-input');
|
||||||
const weightGrams = parseInt(weightInput.value, 10);
|
const weightGrams = parseInt(weightInput.value, 10);
|
||||||
|
|
||||||
if (weightGrams > 0) {
|
if (weightGrams > 0) {
|
||||||
// Convert back to kg for the cart's subtotal math
|
|
||||||
const weightKg = weightGrams / 1000;
|
const weightKg = weightGrams / 1000;
|
||||||
|
|
||||||
addToCart(pendingProduct, weightKg);
|
if (editingCartIndex !== null) {
|
||||||
|
// We are editing an existing row
|
||||||
|
cart[editingCartIndex].qty = weightKg;
|
||||||
|
cart[editingCartIndex].subtotal = calculateSubtotal(cart[editingCartIndex].price, weightKg);
|
||||||
|
renderCart();
|
||||||
|
} else {
|
||||||
|
// We are adding a new scan/search
|
||||||
|
addToCart(pendingProduct, weightKg);
|
||||||
|
}
|
||||||
|
|
||||||
// Hide modal and clear input
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('weightModal')).hide();
|
bootstrap.Modal.getInstance(document.getElementById('weightModal')).hide();
|
||||||
weightInput.value = '';
|
weightInput.value = '';
|
||||||
|
editingCartIndex = null; // Clear the tracker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,47 +536,22 @@
|
|||||||
// Don't re-render immediately to avoid losing input focus while typing
|
// Don't re-render immediately to avoid losing input focus while typing
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCart() {
|
function editWeight(index) {
|
||||||
const tbody = document.getElementById('cart-items');
|
editingCartIndex = index;
|
||||||
tbody.innerHTML = '';
|
const item = cart[index];
|
||||||
let total = 0;
|
const weightInput = document.getElementById('weight-input');
|
||||||
|
|
||||||
cart.forEach((item, index) => {
|
// Convert current kg back to grams for the input
|
||||||
total += item.subtotal;
|
weightInput.value = Math.round(item.qty * 1000);
|
||||||
const row = document.createElement('tr');
|
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('weightModal'));
|
||||||
// Logic for quantity controls
|
modal.show();
|
||||||
let qtyControls;
|
|
||||||
if (item.unit === 'kg') {
|
// Auto-focus and highlight the existing number so you can just type over it
|
||||||
qtyControls = `<span>${item.qty.toFixed(3)} kg</span>`;
|
setTimeout(() => {
|
||||||
} else {
|
weightInput.focus();
|
||||||
qtyControls = `
|
weightInput.select();
|
||||||
<div class="d-flex align-items-center gap-1">
|
}, 500);
|
||||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, -1)">-</button>
|
|
||||||
<input type="number" class="form-control form-control-sm text-center p-0"
|
|
||||||
style="width: 50px;" value="${item.qty}"
|
|
||||||
onchange="manualQty(${index}, this.value)"
|
|
||||||
onblur="renderCart()">
|
|
||||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, 1)">+</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
row.innerHTML = `
|
|
||||||
<td class="font-monospace small text-muted">${item.barcode}</td>
|
|
||||||
<td>${item.name}</td>
|
|
||||||
<td>${clp.format(item.price)}</td>
|
|
||||||
<td>${qtyControls}</td>
|
|
||||||
<td>${clp.format(item.subtotal)}</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn-danger-discord btn-sm" onclick="removeItem(${index}, '${item.name}')">
|
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
tbody.appendChild(row);
|
|
||||||
});
|
|
||||||
document.getElementById('grand-total').innerText = clp.format(total);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTheme(t) {
|
function applyTheme(t) {
|
||||||
@@ -582,12 +572,6 @@
|
|||||||
applyTheme(next);
|
applyTheme(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize on load
|
|
||||||
(function initTheme() {
|
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
||||||
applyTheme(savedTheme);
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
// 1. Load all products from Python into a JavaScript array safely
|
// 1. Load all products from Python into a JavaScript array safely
|
||||||
const allProducts = [
|
const allProducts = [
|
||||||
@@ -682,11 +666,63 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Make sure we clear the tracker if the user hits Cancel
|
||||||
|
document.getElementById('weightModal').addEventListener('hidden.bs.modal', function () {
|
||||||
|
document.getElementById('weight-input').value = '';
|
||||||
|
pendingProduct = null;
|
||||||
|
editingCartIndex = null;
|
||||||
|
});
|
||||||
|
|
||||||
function calculateSubtotal(price, qty) {
|
function calculateSubtotal(price, qty) {
|
||||||
const rawTotal = price * qty;
|
const rawTotal = price * qty;
|
||||||
// Ley del Redondeo: rounds to the nearest 10
|
// Ley del Redondeo: rounds to the nearest 10
|
||||||
return Math.round(rawTotal / 10) * 10;
|
return Math.round(rawTotal / 10) * 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveCart() {
|
||||||
|
localStorage.setItem('seki_cart', JSON.stringify(cart));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCart() {
|
||||||
|
const saved = localStorage.getItem('seki_cart');
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
cart = JSON.parse(saved);
|
||||||
|
renderCart();
|
||||||
|
} catch(e) {
|
||||||
|
console.error("Error cargando el carrito", e);
|
||||||
|
cart = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeItem(idx) {
|
||||||
|
itemIndexToRemove = idx;
|
||||||
|
// Look up the name safely from the array
|
||||||
|
document.getElementById('removeItemName').innerText = cart[idx].name;
|
||||||
|
|
||||||
|
// getOrCreateInstance prevents UI-breaking ghost backdrops
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('removeConfirmModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the listener is intact
|
||||||
|
document.getElementById('btn-confirm-remove').addEventListener('click', () => {
|
||||||
|
if (itemIndexToRemove !== null) {
|
||||||
|
cart.splice(itemIndexToRemove, 1);
|
||||||
|
renderCart(); // This will auto-save the cart now!
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('removeConfirmModal')).hide();
|
||||||
|
itemIndexToRemove = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize on load
|
||||||
|
(function initTheme() {
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
})();
|
||||||
|
|
||||||
|
loadCart();
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user