Compare commits

...

2 Commits

3 changed files with 177 additions and 44 deletions

21
app.py
View File

@@ -189,6 +189,27 @@ def scan():
def serve_cache(filename):
return send_from_directory(CACHE_DIR, filename)
@app.route('/bulk_price_update', methods=['POST'])
@login_required
def bulk_price_update():
data = request.get_json()
barcodes = data.get('barcodes', [])
new_price = data.get('new_price')
if not barcodes or new_price is None:
return jsonify({"error": "Missing data"}), 400
try:
with sqlite3.connect(DB_FILE) as conn:
# Use executemany for efficiency
params = [(float(new_price), b) for b in barcodes]
conn.executemany('UPDATE products SET price = ? WHERE barcode = ?', params)
conn.commit()
return jsonify({"status": "success"}), 200
except Exception as e:
print(f"Bulk update failed: {e}")
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

View File

@@ -157,26 +157,80 @@ th {
font-weight: bold;
}
#new-product-prompt {
display: none;
/* --- BULK ACTIONS PERMANENT BAR --- */
.bulk-actions {
display: flex;
background: var(--accent);
color: white;
padding: 12px 20px;
padding: 10px 20px;
border-radius: 8px;
margin-bottom: 20px;
animation: slideDown 0.4s ease;
margin-bottom: 15px;
justify-content: space-between;
align-items: center;
min-height: 60px;
box-sizing: border-box;
width: 100%;
}
.bulk-controls {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
/* Override the global 100% width for this specific input */
.bulk-actions input#bulk-price-input {
width: 130px !important;
margin: 0 !important;
padding: 8px !important;
height: 36px !important;
background: rgba(0,0,0,0.2) !important;
border: 1px solid rgba(255,255,255,0.3) !important;
color: white !important;
}
.bulk-actions button {
height: 36px;
white-space: nowrap;
padding: 0 15px;
margin: 0;
}
/* --- RESPONSIVE DESIGN (MOBILE) --- */
@media (max-width: 900px) {
.main-container {
flex-direction: column;
}
.column {
min-width: 100%;
}
.bulk-actions {
flex-direction: column;
height: auto;
padding: 15px;
gap: 10px;
text-align: center;
}
.bulk-controls {
width: 100%;
justify-content: center;
}
.bulk-actions input#bulk-price-input {
width: 100% !important; /* On mobile, let it be wide */
}
/* Hide barcode on very small screens to save space */
td:nth-child(2), th:nth-child(2) {
display: none;
}
}
@keyframes slideDown {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}

View File

@@ -56,35 +56,48 @@
<div class="column">
<div class="card">
<h3 style="text-align: left; margin-top:0;">Inventario</h3>
<input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Buscar por nombre o código...">
<table id="inventoryTable">
<thead>
<tr>
<th>Código</th>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for p in products %}
<tr>
<td style="font-family: monospace;">{{ p[0] }}</td>
<td>{{ 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] }}')">Editar</button>
<form action="/delete/{{ p[0] }}" method="POST" style="display:inline;">
<button type="submit" class="btn-del"
onclick="return confirm('¿Eliminar producto?')">Borrar</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Bulk Action Bar -->
<div id="bulk-bar" class="bulk-actions">
<span><b id="selected-count">0</b> seleccionados</span>
<div class="bulk-controls">
<input type="number" id="bulk-price-input" placeholder="Precio CLP">
<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="Buscar...">
<div style="overflow-x: auto; width: 100%;">
<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>
<td><input type="checkbox" class="product-checkbox" data-barcode="{{ p[0] }}" onclick="updateBulkBar()"></td>
<td style="font-family: monospace;">{{ p[0] }}</td>
<td>{{ 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] }}')">Editar</button>
<form action="/delete/{{ p[0] }}" method="POST" style="display:inline;">
<button type="submit" class="btn-del"
onclick="return confirm('¿Eliminar producto?')">Borrar</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
@@ -194,6 +207,51 @@
tr[i].style.display = content.toUpperCase().indexOf(filter) > -1 ? "" : "none";
}
}
function toggleAll(source) {
const checkboxes = document.querySelectorAll('.product-checkbox');
checkboxes.forEach(cb => cb.checked = source.checked);
updateBulkBar();
}
function updateBulkBar() {
const checked = document.querySelectorAll('.product-checkbox:checked');
document.getElementById('selected-count').innerText = checked.length;
const okBtn = document.querySelector('.bulk-controls button[onclick="applyBulkPrice()"]');
if (okBtn) {
okBtn.disabled = checked.length === 0;
okBtn.style.opacity = checked.length === 0 ? "0.5" : "1";
}
}
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 || price < 0) return alert("Ingresa un precio válido");
const barcodes = Array.from(document.querySelectorAll('.product-checkbox:checked'))
.map(cb => cb.getAttribute('data-barcode'));
if (!confirm(`¿Actualizar el precio de ${barcodes.length} productos a ${clpFormatter.format(price)}?`)) return;
const response = await fetch('/bulk_price_update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ barcodes: barcodes, new_price: price })
});
if (response.ok) {
window.location.reload();
} else {
alert("Error al actualizar productos.");
}
}
</script>
</body>