Files
SekiPOS/templates/macros/modals.html
SekiDesu0 47cc480cf5 feat: add expenses module, restaurant mode, and dynamic sales filters
- Gastos (Expenses): Added `/gastos` route, auto-creation of `expenses` DB table, and `gastos.html` to track net profit with split month/year dropdowns.
- Sales & Filters: Overhauled `/sales` backend to use pagination. Top summary cards now accurately reflect the selected payment method filter.
- Checkout Improvements:
  - Added "Transferencia" as a payment option with numpad shortcuts.
  - Built a "Pinned Products" quick-access grid using localStorage.
  - Implemented a global processing lock to prevent duplicate sales on double-clicks.
  - Burned the default HTML number arrows with custom CSS.
- Global Settings & Receipts:
  - Created a global settings modal accessible from the navbar.
  - Added localStorage toggles for custom business name and auto-print.
  - Added "Restaurant Mode" toggle to prompt for Client Name and Pickup Time, which now dynamically prints on the receipt.
- Bug Fixes: Resolved Jinja `TemplateSyntaxError` crash and removed the duplicate search bar in the checkout view.
2026-04-15 22:58:12 -04:00

180 lines
8.7 KiB
HTML

{% macro confirm_modal(id, title, button_class, button_text, onclick_fn) %}
<div class="modal fade" id="{{ id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{{ caller() }}
<div class="d-grid gap-2 mt-3">
<button class="btn {{ button_class }}" onclick="{{ onclick_fn }}">
{{ button_text }}
</button>
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
</div>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro scanner_modal() %}
<div class="modal fade" id="scannerModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Escanear Código</h5>
<button type="button" class="btn-close" onclick="stopScanner()" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3 d-flex align-items-end gap-2">
<div class="flex-grow-1">
<label class="form-label small text-muted">Seleccionar Cámara:</label>
<select id="camera-select" class="form-select form-select-sm" onchange="switchCamera(this.value)">
<option value="">Cargando cámaras...</option>
</select>
</div>
<button id="torch-btn" class="btn btn-outline-secondary btn-sm" onclick="toggleTorch()" style="height: 31px; min-width: 40px; display: none;"></button>
</div>
<div id="reader" style="width: 100%; border-radius: 8px; overflow: hidden;"></div>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro render_receipt(id_suffix="") %}
<div id="receipt-print-zone{{ id_suffix }}" class="d-none d-print-block">
<style>
@media print {
@page { margin: 0; size: 80mm auto; }
nav, .discord-card, .modal, .row { display: none !important; }
body * { visibility: hidden; }
#receipt-print-zone{{ id_suffix }}, #receipt-print-zone{{ id_suffix }} * { visibility: visible; }
#receipt-print-zone{{ id_suffix }} {
position: absolute; left: 0; top: 0; width: 80mm;
padding: 2mm 5mm; margin: 0; display: block !important;
font-family: 'Courier New', Courier, monospace; font-size: 10px; color: #000;
}
}
.receipt-table { width: 100%; border-collapse: collapse; font-family: monospace; font-size: 12px; }
.receipt-header { text-align: center; margin-bottom: 10px; border-bottom: 1px dashed #000; padding-bottom: 5px; }
.receipt-total-row { border-top: 1px dashed #000; margin-top: 5px; padding-top: 5px; font-weight: bold; }
</style>
<div class="receipt-header">
<h3 style="margin: 0; font-weight: 800;" class="receipt-biz-name">SekiPOS</h3>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.receipt-biz-name').forEach(el => {
el.innerText = localStorage.getItem('seki_biz_name') || 'SekiPOS';
});
});
</script>
<div style="font-size: 10px; margin-bottom: 5px;" id="receipt-type{{ id_suffix }}">Comprobante de Venta</div>
<div style="font-size: 11px; font-weight: bold;">
Ticket Nº <span id="receipt-ticket-id{{ id_suffix }}"></span>
</div>
<div id="receipt-date{{ id_suffix }}" style="font-size: 11px;"></div>
<div id="receipt-order-info{{ id_suffix }}" style="display: none; margin-top: 5px; padding-top: 5px; border-top: 1px dashed #000; text-align: left; font-size: 11px;">
<div style="font-weight: bold;">Cliente: <span id="receipt-client-name{{ id_suffix }}"></span></div>
<div id="receipt-pickup-container{{ id_suffix }}" style="display: none; font-weight: bold;">Retiro: <span id="receipt-pickup-time{{ id_suffix }}"></span></div>
<div>Notas: <span id="receipt-order-notes{{ id_suffix }}"></span></div>
</div>
</div>
<table class="receipt-table">
<thead>
<tr>
<th style="width: 15%; text-align: left;">Cant</th>
<th style="width: 60%; padding-left: 5px; text-align: left;">Desc</th>
<th style="width: 25%; text-align: right;">Total</th>
</tr>
</thead>
<tbody id="receipt-items-print{{ id_suffix }}"></tbody>
</table>
<div class="receipt-total-row d-flex justify-content-between">
<span>TOTAL:</span>
<span id="receipt-total-print{{ id_suffix }}"></span>
</div>
<div id="receipt-payment-info{{ id_suffix }}">
<div class="d-flex justify-content-between">
<span>RECIBIDO:</span>
<span id="receipt-paid-print{{ id_suffix }}"></span>
</div>
<div class="d-flex justify-content-between">
<span>VUELTO:</span>
<span id="receipt-change-print{{ id_suffix }}"></span>
</div>
</div>
<div style="text-align: center; margin-top: 20px; font-size: 10px;">¡Gracias por su compra!</div>
</div>
{% endmacro %}
{% macro settings_modal() %}
<div class="modal fade" id="settingsModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-gear-fill me-2"></i>Configuración del POS</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-4">
<label class="form-label text-muted small mb-1">Nombre del Local (Impreso en la Boleta)</label>
<input type="text" id="setting-biz-name" class="form-control" placeholder="Ej: Mi Tiendita" autocomplete="off">
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="setting-auto-print">
<label class="form-check-label text-muted small" for="setting-auto-print">
Imprimir automáticamente al finalizar venta
</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="setting-ask-order-details">
<label class="form-check-label text-muted small" for="setting-ask-order-details">
Solicitar Nombre/Notas al cobrar (Modo Comida)
</label>
</div>
</div>
<div class="modal-footer d-flex">
<button class="btn btn-secondary flex-grow-1" data-bs-dismiss="modal">Cancelar</button>
<button class="btn btn-primary flex-grow-1" onclick="savePosSettings()">Guardar</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modalEl = document.getElementById('settingsModal');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', () => {
document.getElementById('setting-biz-name').value = localStorage.getItem('seki_biz_name') || 'SekiPOS';
document.getElementById('setting-auto-print').checked = localStorage.getItem('seki_auto_print') !== 'false';
document.getElementById('setting-ask-order-details').checked = localStorage.getItem('seki_ask_order_details') === 'true';
});
}
});
function savePosSettings() {
const bizName = document.getElementById('setting-biz-name').value.trim() || 'SekiPOS';
const autoPrint = document.getElementById('setting-auto-print').checked;
const askDetails = document.getElementById('setting-ask-order-details').checked;
localStorage.setItem('seki_biz_name', bizName);
localStorage.setItem('seki_auto_print', autoPrint);
localStorage.setItem('seki_ask_order_details', askDetails);
document.querySelectorAll('.receipt-biz-name').forEach(el => { el.innerText = bizName; });
bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide();
}
</script>
{% endmacro %}