diff --git a/static/css/style.css b/static/css/style.css index 92ff3e8..010600f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,4 +1,59 @@ -/* --- Rozmiary i kursory --- */ +/* ========================================================= + Variables (single source of truth) +========================================================= */ +:root { + /* brand / info */ + --primary: #184076; + --primary-border: #153866; + --primary-text: #e6f0ff; + + --info: var(--primary); + --info-border: var(--primary-border); + --info-text: var(--primary-text); + + /* success */ + --success: #1c6930; + --success-border: #165024; + --success-text: #eaffea; + + /* warning */ + --warning: #665c1e; + --warning-border: #4d4415; + --warning-text: #fffbe5; + + /* danger */ + --danger: #6e1a1e; + --danger-border: #531417; + --danger-text: #ffeaea; + + /* neutrals / dark */ + --dark-900: #181a1b; + --dark-800: #1c1f22; + --dark-750: #1f2225; + --dark-700: #212529; + --dark-650: #23272a; + --dark-600: #2a2d31; + --dark-550: #2b2f33; + --dark-500: #2c2f33; + --dark-480: #2c3034; + --dark-470: #2a2d31; + --dark-450: #3a3f44; + --dark-400: #343a40; + --dark-350: #3d4248; + --dark-300: #495057; + + --text-strong: #f8f9fa; + --text: #e2e3e5; + --text-dim: #e1e1e1; + --muted: #6c757d; + + /* defaults */ + --progress-default: #3d7bd6; +} + +/* ========================================================= + Utilities & Sizes +========================================================= */ .large-checkbox { width: 1.5em; height: 1.5em; @@ -8,146 +63,182 @@ 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; + background-color: var(--dark-500) !important; + color: #fff !important; } -/* --- Styl przycisku wyboru pliku --- */ +#empty-placeholder { + font-style: italic; + pointer-events: none; +} + +.fade-out { + opacity: 0; + transition: opacity 0.5s ease; +} + +@media (pointer: fine) { + .only-mobile { + display: none !important; + } +} + +/* Bootstrap bg overrides via variables */ +.bg-success { + background-color: var(--success) !important; +} + +.bg-warning { + background-color: var(--warning) !important; +} + +/* ========================================================= + Buttons +========================================================= */ +/* Primary */ +.btn-primary { + background-color: var(--primary) !important; + border-color: var(--primary-border) !important; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active { + background-color: #13315f !important; + border-color: #10284f !important; +} + +/* Success */ +.btn-success { + background-color: var(--success) !important; + border-color: var(--success-border) !important; + color: #fff !important; +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active { + background-color: #155627 !important; + border-color: #124521 !important; + color: #fff !important; +} + +/* Warning */ +.btn-warning { + background-color: var(--warning) !important; + border-color: var(--warning-border) !important; + color: var(--warning-text) !important; +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active { + background-color: #5c4c17 !important; + border-color: #3e3610 !important; + color: var(--warning-text) !important; +} + +/* Outline */ +.btn-outline-success { + color: var(--success) !important; + border-color: var(--success) !important; +} + +.btn-outline-success:hover, +.btn-outline-success:focus, +.btn-outline-success:active { + background-color: var(--success) !important; + border-color: var(--success-border) !important; + color: #fff !important; +} + +.btn-outline-warning { + color: #d9c97a !important; + border-color: var(--warning) !important; +} + +.btn-outline-warning:hover, +.btn-outline-warning:focus, +.btn-outline-warning:active { + background-color: var(--warning) !important; + border-color: var(--warning-border) !important; + color: var(--warning-text) !important; +} + +/* File input button */ input[type="file"]::file-selector-button { - background-color: #225d36; - color: #fff; + background-color: #1b4a29; + color: #f0f0f0; border: none; - padding: 0.5em 1em; + padding: .5em 1em; border-radius: 4px; font-weight: bold; cursor: pointer; - transition: background 0.2s; + transition: background .2s; } -/* --- Ciemniejsze alerty Bootstrapa --- */ -.alert-success { - background-color: #225d36 !important; - color: #eaffea !important; - border-color: #174428 !important; +/* ========================================================= + Forms (inputs, selects, switches, placeholders) +========================================================= */ +.form-select, +.form-control, +textarea.form-control { + background-color: var(--dark-700) !important; + color: var(--text-strong) !important; + border: 1px solid var(--dark-300) !important; } -.alert-danger { - background-color: #7a1f23 !important; - color: #ffeaea !important; - border-color: #531417 !important; +.form-select:focus, +.form-control:focus, +textarea.form-control:focus { + background-color: var(--dark-800) !important; + border-color: var(--primary) !important; + color: #fff !important; + box-shadow: 0 0 0 .25rem rgba(24, 64, 118, .35) !important; } -.alert-info { - background-color: #1d3a4d !important; - color: #eaf6ff !important; - border-color: #152837 !important; +.form-control:disabled, +textarea.form-control:disabled { + background-color: var(--dark-550) !important; + color: var(--muted) !important; + cursor: not-allowed; } -.alert-warning { - background-color: #665c1e !important; - color: #fffbe5 !important; - border-color: #4d4415 !important; +/* Switch */ +.form-switch .form-check-input { + background-color: var(--dark-400) !important; + border-color: var(--dark-300) !important; } -/* Badge - kolory pasujące do ciemnych alertów */ -.badge.bg-success, -.badge.text-bg-success { - background-color: #225d36 !important; - color: #eaffea !important; +.form-switch .form-check-input:checked { + background-color: var(--primary) !important; + border-color: var(--primary-border) !important; } -.badge.bg-danger, -.badge.text-bg-danger { - background-color: #7a1f23 !important; - color: #ffeaea !important; +/* Placeholders */ +.form-control::placeholder, +.bg-dark .form-control::placeholder { + color: #aaa !important; + opacity: 1 !important; } -.badge.bg-info, -.badge.text-bg-info { - background-color: #1d3a4d !important; - color: #eaf6ff !important; +/* Paired corners (utility) */ +#tempToggle { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } -.badge.bg-warning, -.badge.text-bg-warning { - background-color: #665c1e !important; - color: #fffbe5 !important; +input.form-control { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } -.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 --- */ +/* XXL custom checkbox */ input[type="checkbox"].large-checkbox { appearance: none; -webkit-appearance: none; @@ -173,16 +264,16 @@ input[type="checkbox"].large-checkbox::before { top: 50%; transform: translateY(-50%); line-height: 1; - transition: color 0.2s; + transition: color .2s; } input[type="checkbox"].large-checkbox:checked::before { content: '✓'; - color: #ffffff; + color: #fff; } input[type="checkbox"].large-checkbox:disabled::before { - opacity: 0.5; + opacity: .5; cursor: not-allowed; } @@ -190,36 +281,206 @@ 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; +/* Tom-Select / TS */ +.tom-dark .ts-control { + background-color: var(--dark-700) !important; + color: #fff !important; + border: 1px solid var(--dark-300) !important; + border-radius: .375rem; + min-height: 38px; + padding: .25rem .5rem; 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; - } +.tom-dark .ts-control .item { + background-color: var(--dark-400) !important; + color: #fff !important; + border-radius: .25rem; + padding: 2px 8px; + margin-right: 4px; +} + +.ts-dropdown { + background-color: var(--dark-700) !important; + color: #fff !important; + border: 1px solid var(--dark-300); + border-radius: .375rem; + z-index: 9999 !important; + max-height: 300px; + overflow-y: auto; +} + +.ts-dropdown .active { + background-color: var(--dark-300) !important; + color: #fff !important; +} + +td select.tom-dark { + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + +/* ========================================================= + Alerts, Badges, Background helpers +========================================================= */ +/* Alerts */ +.alert-success { + background-color: #225d36 !important; + color: var(--success-text) !important; + border-color: #174428 !important; +} + +.alert-danger { + background-color: #7a1f23 !important; + color: var(--danger-text) !important; + border-color: #531417 !important; +} + +.alert-info { + background-color: var(--primary) !important; + color: var(--primary-text) !important; + border-color: var(--primary-border) !important; +} + +.alert-warning { + background-color: var(--warning) !important; + color: var(--warning-text) !important; + border-color: var(--warning-border) !important; +} + +.alert-light { + background-color: #3a3f44 !important; + color: var(--text-strong) !important; + border-color: var(--dark-480) !important; +} + +/* Badges */ +.badge.bg-success, +.badge.text-bg-success { + background-color: #225d36 !important; + color: var(--success-text) !important; +} + +.badge.bg-danger, +.badge.text-bg-danger { + background-color: #7a1f23 !important; + color: var(--danger-text) !important; +} + +.badge.bg-info, +.badge.text-bg-info { + background-color: #1d3a4d !important; + color: #eaf6ff !important; +} + +.badge.bg-warning, +.badge.text-bg-warning { + background-color: var(--warning) !important; + color: var(--warning-text) !important; +} + +.badge.bg-secondary, +.badge.text-bg-secondary { + background-color: var(--dark-400) !important; + color: #e2e3e5 !important; +} + +.badge.bg-primary, +.badge.text-bg-primary { + background-color: var(--primary) !important; + color: var(--primary-text) !important; +} + +.badge.bg-light, +.badge.text-bg-light { + background-color: var(--dark-350) !important; + color: #f1f3f5 !important; +} + +.badge.bg-dark, +.badge.text-bg-dark { + background-color: var(--dark-900) !important; + color: var(--text-strong) !important; +} + +/* ========================================================= + Progress +========================================================= */ +.progress-dark { + background-color: var(--dark-700) !important; + border-radius: 20px !important; + overflow: hidden; +} + +.progress { + background-color: #2a2d31 !important; + border-radius: 20px !important; +} + +.progress-bar { + border-radius: 0 !important; + transition: width .4s ease, background-color .4s ease; + background-color: var(--progress-default) !important; +} + +.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; +} + +.progress-bar.bg-success { + background-color: var(--success) !important; +} + +.progress-bar.bg-danger { + background-color: var(--danger) !important; +} + +.progress-bar.bg-warning { + background-color: var(--warning) !important; + color: #fff !important; +} + +.progress-bar.bg-info { + background-color: #16425a !important; +} + +/* Label (parent must be position-relative) */ +.progress-label { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + white-space: nowrap; +} + +/* ========================================================= + Cards & Tables +========================================================= */ +.card { + background-color: var(--dark-500) !important; + border: 1px solid var(--dark-450) !important; + color: var(--text) !important; +} + +.card-header, +.card-footer { + background-color: var(--dark-650) !important; + border-color: var(--dark-450) !important; + color: #f1f3f5 !important; +} + +.card .table { + border-radius: 0 !important; + overflow: hidden; + margin-bottom: 0; } .table-responsive { @@ -231,47 +492,96 @@ input.form-control { min-width: 1000px; } -.bg-dark .form-control::placeholder { - color: #ccc !important; - opacity: 1; +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, .025); } -.toast-body { - color: #ffffff !important; - font-weight: 500 !important; +.table-dark tbody tr:hover { + background-color: rgba(255, 255, 255, .04); } -.toast { - animation: fadeInUp 0.5s ease; +.table-dark thead th { + background-color: var(--dark-800); + color: var(--text-dim); + font-weight: 500; + border-bottom: 1px solid var(--dark-450); } -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } +.table-dark td, +.table-dark th { + padding: .6rem .75rem; + vertical-align: middle; + border-top: 1px solid var(--dark-450); +} - to { - opacity: 1; - transform: translateY(0); - } +/* ========================================================= + Navs & Pagination +========================================================= */ +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + background-color: var(--dark-500) !important; + color: var(--text-strong) !important; + border-color: var(--dark-450) var(--dark-450) var(--dark-500) !important; +} + +.page-link { + color: #e0e0e0 !important; + background-color: var(--dark-750) !important; + border: 1px solid var(--dark-450) !important; +} + +.page-link:hover, +.page-link:focus { + color: #fff !important; + background-color: var(--dark-400) !important; + border-color: var(--dark-300) !important; +} + +.page-item.active .page-link { + color: #fff !important; + background-color: var(--primary) !important; + border-color: var(--primary-border) !important; +} + +.page-item.disabled .page-link { + color: var(--muted) !important; + background-color: var(--dark-550) !important; + border-color: var(--dark-450) !important; +} + +/* ========================================================= + Lists & Misc UI +========================================================= */ +.list-group-item { + display: flex; + align-items: center; + justify-content: space-between; +} + +.list-group-item:first-child, +.list-group-item:last-child { + border-radius: 0 !important; +} + +#items li.hide-purchased { + display: none !important; +} + +#mass-add-list li { + transition: background .2s; } #mass-add-list li.active { background: #198754 !important; color: #fff !important; - border: 1px solid #000000 !important; -} - -#mass-add-list li { - transition: background 0.2s; + border: 1px solid #000 !important; } .quantity-input { width: 60px; - background: #343a40; + background: var(--dark-400); color: #fff; - border: 1px solid #495057; + border: 1px solid var(--dark-300); border-radius: 4px; text-align: center; } @@ -288,129 +598,201 @@ input.form-control { gap: 4px; } -.list-group-item { - display: flex; - align-items: center; - justify-content: space-between; +/* ========================================================= + Toasts & Info Bar +========================================================= */ +.toast { + animation: fadeInUp .5s ease; } -#empty-placeholder { - font-style: italic; - pointer-events: none; -} +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } -#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; + to { + opacity: 1; + transform: translateY(0); } } - -.ts-dropdown .active { - background-color: #495057 !important; +/* Base toast when not using text-bg-* */ +.toast:not([class*="text-bg-"]) { + background-color: var(--dark-500) !important; + color: #f1f1f1 !important; + border: 1px solid var(--dark-450) !important; + animation: fadeInUp .5s ease; } -.pagination-dark .page-link { - color: #fff; - background-color: #212529; - border: 1px solid #495057; +.toast .toast-body { + color: inherit !important; } -.pagination-dark .page-link:hover { - background-color: #343a40; - border-color: #6c757d; - color: #fff; +.toast .btn-close { + filter: invert(1) grayscale(100%) brightness(200%); } -.pagination-dark .page-item.active .page-link { - background-color: #0d6efd; - border-color: #0d6efd; - color: #fff; +/* Typed toasts (override Bootstrap text-bg-*) */ +.toast.text-bg-primary { + background-color: var(--info) !important; + color: var(--info-text) !important; + border-color: var(--info-border) !important; } -.pagination-dark .page-item.disabled .page-link { - background-color: #2b3035; - border-color: #495057; - color: #6c757d; +.toast.text-bg-info { + background-color: var(--info) !important; + color: var(--info-text) !important; + border-color: var(--info-border) !important; } -.tom-dark .ts-control { - background-color: #212529 !important; +.toast.text-bg-success { + background-color: var(--success) !important; + color: var(--success-text) !important; + border-color: var(--success-border) !important; +} + +.toast.text-bg-warning { + background-color: var(--warning) !important; + color: var(--warning-text) !important; + border-color: var(--warning-border) !important; +} + +.toast.text-bg-danger { + background-color: var(--danger) !important; + color: var(--danger-text) !important; + border-color: var(--danger-border) !important; +} + +.toast-body { color: #fff !important; - border: 1px solid #495057 !important; - border-radius: 0.375rem; - min-height: 38px; - padding: 0.25rem 0.5rem; - box-sizing: border-box; + font-weight: 500 !important; } -.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 { +.info-bar-fixed { width: 100%; - max-width: 100%; + color: var(--text-strong); + background-color: var(--dark-700); + border-radius: 12px 12px 0 0; + text-align: center; + padding: 10px 10px; + font-size: .95rem; box-sizing: border-box; + margin-top: 2rem; + box-shadow: 0 -1px 4px rgba(0, 0, 0, .25); } -.table-dark.table-striped tbody tr:nth-of-type(odd) { - background-color: rgba(255, 255, 255, 0.025); +@media (max-width: 768px) { + .info-bar-fixed { + position: static; + font-size: .85rem; + padding: 8px 4px; + border-radius: 0; + } } -.table-dark tbody tr:hover { - background-color: rgba(255, 255, 255, 0.04); +/* ========================================================= + Modals (incl. fullscreen chart modal) +========================================================= */ +.modal-content { + background-color: var(--dark-470) !important; + color: #f1f1f1 !important; + border: 1px solid var(--dark-450) !important; } -.table-dark thead th { - background-color: #1c1f22; - color: #e1e1e1; - font-weight: 500; - border-bottom: 1px solid #3a3f44; +.modal-header, +.modal-footer { + background-color: var(--dark-650) !important; + border-color: var(--dark-450) !important; } -.table-dark td, -.table-dark th { - padding: 0.6rem 0.75rem; - vertical-align: middle; - border-top: 1px solid #3a3f44; +/* Fullscreen chart modal */ +#chartFullscreenModal .modal-dialog { + max-width: 100vw; + width: 100vw; + margin: 0; } -.card .table { - border-radius: 0 !important; +#chartFullscreenModal .modal-content { + height: 100vh; + border-radius: 0; +} + +#chartFullscreenModal .modal-body { + display: flex; + flex: 1 1 auto; + padding: 0; overflow: hidden; - margin-bottom: 0; +} + +#chartFullscreenCanvas { + display: block; + width: 100%; + height: 100%; +} + +/* ========================================================= + Dropdown (TS already above) — active +========================================================= */ +.ts-dropdown .active { + background-color: var(--dark-300) !important; +} + +.list-group-item.bg-success { + background-color: var(--success) !important; + border-color: var(--success-border) !important; + color: var(--success-text) !important; + --bs-bg-opacity: 1 !important; +} + +.list-group-item.bg-warning { + background-color: var(--warning) !important; + border-color: var(--warning-border) !important; + color: var(--warning-text) !important; + --bs-bg-opacity: 1 !important; +} + +.btn-outline-light { + color: #f8f9fa !important; + border-color: #f8f9fa !important; + background-color: transparent !important; + /* brak białego tła domyślnie */ +} + +.btn-outline-light:hover, +.btn-outline-light:focus { + background-color: #6c757d !important; + /* szare, jak wcześniej */ + color: #fff !important; + border-color: #6c757d !important; +} + +.btn-outline-light:active, +.btn-outline-light.active, +.show>.btn-outline-light.dropdown-toggle { + background-color: #5a6268 !important; + /* ciemniejsze szare na active */ + color: #fff !important; + border-color: #545b62 !important; +} + +.btn-outline-info { + color: var(--info) !important; + border-color: var(--info) !important; + background-color: transparent !important; +} + +.btn-outline-info:hover, +.btn-outline-info:focus { + background-color: #1d4d8c !important; + border-color: #1d4d8c !important; + color: var(--info-text) !important; +} + +.btn-outline-info:active, +.btn-outline-info.active, +.show>.btn-outline-info.dropdown-toggle { + background-color: var(--info) !important; + border-color: var(--info-border) !important; + color: var(--info-text) !important; } \ No newline at end of file diff --git a/static/css/style_old.css b/static/css/style_old.css new file mode 100644 index 0000000..bdf7f22 --- /dev/null +++ b/static/css/style_old.css @@ -0,0 +1,416 @@ +/* --- 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; +} \ No newline at end of file diff --git a/static/js/chart_split_controls.js b/static/js/chart_split_controls.js deleted file mode 100644 index 4664fde..0000000 --- a/static/js/chart_split_controls.js +++ /dev/null @@ -1,58 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - // Znajdź przyciski - const toggleMonthlySplit = document.getElementById('toggleMonthlySplit'); - const toggleDailySplit = document.getElementById('toggleDailySplit'); - const toggleCategorySplit = document.getElementById('toggleCategorySplit'); - - // Funkcja ustawiająca aktywność przycisków podziału czasu - function setActiveTimeSplit(active) { - if (active === 'monthly') { - toggleMonthlySplit.classList.add('btn-primary'); - toggleMonthlySplit.classList.remove('btn-outline-light'); - toggleMonthlySplit.setAttribute('aria-pressed', 'true'); - - toggleDailySplit.classList.remove('btn-primary'); - toggleDailySplit.classList.add('btn-outline-light'); - toggleDailySplit.setAttribute('aria-pressed', 'false'); - } else if (active === 'daily') { - toggleDailySplit.classList.add('btn-primary'); - toggleDailySplit.classList.remove('btn-outline-light'); - toggleDailySplit.setAttribute('aria-pressed', 'true'); - - toggleMonthlySplit.classList.remove('btn-primary'); - toggleMonthlySplit.classList.add('btn-outline-light'); - toggleMonthlySplit.setAttribute('aria-pressed', 'false'); - } - } - - // Obsługa kliknięć przycisków czasu - toggleMonthlySplit.addEventListener('click', function() { - setActiveTimeSplit('monthly'); - loadExpenses('monthly'); - }); - - toggleDailySplit.addEventListener('click', function() { - setActiveTimeSplit('daily'); - loadExpenses('daily'); - }); - - // Obsługa kliknięcia przycisku podziału kategorii - toggleCategorySplit.addEventListener('click', function() { - const isActive = this.classList.contains('btn-primary'); - if (isActive) { - this.classList.remove('btn-primary'); - this.classList.add('btn-outline-light'); - this.setAttribute('aria-pressed', 'false'); - loadExpenses(); // wyłącz podział kategorii - } else { - this.classList.add('btn-primary'); - this.classList.remove('btn-outline-light'); - this.setAttribute('aria-pressed', 'true'); - loadExpenses({ bycategory: true }); // włącz podział kategorii - } - }); - - // Inicjalizacja - domyślnie ustaw podział dzienny - setActiveTimeSplit('daily'); - loadExpenses('daily'); -}); diff --git a/static/js/download_chart.js b/static/js/download_chart.js new file mode 100644 index 0000000..8598d0b --- /dev/null +++ b/static/js/download_chart.js @@ -0,0 +1,67 @@ +// download_chart.js — eksport PNG z ciemnym tłem (tymczasowo), bez wielokrotnego bindowania + +document.addEventListener("DOMContentLoaded", () => { + const dlBtn = document.getElementById("downloadMainChartBtn"); + if (!dlBtn) return; + + // helper: bezpieczna nazwa pliku + const sanitize = (s) => + (s || "") + .normalize("NFD").replace(/[\u0300-\u036f]/g, "") + .replace(/[^a-zA-Z0-9-_]+/g, "_") + .replace(/_+/g, "_").replace(/^_+|_+$/g, ""); + + // helper: eksport z tymczasowym tłem + const exportChartPNG = (chart, bgColor = "#1e1e1e") => { + const canvas = chart.canvas; + const ctx = canvas.getContext("2d"); + + // 1) zapisz obraz + const snapshot = ctx.getImageData(0, 0, canvas.width, canvas.height); + + // 2) podłóż tło pod istniejący rysunek + ctx.save(); + ctx.globalCompositeOperation = "destination-over"; + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + // 3) wygeneruj PNG + const dataUrl = chart.toBase64Image("image/png", 1.0); + + // 4) przywróć pierwotny obraz (transparentny) + ctx.putImageData(snapshot, 0, 0); + + return dataUrl; + }; + + // jednorazowe bindowanie click + if (!dlBtn.dataset.bound) { + dlBtn.addEventListener("click", () => { + const chart = window.expensesChart || Chart.getChart(document.getElementById("expensesChart")); + if (!chart) return; + + // nazwa: zakres + timestamp + const now = new Date(); + const pad = (n) => String(n).padStart(2, "0"); + const stamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`; + const rangeLabel = document.getElementById("chartRangeLabel")?.textContent || ""; + const filename = `wydatki-${sanitize(rangeLabel)}-${stamp}.png`; + + // (opcjonalnie) upewnij się, że layout jest świeży + chart.resize(); + chart.update("none"); + + const a = document.createElement("a"); + a.href = exportChartPNG(chart, "#1e1e1e"); // tu ustawiasz kolor tła eksportu + a.download = filename; + a.click(); + }); + dlBtn.dataset.bound = "1"; + } + + // aktywuj przycisk, gdy wykres istnieje + const enableIfReady = () => { dlBtn.disabled = !window.expensesChart; }; + document.addEventListener("expensesChart:ready", enableIfReady); + enableIfReady(); +}); diff --git a/static/js/expense_chart.js b/static/js/expense_chart.js index 145d7d0..19076a2 100644 --- a/static/js/expense_chart.js +++ b/static/js/expense_chart.js @@ -89,7 +89,9 @@ document.addEventListener("DOMContentLoaded", function () { .then((data) => { if (!ctx) return; - if (expensesChart) expensesChart.destroy(); + if (expensesChart) { expensesChart.destroy(); window.expensesChart = null; } + + //if (expensesChart) expensesChart.destroy(); const tooltipOptions = { mode: "index", @@ -105,6 +107,7 @@ document.addEventListener("DOMContentLoaded", function () { if (categorySplit) { // Stacked per-kategoria – backend zwraca datasets z labelami kategorii :contentReference[oaicite:6]{index=6} expensesChart = new Chart(ctx, { + type: "bar", data: { labels: data.labels || [], datasets: data.datasets || [] }, options: { @@ -116,6 +119,7 @@ document.addEventListener("DOMContentLoaded", function () { } else { // Całościowo – backend zwraca labels + expenses (sumy) :contentReference[oaicite:7]{index=7} expensesChart = new Chart(ctx, { + type: "bar", data: { labels: data.labels || [], @@ -132,6 +136,10 @@ document.addEventListener("DOMContentLoaded", function () { }); } + // na potrzeby otwarciu w modalu + window.expensesChart = expensesChart; + document.dispatchEvent(new Event('expensesChart:ready')); + applyRangeLabel(range, startDate, endDate); }) .catch((e) => console.error("Błąd pobierania danych:", e)); diff --git a/static/js/modal_chart.js b/static/js/modal_chart.js new file mode 100644 index 0000000..dccc68c --- /dev/null +++ b/static/js/modal_chart.js @@ -0,0 +1,118 @@ +// modal_chart.js — final: kopiuje kolory z oryginałów, bez fallbacków i bez debugów + +function openChartFullscreen(sourceChartIdOrKey, title) { + const modalEl = document.getElementById("chartFullscreenModal"); + const canvas = document.getElementById("chartFullscreenCanvas"); + const titleEl = document.getElementById("chartModalTitle"); + if (titleEl) titleEl.textContent = title || "Wykres"; + + // Znajdź wykres źródłowy (po elemencie, id Chart.js lub globalu) + const srcEl = document.getElementById(sourceChartIdOrKey); + const srcChart = + (srcEl && Chart.getChart(srcEl)) || + Chart.getChart(sourceChartIdOrKey) || + window[sourceChartIdOrKey] || + window.expensesChart || + null; + + if (!srcChart) { + bootstrap.Modal.getOrCreateInstance(modalEl).show(); + return; + } + + // Skopiuj labels i datasets 1:1 (tylko bezpieczne klucze, żeby nie przenosić referencji Chart.js) + const safeDataset = (d) => { + const out = { + // dane i opis + label: d.label, + data: Array.isArray(d.data) ? d.data.slice() : [], + type: d.type, + // kolory / styl — dokładnie z oryginału, jeśli były + backgroundColor: d.backgroundColor, + borderColor: d.borderColor, + borderWidth: d.borderWidth, + borderSkipped: d.borderSkipped, + // stacking / kolejność + stack: d.stack, + order: d.order, + // wszystko co może być ważne dla Twoich barów/konfiguracji + parsing: d.parsing, + indexAxis: d.indexAxis, + }; + // usuń klucze undefined (Chart.js lubi czyste configi) + Object.keys(out).forEach((k) => out[k] === undefined && delete out[k]); + return out; + }; + + const freshData = { + labels: Array.isArray(srcChart.data?.labels) ? srcChart.data.labels.slice() : [], + datasets: (srcChart.data?.datasets || []).map(safeDataset), + }; + + // Typ wykresu z oryginału (np. "bar") + const chartType = (srcChart.config && srcChart.config.type) || "bar"; + + // Minimalne, bezpieczne opcje: responsywność + stacking + orientacja + const scx = srcChart.config?.options?.scales?.x || {}; + const scy = srcChart.config?.options?.scales?.y || {}; + + const freshOptions = { + responsive: true, + maintainAspectRatio: false, + // jeżeli oryginał miał pion/poziom, zachowaj + indexAxis: srcChart.config?.options?.indexAxis || "x", + // nie kopiujemy całych pluginów (unikamy referencji) — domyślne legend/tooltip są OK + plugins: {}, + scales: { + x: { stacked: !!scx.stacked }, + y: { stacked: !!scy.stacked, beginAtZero: scy.beginAtZero !== false }, + }, + }; + + // Helper: zniszcz wykres na canvasie modala, jeśli istnieje + const destroyOnCanvas = () => { + if (canvas._chartInstance) { + try { canvas._chartInstance.destroy(); } catch { } + canvas._chartInstance = null; + } + const existing = Chart.getChart(canvas); + if (existing) { + try { existing.destroy(); } catch { } + } + }; + destroyOnCanvas(); + + // Po pokazaniu modala twórz wykres (gdy ma już wymiary) + const onShown = () => { + destroyOnCanvas(); + const ctx = canvas.getContext("2d"); + canvas._chartInstance = new Chart(ctx, { + type: chartType, + data: freshData, + options: freshOptions, + }); + // lekki nudge layoutu + requestAnimationFrame(() => { + canvas._chartInstance.resize(); + canvas._chartInstance.update(); + }); + }; + + const onHidden = () => { destroyOnCanvas(); }; + + const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + modalEl.addEventListener("shown.bs.modal", onShown, { once: true }); + modalEl.addEventListener("hidden.bs.modal", onHidden, { once: true }); + + modal.show(); +} + +// Odblokuj ⛶ gdy bazowy wykres gotowy +document.addEventListener("expensesChart:ready", () => { + const b = document.getElementById("openFsBtn"); + if (b) b.disabled = false; +}); +document.addEventListener("DOMContentLoaded", () => { + const b = document.getElementById("openFsBtn"); + if (b && window.expensesChart) b.disabled = false; +}); \ No newline at end of file diff --git a/templates/expenses.html b/templates/expenses.html index b782964..1281412 100644 --- a/templates/expenses.html +++ b/templates/expenses.html @@ -133,6 +133,16 @@
+
+
+ + + +
+
+
Podział według czasu
@@ -145,7 +155,8 @@
Kategorie/Sumy wydatków
- +
@@ -181,6 +192,21 @@
+ + + {% endblock %} {% block scripts %} @@ -191,4 +217,6 @@ + + {% endblock %} \ No newline at end of file