funkcja_niekupione #2

Merged
gru merged 14 commits from funkcja_niekupione into master 2025-07-18 22:07:29 +02:00
6 changed files with 71 additions and 59 deletions
Showing only changes of commit 6431393baf - Show all commits

72
app.py
View File

@@ -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(

View File

@@ -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();
});

View File

@@ -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 => {

View File

@@ -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;
}
});

View File

@@ -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

View File

@@ -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">