Files
SekiPOS/templates/index.html
SekiDesu0 0dcf0bc930 modified: static/styleIndex.css
modified:   templates/index.html
2026-02-26 21:46:42 -03:00

189 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SekiPOS - Inventory</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<link rel="stylesheet" href="./static/styleIndex.css">
</head>
<body>
<div class="header-bar">
<h2>SekiPOS v1.5</h2>
<div style="display: flex; align-items: center; gap: 10px;">
<button onclick="toggleTheme()" id="theme-text" style="padding: 5px 10px; font-size: 0.8em;">Modo Oscuro</button>
<a href="/logout" style="color: var(--danger); text-decoration: none; font-size: 0.9em; font-weight: bold;">Salir</a>
</div>
</div>
<div class="main-container">
<div class="column">
<div id="new-product-prompt">
<span>Nuevo: <b id="new-barcode-display"></b></span>
<button onclick="dismissPrompt()" style="background:rgba(0,0,0,0.2); color:white;">Omitir</button>
</div>
<div class="card">
<h3 style="color: var(--text-muted); margin-top:0;">Último Escaneado</h3>
<img id="display-img" src="./static/placeholder.png">
<h2 id="display-name">Esperando scan...</h2>
<div class="price-tag" id="display-price">$0</div>
<p id="display-barcode" style="font-family: monospace; opacity: 0.5;"></p>
</div>
<div class="card">
<h3 id="form-title" style="margin-top:0;">Editar/Crear</h3>
<form action="/upsert" method="POST" id="product-form">
<input type="text" name="barcode" id="form-barcode" placeholder="Barcode" required>
<input type="text" name="name" id="form-name" placeholder="Nombre" required>
<input type="number" name="price" id="form-price" placeholder="Precio (CLP)" required>
<input type="text" name="image_url" id="form-image" placeholder="URL Imagen">
<button type="submit" class="btn-save">Guardar Cambios</button>
</form>
</div>
</div>
<div class="column">
<div class="card">
<div id="bulk-bar" class="bulk-actions">
<span><b id="selected-count">0</b> listos</span>
<div class="bulk-controls">
<input type="number" id="bulk-price-input" placeholder="Precio">
<button onclick="applyBulkPrice()" style="background:white; color:var(--accent);">OK</button>
<button onclick="clearSelection()" class="btn-del"></button>
</div>
</div>
<input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Filtrar productos...">
<div class="table-wrapper">
<table id="inventoryTable">
<thead>
<tr>
<th><input type="checkbox" id="select-all" onclick="toggleAll(this)"></th>
<th>Código</th>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for p in products %}
<tr data-barcode="{{ p[0] }}">
<td><input type="checkbox" class="product-checkbox" onclick="updateBulkBar()"></td>
<td style="font-family: monospace;">{{ p[0] }}</td>
<td class="name-cell">{{ p[1] }}</td>
<td class="price-cell" data-value="{{ p[2] }}"></td>
<td style="white-space: nowrap;">
<button class="btn-edit" onclick="editProduct('{{ p[0] }}', '{{ p[1] }}', '{{ p[2] }}', '{{ p[3] }}')">E</button>
<form action="/delete/{{ p[0] }}" method="POST" style="display:inline;">
<button type="submit" class="btn-del" onclick="return confirm('¿Borrar?')">D</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
var socket = io();
const clp = new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', minimumFractionDigits: 0 });
function formatAll() {
document.querySelectorAll('.price-cell').forEach(td => {
td.innerText = clp.format(td.getAttribute('data-value'));
});
}
formatAll();
socket.on('new_scan', d => {
document.getElementById('display-name').innerText = d.name;
document.getElementById('display-price').innerText = clp.format(d.price);
document.getElementById('display-barcode').innerText = d.barcode;
document.getElementById('display-img').src = d.image || './static/placeholder.png';
updateForm(d.barcode, d.name, d.price, d.image, "Editando: " + d.name);
});
socket.on('scan_error', d => {
const prompt = document.getElementById('new-product-prompt');
document.getElementById('new-barcode-display').innerText = d.barcode;
prompt.style.display = 'flex';
updateForm(d.barcode, d.name || '', '', d.image || '', "Crear: " + d.barcode);
});
function updateForm(b, n, p, i, t) {
dismissPrompt();
document.getElementById('form-barcode').value = b;
document.getElementById('form-name').value = n;
document.getElementById('form-price').value = p;
document.getElementById('form-image').value = i;
document.getElementById('form-title').innerText = t;
}
function dismissPrompt() { document.getElementById('new-product-prompt').style.display = 'none'; }
function editProduct(b, n, p, i) {
updateForm(b, n, p, i, "Editando: " + n);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function toggleAll(source) {
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = source.checked);
updateBulkBar();
}
function updateBulkBar() {
const count = document.querySelectorAll('.product-checkbox:checked').length;
document.getElementById('selected-count').innerText = count;
}
function clearSelection() {
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = false);
document.getElementById('select-all').checked = false;
updateBulkBar();
}
async function applyBulkPrice() {
const price = document.getElementById('bulk-price-input').value;
if (!price) return;
const checked = document.querySelectorAll('.product-checkbox:checked');
const barcodes = Array.from(checked).map(cb => cb.closest('tr').getAttribute('data-barcode'));
const res = await fetch('/bulk_price_update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ barcodes, new_price: price })
});
if (res.ok) {
checked.forEach(cb => {
const row = cb.closest('tr');
const cell = row.querySelector('.price-cell');
cell.setAttribute('data-value', price);
cell.innerText = clp.format(price);
cb.checked = false;
});
updateBulkBar();
}
}
function searchTable() {
let q = document.getElementById("searchInput").value.toUpperCase();
document.querySelectorAll("#inventoryTable tbody tr").forEach(tr => {
tr.style.display = tr.innerText.toUpperCase().includes(q) ? "" : "none";
});
}
function toggleTheme() {
const t = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', t);
localStorage.setItem('theme', t);
}
document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'light');
</script>
</body>
</html>