masowe dodawanie produktow
This commit is contained in:
31
add_products.py
Normal file
31
add_products.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# add_products_from_openfoodfacts.py
|
||||
# Uruchom w katalogu z app.py
|
||||
|
||||
import urllib.request
|
||||
import json
|
||||
from app import db, SuggestedProduct, app
|
||||
|
||||
# URL do pobrania polskich produktów (tylko nazwy)
|
||||
url = 'https://pl.openfoodfacts.org/cgi/search.pl?search_simple=1&action=process&json=1&fields=product_name&page_size=1000'
|
||||
|
||||
with urllib.request.urlopen(url) as response:
|
||||
data = json.loads(response.read())
|
||||
|
||||
produkty = []
|
||||
for prod in data.get('products', []):
|
||||
name = prod.get('product_name', '').strip()
|
||||
if name:
|
||||
produkty.append(name)
|
||||
|
||||
print(f"Znaleziono {len(produkty)} produktów do dodania.")
|
||||
|
||||
dodane = 0
|
||||
with app.app_context():
|
||||
for name in produkty:
|
||||
if not SuggestedProduct.query.filter_by(name=name).first():
|
||||
prod = SuggestedProduct(name=name)
|
||||
db.session.add(prod)
|
||||
dodane += 1
|
||||
db.session.commit()
|
||||
|
||||
print(f'Dodano {dodane} produktów do bazy.')
|
7
app.py
7
app.py
@@ -44,6 +44,7 @@ PROTECTED_JS_FILES = {
|
||||
"expenses.js",
|
||||
"toggle_button.js",
|
||||
"user_management.js",
|
||||
"mass_add.js"
|
||||
}
|
||||
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
@@ -595,6 +596,12 @@ def uploaded_file(filename):
|
||||
response.headers['Content-Type'] = mime
|
||||
return response
|
||||
|
||||
@app.route('/all_products')
|
||||
@login_required
|
||||
def all_products():
|
||||
suggestions = SuggestedProduct.query.order_by(SuggestedProduct.name).all()
|
||||
return jsonify([s.name for s in suggestions])
|
||||
|
||||
@app.route('/admin')
|
||||
@login_required
|
||||
@admin_required
|
||||
|
@@ -192,4 +192,24 @@ input.form-control {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#mass-add-list li.active {
|
||||
background: #198754 !important;
|
||||
color: #fff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
#mass-add-list li {
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.quantity-input {
|
||||
width: 60px;
|
||||
background: #343a40;
|
||||
color: #fff;
|
||||
border: 1px solid #495057;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.add-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
88
static/js/mass_add.js
Normal file
88
static/js/mass_add.js
Normal file
@@ -0,0 +1,88 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const modal = document.getElementById('massAddModal');
|
||||
const productList = document.getElementById('mass-add-list');
|
||||
let addedProducts = new Set();
|
||||
|
||||
document.querySelectorAll('#items li').forEach(li => {
|
||||
if (li.dataset.name) addedProducts.add(li.dataset.name.toLowerCase());
|
||||
});
|
||||
|
||||
modal.addEventListener('show.bs.modal', async function () {
|
||||
productList.innerHTML = '<li class="list-group-item bg-dark text-light">Ładowanie...</li>';
|
||||
try {
|
||||
const res = await fetch('/all_products');
|
||||
const suggestions = await res.json();
|
||||
productList.innerHTML = '';
|
||||
suggestions.forEach(name => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light';
|
||||
li.textContent = name;
|
||||
|
||||
if (addedProducts.has(name.toLowerCase())) {
|
||||
li.classList.add('active');
|
||||
li.innerHTML += '<span class="ms-2 text-success">✓</span>';
|
||||
} else {
|
||||
// Pole do ilości
|
||||
const qty = document.createElement('input');
|
||||
qty.type = 'number';
|
||||
qty.min = 1;
|
||||
qty.value = 1;
|
||||
qty.className = 'quantity-input ms-2';
|
||||
qty.title = 'Ilość';
|
||||
|
||||
// Przycisk dodania
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-sm btn-primary add-btn';
|
||||
btn.textContent = '+';
|
||||
btn.onclick = () => {
|
||||
const quantity = parseInt(qty.value) || 1;
|
||||
socket.emit('add_item', { list_id: LIST_ID, name: name, quantity: quantity });
|
||||
};
|
||||
|
||||
li.textContent = name;
|
||||
li.appendChild(qty);
|
||||
li.appendChild(btn);
|
||||
}
|
||||
productList.appendChild(li);
|
||||
});
|
||||
} catch (err) {
|
||||
productList.innerHTML = '<li class="list-group-item text-danger bg-dark">Błąd ładowania danych</li>';
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('item_added', data => {
|
||||
document.querySelectorAll('#mass-add-list li').forEach(li => {
|
||||
if (li.textContent.trim().startsWith(data.name) && !li.classList.contains('active')) {
|
||||
li.classList.add('active');
|
||||
li.innerHTML = `${data.name} <span class="ms-2 text-success">✓</span>`;
|
||||
li.onclick = null;
|
||||
}
|
||||
});
|
||||
|
||||
const itemsContainer = document.getElementById('items');
|
||||
if (!itemsContainer) return;
|
||||
if (document.getElementById(`item-${data.id}`)) return;
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item d-flex justify-content-between align-items-center flex-wrap item-not-checked';
|
||||
li.id = `item-${data.id}`;
|
||||
li.dataset.name = data.name;
|
||||
|
||||
let quantityBadge = '';
|
||||
if (data.quantity && data.quantity > 1) {
|
||||
quantityBadge = `<span class="badge bg-secondary ms-2">${data.quantity}×</span>`;
|
||||
}
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="check-${data.id}">
|
||||
<label class="form-check-label" for="check-${data.id}">
|
||||
${data.name}
|
||||
</label>
|
||||
${quantityBadge}
|
||||
</div>
|
||||
`;
|
||||
itemsContainer.appendChild(li);
|
||||
addedProducts.add(data.name.toLowerCase());
|
||||
});
|
||||
});
|
@@ -102,11 +102,21 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
</ul>
|
||||
|
||||
{% if not list.is_archived %}
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" id="newItem" name="name" class="form-control" placeholder="Dodaj produkt i ilość" required>
|
||||
<input type="number" id="newQuantity" name="quantity" class="form-control" placeholder="Ilość" min="1" value="1" style="max-width: 80px;">
|
||||
<button type="button" class="btn btn-success rounded-end" onclick="addItem({{ list.id }})">➕ Dodaj</button>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-12 col-md-3">
|
||||
<button class="btn btn-primary w-100 h-100" data-bs-toggle="modal" data-bs-target="#massAddModal">
|
||||
Dodaj z listy
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12 col-md-9">
|
||||
<div class="input-group w-100">
|
||||
<input type="text" id="newItem" name="name" class="form-control" placeholder="Dodaj produkt i ilość" required>
|
||||
<input type="number" id="newQuantity" name="quantity" class="form-control" placeholder="Ilość" min="1" value="1" style="max-width: 90px;">
|
||||
<button type="button" class="btn btn-success rounded-end" onclick="addItem({{ list.id }})">➕ Dodaj</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% set receipt_pattern = 'list_' ~ list.id %}
|
||||
@@ -127,7 +137,26 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
<p><span class="badge bg-secondary">Brak wgranych paragonów do tej listy.</span></p>
|
||||
{% endif %}
|
||||
|
||||
<div class="modal fade" id="massAddModal" tabindex="-1" aria-labelledby="massAddModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="massAddModalLabel">Masowe dodawanie produktów</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul id="mass-add-list" class="list-group">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Zamknij</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.js') }}"></script>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user