more changes
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user