5 Commits

Author SHA1 Message Date
Mateusz Gruszczyński
9ca2f8f7ea fix in prev. 2026-03-30 14:49:41 +02:00
Mateusz Gruszczyński
36a1378429 hotfixes 2026-03-30 10:12:29 +02:00
Mateusz Gruszczyński
84b4a5b482 fix w css list 2026-03-29 13:40:13 +02:00
Mateusz Gruszczyński
a4d3da1d64 fix text position 2026-03-27 11:50:13 +01:00
Mateusz Gruszczyński
e14ea5445e fix text position 2026-03-27 11:50:01 +01:00
10 changed files with 290 additions and 6623 deletions

View File

@@ -594,9 +594,9 @@ def view_list(list_id):
percent = (purchased_count / total_count * 100) if total_count > 0 else 0
for item in items:
if item.added_by != shopping_list.owner_id:
if item.added_by and item.added_by != shopping_list.owner_id:
item.added_by_display = (
item.added_by_user.username if item.added_by_user else "?"
item.added_by_user.username if item.added_by_user else None
)
else:
item.added_by_display = None

View File

@@ -54,6 +54,7 @@
/* =========================================================
Utilities & Sizes
========================================================= */
.large-checkbox {
width: 1.5em;
height: 1.5em;
@@ -183,7 +184,7 @@ input[type="file"]::file-selector-button {
}
/* =========================================================
Forms (inputs, selects, switches, placeholders)
Forms
========================================================= */
.form-select,
.form-control,
@@ -559,8 +560,9 @@ td select.tom-dark {
justify-content: space-between;
}
.list-group-item:first-child,
.list-group-item:last-child {
/* Zachowaj ostre krawędzie tylko dla list flush (np. w panelach edycji) */
.list-group-flush > .list-group-item:first-child,
.list-group-flush > .list-group-item:last-child {
border-radius: 0 !important;
}
@@ -972,7 +974,7 @@ td select.tom-dark {
/* 421576px: lekko ciaśniej, ale tekst zostaje */
@media (min-width: 421px) and (max-width: 576px) {
.btn-group-compact .btn {
padding: 0.25rem 0.5rem;
padding: 0.24rem 0.45rem;
font-size: 0.82rem;
line-height: 1.1;
}
@@ -982,19 +984,6 @@ td select.tom-dark {
}
}
/* Medium-narrow screens */
@media (min-width: 421px) and (max-width: 576px) {
.btn-group-compact .btn {
padding: 0.24rem 0.45rem; /* ciaśniej */
font-size: 0.82rem;
line-height: 1.1;
}
.btn-group-compact .btn-text {
font-size: 0.75rem;
}
}
/* ================================================
RESPONSIVE NAVBAR
@@ -2554,18 +2543,6 @@ form[data-unsaved-warning="true"].is-dirty::after {
border-bottom-left-radius: 0 !important;
}
.app-navbar .container-xxl {
row-gap: .55rem;
}
.app-navbar__actions {
min-width: 0;
}
.app-navbar__actions .btn {
min-width: 0;
}
@media (max-width: 767.98px) {
.table-responsive {
overflow-x: auto !important;
@@ -2976,48 +2953,23 @@ input[type="checkbox"].form-check-input,
white-space: nowrap;
cursor: pointer;
}
.share-page-toolbar {
gap: .75rem;
}
.share-page-toolbar__spacer {
flex: 1 1 auto;
}
.endpoint-list_share .list-item-actions,
.endpoint-shared_list .list-item-actions {
gap: .5rem;
}
.endpoint-list_share .list-item-actions .btn,
.endpoint-shared_list .list-item-actions .btn {
min-width: 2.75rem;
min-height: 2.5rem;
padding: .5rem .72rem;
}
.endpoint-list_share .app-navbar__actions .btn,
.endpoint-shared_list .app-navbar__actions .btn {
border-radius: .9rem !important;
}
@media (max-width: 767.98px) {
.create-list-input-group {
flex-wrap: nowrap !important;
}
.create-list-input-group > .form-control {
min-width: 0;
}
.create-list-input-group > #tempToggle {
min-width: 8.75rem;
font-size: .92rem;
}
.share-page-toolbar {
justify-content: flex-end;
}
.share-page-toolbar__spacer {
display: none;
}
.hide-purchased-switch {
padding-left: 2.95rem;
}
}
/* unified bootstrap-like switches */
.switch-grid {
@@ -3084,17 +3036,7 @@ input[type="checkbox"].form-check-input,
width: 100%;
}
@media (max-width: 767.98px) {
.switch-grid {
grid-template-columns: 1fr;
}
.hide-purchased-switch.form-check.app-switch {
width: 100%;
}
}
/* consolidation pass 2026-03-30: merged safe duplicates and overlapping hotfixes */
/* final UX polish 2026-03-14 */
:root {
--nav-btn-height: 2.8rem;
@@ -3105,6 +3047,7 @@ input[type="checkbox"].form-check-input,
align-items: center;
justify-content: space-between;
gap: .8rem;
row-gap: .55rem;
flex-wrap: nowrap;
}
@@ -3122,6 +3065,7 @@ input[type="checkbox"].form-check-input,
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: var(--nav-btn-height);
padding: .6rem .95rem;
white-space: nowrap;
@@ -3216,7 +3160,9 @@ input[type="checkbox"].form-check-input,
display: block;
}
/* share page toolbar and header buttons */
/* =========================================================
Share page toolbar and shared-list header actions
========================================================= */
.share-page-toolbar {
display: flex;
align-items: center;
@@ -3225,6 +3171,10 @@ input[type="checkbox"].form-check-input,
width: 100%;
}
.share-page-toolbar__spacer {
flex: 1 1 auto;
}
.share-page-toolbar .form-check {
margin-bottom: 0;
}
@@ -3288,10 +3238,19 @@ input[type="checkbox"].form-check-input,
justify-content: stretch;
}
.share-page-toolbar__spacer {
display: none;
}
.hide-purchased-switch {
padding-left: 2.95rem;
}
.hide-purchased-switch.form-check.app-switch {
width: 100%;
}
.switch-grid,
.endpoint-edit_my_list .switch-grid {
grid-template-columns: 1fr;
}
@@ -4142,6 +4101,9 @@ input[type="checkbox"].form-check-input,
}
/* =========================================================
Shopping item rows: shared base alignment + mobile fixes
========================================================= */
/* v10.2 item row consistency and mobile share fixes */
.shopping-item-text {
line-height: 1.35;
@@ -4429,111 +4391,10 @@ body.sorting-active .shopping-item-row .large-checkbox {
}
/* hotfix 2026-03-15 v3: /share item layout parity with /list */
/* hotfix 2026-03-15: /share item layout parity with /list */
.endpoint-list_share .shopping-item-row,
.endpoint-shared_list .shopping-item-row {
overflow: hidden;
}
.endpoint-list_share .shopping-item-main,
.endpoint-shared_list .shopping-item-main {
align-items: center;
}
.endpoint-list_share .shopping-item-content,
.endpoint-shared_list .shopping-item-content {
flex: 1 1 auto;
min-width: 0;
width: 100%;
}
.endpoint-list_share .shopping-item-head,
.endpoint-shared_list .shopping-item-head {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
column-gap: .65rem;
row-gap: .35rem;
}
.endpoint-list_share .shopping-item-text,
.endpoint-shared_list .shopping-item-text {
display: flex;
flex-wrap: wrap;
align-items: center;
align-content: center;
min-width: 0;
width: 100%;
gap: .35rem;
}
.endpoint-list_share .shopping-item-name,
.endpoint-shared_list .shopping-item-name {
display: inline;
min-width: 0;
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.endpoint-list_share .shopping-item-text .info-line,
.endpoint-shared_list .shopping-item-text .info-line {
display: block;
flex: 0 0 100%;
width: 100%;
margin-top: .15rem;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions {
display: inline-flex;
flex-wrap: nowrap;
align-items: center;
align-self: center;
justify-content: flex-end;
margin-left: 0;
white-space: nowrap;
}
.endpoint-list_share .shopping-item-actions .btn,
.endpoint-shared_list .shopping-item-actions .btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
.endpoint-list_share .shopping-item-main > .large-checkbox,
.endpoint-shared_list .shopping-item-main > .large-checkbox,
.endpoint-view_list .shopping-item-main > .large-checkbox {
align-self: center;
}
@media (max-width: 575.98px) {
.endpoint-list_share .shopping-item-head,
.endpoint-shared_list .shopping-item-head {
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
}
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions {
align-self: start;
gap: .3rem;
}
.endpoint-list_share .shopping-item-actions .btn,
.endpoint-shared_list .shopping-item-actions .btn {
min-width: 2.15rem;
padding: .32rem .45rem;
}
}
/* hotfix 2026-03-15 v4: /share parity with /list */
.endpoint-list_share .shopping-item-row,
.endpoint-shared_list .shopping-item-row {
padding: .8rem .95rem;
}
@@ -4605,8 +4466,10 @@ body.sorting-active .shopping-item-row .large-checkbox {
flex: 0 0 auto;
flex-wrap: nowrap;
align-items: center;
align-self: center;
justify-content: flex-end;
gap: .35rem;
margin-left: 0;
white-space: nowrap;
}
@@ -4620,16 +4483,12 @@ body.sorting-active .shopping-item-row .large-checkbox {
}
.endpoint-list_share .shopping-item-main > .large-checkbox,
.endpoint-shared_list .shopping-item-main > .large-checkbox {
.endpoint-shared_list .shopping-item-main > .large-checkbox,
.endpoint-view_list .shopping-item-main > .large-checkbox {
flex: 0 0 auto;
align-self: center;
}
.endpoint-list_share input[type="checkbox"].large-checkbox::before,
.endpoint-shared_list input[type="checkbox"].large-checkbox::before {
font-size: 1.75em;
}
@media (max-width: 575.98px) {
.endpoint-list_share .shopping-item-main,
.endpoint-shared_list .shopping-item-main {
@@ -4653,6 +4512,7 @@ body.sorting-active .shopping-item-row .large-checkbox {
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions {
align-self: start;
width: auto;
margin-left: auto;
gap: .25rem;
@@ -4665,6 +4525,11 @@ body.sorting-active .shopping-item-row .large-checkbox {
}
}
.endpoint-list_share input[type="checkbox"].large-checkbox::before,
.endpoint-shared_list input[type="checkbox"].large-checkbox::before {
font-size: 1.75em;
}
/* mobile menu simplification 2026-03-15 */
.app-mobile-menu {
display: flex;
@@ -5047,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,
@@ -5116,6 +4979,8 @@ body.sorting-active .shopping-item-row .large-checkbox {
padding: 0 .72rem !important;
}
}
@media (max-width: 575.98px) {
.endpoint-list_share .shopping-action-btn--countdown,
.endpoint-shared_list .shopping-action-btn--countdown,
.endpoint-view_list .shopping-action-btn--countdown,
@@ -5130,74 +4995,9 @@ body:not(.sorting-active) .drag-handle {
}
/* final hotfix 2026-03-17: consistent password toggle on auth/admin */
.ui-password-group {
display: flex !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
gap: 0 !important;
width: 100%;
}
.ui-password-group > .form-control {
flex: 1 1 auto !important;
width: 1% !important;
min-width: 0 !important;
max-width: none !important;
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-right: 0 !important;
}
.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;
background: var(--dark-700) !important;
color: var(--text-strong) !important;
border: 1px solid var(--dark-300) !important;
border-left: 0 !important;
border-top-right-radius: 14px !important;
border-bottom-right-radius: 14px !important;
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
box-shadow: none !important;
outline: none !important;
line-height: 1;
transition: background-color .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease;
}
.ui-password-group > .ui-password-toggle:hover,
.ui-password-group > .ui-password-toggle:focus,
.ui-password-group > .ui-password-toggle:focus-visible {
background: var(--dark-800) !important;
color: #fff !important;
border-color: var(--primary) !important;
box-shadow: 0 0 0 .25rem rgba(24, 64, 118, .18) !important;
}
.ui-password-group > .ui-password-toggle.is-active {
background: #2a3550 !important;
color: #fff !important;
}
@media (max-width: 575.98px) {
.ui-password-group > .ui-password-toggle {
flex-basis: 44px !important;
width: 44px !important;
min-width: 44px !important;
}
}
/* final hotfix 2026-03-17b: password toggle parity on login/system-auth/admin-users */
/* =========================================================
Password toggle group: shared final version for login/auth/admin
========================================================= */
.ui-password-group {
display: flex !important;
flex-wrap: nowrap !important;
@@ -5646,7 +5446,9 @@ body:not(.sorting-active) .drag-handle {
}
/* mobile user chip 2026-03-19 */
/* =========================================================
Mobile navbar and user chip
========================================================= */
.app-navbar__meta--mobile {
display: none;
}
@@ -5666,16 +5468,28 @@ body:not(.sorting-active) .drag-handle {
}
@media (max-width: 991.98px) {
.app-navbar .container-xxl {
grid-template-columns: minmax(0, 1fr) auto auto;
}
.app-navbar__meta--mobile {
display: flex !important;
width: auto;
justify-content: flex-end;
justify-self: end;
grid-column: 2;
min-width: 0;
max-width: min(42vw, 12rem);
}
.app-user-chip--mobile {
display: inline-flex;
}
.app-mobile-menu {
grid-column: 3;
justify-self: end;
}
}
@media (max-width: 575.98px) {
@@ -5700,26 +5514,6 @@ body:not(.sorting-active) .drag-handle {
}
}
/* mobile navbar layout fix 2026-03-19 */
@media (max-width: 991.98px) {
.app-navbar .container-xxl {
grid-template-columns: minmax(0, 1fr) auto auto;
}
.app-navbar__meta--mobile {
grid-column: 2;
justify-self: end;
min-width: 0;
max-width: min(42vw, 12rem);
}
.app-mobile-menu {
grid-column: 3;
justify-self: end;
}
}
@media (max-width: 430px) {
.app-navbar .container-xxl {
grid-template-columns: minmax(0, 1fr) auto auto;
@@ -5799,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;
@@ -5875,3 +5668,121 @@ body:not(.sorting-active) .drag-handle {
justify-self: end;
}
}
/* v15 2026-03-27: /share mobile row text parity with /list */
@media (max-width: 575.98px) {
.endpoint-list_share .shopping-item-main,
.endpoint-shared_list .shopping-item-main {
align-items: center;
}
.endpoint-list_share .shopping-item-text,
.endpoint-shared_list .shopping-item-text,
.endpoint-list_share .shopping-item-main > .large-checkbox,
.endpoint-shared_list .shopping-item-main > .large-checkbox,
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions {
align-self: center;
}
.endpoint-list_share .shopping-item-actions,
.endpoint-shared_list .shopping-item-actions {
margin-left: auto;
justify-self: auto;
}
}
/* hotfix 2026-03-30: first list item visual parity on /list and /share */
.endpoint-list #items,
.endpoint-view_list #items,
.endpoint-list_share #items,
.endpoint-shared_list #items {
display: flex;
flex-direction: column;
}
.endpoint-list #items > .list-group-item,
.endpoint-view_list #items > .list-group-item,
.endpoint-list_share #items > .list-group-item,
.endpoint-shared_list #items > .list-group-item {
margin: 0 !important;
border-width: 1px !important;
box-shadow: 0 4px 14px rgba(0,0,0,0.12) !important;
background-clip: padding-box;
}
.endpoint-list #items > .list-group-item + .list-group-item,
.endpoint-view_list #items > .list-group-item + .list-group-item,
.endpoint-list_share #items > .list-group-item + .list-group-item,
.endpoint-shared_list #items > .list-group-item + .list-group-item {
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;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,416 +0,0 @@
/* --- Rozmiary i kursory --- */
.large-checkbox {
width: 1.5em;
height: 1.5em;
}
.clickable-item {
cursor: pointer;
}
/* --- Kolory tła (nadpisane klasy Bootstrapa) --- */
.bg-success {
background-color: #1e7e34 !important;
}
.btn-outline-light:hover {
background-color: #ffc107 !important;
color: #000 !important;
border-color: #ffc107 !important;
}
.progress-dark {
background-color: #212529 !important;
border-radius: 20px !important;
overflow: hidden;
}
.progress-bar {
border-radius: 0 !important;
transition: width 0.4s ease, background-color 0.4s ease;
}
.progress-bar:first-child {
border-top-left-radius: 20px !important;
border-bottom-left-radius: 20px !important;
}
.progress-bar:last-child {
border-top-right-radius: 20px !important;
border-bottom-right-radius: 20px !important;
}
/* rodzic już ma position-relative */
.progress-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
/* klikalne przyciski obok paska nie ucierpią */
white-space: nowrap;
}
.progress-thin {
height: 12px;
}
.item-not-checked {
background-color: #2c2f33 !important;
color: white !important;
}
/* --- Styl przycisku wyboru pliku --- */
input[type="file"]::file-selector-button {
background-color: #225d36;
color: #fff;
border: none;
padding: 0.5em 1em;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
/* --- Ciemniejsze alerty Bootstrapa --- */
.alert-success {
background-color: #225d36 !important;
color: #eaffea !important;
border-color: #174428 !important;
}
.alert-danger {
background-color: #7a1f23 !important;
color: #ffeaea !important;
border-color: #531417 !important;
}
.alert-info {
background-color: #1d3a4d !important;
color: #eaf6ff !important;
border-color: #152837 !important;
}
.alert-warning {
background-color: #665c1e !important;
color: #fffbe5 !important;
border-color: #4d4415 !important;
}
/* Badge - kolory pasujące do ciemnych alertów */
.badge.bg-success,
.badge.text-bg-success {
background-color: #225d36 !important;
color: #eaffea !important;
}
.badge.bg-danger,
.badge.text-bg-danger {
background-color: #7a1f23 !important;
color: #ffeaea !important;
}
.badge.bg-info,
.badge.text-bg-info {
background-color: #1d3a4d !important;
color: #eaf6ff !important;
}
.badge.bg-warning,
.badge.text-bg-warning {
background-color: #665c1e !important;
color: #fffbe5 !important;
}
.badge.bg-secondary,
.badge.text-bg-secondary {
background-color: #343a40 !important;
color: #e2e3e5 !important;
}
.badge.bg-primary,
.badge.text-bg-primary {
background-color: #184076 !important;
color: #e6f0ff !important;
}
.badge.bg-light,
.badge.text-bg-light {
background-color: #444950 !important;
color: #f8f9fa !important;
}
.badge.bg-dark,
.badge.text-bg-dark {
background-color: #181a1b !important;
color: #f8f9fa !important;
}
/* --- Styl dla własnych checkboxów --- */
input[type="checkbox"].large-checkbox {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 1.5em;
height: 1.5em;
margin: 0;
padding: 0;
outline: none;
background: none;
cursor: pointer;
position: relative;
vertical-align: middle;
}
input[type="checkbox"].large-checkbox::before {
content: '✗';
color: #dc3545;
font-size: 1.5em;
font-weight: bold;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
line-height: 1;
transition: color 0.2s;
}
input[type="checkbox"].large-checkbox:checked::before {
content: '✓';
color: #ffffff;
}
input[type="checkbox"].large-checkbox:disabled::before {
opacity: 0.5;
cursor: not-allowed;
}
input[type="checkbox"].large-checkbox:disabled {
cursor: not-allowed;
}
#tempToggle {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
input.form-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.info-bar-fixed {
width: 100%;
color: #f8f9fa;
background-color: #212529;
border-radius: 12px 12px 0 0;
text-align: center;
padding: 10px 10px;
font-size: 0.95rem;
box-sizing: border-box;
margin-top: 2rem;
box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.25);
}
@media (max-width: 768px) {
.info-bar-fixed {
position: static;
font-size: 0.85rem;
padding: 8px 4px;
border-radius: 0;
}
}
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table-responsive table {
min-width: 1000px;
}
.bg-dark .form-control::placeholder {
color: #ccc !important;
opacity: 1;
}
.toast-body {
color: #ffffff !important;
font-weight: 500 !important;
}
.toast {
animation: fadeInUp 0.5s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#mass-add-list li.active {
background: #198754 !important;
color: #fff !important;
border: 1px solid #000000 !important;
}
#mass-add-list li {
transition: background 0.2s;
}
.quantity-input {
width: 60px;
background: #343a40;
color: #fff;
border: 1px solid #495057;
border-radius: 4px;
text-align: center;
}
.add-btn {
margin-left: 10px;
}
.quantity-controls {
min-width: 120px;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
.list-group-item {
display: flex;
align-items: center;
justify-content: space-between;
}
#empty-placeholder {
font-style: italic;
pointer-events: none;
}
#items li.hide-purchased {
display: none !important;
}
.list-group-item:first-child,
.list-group-item:last-child {
border-radius: 0 !important;
}
.fade-out {
opacity: 0;
transition: opacity 0.5s ease;
}
@media (pointer: fine) {
.only-mobile {
display: none !important;
}
}
.ts-dropdown .active {
background-color: #495057 !important;
}
.pagination-dark .page-link {
color: #fff;
background-color: #212529;
border: 1px solid #495057;
}
.pagination-dark .page-link:hover {
background-color: #343a40;
border-color: #6c757d;
color: #fff;
}
.pagination-dark .page-item.active .page-link {
background-color: #0d6efd;
border-color: #0d6efd;
color: #fff;
}
.pagination-dark .page-item.disabled .page-link {
background-color: #2b3035;
border-color: #495057;
color: #6c757d;
}
.tom-dark .ts-control {
background-color: #212529 !important;
color: #fff !important;
border: 1px solid #495057 !important;
border-radius: 0.375rem;
min-height: 38px;
padding: 0.25rem 0.5rem;
box-sizing: border-box;
}
.tom-dark .ts-control .item {
background-color: #343a40 !important;
color: #fff !important;
border-radius: 0.25rem;
padding: 2px 8px;
margin-right: 4px;
}
.ts-dropdown {
background-color: #212529 !important;
color: #fff !important;
border: 1px solid #495057;
border-radius: 0.375rem;
z-index: 9999 !important;
max-height: 300px;
overflow-y: auto;
}
.ts-dropdown .active {
background-color: #495057 !important;
color: #fff !important;
}
td select.tom-dark {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.table-dark.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(255, 255, 255, 0.025);
}
.table-dark tbody tr:hover {
background-color: rgba(255, 255, 255, 0.04);
}
.table-dark thead th {
background-color: #1c1f22;
color: #e1e1e1;
font-weight: 500;
border-bottom: 1px solid #3a3f44;
}
.table-dark td,
.table-dark th {
padding: 0.6rem 0.75rem;
vertical-align: middle;
border-top: 1px solid #3a3f44;
}
.card .table {
border-radius: 0 !important;
overflow: hidden;
margin-bottom: 0;
}

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

@@ -2,30 +2,32 @@
{% set purchased_count = purchased_count or 0 %}
{% set not_purchased_count = not_purchased_count or 0 %}
{% set accounted_count = purchased_count + not_purchased_count %}
{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
{% set purchased_percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
{% set not_purchased_percent = (not_purchased_count / total_count * 100) if total_count > 0 else 0 %}
{% set remaining_count = (total_count - accounted_count) if total_count > accounted_count else 0 %}
{% set remaining_percent = (remaining_count / total_count * 100) if total_count > 0 else 100 %}
{% set percent = ((purchased_count / total_count) * 100) if total_count > 0 else 0 %}
{% set purchased_percent = ((purchased_count / total_count) * 100) if total_count > 0 else 0 %}
{% set not_purchased_percent = ((not_purchased_count / total_count) * 100) if total_count > 0 else 0 %}
{% set remaining_percent = ((remaining_count / total_count) * 100) if total_count > 0 else 0 %}
<div class="main-list-progress-wrap mt-2">
<div class="main-list-progress progress progress-dark progress-thin position-relative"
aria-label="Postęp listy {{ purchased_count }} z {{ total_count }} kupionych">
<div class="progress-bar bg-success" role="progressbar"
style="width: {{ purchased_percent }}%"
aria-valuemin="0" aria-valuemax="100"></div>
style="width: {{ '%.6f'|format(purchased_percent) }}%"
aria-valuemin="0" aria-valuemax="100"
aria-valuenow="{{ percent|round(0)|int }}"></div>
<div class="progress-bar bg-warning" role="progressbar"
style="width: {{ not_purchased_percent }}%"
style="width: {{ '%.6f'|format(not_purchased_percent) }}%"
aria-valuemin="0" aria-valuemax="100"></div>
<div class="progress-bar bg-transparent" role="progressbar"
style="width: {{ remaining_percent }}%"
style="width: {{ '%.6f'|format(remaining_percent) }}%"
aria-valuemin="0" aria-valuemax="100"></div>
<span class="progress-label main-list-progress__label small fw-bold {% if percent < 51 %}text-white{% else %}text-dark{% endif %}">
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0)|int }}%)
{% if total_expense > 0 %} — 💸 {{ '%.2f'|format(total_expense) }} PLN{% endif %}
</span>
</div>
</div>
</div>

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 %}

View File

@@ -70,7 +70,7 @@
<h5 id="progress-title" class="mb-2">
Postęp listy —
<span id="purchased-count">{{ purchased_count }}</span>/<span id="total-count">{{ total_count }}</span> kupionych
(<span id="percent-value">{{ percent|int }}</span>%)
(<span id="percent-value">{{ percent|round(0)|int }}</span>%)
</h5>
<div class="progress progress-dark position-relative">