diff --git a/app.py b/app.py index 44bc956..3152332 100644 --- a/app.py +++ b/app.py @@ -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) diff --git a/static/styleIndex.css b/static/styleIndex.css index 94031bd..a758576 100644 --- a/static/styleIndex.css +++ b/static/styleIndex.css @@ -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; - } -} \ No newline at end of file + from { transform: translateY(-20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} diff --git a/templates/index.html b/templates/index.html index e30d11a..4676b91 100644 --- a/templates/index.html +++ b/templates/index.html @@ -56,35 +56,48 @@

Inventario

- - - - - - - - - - - - - {% for p in products %} - - - - - - - {% endfor %} - -
CódigoNombrePrecioAcciones
{{ p[0] }}{{ p[1] }} - -
- -
-
+ + +
+ 0 seleccionados +
+ + + +
+
+ +
+ + + + + + + + + + + + {% for p in products %} + + + + + + + + {% endfor %} + +
CódigoNombrePrecioAcciones
{{ p[0] }}{{ p[1] }} + +
+ +
+
+
@@ -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."); + } + }