modified: app.py

modified:   static/styleIndex.css
	modified:   templates/index.html
This commit is contained in:
2026-02-26 21:08:41 -03:00
parent 80bf539484
commit 4faee03762
3 changed files with 171 additions and 0 deletions

27
app.py
View File

@@ -189,6 +189,33 @@ def scan():
def serve_cache(filename): def serve_cache(filename):
return send_from_directory(CACHE_DIR, filename) return send_from_directory(CACHE_DIR, filename)
@app.route('/bulk_delete', methods=['POST'])
@login_required
def bulk_delete():
barcodes = request.json.get('barcodes', [])
with sqlite3.connect(DB_FILE) as conn:
conn.executemany('DELETE FROM products WHERE barcode = ?', [(b,) for b in barcodes])
conn.commit()
# Optional: Logic to delete cached images could go here
return jsonify({"status": "ok"})
@app.route('/bulk_edit_price', methods=['POST'])
@login_required
def bulk_edit_price():
data = request.json
barcodes = data.get('barcodes', [])
new_price = data.get('price')
try:
price = float(new_price)
with sqlite3.connect(DB_FILE) as conn:
conn.executemany('UPDATE products SET price = ? WHERE barcode = ?',
[(price, b) for b in barcodes])
conn.commit()
return jsonify({"status": "ok"})
except ValueError:
return jsonify({"status": "error", "message": "Invalid price"}), 400
if __name__ == '__main__': if __name__ == '__main__':
init_db() init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True) socketio.run(app, host='0.0.0.0', port=5000, debug=True)

View File

@@ -179,4 +179,83 @@ th {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
}
#bulk-action-bar {
display: none;
/* Controlled by JS */
background: var(--accent);
padding: 12px 20px;
border-radius: 8px;
margin-bottom: 20px;
color: white;
align-items: center;
gap: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
animation: slideDown 0.3s ease;
}
#bulk-action-bar span {
font-weight: bold;
font-size: 0.9em;
white-space: nowrap;
}
#bulk-action-bar input[type="number"] {
width: 120px;
margin: 0;
height: 38px;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
}
/* Ensure the parent container can hold the absolute bar */
.column {
position: relative;
}
#bulk-action-bar {
display: none;
position: absolute;
top: 0;
left: 25px; /* Matches card padding */
right: 25px;
z-index: 10;
background: var(--accent);
padding: 12px 20px;
border-radius: 8px;
color: white;
align-items: center;
gap: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
animation: slideIn 0.2s ease-out;
}
@keyframes slideIn {
from { transform: translateY(-10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.btn-bulk-save {
background: #2ecc71;
/* A more professional green */
color: white;
flex-grow: 1;
font-weight: bold;
height: 38px;
}
.btn-bulk-save:hover {
background: #27ae60;
}
.btn-bulk-del {
background: rgba(255, 255, 255, 0.2);
color: white;
height: 38px;
}
.btn-bulk-del:hover {
background: var(--danger);
} }

View File

@@ -56,11 +56,19 @@
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<h3 style="text-align: left; margin-top:0;">Inventario</h3> <h3 style="text-align: left; margin-top:0;">Inventario</h3>
<div id="bulk-action-bar">
<span id="selected-count">0 seleccionados</span>
<input type="number" id="bulk-price-input" placeholder="Precio CLP">
<button onclick="applyBulkPrice()" class="btn-bulk-save">Actualizar Precio</button>
<button onclick="applyBulkDelete()" class="btn-bulk-del">Borrar</button>
</div>
<input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Buscar por nombre o código..."> <input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Buscar por nombre o código...">
<table id="inventoryTable"> <table id="inventoryTable">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" id="select-all" onclick="toggleAll(this)"></th>
<th>Código</th> <th>Código</th>
<th>Nombre</th> <th>Nombre</th>
<th>Precio</th> <th>Precio</th>
@@ -70,6 +78,8 @@
<tbody> <tbody>
{% for p in products %} {% for p in products %}
<tr> <tr>
<td><input type="checkbox" class="product-checkbox" value="{{ p[0] }}"
onchange="updateBulkBar()"></td>
<td style="font-family: monospace;">{{ p[0] }}</td> <td style="font-family: monospace;">{{ p[0] }}</td>
<td>{{ p[1] }}</td> <td>{{ p[1] }}</td>
<td class="price-cell" data-value="{{ p[2] }}"></td> <td class="price-cell" data-value="{{ p[2] }}"></td>
@@ -194,6 +204,61 @@
tr[i].style.display = content.toUpperCase().indexOf(filter) > -1 ? "" : "none"; tr[i].style.display = content.toUpperCase().indexOf(filter) > -1 ? "" : "none";
} }
} }
function toggleAll(source) {
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = source.checked);
updateBulkBar();
}
function updateBulkBar() {
const selected = document.querySelectorAll('.product-checkbox:checked');
const bar = document.getElementById('bulk-action-bar');
const countDisplay = document.getElementById('selected-count');
if (selected.length > 0) {
bar.style.display = 'flex';
countDisplay.innerText = `${selected.length} seleccionados`;
} else {
bar.style.display = 'none';
}
}
function getSelectedBarcodes() {
return Array.from(document.querySelectorAll('.product-checkbox:checked')).map(cb => cb.value);
}
async function applyBulkPrice() {
const barcodes = getSelectedBarcodes();
const price = document.getElementById('bulk-price-input').value;
if (!price) return alert("Ingresa un precio");
const resp = await fetch('/bulk_edit_price', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ barcodes, price })
});
if (resp.ok) location.reload();
}
async function applyBulkDelete() {
if (!confirm("¿Eliminar todos los productos seleccionados?")) return;
const barcodes = getSelectedBarcodes();
const resp = await fetch('/bulk_delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ barcodes })
});
if (resp.ok) location.reload();
}
// Clear checkboxes on reload to prevent ghost selections
window.addEventListener('load', () => {
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = false);
const selectAll = document.getElementById('select-all');
if (selectAll) selectAll.checked = false;
updateBulkBar();
});
</script> </script>
</body> </body>