Added numpad shortcuts at checkout

This commit is contained in:
2026-03-30 21:14:47 -03:00
parent cc6fc28c4a
commit b2bc0801f5
3 changed files with 121 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
# SekiPOS v2.0 🍫🥤 # SekiPOS v2.1 🍫🥤
A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching. A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching.

View File

@@ -87,7 +87,7 @@
</div> </div>
</div> </div>
<div class="modal fade" id="variosModal" tabindex="-1"> <div class="modal" id="variosModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-sm"> <div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header border-0 pb-0"> <div class="modal-header border-0 pb-0">
@@ -99,8 +99,10 @@
<div class="modal-body text-center pt-1 pb-4"> <div class="modal-body text-center pt-1 pb-4">
<div class="mb-3 text-start"> <div class="mb-3 text-start">
<label class="text-muted small mb-1">Precio (CLP)</label> <label class="text-muted small mb-1">Precio (CLP)</label>
<input type="number" id="varios-price-input" class="form-control form-control-lg text-center fw-bold fs-4" <input type="text" inputmode="numeric" id="varios-price-input" class="form-control form-control-lg text-center fw-bold fs-4"
placeholder="$0" onkeydown="if(event.key === 'Enter') addVariosToCart()"> placeholder="$0"
oninput="let v = this.value.replace(/\D/g, ''); this.value = v ? parseInt(v, 10).toLocaleString('es-CL') : '';"
onkeydown="if(event.key === 'Enter') addVariosToCart()">
</div> </div>
<button class="btn btn-warning w-100 py-3 fw-bold" onclick="addVariosToCart()"> <button class="btn btn-warning w-100 py-3 fw-bold" onclick="addVariosToCart()">
<i class="bi bi-cart-plus me-1"></i> Agregar al Carrito <i class="bi bi-cart-plus me-1"></i> Agregar al Carrito
@@ -152,7 +154,7 @@
</div> </div>
</div> </div>
<div class="modal fade" id="vueltoModal" tabindex="-1" data-bs-backdrop="static"> <div class="modal" id="vueltoModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-sm"> <div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header border-0 pb-0"> <div class="modal-header border-0 pb-0">
@@ -181,7 +183,7 @@
</div> </div>
</div> </div>
<div class="modal fade" id="quickSaleModal" tabindex="-1"> <div class="modal" id="quickSaleModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-sm"> <div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header border-0 pb-0"> <div class="modal-header border-0 pb-0">
@@ -191,8 +193,10 @@
<div class="modal-body text-center pt-1 pb-4"> <div class="modal-body text-center pt-1 pb-4">
<div class="mb-3 text-start"> <div class="mb-3 text-start">
<label class="text-muted small mb-1">Monto (CLP)</label> <label class="text-muted small mb-1">Monto (CLP)</label>
<input type="number" id="quick-sale-amount" class="form-control form-control-lg text-center fw-bold fs-4" <input type="text" inputmode="numeric" id="quick-sale-amount" class="form-control form-control-lg text-center fw-bold fs-4"
placeholder="$0" onkeydown="if(event.key === 'Enter') processQuickSale()"> placeholder="$0"
oninput="let v = this.value.replace(/\D/g, ''); this.value = v ? parseInt(v, 10).toLocaleString('es-CL') : '';"
onkeydown="if(event.key === 'Enter') processQuickSale()">
</div> </div>
<button class="btn btn-primary w-100 py-3 fw-bold" onclick="processQuickSale()"> <button class="btn btn-primary w-100 py-3 fw-bold" onclick="processQuickSale()">
<i class="bi bi-lightning-charge me-1"></i> Finalizar Venta <i class="bi bi-lightning-charge me-1"></i> Finalizar Venta
@@ -636,13 +640,17 @@
} }
function openVariosModal() { function openVariosModal() {
document.getElementById('varios-price-input').value = ''; const input = document.getElementById('varios-price-input');
input.value = '';
bootstrap.Modal.getOrCreateInstance(document.getElementById('variosModal')).show(); bootstrap.Modal.getOrCreateInstance(document.getElementById('variosModal')).show();
setTimeout(() => document.getElementById('varios-price-input').focus(), 500); input.focus(); // Instant focus
} }
function addVariosToCart() { function addVariosToCart() {
const price = parseInt(document.getElementById('varios-price-input').value, 10); // Grab the value and strip the dots before parsing
const rawValue = document.getElementById('varios-price-input').value.replace(/\./g, '');
const price = parseInt(rawValue, 10);
if (isNaN(price) || price <= 0) return alert("Ingresa un precio válido."); if (isNaN(price) || price <= 0) return alert("Ingresa un precio válido.");
addToCart({ addToCart({
@@ -709,21 +717,23 @@
const total = cart.reduce((sum, item) => sum + item.subtotal, 0); const total = cart.reduce((sum, item) => sum + item.subtotal, 0);
document.getElementById('vuelto-total-display').innerText = clp.format(total); document.getElementById('vuelto-total-display').innerText = clp.format(total);
document.getElementById('monto-recibido').value = ''; const input = document.getElementById('monto-recibido');
input.value = '';
document.getElementById('vuelto-amount').innerText = clp.format(0); document.getElementById('vuelto-amount').innerText = clp.format(0);
document.getElementById('vuelto-amount').className = "fs-1 fw-bold text-success"; document.getElementById('vuelto-amount').className = "fs-1 fw-bold text-success";
document.getElementById('btn-confirm-vuelto').disabled = false; document.getElementById('btn-confirm-vuelto').disabled = false;
const quickBox = document.getElementById('vuelto-quick-buttons'); const quickBox = document.getElementById('vuelto-quick-buttons');
quickBox.innerHTML = `<button class="btn btn-sm btn-outline-secondary fw-bold" onclick="setMonto(${total})">Exacto</button>`; quickBox.innerHTML = `<button class="btn btn-sm btn-outline-secondary fw-bold" onclick="setMonto(${total})">Exacto</button>`;
[5000, 10000, 20000].forEach(bill => { [2000, 5000, 10000, 20000].forEach(bill => {
if (bill > total && (bill - total) <= 20000) { if (bill > total && (bill - total) <= 20000) {
quickBox.innerHTML += `<button class="btn btn-sm btn-outline-secondary fw-bold" onclick="setMonto(${bill})">${clp.format(bill)}</button>`; quickBox.innerHTML += `<button class="btn btn-sm btn-outline-secondary fw-bold" onclick="setMonto(${bill})">${clp.format(bill)}</button>`;
} }
}); });
bootstrap.Modal.getOrCreateInstance(document.getElementById('vueltoModal')).show(); bootstrap.Modal.getOrCreateInstance(document.getElementById('vueltoModal')).show();
setTimeout(() => document.getElementById('monto-recibido').focus(), 500); input.focus(); // Instant focus
} }
function setMonto(amount) { function setMonto(amount) {
@@ -788,13 +798,17 @@
} }
function openQuickSaleModal() { function openQuickSaleModal() {
document.getElementById('quick-sale-amount').value = ''; const input = document.getElementById('quick-sale-amount');
input.value = '';
bootstrap.Modal.getOrCreateInstance(document.getElementById('quickSaleModal')).show(); bootstrap.Modal.getOrCreateInstance(document.getElementById('quickSaleModal')).show();
setTimeout(() => document.getElementById('quick-sale-amount').focus(), 500); input.focus(); // Instant focus
} }
async function processQuickSale() { async function processQuickSale() {
const amount = parseInt(document.getElementById('quick-sale-amount').value, 10); // Strip the dots before asking JS to do math
const rawValue = document.getElementById('quick-sale-amount').value.replace(/\./g, '');
const amount = parseInt(rawValue, 10);
if (isNaN(amount) || amount <= 0) return alert("Ingresa un monto válido."); if (isNaN(amount) || amount <= 0) return alert("Ingresa un monto válido.");
if (amount === 666) { if (amount === 666) {
@@ -865,18 +879,96 @@
}); });
document.addEventListener('keydown', function(event) { document.addEventListener('keydown', function(event) {
if (event.key === 'Enter') { const activeTag = document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
const removeModal = document.getElementById('removeConfirmModal'); const isTyping = activeTag === 'input' || activeTag === 'textarea';
const clearModal = document.getElementById('clearCartModal');
// Find modals that are fully open OR in the middle of their fade-in animation
if (removeModal && removeModal.classList.contains('show')) { const openModal = document.querySelector('.modal.show, .modal[style*="display: block"]');
event.preventDefault();
executeRemoveItem(); // 1. The Eject Button: NumpadDecimal (.) or Delete
} else if (clearModal && clearModal.classList.contains('show')) { if (event.code === 'NumpadDecimal' || event.key === 'Delete') {
if (isTyping && event.key === 'Delete') return;
if (openModal) {
event.preventDefault(); event.preventDefault();
executeClearCart(); const modalInstance = bootstrap.Modal.getInstance(openModal) || bootstrap.Modal.getOrCreateInstance(openModal);
if (modalInstance) modalInstance.hide();
return;
} }
} }
// 2. Payment Modal Selection: 1 for Efectivo, 2 for Tarjeta
if (openModal && openModal.id === 'paymentModal') {
if (event.code === 'Numpad1' || event.key === '1') {
event.preventDefault();
openVueltoModal();
return;
}
if (event.code === 'Numpad2' || event.key === '2') {
event.preventDefault();
executeCheckout('tarjeta');
return;
}
}
// 3. The Money Button: Enter
if (event.code === 'NumpadEnter' || event.key === 'Enter') {
if (itemIndexToRemove !== null) {
event.preventDefault();
executeRemoveItem();
return;
}
const clearModal = document.getElementById('clearCartModal');
if (clearModal && (clearModal.classList.contains('show') || clearModal.style.display === 'block')) {
event.preventDefault();
executeClearCart();
return;
}
if (!openModal && !isTyping && cart.length > 0) {
event.preventDefault();
processSale();
return;
}
}
// 4. The Varios Button: +
if (event.code === 'NumpadAdd' || event.key === '+') {
if (isTyping) return;
event.preventDefault();
openVariosModal();
return;
}
// 5. The Oops Button: - (Remove last item)
if (event.code === 'NumpadSubtract' || event.key === '-') {
if (isTyping) return;
if (!openModal && cart.length > 0) {
event.preventDefault();
removeItem(cart.length - 1);
return;
}
}
// 6. The Speedrun Button: * (Venta Rápida)
if (event.code === 'NumpadMultiply' || event.key === '*') {
if (isTyping) return;
event.preventDefault();
openQuickSaleModal();
return;
}
});
// Safety cleanup: If you close the remove modal with ESC or the Eject button, clear the memory
document.getElementById('removeConfirmModal').addEventListener('hidden.bs.modal', function () {
itemIndexToRemove = null;
});
// Safety cleanup: If you close the remove modal with ESC or the Eject button, clear the memory
document.getElementById('removeConfirmModal').addEventListener('hidden.bs.modal', function () {
itemIndexToRemove = null;
}); });
/* ========================================= /* =========================================
@@ -971,5 +1063,7 @@
========================================= */ ========================================= */
loadCart(); loadCart();
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -1,7 +1,7 @@
<nav class="navbar navbar-expand-md sticky-top px-3 mb-3"> <nav class="navbar navbar-expand-md sticky-top px-3 mb-3">
<span class="navbar-brand"> <span class="navbar-brand">
SekiPOS SekiPOS
<small class="text-muted fw-normal" style="font-size:0.65rem;">v2.0</small> <small class="text-muted fw-normal" style="font-size:0.65rem;">v2.1</small>
</span> </span>
<div class="ms-3 gap-2 d-flex"> <div class="ms-3 gap-2 d-flex">