more changes

This commit is contained in:
Mateusz Gruszczyński
2026-03-17 11:49:36 +01:00
parent 14a544c9c4
commit a299783a6c
13 changed files with 494 additions and 182 deletions

View File

@@ -1305,11 +1305,12 @@ def admin_settings():
for c in categories:
field = f"color_{c.id}"
vals = request.form.getlist(field)
val = (vals[-1] if vals else "").strip()
enabled_field = f"override_enabled_{c.id}"
val = (request.form.get(field) or "").strip()
override_enabled = (request.form.get(enabled_field) or "0").strip() == "1"
existing = CategoryColorOverride.query.filter_by(category_id=c.id).first()
if val and re.fullmatch(r"^#[0-9A-Fa-f]{6}$", val):
if override_enabled and val and re.fullmatch(r"^#[0-9A-Fa-f]{6}$", val):
if not existing:
db.session.add(CategoryColorOverride(category_id=c.id, color_hex=val))
else:

View File

@@ -815,6 +815,98 @@ td select.tom-dark {
color: var(--danger) !important;
}
.settings-category-card {
background: rgba(255,255,255,.03);
border: 1px solid rgba(255,255,255,.09);
border-radius: 16px;
padding: 1rem;
height: 100%;
}
.settings-category-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: .75rem;
}
.settings-category-name {
font-size: 1rem;
font-weight: 700;
line-height: 1.2;
}
.settings-override-badge {
white-space: nowrap;
}
.settings-color-controls {
display: flex;
align-items: stretch;
gap: .75rem;
}
.settings-color-controls .category-color {
width: 72px;
min-width: 72px;
height: auto;
padding: .35rem;
border-radius: 14px !important;
border: 1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.04);
}
.settings-color-actions {
display: flex;
flex: 1 1 auto;
}
.settings-color-actions .btn {
flex: 1 1 0;
min-height: 44px;
border-radius: 14px !important;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
}
.settings-color-actions .btn + .btn {
margin-left: .5rem;
}
@media (min-width: 992px) {
.settings-category-name {
font-size: 1.08rem;
}
}
@media (max-width: 767.98px) {
.settings-category-card {
padding: .9rem;
}
.settings-color-controls {
flex-direction: column;
}
.settings-color-controls .category-color {
width: 100%;
min-width: 0;
height: 48px;
}
.settings-color-actions {
flex-direction: column;
}
.settings-color-actions .btn + .btn {
margin-left: 0;
margin-top: .5rem;
}
}
/* ========== Kolorowe wskaźniki pod pickerem ========== */
.color-indicators .indicator {
display: grid;
@@ -3333,7 +3425,7 @@ input[type="checkbox"].form-check-input,
.endpoint-main_page .list-group-item > .main-list-row {
display: flex;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
width: 100%;
@@ -3348,14 +3440,16 @@ input[type="checkbox"].form-check-input,
.endpoint-main_page .list-main-title {
display: flex;
align-items: center;
align-content: center;
flex-wrap: wrap;
gap: 0.15rem;
min-width: 0;
line-height: 1;
}
.endpoint-main_page .list-main-actions {
flex: 0 0 auto;
align-self: flex-start;
align-self: center;
}
@media (max-width: 575.98px) {
@@ -3374,8 +3468,10 @@ input[type="checkbox"].form-check-input,
min-width: 0;
display: inline-flex;
align-items: center;
align-content: center;
flex-wrap: wrap;
gap: .15rem;
line-height: 1;
}
.shopping-item-row {
@@ -3445,7 +3541,7 @@ input[type="checkbox"].form-check-input,
@media (max-width: 575.98px) {
.endpoint-main_page .list-group-item > .main-list-row {
flex-direction: row;
align-items: flex-start;
align-items: center;
}
.endpoint-main_page .list-main-actions {
@@ -4742,3 +4838,220 @@ body.sorting-active .shopping-item-row .large-checkbox {
min-width: 44px !important;
}
}
/* final hotfix 2026-03-17: list/share parity, pending spinner, auth inputs */
.shopping-item-row {
position: relative;
}
.shopping-item-spinner {
position: absolute;
top: .7rem;
right: .7rem;
z-index: 2;
pointer-events: none;
}
.shopping-item-row.is-pending .shopping-item-actions {
opacity: .72;
}
.shopping-item-actions {
display: inline-flex;
flex: 0 0 auto;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-end;
gap: .35rem;
min-height: 2.35rem;
}
.shopping-action-btn {
display: inline-flex !important;
align-items: center;
justify-content: center;
width: 2.35rem;
height: 2.35rem;
min-width: 2.35rem;
padding: 0 !important;
line-height: 1;
border-radius: .7rem !important;
flex: 0 0 2.35rem;
}
.shopping-action-btn--wide {
width: auto;
min-width: 5.9rem;
padding: 0 .8rem !important;
flex: 0 0 auto;
}
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions,
.endpoint-list .shopping-item-actions {
min-height: 2.35rem;
}
.endpoint-list_share .shopping-action-btn,
.endpoint-shared_list .shopping-action-btn,
.endpoint-list .shopping-action-btn {
width: 2.35rem;
height: 2.35rem;
min-width: 2.35rem;
border-radius: .7rem !important;
}
.endpoint-list_share .shopping-action-btn--wide,
.endpoint-shared_list .shopping-action-btn--wide,
.endpoint-list .shopping-action-btn--wide {
width: auto;
min-width: 5.9rem;
}
@media (max-width: 575.98px) {
.shopping-item-spinner {
top: .55rem;
right: .55rem;
}
.shopping-action-btn,
.endpoint-list_share .shopping-action-btn,
.endpoint-shared_list .shopping-action-btn,
.endpoint-list .shopping-action-btn {
width: 2.15rem;
height: 2.15rem;
min-width: 2.15rem;
border-radius: .65rem !important;
}
.shopping-action-btn--wide,
.endpoint-list_share .shopping-action-btn--wide,
.endpoint-shared_list .shopping-action-btn--wide,
.endpoint-list .shopping-action-btn--wide {
width: auto;
min-width: 5.4rem;
padding: 0 .72rem !important;
}
}
.endpoint-login .card .form-control,
.endpoint-system_auth .card .form-control,
.endpoint-user_management .ui-password-group > .form-control,
.endpoint-user_management .modal .ui-password-group > .form-control {
min-height: 42px;
border-radius: 14px !important;
}
.endpoint-user_management .ui-password-group,
.endpoint-user_management .modal .ui-password-group {
display: flex !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
gap: 0 !important;
}
.endpoint-user_management .ui-password-group > .form-control,
.endpoint-user_management .modal .ui-password-group > .form-control {
flex: 1 1 auto !important;
width: auto !important;
max-width: none !important;
border-radius: 14px 0 0 14px !important;
border-right: 0 !important;
}
.endpoint-user_management .ui-password-group > .ui-password-toggle,
.endpoint-user_management .modal .ui-password-group > .ui-password-toggle {
appearance: none;
-webkit-appearance: none;
display: inline-flex !important;
align-items: center;
justify-content: center;
flex: 0 0 46px !important;
width: 46px !important;
min-width: 46px !important;
padding: 0 !important;
margin: 0 !important;
color: rgba(255,255,255,.78);
background: #1f2738 !important;
border: 1px solid var(--bs-border-color, #495057) !important;
border-left: 0 !important;
border-radius: 0 14px 14px 0 !important;
outline: none !important;
box-shadow: none !important;
line-height: 1;
font-size: 1rem;
}
.endpoint-user_management .ui-password-group > .ui-password-toggle:hover,
.endpoint-user_management .ui-password-group > .ui-password-toggle:focus,
.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:hover,
.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus {
color: #fff;
background: #253047 !important;
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,
.endpoint-list .shopping-item-actions {
gap: .35rem !important;
min-height: 2.35rem !important;
}
.endpoint-list_share .shopping-action-btn,
.endpoint-shared_list .shopping-action-btn,
.endpoint-view_list .shopping-action-btn,
.endpoint-list .shopping-action-btn {
width: 2.35rem !important;
height: 2.35rem !important;
min-width: 2.35rem !important;
min-height: 2.35rem !important;
padding: 0 !important;
border-radius: .7rem !important;
font-size: 1rem !important;
line-height: 1 !important;
}
.endpoint-list_share .shopping-action-btn--wide,
.endpoint-shared_list .shopping-action-btn--wide,
.endpoint-view_list .shopping-action-btn--wide,
.endpoint-list .shopping-action-btn--wide {
width: auto !important;
min-width: 5.9rem !important;
padding: 0 .8rem !important;
}
.endpoint-list_share .shopping-action-btn > *,
.endpoint-shared_list .shopping-action-btn > *,
.endpoint-view_list .shopping-action-btn > *,
.endpoint-list .shopping-action-btn > * {
line-height: 1 !important;
}
@media (max-width: 575.98px) {
.endpoint-list_share .shopping-action-btn,
.endpoint-shared_list .shopping-action-btn,
.endpoint-view_list .shopping-action-btn,
.endpoint-list .shopping-action-btn {
width: 2.15rem !important;
height: 2.15rem !important;
min-width: 2.15rem !important;
min-height: 2.15rem !important;
border-radius: .65rem !important;
}
.endpoint-list_share .shopping-action-btn--wide,
.endpoint-shared_list .shopping-action-btn--wide,
.endpoint-view_list .shopping-action-btn--wide,
.endpoint-list .shopping-action-btn--wide {
min-width: 5.4rem !important;
padding: 0 .72rem !important;
}
}
body:not(.sorting-active) .drag-handle {
display: none !important;
}

View File

@@ -1,130 +1,114 @@
(function () {
const form = document.getElementById("settings-form");
const resetAllBtn = document.getElementById("reset-all");
if (!form) return;
function ensureHiddenClear(input) {
let hidden = input.parentElement.querySelector(`input[type="hidden"][name="${input.name}"]`);
if (!hidden) {
hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = input.name;
hidden.value = "";
input.parentElement.appendChild(hidden);
}
function getCard(input) {
return input.closest(".settings-category-card");
}
function removeHiddenClear(input) {
const hidden = input.parentElement.querySelector(`input[type="hidden"][name="${input.name}"]`);
if (hidden) hidden.remove();
function getAutoHex(input) {
const autoHex = (input.dataset.auto || "").trim();
return autoHex ? autoHex.toUpperCase() : "#000000";
}
function setOverrideState(input, enabled) {
const card = getCard(input);
const flag = card?.querySelector('.override-enabled');
const badge = card?.querySelector('[data-role="override-status"]');
input.dataset.hasOverride = enabled ? "1" : "0";
if (flag) flag.value = enabled ? "1" : "0";
if (badge) {
badge.textContent = enabled ? "Nadpisany" : "Domyślny";
badge.classList.toggle('text-bg-info', enabled);
badge.classList.toggle('text-bg-secondary', !enabled);
}
}
function updatePreview(input) {
const card = input.closest(".col-12, .col-md-6, .col-lg-4");
const hexAutoEl = card.querySelector(".hex-auto");
const hexEffEl = card.querySelector(".hex-effective");
const barAuto = card.querySelector('.bar[data-kind="auto"]');
const barEff = card.querySelector('.bar[data-kind="effective"]');
const card = getCard(input);
if (!card) return;
const hexAutoEl = card.querySelector('.hex-auto');
const hexEffEl = card.querySelector('.hex-effective');
const barAuto = card.querySelector('.bar[data-kind="auto"]');
const barEff = card.querySelector('.bar[data-kind="effective"]');
const autoHex = getAutoHex(input);
const effectiveHex = ((input.value || autoHex).trim() || autoHex).toUpperCase();
const hasOverride = input.dataset.hasOverride === '1';
const raw = (input.value || "").trim();
const autoHex = hexAutoEl.textContent.trim();
const effHex = (raw || autoHex).toUpperCase();
if (barEff) barEff.style.backgroundColor = effHex;
if (hexEffEl) hexEffEl.textContent = effHex;
if (!raw) {
ensureHiddenClear(input);
input.disabled = true;
} else {
removeHiddenClear(input);
input.disabled = false;
}
if (barAuto) barAuto.style.backgroundColor = autoHex;
if (hexAutoEl) hexAutoEl.textContent = autoHex;
if (barEff) barEff.style.backgroundColor = effectiveHex;
if (hexEffEl) hexEffEl.textContent = effectiveHex;
setOverrideState(input, hasOverride);
}
form.querySelectorAll(".use-default").forEach(btn => {
btn.addEventListener("click", () => {
const name = btn.getAttribute("data-target");
const input = form.querySelector(`input[name="${name}"]`);
if (!input) return;
input.value = "";
updatePreview(input);
});
});
form.querySelectorAll(".reset-one").forEach(btn => {
btn.addEventListener("click", () => {
const name = btn.getAttribute("data-target");
const input = form.querySelector(`input[name="${name}"]`);
if (!input) return;
input.value = "";
updatePreview(input);
});
});
resetAllBtn?.addEventListener("click", () => {
form.querySelectorAll('input[type="color"].category-color').forEach(input => {
input.value = "";
updatePreview(input);
});
});
form.querySelectorAll('input[type="color"].category-color').forEach(input => {
function applyDefaultVisual(input, keepOverride) {
input.value = getAutoHex(input);
setOverrideState(input, !!keepOverride);
updatePreview(input);
input.addEventListener("input", () => updatePreview(input));
input.addEventListener("change", () => updatePreview(input));
});
}
form.addEventListener("submit", () => {
form.querySelectorAll('input[type="color"].category-color').forEach(updatePreview);
});
form.querySelectorAll(".use-default").forEach(btn => {
btn.addEventListener("click", () => {
const name = btn.getAttribute("data-target");
const input = form.querySelector(`input[name="${name}"]`);
form.querySelectorAll('.use-default').forEach((btn) => {
btn.addEventListener('click', () => {
const input = form.querySelector(`#${btn.dataset.target}`);
if (!input) return;
applyDefaultVisual(input, true);
});
});
const card = input.closest(".col-12, .col-md-6, .col-lg-4") || input.closest(".col-12");
let autoHex = (input.dataset.auto || "").trim();
if (!autoHex && card) {
autoHex = (card.querySelector(".hex-auto")?.textContent || "").trim();
}
if (autoHex && !autoHex.startsWith("#")) autoHex = `#${autoHex}`;
form.querySelectorAll('.reset-one').forEach((btn) => {
btn.addEventListener('click', () => {
const input = form.querySelector(`#${btn.dataset.target}`);
if (!input) return;
applyDefaultVisual(input, false);
});
});
if (autoHex) {
input.disabled = false;
removeHiddenClear(input);
input.value = autoHex;
updatePreview(input);
}
resetAllBtn?.addEventListener('click', () => {
form.querySelectorAll('input[type="color"].category-color').forEach((input) => {
applyDefaultVisual(input, false);
});
});
form.querySelectorAll('input[type="color"].category-color').forEach((input) => {
updatePreview(input);
input.addEventListener('input', () => {
setOverrideState(input, true);
updatePreview(input);
});
input.addEventListener('change', () => {
setOverrideState(input, true);
updatePreview(input);
});
});
(function () {
const slider = document.getElementById("ocr_sensitivity");
const badge = document.getElementById("ocr_sens_badge");
const value = document.getElementById("ocr_sens_value");
const slider = document.getElementById('ocr_sensitivity');
const badge = document.getElementById('ocr_sens_badge');
const value = document.getElementById('ocr_sens_value');
if (!slider || !badge || !value) return;
function labelFor(v) {
v = Number(v);
if (v <= 3) return "Niski";
if (v <= 7) return "Średni";
return "Wysoki";
if (v <= 3) return 'Niski';
if (v <= 7) return 'Średni';
return 'Wysoki';
}
function clsFor(v) {
v = Number(v);
if (v <= 3) return "sens-low";
if (v <= 7) return "sens-mid";
return "sens-high";
if (v <= 3) return 'sens-low';
if (v <= 7) return 'sens-mid';
return 'sens-high';
}
function update() {
value.textContent = `(${slider.value})`;
badge.textContent = labelFor(slider.value);
badge.classList.remove("sens-low","sens-mid","sens-high");
badge.classList.remove('sens-low', 'sens-mid', 'sens-high');
badge.classList.add(clsFor(slider.value));
}
slider.addEventListener("input", update);
slider.addEventListener("change", update);
slider.addEventListener('input', update);
slider.addEventListener('change', update);
update();
})();
})();

View File

@@ -25,16 +25,15 @@ document.addEventListener("DOMContentLoaded", () => {
}
checkbox.disabled = true;
row.classList.add('opacity-50');
row.classList.add('opacity-50', 'is-pending');
// Dodaj spinner tylko jeśli nie ma
let existingSpinner = row.querySelector('.spinner-border');
let existingSpinner = row.querySelector('.shopping-item-spinner');
if (!existingSpinner) {
const spinner = document.createElement('span');
spinner.className = 'spinner-border spinner-border-sm ms-2';
spinner.className = 'shopping-item-spinner spinner-border spinner-border-sm';
spinner.setAttribute('role', 'status');
spinner.setAttribute('aria-hidden', 'true');
checkbox.parentElement.appendChild(spinner);
row.appendChild(spinner);
}
});
});

View File

@@ -4,7 +4,7 @@ function updateItemState(itemId, isChecked) {
checkbox.checked = isChecked;
checkbox.disabled = false;
const li = checkbox.closest('li');
li.classList.remove('opacity-50', 'bg-light', 'text-dark', 'bg-success', 'text-white');
li.classList.remove('opacity-50', 'is-pending', 'bg-light', 'text-dark', 'bg-success', 'text-white', 'bg-warning', 'item-not-checked');
if (isChecked) {
li.classList.add('bg-success', 'text-white');
@@ -12,8 +12,7 @@ function updateItemState(itemId, isChecked) {
li.classList.add('item-not-checked');
}
const sp = li.querySelector('.spinner-border');
if (sp) sp.remove();
li.querySelectorAll('.shopping-item-spinner, .spinner-border').forEach(sp => sp.remove());
}
updateProgressBar();
applyHidePurchased();
@@ -294,7 +293,7 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
}`;
const isOwner = window.IS_OWNER === true || window.IS_OWNER === 'true';
const allowEdit = !isShare || showEditOnly || isOwner;
const isArchived = window.IS_ARCHIVED === true || window.IS_ARCHIVED === 'true';
const safeName = escapeHtml(item.name || '');
const nameForEdit = JSON.stringify(String(item.name || ''));
const quantity = Number.isInteger(item.quantity) ? item.quantity : parseInt(item.quantity, 10) || 1;
@@ -302,7 +301,10 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
? `<span class="badge rounded-pill bg-secondary">x${quantity}</span>`
: '';
const checkboxHtml = `<input id="checkbox-${item.id}" class="large-checkbox" type="checkbox" ${item.purchased ? 'checked' : ''} ${item.not_purchased ? 'disabled' : ''}>`;
const canEditListItem = !isShare;
const canShowShareActions = isShare && !showEditOnly;
const canMarkNotPurchased = !item.not_purchased && !isArchived;
const checkboxHtml = `<input id="checkbox-${item.id}" class="large-checkbox" type="checkbox" ${item.purchased ? 'checked' : ''} ${(item.not_purchased || isArchived) ? 'disabled' : ''}>`;
const infoParts = [];
if (item.note) {
@@ -319,35 +321,28 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
? `<span class="info-line small" id="info-${item.id}">${infoParts.join(' ')}</span>`
: '';
const iconBtn = 'btn btn-outline-light btn-sm shopping-action-btn';
const wideBtn = 'btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide';
let actionButtons = '';
if (!isShare) {
if (canEditListItem) {
actionButtons += `
<button type="button" class="btn btn-outline-light btn-sm drag-handle" title="Przesuń produkt" aria-label="Przesuń produkt">☰</button>`;
}
if (allowEdit) {
actionButtons += `
<button type="button" class="btn btn-outline-light btn-sm"
onclick="editItem(${item.id}, ${nameForEdit}, ${quantity})">✏️</button>
<button type="button" class="btn btn-outline-light btn-sm"
onclick="deleteItem(${item.id})">🗑️</button>`;
<button type="button" class="${iconBtn} drag-handle" title="Przesuń produkt" aria-label="Przesuń produkt" ${isArchived ? 'disabled' : ''}>☰</button>
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="editItem(${item.id}, ${nameForEdit}, ${quantity})"`}>✏️</button>
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="deleteItem(${item.id})"`}>🗑️</button>`;
}
if (item.not_purchased) {
actionButtons += `
<button type="button" class="btn btn-outline-light btn-sm"
onclick="unmarkNotPurchased(${item.id})">✅ Przywróć</button>`;
} else if (isOwner || (isShare && !showEditOnly)) {
<button type="button" class="${wideBtn}" ${isArchived ? 'disabled' : `onclick="unmarkNotPurchased(${item.id})"`}>✅ Przywróć</button>`;
} else if (!isShare || canShowShareActions || isOwner) {
actionButtons += `
<button type="button" class="btn btn-outline-light btn-sm"
onclick="markNotPurchasedModal(event, ${item.id})">⚠️</button>`;
<button type="button" class="${iconBtn}" ${canMarkNotPurchased ? `onclick="markNotPurchasedModal(event, ${item.id})"` : 'disabled'}>⚠️</button>`;
}
if (isShare && !showEditOnly && !isOwner) {
if (canShowShareActions) {
actionButtons += `
<button type="button" class="btn btn-outline-light btn-sm"
onclick="openNoteModal(event, ${item.id})">📝</button>`;
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="openNoteModal(event, ${item.id})"`}>📝</button>`;
}
li.innerHTML = `
@@ -360,7 +355,7 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
${quantityBadge}
${infoHtml}
</div>
<div class="d-flex align-items-center list-item-actions shopping-item-actions" role="group">
<div class="list-item-actions shopping-item-actions" role="group">
${actionButtons}
</div>
</div>

View File

@@ -88,15 +88,15 @@ function setupList(listId, username) {
}
e.target.disabled = true;
li.classList.add('opacity-50');
li.classList.add('opacity-50', 'is-pending');
let existingSpinner = li.querySelector('.spinner-border');
let existingSpinner = li.querySelector('.shopping-item-spinner');
if (!existingSpinner) {
const spinner = document.createElement('span');
spinner.className = 'spinner-border spinner-border-sm ms-2';
spinner.className = 'shopping-item-spinner spinner-border spinner-border-sm';
spinner.setAttribute('role', 'status');
spinner.setAttribute('aria-hidden', 'true');
e.target.parentElement.appendChild(spinner);
li.appendChild(spinner);
}
}
}
@@ -139,7 +139,7 @@ function setupList(listId, username) {
note: ''
};
const li = renderItem(item, false, true); // ← tryb 15s
const li = renderItem(item, window.IS_SHARE, true);
document.getElementById('items').appendChild(li);
toggleEmptyPlaceholder();
updateProgressBar();
@@ -176,7 +176,7 @@ function setupList(listId, username) {
setTimeout(() => {
const existing = document.getElementById(`item-${data.id}`);
if (existing) {
const updated = renderItem(item, true);
const updated = renderItem(item, window.IS_SHARE);
existing.replaceWith(updated);
}
}, 15000);

View File

@@ -5,7 +5,6 @@ function enableSortMode() {
if (isSorting) return;
isSorting = true;
window.isSorting = true;
localStorage.setItem('sortModeEnabled', 'true');
const itemsContainer = document.getElementById('items');
const listId = window.LIST_ID;
@@ -57,7 +56,6 @@ function disableSortMode() {
}
isSorting = false;
localStorage.removeItem('sortModeEnabled');
window.isSorting = false;
if (window.currentItems) {
updateListSmoothly(window.currentItems);
@@ -88,8 +86,8 @@ function updateSortButtonUI(active) {
}
document.addEventListener('DOMContentLoaded', () => {
const wasSorting = localStorage.getItem('sortModeEnabled') === 'true';
if (wasSorting) {
enableSortMode();
}
isSorting = false;
window.isSorting = false;
document.body.classList.remove('sorting-active');
updateSortButtonUI(false);
});

View File

@@ -66,34 +66,44 @@
{% set hex_auto = auto_colors[c.id] %}
{% set hex_effective = effective_colors[c.id] %}
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label d-block mb-2">{{ c.name }}</label>
<div class="settings-category-card h-100">
<div class="settings-category-header mb-2">
<label class="form-label d-block mb-0 settings-category-name" for="color_{{ c.id }}">{{ c.name }}</label>
<span class="badge settings-override-badge {{ 'text-bg-info' if hex_override else 'text-bg-secondary' }}" data-role="override-status">
{{ 'Nadpisany' if hex_override else 'Domyślny' }}
</span>
</div>
<div class="input-group">
<input
type="color"
class="form-control form-control-color category-color"
name="color_{{ c.id }}"
value="{{ hex_override or '' }}"
data-auto="{{ hex_auto }}"
{% if not hex_override %}data-empty="1"{% endif %}
aria-label="Kolor kategorii {{ c.name }}"
>
<input type="hidden" name="override_enabled_{{ c.id }}" value="{{ '1' if hex_override else '0' }}" class="override-enabled">
<div class="btn-group" role="group" aria-label="Akcje koloru">
<button type="button"
class="btn btn-outline-light btn-sm reset-one"
data-target="color_{{ c.id }}">
🔄 Reset
</button>
<button type="button"
class="btn btn-outline-light btn-sm use-default"
data-target="color_{{ c.id }}">
🎯 Przywróć domyślny
</button>
</div>
</div>
<div class="settings-color-controls">
<input
type="color"
class="form-control form-control-color category-color"
id="color_{{ c.id }}"
name="color_{{ c.id }}"
value="{{ hex_effective }}"
data-auto="{{ hex_auto }}"
data-effective="{{ hex_effective }}"
data-has-override="{{ '1' if hex_override else '0' }}"
aria-label="Kolor kategorii {{ c.name }}"
>
<div class="color-indicators mt-2">
<div class="settings-color-actions" role="group" aria-label="Akcje koloru">
<button type="button"
class="btn btn-outline-light btn-sm reset-one"
data-target="color_{{ c.id }}">
🔄 Wyczyść nadpisanie
</button>
<button type="button"
class="btn btn-outline-light btn-sm use-default"
data-target="color_{{ c.id }}">
🎯 Ustaw kolor domyślny
</button>
</div>
</div>
<div class="color-indicators mt-3">
<div class="indicator">
<span class="badge text-bg-dark me-2">Efektywny</span>
<span class="bar" data-kind="effective" style="background-color: {{ hex_effective }};"></span>
@@ -104,6 +114,7 @@
<span class="bar" data-kind="auto" style="background-color: {{ hex_auto }};"></span>
<span class="hex hex-auto ms-2">{{ hex_auto|upper }}</span>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@@ -22,8 +22,10 @@
</div>
<div class="col-md-4">
<label for="password" class="form-label text-white-50">Hasło</label>
<input type="password" id="password" name="password"
class="form-control bg-dark text-white border-secondary rounded" placeholder="min. 6 znaków" required>
<div class="input-group ui-password-group">
<input type="password" id="password" name="password"
class="form-control bg-dark text-white border-secondary rounded" placeholder="min. 6 znaków" required>
</div>
</div>
<div class="col-md-4 d-grid">
<button type="submit" class="btn btn-outline-light"> Dodaj użytkownika</button>
@@ -105,8 +107,10 @@
</div>
<div class="modal-body">
<p id="resetUsernameLabel">Dla użytkownika: <strong></strong></p>
<input type="password" name="password" placeholder="Nowe hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
<div class="input-group ui-password-group">
<input type="password" name="password" placeholder="Nowe hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
</div>
<div class="modal-footer border-0">
<button type="submit" class="btn btn-sm btn-outline-light w-100">💾 Zapisz nowe hasło</button>

View File

@@ -129,18 +129,18 @@
</div>
<div class="list-item-actions shopping-item-actions" role="group">
{% if not is_share %}
<button type="button" class="btn btn-outline-light btn-sm drag-handle" title="Przesuń produkt" aria-label="Przesuń produkt" {% if list.is_archived %}disabled{% endif %}></button>
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn drag-handle" title="Przesuń produkt" aria-label="Przesuń produkt" {% if list.is_archived %}disabled{% endif %}></button>
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn" {% if list.is_archived %}disabled{% else
%}onclick="editItem({{ item.id }}, '{{ item.name }}', {{ item.quantity or 1 }})" {% endif %}>✏️</button>
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn" {% if list.is_archived %}disabled{% else
%}onclick="deleteItem({{ item.id }})" {% endif %}>🗑️</button>
{% endif %}
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide" {% if list.is_archived %}disabled{% else
%}onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>✅ Przywróć</button>
{% elif not item.not_purchased %}
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn" {% if list.is_archived %}disabled{% else
%}onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>⚠️</button>
{% endif %}
</div>
@@ -369,6 +369,7 @@
const isShare = document.getElementById('items').dataset.isShare === 'true';
window.IS_SHARE = isShare;
window.LIST_ID = {{ list.id }};
window.IS_ARCHIVED = {{ 'true' if list.is_archived else 'false' }};
window.IS_OWNER = {{ 'true' if is_owner else 'false' }};
</script>
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.js') }}?v={{ APP_VERSION }}"></script>

View File

@@ -66,18 +66,18 @@
</div>
<div class="list-item-actions shopping-item-actions" role="group">
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else %}
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide" {% if list.is_archived %}disabled{% else %}
onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>
✅ Przywróć
</button>
{% else %}
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else %}
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn" {% if list.is_archived %}disabled{% else %}
onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>
⚠️
</button>
{% endif %}
<button type="button" class="btn btn-outline-light btn-sm" {% if list.is_archived %}disabled{% else %}
<button type="button" class="btn btn-outline-light btn-sm shopping-action-btn" {% if list.is_archived %}disabled{% else %}
onclick="openNoteModal(event, {{ item.id }})" {% endif %}>
📝
</button>
@@ -230,6 +230,8 @@
const isShare = document.getElementById('items').dataset.isShare === 'true';
window.IS_SHARE = isShare;
window.LIST_ID = {{ list.id }};
window.IS_ARCHIVED = {{ 'true' if list.is_archived else 'false' }};
window.IS_OWNER = {{ 'true' if (current_user.is_authenticated and list.user_id == current_user.id) else 'false' }};
if (typeof isSorting === 'undefined') {
var isSorting = false;
}

View File

@@ -14,8 +14,10 @@
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
<div class="mb-3">
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
<div class="input-group ui-password-group">
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
</div>
<button type="submit" class="btn btn-success w-100">🔑 Zaloguj</button>
</form>

View File

@@ -11,8 +11,10 @@
<div class="card-body">
<form method="post">
<div class="mb-3">
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
<div class="input-group ui-password-group">
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
</div>
<button type="submit" class="btn btn-success w-100">🔓 Wejdź</button>
</form>