fix in prev.

This commit is contained in:
Mateusz Gruszczyński
2026-03-30 14:49:41 +02:00
parent 36a1378429
commit 9ca2f8f7ea
4 changed files with 165 additions and 80 deletions

View File

@@ -54,15 +54,7 @@
/* =========================================================
Utilities & Sizes
========================================================= */
/*
Main structure of this file:
1. Design tokens / utilities
2. Bootstrap overrides
3. Forms / tables / toasts / modals
4. Shared layout components
5. Endpoint-specific sections
6. Responsive fixes and hotfixes
*/
.large-checkbox {
width: 1.5em;
height: 1.5em;
@@ -192,7 +184,7 @@ input[type="file"]::file-selector-button {
}
/* =========================================================
Forms (inputs, selects, switches, placeholders)
Forms
========================================================= */
.form-select,
.form-control,
@@ -4920,8 +4912,6 @@ body.sorting-active .shopping-item-row .large-checkbox {
box-shadow: none !important;
}
/* v14 fixes: share/list action parity + sort handle visibility */
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions,
.endpoint-view_list .shopping-item-actions,
@@ -5603,7 +5593,6 @@ body:not(.sorting-active) .drag-handle {
}
}
/* --- Main page progress summary cards --- */
.endpoint-main_page #mainStatsCollapse.collapsing,
.endpoint-main_page #mainStatsCollapse.show {
overflow: visible;
@@ -5729,3 +5718,71 @@ body:not(.sorting-active) .drag-handle {
margin-top: 0 !important;
border-top-width: 1px !important;
}
/* =========================================================
Preview product list
========================================================= */
.preview-product-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.preview-product-summary {
padding: 0 0 0.85rem;
margin-bottom: 0.1rem;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.preview-product-section {
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.preview-product-section-title {
margin: 0;
font-size: 1.05rem;
font-weight: 700;
}
.preview-modal-items {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
#productPreviewModal .preview-modal-list-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
width: 100%;
min-width: 0;
padding: 0.9rem 1rem;
margin: 0 !important;
border-radius: 16px !important;
border: 1px solid rgba(255,255,255,0.08) !important;
background: linear-gradient(180deg, rgba(11,22,40,0.92) 0%, rgba(8,16,30,0.92) 100%) !important;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
}
#productPreviewModal .preview-modal-list-item:first-child,
#productPreviewModal .preview-modal-list-item:last-child,
#productPreviewModal .list-group-flush > .list-group-item:first-child,
#productPreviewModal .list-group-flush > .list-group-item:last-child {
border-radius: 16px !important;
}
#productPreviewModal .preview-modal-list-item__name {
min-width: 0;
overflow-wrap: anywhere;
flex: 1 1 auto;
}
#productPreviewModal .preview-modal-list-item .badge {
flex-shrink: 0;
min-width: 2.5rem;
border-radius: 10px;
}

View File

@@ -1,6 +1,69 @@
document.addEventListener("DOMContentLoaded", function () {
const modalElement = document.getElementById("productPreviewModal");
if (!modalElement || typeof bootstrap === "undefined") return;
const modal = new bootstrap.Modal(modalElement);
const modalTitle = document.getElementById("previewModalLabel");
const productList = document.getElementById("product-list");
if (!modalTitle || !productList) return;
const renderState = (message, extraClass = "text-white") => {
productList.innerHTML = "";
const wrapper = document.createElement("div");
wrapper.className = "preview-modal-items";
const item = document.createElement("div");
item.className = `preview-modal-list-item ${extraClass}`.trim();
item.textContent = message;
wrapper.appendChild(item);
productList.appendChild(wrapper);
};
const createSection = (titleText) => {
const section = document.createElement("section");
section.className = "preview-product-section";
const title = document.createElement("h6");
title.className = "preview-product-section-title";
title.textContent = titleText;
const items = document.createElement("div");
items.className = "preview-modal-items";
section.appendChild(title);
section.appendChild(items);
return { section, items };
};
const createItem = (itemData) => {
const row = document.createElement("div");
row.className = "preview-modal-list-item";
const name = document.createElement("span");
name.className = "preview-modal-list-item__name";
name.textContent = itemData.name;
const badge = document.createElement("span");
badge.className = "badge";
if (itemData.purchased) {
badge.classList.add("bg-success");
} else if (itemData.not_purchased) {
badge.classList.add("bg-warning", "text-dark");
} else {
badge.classList.add("bg-secondary");
}
badge.textContent = `x${itemData.quantity}`;
row.appendChild(name);
row.appendChild(badge);
return row;
};
modalElement.addEventListener("hidden.bs.modal", function () {
document.querySelectorAll(".modal-backdrop").forEach((el) => el.remove());
@@ -11,101 +74,66 @@ document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".preview-btn").forEach((btn) => {
btn.addEventListener("click", async () => {
const listId = btn.dataset.listId;
const modalTitle = document.getElementById("previewModalLabel");
const productList = document.getElementById("product-list");
modalTitle.textContent = "Ładowanie...";
productList.innerHTML = `
<li class="list-group-item bg-dark text-white">
⏳ Ładowanie produktów...
</li>`;
renderState("⏳ Ładowanie produktów...");
modal.show();
try {
const res = await fetch(`/admin/list_items/${listId}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
const totalCount = Number(data.total_count || 0);
const purchasedCount = Number(data.purchased_count || 0);
const totalExpense = Number(data.total_expense || 0);
const percent = totalCount > 0 ? Math.round((purchasedCount / totalCount) * 100) : 0;
modalTitle.textContent = `🛒 ${data.title}`;
productList.innerHTML = "";
// 🔢 PODSUMOWANIE
const summary = document.createElement("div");
summary.className = "mb-3";
const percent =
data.total_count > 0
? Math.round((data.purchased_count / data.total_count) * 100)
: 0;
summary.className = "preview-product-summary";
summary.innerHTML = `
<p class="mb-1">📦 <strong>${data.total_count}</strong> produktów</p>
<p class="mb-1">✅ Kupione: <strong>${data.purchased_count}</strong> (${percent}%)</p>
<p class="mb-1">💸 Wydatek: <strong>${data.total_expense.toFixed(2)} zł</strong></p>
<hr class="my-2">
`;
<p class="mb-1">📦 <strong>${totalCount}</strong> produktów</p>
<p class="mb-1">✅ Kupione: <strong>${purchasedCount}</strong> (${percent}%)</p>
<p class="mb-0">💸 Wydatek: <strong>${totalExpense.toFixed(2)} zł</strong></p>`;
productList.appendChild(summary);
// 🛒 LISTY PRODUKTÓW
const purchasedList = document.createElement("ul");
purchasedList.className = "list-group list-group-flush mb-3";
const notPurchasedList = document.createElement("ul");
notPurchasedList.className = "list-group list-group-flush";
const purchased = createSection("✔️ Kupione");
const pending = createSection("🚫 Niekupione / Nieoznaczone");
let hasPurchased = false;
let hasUnpurchased = false;
let hasPending = false;
data.items.forEach((item) => {
const li = document.createElement("li");
li.className =
"list-group-item bg-dark text-white d-flex justify-content-between";
li.innerHTML = `
<span>${item.name}</span>
<span class="badge ${item.purchased
? "bg-success"
: item.not_purchased
? "bg-warning text-dark"
: "bg-secondary"
}">
x${item.quantity}
</span>`;
(data.items || []).forEach((item) => {
const row = createItem(item);
if (item.purchased) {
purchasedList.appendChild(li);
purchased.items.appendChild(row);
hasPurchased = true;
} else {
notPurchasedList.appendChild(li);
hasUnpurchased = true;
pending.items.appendChild(row);
hasPending = true;
}
});
if (hasPurchased) {
const h5 = document.createElement("h6");
h5.textContent = "✔️ Kupione";
productList.appendChild(h5);
productList.appendChild(purchasedList);
productList.appendChild(purchased.section);
}
if (hasUnpurchased) {
const h5 = document.createElement("h6");
h5.textContent = "🚫 Niekupione / Nieoznaczone";
productList.appendChild(h5);
productList.appendChild(notPurchasedList);
if (hasPending) {
productList.appendChild(pending.section);
}
if (!hasPurchased && !hasUnpurchased) {
productList.innerHTML = `
<li class="list-group-item bg-dark text-muted fst-italic">
Brak produktów
</li>`;
if (!hasPurchased && !hasPending) {
renderState("Brak produktów", "text-muted fst-italic");
}
} catch (err) {
} catch (error) {
modalTitle.textContent = "Błąd";
productList.innerHTML = `
<li class="list-group-item bg-dark text-danger">
❌ Błąd podczas ładowania
</li>`;
renderState("❌ Błąd podczas ładowania", "text-danger");
}
});
});

View File

@@ -328,7 +328,7 @@
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body">
<ul id="product-list" class="list-group list-group-flush"></ul>
<div id="product-list" class="preview-product-list"></div>
</div>
</div>
</div>
@@ -341,7 +341,7 @@
checkboxes.forEach(cb => cb.checked = this.checked);
});
</script>
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}?v=3"></script>
{% endblock %}
{% endblock %}

View File

@@ -137,7 +137,7 @@
aria-label="Zamknij"></button>
</div>
<div class="modal-body">
<ul id="product-list" class="list-group list-group-flush"></ul>
<div id="product-list" class="preview-product-list"></div>
</div>
</div>
</div>
@@ -146,6 +146,6 @@
{% endblock %}
{% block scripts %}
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}?v=3"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'categories_select_admin.js') }}"></script>
{% endblock %}