funkcja_niekupione #2
72
app.py
72
app.py
@@ -1543,7 +1543,7 @@ def handle_disconnect(sid):
|
||||
@socketio.on("add_item")
|
||||
def handle_add_item(data):
|
||||
list_id = data["list_id"]
|
||||
name = data["name"]
|
||||
name = data["name"].strip()
|
||||
quantity = data.get("quantity", 1)
|
||||
|
||||
try:
|
||||
@@ -1553,34 +1553,56 @@ def handle_add_item(data):
|
||||
except:
|
||||
quantity = 1
|
||||
|
||||
new_item = Item(
|
||||
list_id=list_id,
|
||||
name=name,
|
||||
quantity=quantity,
|
||||
added_by=current_user.id if current_user.is_authenticated else None,
|
||||
)
|
||||
db.session.add(new_item)
|
||||
# Szukamy istniejącego itemu w tej liście (ignorując wielkość liter)
|
||||
existing_item = Item.query.filter(
|
||||
Item.list_id == list_id,
|
||||
func.lower(Item.name) == name.lower(),
|
||||
Item.not_purchased == False
|
||||
).first()
|
||||
|
||||
if not SuggestedProduct.query.filter_by(name=name).first():
|
||||
new_suggestion = SuggestedProduct(name=name)
|
||||
db.session.add(new_suggestion)
|
||||
if existing_item:
|
||||
existing_item.quantity += quantity
|
||||
db.session.commit()
|
||||
|
||||
db.session.commit()
|
||||
emit(
|
||||
"item_edited",
|
||||
{
|
||||
"item_id": existing_item.id,
|
||||
"new_name": existing_item.name,
|
||||
"new_quantity": existing_item.quantity
|
||||
},
|
||||
to=str(list_id),
|
||||
)
|
||||
else:
|
||||
new_item = Item(
|
||||
list_id=list_id,
|
||||
name=name,
|
||||
quantity=quantity,
|
||||
added_by=current_user.id if current_user.is_authenticated else None,
|
||||
)
|
||||
db.session.add(new_item)
|
||||
|
||||
emit(
|
||||
"item_added",
|
||||
{
|
||||
"id": new_item.id,
|
||||
"name": new_item.name,
|
||||
"quantity": new_item.quantity,
|
||||
"added_by": (
|
||||
current_user.username if current_user.is_authenticated else "Gość"
|
||||
),
|
||||
},
|
||||
to=str(list_id),
|
||||
include_self=True,
|
||||
)
|
||||
if not SuggestedProduct.query.filter(func.lower(SuggestedProduct.name) == name.lower()).first():
|
||||
new_suggestion = SuggestedProduct(name=name)
|
||||
db.session.add(new_suggestion)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
emit(
|
||||
"item_added",
|
||||
{
|
||||
"id": new_item.id,
|
||||
"name": new_item.name,
|
||||
"quantity": new_item.quantity,
|
||||
"added_by": (
|
||||
current_user.username if current_user.is_authenticated else "Gość"
|
||||
),
|
||||
},
|
||||
to=str(list_id),
|
||||
include_self=True,
|
||||
)
|
||||
|
||||
# Aktualizacja postępu
|
||||
purchased_count, total_count, percent = get_progress(list_id)
|
||||
|
||||
emit(
|
||||
|
@@ -217,7 +217,7 @@ function applyHidePurchased(isInit = false) {
|
||||
}
|
||||
|
||||
function toggleVisibility(listId) {
|
||||
fetch('/toggle_visibility/' + listId, {method: 'POST'})
|
||||
fetch('/toggle_visibility/' + listId, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const shareHeader = document.getElementById('share-header');
|
||||
@@ -297,10 +297,9 @@ function updateListSmoothly(newItems) {
|
||||
}
|
||||
|
||||
// Klasy tła
|
||||
li.className = `list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item ${
|
||||
item.purchased ? 'bg-success text-white' :
|
||||
li.className = `list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item ${item.purchased ? 'bg-success text-white' :
|
||||
item.not_purchased ? 'bg-warning text-dark' : 'item-not-checked'
|
||||
}`;
|
||||
}`;
|
||||
|
||||
// HTML wewnętrzny
|
||||
li.innerHTML = `
|
||||
@@ -317,7 +316,7 @@ function updateListSmoothly(newItems) {
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
${item.not_purchased ? `
|
||||
<button type="button" class="btn btn-outline-success"
|
||||
<button type="button" class="btn btn-outline-light me-auto"
|
||||
onclick="unmarkNotPurchased(${item.id})">
|
||||
✅ Przywróć
|
||||
</button>
|
||||
@@ -334,11 +333,11 @@ function updateListSmoothly(newItems) {
|
||||
` : ''}
|
||||
`}
|
||||
${!window.IS_SHARE ? `
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="editItem(${item.id}, '${item.name.replace(/'/g, "\\'")}', ${item.quantity || 1})">
|
||||
✏️
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="deleteItem(${item.id})">
|
||||
🗑️
|
||||
</button>
|
||||
@@ -358,7 +357,7 @@ function updateListSmoothly(newItems) {
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const receiptSection = document.getElementById("receiptSection");
|
||||
const toggleBtn = document.querySelector('[data-bs-target="#receiptSection"]');
|
||||
|
||||
@@ -377,14 +376,14 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const toggle = document.getElementById('hidePurchasedToggle');
|
||||
if (!toggle) return;
|
||||
|
||||
const savedState = localStorage.getItem('hidePurchasedToggle');
|
||||
toggle.checked = savedState === 'true';
|
||||
applyHidePurchased(true);
|
||||
toggle.addEventListener('change', function() {
|
||||
toggle.addEventListener('change', function () {
|
||||
localStorage.setItem('hidePurchasedToggle', toggle.checked ? 'true' : 'false');
|
||||
applyHidePurchased();
|
||||
});
|
||||
|
@@ -144,11 +144,11 @@ function setupList(listId, username) {
|
||||
<span id="name-${data.id}" class="text-white">${data.name} ${quantityBadge}</span>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="editItem(${data.id}, '${data.name.replace(/'/g, "\\'")}', ${data.quantity || 1})">
|
||||
✏️
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="deleteItem(${data.id})">
|
||||
🗑️
|
||||
</button>
|
||||
@@ -156,6 +156,7 @@ function setupList(listId, username) {
|
||||
`;
|
||||
|
||||
document.getElementById('items').prepend(li);
|
||||
toggleEmptyPlaceholder();
|
||||
});
|
||||
|
||||
socket.on('item_deleted', data => {
|
||||
|
@@ -2,11 +2,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const modal = document.getElementById('massAddModal');
|
||||
const productList = document.getElementById('mass-add-list');
|
||||
|
||||
// Funkcja normalizacji (usuwa diakrytyki i zamienia na lowercase)
|
||||
function normalize(str) {
|
||||
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
modal.addEventListener('show.bs.modal', async function () {
|
||||
let addedProducts = new Set();
|
||||
document.querySelectorAll('#items li').forEach(li => {
|
||||
if (li.dataset.name) {
|
||||
addedProducts.add(li.dataset.name.toLowerCase());
|
||||
addedProducts.add(normalize(li.dataset.name));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,8 +25,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light';
|
||||
|
||||
if (addedProducts.has(name.toLowerCase())) {
|
||||
// Produkt już dodany — oznacz jako nieaktywny
|
||||
if (addedProducts.has(normalize(name))) {
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = name;
|
||||
li.appendChild(nameSpan);
|
||||
@@ -32,17 +36,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
badge.textContent = 'Dodano';
|
||||
li.appendChild(badge);
|
||||
} else {
|
||||
// Nazwa produktu
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = name;
|
||||
nameSpan.style.flex = '1 1 auto';
|
||||
li.appendChild(nameSpan);
|
||||
|
||||
// Kontener na minus, pole i plus
|
||||
const qtyWrapper = document.createElement('div');
|
||||
qtyWrapper.className = 'd-flex align-items-center ms-2 quantity-controls';
|
||||
|
||||
// Minus
|
||||
const minusBtn = document.createElement('button');
|
||||
minusBtn.type = 'button';
|
||||
minusBtn.className = 'btn btn-outline-light btn-sm px-2';
|
||||
@@ -51,18 +52,15 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
qty.value = Math.max(1, parseInt(qty.value) - 1);
|
||||
};
|
||||
|
||||
// Pole ilości
|
||||
const qty = document.createElement('input');
|
||||
qty.type = 'number';
|
||||
qty.min = 1;
|
||||
qty.value = 1;
|
||||
qty.className = 'form-control text-center p-1';
|
||||
qty.classList.add('rounded');
|
||||
qty.className = 'form-control text-center p-1 rounded';
|
||||
qty.style.width = '50px';
|
||||
qty.style.margin = '0 2px';
|
||||
qty.title = 'Ilość';
|
||||
|
||||
// Plus
|
||||
const plusBtn = document.createElement('button');
|
||||
plusBtn.type = 'button';
|
||||
plusBtn.className = 'btn btn-outline-light btn-sm px-2';
|
||||
@@ -75,7 +73,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
qtyWrapper.appendChild(qty);
|
||||
qtyWrapper.appendChild(plusBtn);
|
||||
|
||||
// Przycisk dodania
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-sm btn-primary ms-4';
|
||||
btn.textContent = '+';
|
||||
@@ -99,25 +96,19 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('#mass-add-list li').forEach(li => {
|
||||
const itemName = li.firstChild.textContent.trim();
|
||||
|
||||
if (itemName === data.name && !li.classList.contains('opacity-50')) {
|
||||
// Usuń wszystkie dzieci
|
||||
if (normalize(itemName) === normalize(data.name) && !li.classList.contains('opacity-50')) {
|
||||
while (li.firstChild) {
|
||||
li.removeChild(li.firstChild);
|
||||
}
|
||||
|
||||
// Ustaw nazwę
|
||||
li.textContent = data.name;
|
||||
|
||||
// Dodaj klasę wyszarzenia
|
||||
li.classList.add('opacity-50');
|
||||
|
||||
// Dodaj badge
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'badge bg-success ms-auto';
|
||||
badge.textContent = 'Dodano';
|
||||
li.appendChild(badge);
|
||||
|
||||
// Zablokuj kliknięcia
|
||||
li.onclick = null;
|
||||
}
|
||||
});
|
||||
|
@@ -84,7 +84,7 @@
|
||||
<label class="form-check-label ms-2" for="hidePurchasedToggle">Ukryj zaznaczone</label>
|
||||
</div>
|
||||
|
||||
<ul id="items" class="list-group mb-3">
|
||||
<ul id="items" class="list-group mb-3" data-is-share="{{ 'true' if is_share else 'false' }}">
|
||||
{% for item in items %}
|
||||
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
|
||||
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
|
||||
|
@@ -25,13 +25,12 @@
|
||||
<label class="form-check-label ms-2" for="hidePurchasedToggle">Ukryj zaznaczone</label>
|
||||
</div>
|
||||
|
||||
<ul id="items" class="list-group mb-3">
|
||||
<ul id="items" class="list-group mb-3" data-is-share="{{ 'true' if is_share else 'false' }}">
|
||||
{% for item in items %}
|
||||
|
||||
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
|
||||
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
|
||||
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}"
|
||||
data-is-share="{{ 'true' if is_share else 'false' }}">
|
||||
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}">
|
||||
|
||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||
|
||||
|
Reference in New Issue
Block a user