5 Commits

Author SHA1 Message Date
Mateusz Gruszczyński
68f235d605 fix w sugestiach i js 2025-08-15 23:29:13 +02:00
Mateusz Gruszczyński
ea46dd43e1 fix w sugestiach 2025-08-15 23:03:26 +02:00
Mateusz Gruszczyński
4b99b109bd fix w sugestiach 2025-08-15 22:29:40 +02:00
Mateusz Gruszczyński
028ae3c26e fix w sugestiach 2025-08-15 22:25:22 +02:00
Mateusz Gruszczyński
71b14411e5 usuniecie zbednego kodu i poprawki 2025-08-15 15:54:40 +02:00
10 changed files with 139 additions and 111 deletions

32
app.py
View File

@@ -960,6 +960,12 @@ def get_active_months_query(visible_lists_query=None):
return [row.month for row in active_months]
def normalize_name(name):
if not name:
return ""
return re.sub(r'\s+', ' ', name).strip().lower()
############# OCR ###########################
@@ -2849,23 +2855,20 @@ def list_products():
seen_names = set()
unique_items = []
for item in all_items:
key = (item.name or "").strip().lower()
key = normalize_name(item.name)
if key not in seen_names:
unique_items.append(item)
seen_names.add(key)
stmt = (
select(
func.lower(func.trim(Item.name)).label("name_lower"),
func.coalesce(func.sum(Item.quantity), 0).label("qty_sum"),
usage_counts = dict(
db.session.query(
func.lower(Item.name),
func.coalesce(func.sum(Item.quantity), 0)
)
.where(Item.name.isnot(None))
.group_by(func.lower(func.trim(Item.name)))
.group_by(func.lower(Item.name))
.all()
)
rows = db.session.execute(stmt).all()
usage_counts = {name_lower: qty_sum for name_lower, qty_sum in rows}
total_items = len(unique_items)
total_pages = (total_items + per_page - 1) // per_page
start = (page - 1) * per_page
@@ -2877,12 +2880,13 @@ def list_products():
users_dict = {u.id: u.username for u in users}
suggestions = SuggestedProduct.query.all()
all_suggestions_dict = {
(s.name or "").strip().lower(): s
normalize_name(s.name): s
for s in suggestions
if s.name and s.name.strip()
}
used_suggestion_names = {(i.name or "").strip().lower() for i in unique_items}
used_suggestion_names = {normalize_name(i.name) for i in unique_items}
suggestions_dict = {
name: all_suggestions_dict[name]
@@ -2896,6 +2900,7 @@ def list_products():
query_string = urlencode({k: v for k, v in request.args.items() if k != "page"})
synced_names = set(suggestions_dict.keys())
return render_template(
"admin/list_products.html",
@@ -2908,7 +2913,8 @@ def list_products():
total_pages=total_pages,
query_string=query_string,
total_items=total_items,
usage_counts=usage_counts
usage_counts=usage_counts,
synced_names=synced_names
)

View File

@@ -1,73 +1,91 @@
function bindSyncButton(button) {
button.addEventListener('click', function (e) {
e.preventDefault();
const itemId = button.getAttribute('data-item-id');
button.disabled = true;
fetch(`/admin/sync_suggestion/${itemId}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
showToast(data.message, data.success ? 'success' : 'danger');
if (data.success) {
button.innerText = '✅ Zsynchronizowano';
button.classList.remove('btn-outline-primary');
button.classList.add('btn-success');
} else {
button.disabled = false;
}
})
.catch(() => {
showToast('Błąd synchronizacji', 'danger');
button.disabled = false;
});
});
}
function bindDeleteButton(button) {
button.addEventListener('click', function (e) {
e.preventDefault();
const suggestionId = button.getAttribute('data-suggestion-id');
const row = button.closest('tr');
const itemId = button.getAttribute('data-item-id');
const nameBadge = row?.querySelector('.badge.bg-primary');
const itemName = nameBadge?.innerText.trim().toLowerCase();
button.disabled = true;
fetch(`/admin/delete_suggestion/${suggestionId}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
showToast(data.message, data.success ? 'success' : 'danger');
if (!data.success || !row) {
button.disabled = false;
return;
}
const isProductRow = typeof itemId === 'string' && itemId !== '';
const cell = row.querySelector('td:last-child');
if (!cell) return;
if (isProductRow) {
cell.innerHTML = `<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="${itemId}">🔄 Synchronizuj</button>`;
const syncBtn = cell.querySelector('.sync-btn');
if (syncBtn) bindSyncButton(syncBtn);
} else {
cell.innerHTML = '<span class="badge rounded-pill bg-warning opacity-75">Usunięto synchronizacje</span>';
}
})
.catch(() => {
showToast('Błąd usuwania sugestii', 'danger');
button.disabled = false;
});
});
}
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll('.sync-btn').forEach(btn => {
btn.replaceWith(btn.cloneNode(true));
});
document.querySelectorAll('.delete-suggestion-btn').forEach(btn => {
btn.replaceWith(btn.cloneNode(true));
});
document.querySelectorAll('.sync-btn').forEach(btn => {
btn.addEventListener('click', function (e) {
e.preventDefault();
const itemId = this.getAttribute('data-item-id');
const button = this;
button.disabled = true;
fetch(`/admin/sync_suggestion/${itemId}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
showToast(data.message, data.success ? 'success' : 'danger');
if (data.success) {
button.innerText = '✅ Zsynchronizowano';
button.classList.remove('btn-outline-primary');
button.classList.add('btn-success');
} else {
button.disabled = false;
}
})
.catch(() => {
showToast('Błąd synchronizacji', 'danger');
button.disabled = false;
});
});
const clone = btn.cloneNode(true);
btn.replaceWith(clone);
bindSyncButton(clone);
});
document.querySelectorAll('.delete-suggestion-btn').forEach(btn => {
btn.addEventListener('click', function (e) {
e.preventDefault();
const suggestionId = this.getAttribute('data-suggestion-id');
const button = this;
button.disabled = true;
fetch(`/admin/delete_suggestion/${suggestionId}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
showToast(data.message, data.success ? 'success' : 'danger');
if (data.success) {
const row = button.closest('tr');
if (row) row.remove();
} else {
button.disabled = false;
}
})
.catch(() => {
showToast('Błąd usuwania sugestii', 'danger');
button.disabled = false;
});
});
const clone = btn.cloneNode(true);
btn.replaceWith(clone);
bindDeleteButton(clone);
});
});

View File

@@ -224,11 +224,11 @@
<td>
{% if l.is_archived %}
<span class="badge bg-secondary">Archiwalna</span>
<span class="badge rounded-pill bg-secondary">Archiwalna</span>
{% elif e.expired %}
<span class="badge bg-warning text-dark">Wygasła</span>
<span class="badge rounded-pill bg-warning text-dark">Wygasła</span>
{% else %}
<span class="badge bg-success">Aktywna</span>
<span class="badge rounded-pill bg-success">Aktywna</span>
{% endif %}
</td>
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
@@ -250,8 +250,8 @@
</div>
</div>
</td>
<td><span class="badge bg-primary">{{ e.comments_count }}</span></td>
<td><span class="badge bg-secondary">{{ e.receipts_count }}</span></td>
<td><span class="badge rounded-pill bg-primary">{{ e.comments_count }}</span></td>
<td><span class="badge rounded-pill bg-secondary">{{ e.receipts_count }}</span></td>
<td class="fw-bold
{% if e.total_expense >= 500 %}text-danger
{% elif e.total_expense > 0 %}text-success{% endif %}">

View File

@@ -11,7 +11,7 @@
<div class="card-body">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="m-0">📦 Produkty (z synchronizacją sugestii o unikalnych nazwach)</h4>
<span class="badge bg-info">{{ total_items }} produktów</span>
<span class="badge rounded-pill bg-info">{{ total_items }} produktów</span>
</div>
<div class="card-body p-0">
<table class="table table-dark table-striped align-middle sortable">
@@ -28,7 +28,7 @@
{% for item in items %}
<tr>
<td>{{ item.id }}</td>
<td class="fw-bold"><span class="badge bg-primary">{{ item.name }}</span></td>
<td class="fw-bold"><span class="badge rounded-pill bg-primary">{{ item.name }}</span></td>
<td>
{% if item.added_by and users_dict.get(item.added_by) %}
👤 {{ users_dict[item.added_by] }} ({{ item.added_by }})
@@ -36,15 +36,16 @@
-
{% endif %}
</td>
<td><span class="badge bg-secondary">{{ usage_counts.get(item.name.lower(), 0) }}</span></td>
<td><span class="badge rounded-pill bg-secondary">{{ usage_counts.get(item.name.lower(), 0) }}</span></td>
<td>
{% set suggestion = suggestions_dict.get(item.name.lower()) %}
{% set clean_name = item.name | replace('\xa0', ' ') | trim | lower %}
{% set suggestion = suggestions_dict.get(clean_name) %}
{% if suggestion %}
✅ Istnieje (ID: {{ suggestion.id }})
<button class="btn btn-sm btn-outline-danger ms-1 delete-suggestion-btn"
<button class="btn btn-sm btn-outline-light ms-1 delete-suggestion-btn"
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
{% else %}
<button class="btn btn-sm btn-outline-primary sync-btn" data-item-id="{{ item.id }}">🔄
<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="{{ item.id }}">🔄
Synchronizuj</button>
{% endif %}
</td>
@@ -65,7 +66,7 @@
<div class="card-body">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="m-0">💡 Wszystkie sugestie (poza powiązanymi)</h4>
<span class="badge bg-info">{{ orphan_suggestions|length }} sugestii</span>
<span class="badge rounded-pill bg-info">{{ orphan_suggestions|length }} sugestii</span>
</div>
<div class="card-body p-0">
{% set item_names = items | map(attribute='name') | map('lower') | list %}
@@ -82,9 +83,9 @@
{% if suggestion.name.lower() not in item_names %}
<tr>
<td>{{ suggestion.id }}</td>
<td class="fw-bold"><span class="badge bg-primary">{{ suggestion.name }}</span></td>
<td class="fw-bold"><span class="badge rounded-pill bg-primary">{{ suggestion.name }}</span></td>
<td>
<button class="btn btn-sm btn-outline-danger delete-suggestion-btn"
<button class="btn btn-sm btn-outline-light delete-suggestion-btn"
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
</td>
</tr>

View File

@@ -46,11 +46,14 @@
</td>
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
<td>
{% if l.is_archived %}<span class="badge bg-secondary">Archiwalna</span>{% endif %}
{% if l.is_temporary %}<span class="badge bg-warning text-dark">Tymczasowa</span>{%
{% if l.is_archived %}<span class="badge rounded-pill bg-secondary">Archiwalna</span>{%
endif %}
{% if l.is_public %}<span class="badge bg-success">Publiczna</span>{% else %}
<span class="badge bg-dark">Prywatna</span>{% endif %}
{% if l.is_temporary %}<span
class="badge rounded-pill bg-warning text-dark">Tymczasowa</span>{%
endif %}
{% if l.is_public %}<span class="badge rounded-pill bg-success">Publiczna</span>{% else
%}
<span class="badge rounded-pill bg-dark">Prywatna</span>{% endif %}
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-info preview-btn"

View File

@@ -47,9 +47,9 @@
<td class="fw-bold">{{ user.username }}</td>
<td>
{% if user.is_admin %}
<span class="badge bg-primary">Admin</span>
<span class="badge rounded-pill bg-primary">Admin</span>
{% else %}
<span class="badge bg-secondary">Użytkownik</span>
<span class="badge rounded-pill bg-secondary">Użytkownik</span>
{% endif %}
</td>
<td>

View File

@@ -42,12 +42,12 @@
{% if current_user.is_authenticated %}
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Zalogowany:</span>
<span class="badge bg-success">{{ current_user.username }}</span>
<span class="badge rounded-pill bg-success">{{ current_user.username }}</span>
</div>
{% else %}
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Przeglądasz jako</span>
<span class="badge bg-info">gość</span>
<span class="badge rounded-pill bg-info">gość</span>
</div>
{% endif %}
{% endif %}

View File

@@ -60,7 +60,7 @@
<div class="col-md-6">
<label class="form-label">📆 Utworzono:</label>
<p class="form-control-plaintext text-white">
<span class="badge bg-success rounded-pill text-dark ms-1">
<span class="badge rounded-pill bg-success rounded-pill text-dark ms-1">
{{ list.created_at.strftime('%Y-%m-%d') }}
</span>
</p>

View File

@@ -6,12 +6,12 @@
<h2 class="mb-2">
Lista: <strong>{{ list.title }}</strong>
{% if list.is_archived %}
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
<span class="badge rounded-pill bg-secondary ms-2">(Archiwalna)</span>
{% endif %}
{% if list.category_badges %}
{% for cat in list.category_badges %}
<span class="badge rounded-pill text-dark ms-1" style="background-color: {{ cat.color }};
<span class="badge rounded-pill rounded-pill text-dark ms-1" style="background-color: {{ cat.color }};
font-size: 0.75rem;
opacity: 0.85;">
{{ cat.name }}
@@ -41,7 +41,7 @@
🙈 Lista jest ukryta przed gośćmi
{% endif %}
</strong>
<span id="share-url" class="badge bg-secondary text-wrap"
<span id="share-url" class="badge rounded-pill bg-secondary text-wrap"
style="font-size: 0.7rem; {% if not list.is_public %}display: none;{% endif %}">
{{ request.url_root }}share/{{ list.share_token }}
</span>
@@ -120,7 +120,7 @@
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span class="badge bg-secondary">x{{ item.quantity }}</span>
<span class="badge rounded-pill bg-secondary">x{{ item.quantity }}</span>
{% endif %}
</span>

View File

@@ -6,15 +6,15 @@
🛍️ {{ list.title }}
{% if list.is_archived %}
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
<span class="badge rounded-pill bg-secondary ms-2">(Archiwalna)</span>
{% endif %}
{% if total_expense > 0 %}
<span id="total-expense1" class="badge bg-success ms-2">
<span id="total-expense1" class="badge rounded-pill bg-success ms-2">
💸 {{ '%.2f'|format(total_expense) }} PLN
</span>
{% else %}
<span id="total-expense" class="badge bg-secondary ms-2" style="display: none;">
<span id="total-expense" class="badge rounded-pill bg-secondary ms-2" style="display: none;">
💸 0.00 PLN
</span>
{% endif %}
@@ -22,7 +22,7 @@
{# Kategorie - tylko wyświetlenie, bez linków #}
{% if list.category_badges %}
{% for cat in list.category_badges %}
<span class="badge rounded-pill text-dark ms-1" style="background-color: {{ cat.color }};
<span class="badge rounded-pill rounded-pill text-dark ms-1" style="background-color: {{ cat.color }};
font-size: 0.75rem;
opacity: 0.85;">
{{ cat.name }}
@@ -53,7 +53,7 @@
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span class="badge bg-secondary">x{{ item.quantity }}</span>
<span class="badge rounded-pill bg-secondary">x{{ item.quantity }}</span>
{% endif %}
</span>