diff --git a/custom/otssignange/css/override-dark.css b/custom/otssignange/css/override-dark.css index 2cc43b8..29b5414 100644 --- a/custom/otssignange/css/override-dark.css +++ b/custom/otssignange/css/override-dark.css @@ -187,68 +187,274 @@ hr { #sidebar-wrapper { background: var(--ots-surface); border-right: 1px solid var(--ots-border); + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; } /* OTS sidebar override marker */ .ots-sidebar-wrapper { - box-shadow: inset 0 0 0 1px rgba(79, 140, 255, 0.2); + display: flex; + flex-direction: column; + height: 100vh; + background: var(--ots-surface); } -.ots-sidebar .sidebar-list a, -.ots-sidebar .sidebar-main a { +/* Sidebar Header */ +.ots-sidebar-header { display: flex; align-items: center; - gap: 10px; + gap: 12px; + padding: 16px; + border-bottom: 1px solid var(--ots-border); + background: var(--ots-surface); } +.ots-brand-logo { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(79, 140, 255, 0.15); + border: 1px solid rgba(79, 140, 255, 0.3); + border-radius: 6px; + font-size: 18px; + color: var(--ots-primary); + flex-shrink: 0; +} + +.ots-brand-text { + flex: 1; + min-width: 0; +} + +.ots-brand-name { + display: block; + font-size: 14px; + font-weight: 600; + color: var(--ots-text); + letter-spacing: 0.5px; +} + +.ots-sidebar-close { + background: none; + border: none; + color: var(--ots-text-secondary); + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: color var(--ots-transition); + flex-shrink: 0; +} + +.ots-sidebar-close:hover { + color: var(--ots-text); +} + +/* Sidebar Content */ +.ots-sidebar { + flex: 1; + list-style: none; + margin: 0; + padding: 12px 0; + overflow-y: auto; + overflow-x: hidden; +} + +.ots-sidebar li { + margin: 0; + padding: 0; +} + +/* Sidebar Main Item */ +.ots-sidebar li.sidebar-main > a { + display: flex; + align-items: center; + gap: 12px; + margin: 0 8px; + padding: 10px 12px; + border-radius: 6px; + color: var(--ots-text); + text-decoration: none; + transition: background var(--ots-transition), color var(--ots-transition); + font-size: 14px; + font-weight: 500; +} + +.ots-sidebar li.sidebar-main > a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +.ots-sidebar li.sidebar-main > a.active { + background: rgba(79, 140, 255, 0.2); + color: var(--ots-primary); +} + +/* Sidebar Section (Collapsible) */ +.ots-sidebar li.sidebar-section > a.sidebar-section-toggle { + display: flex; + align-items: center; + gap: 12px; + margin: 0 8px; + padding: 10px 12px; + border-radius: 6px; + color: var(--ots-text); + text-decoration: none; + transition: background var(--ots-transition), color var(--ots-transition); + font-size: 14px; + font-weight: 500; + cursor: pointer; + background: none; + border: none; + text-align: left; + width: calc(100% - 16px); +} + +.ots-sidebar li.sidebar-section > a.sidebar-section-toggle:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +.ots-sidebar li.sidebar-section > a.sidebar-section-toggle.active { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +.ots-section-toggle-icon { + margin-left: auto; + font-size: 12px; + transition: transform var(--ots-transition); +} + +.ots-sidebar li.sidebar-section > a.sidebar-section-toggle.active .ots-section-toggle-icon { + transform: rotate(180deg); +} + +/* Sidebar Subsection */ +.ots-sidebar .sidebar-subsection { + list-style: none; + margin: 0; + padding: 4px 0; + display: none; + background: rgba(79, 140, 255, 0.05); +} + +.ots-sidebar .sidebar-subsection.active { + display: block; +} + +.ots-sidebar .sidebar-subsection li { + margin: 0; + padding: 0; +} + +/* Sidebar List Item */ +.ots-sidebar li.sidebar-list > a { + display: flex; + align-items: center; + gap: 12px; + margin: 2px 12px 2px 28px; + padding: 8px 12px; + border-radius: 4px; + color: var(--ots-text-secondary); + text-decoration: none; + transition: background var(--ots-transition), color var(--ots-transition); + font-size: 13px; +} + +.ots-sidebar li.sidebar-list > a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +.ots-sidebar li.sidebar-list > a.active { + background: rgba(79, 140, 255, 0.15); + color: var(--ots-primary); +} + +/* Navigation Icons and Text */ .ots-nav-icon { - width: 18px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; text-align: center; opacity: 0.85; font-size: 14px; + flex-shrink: 0; } .ots-nav-text { flex: 1; min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -/* Xibo sidebar refinements (dark) */ -#sidebar-wrapper .sidebar-title a { - color: var(--ots-text-faint); +/* Sidebar Spacer */ +.sidebar-spacer { + height: 12px; + margin-top: auto; +} + +/* Sidebar Footer */ +.ots-sidebar-footer { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border-top: 1px solid var(--ots-border); + background: var(--ots-surface); +} + +.ots-user-section { + flex: 1; + min-width: 0; +} + +.ots-user-role { font-size: 11px; - letter-spacing: 0.08em; + color: var(--ots-text-secondary); text-transform: uppercase; - padding: 12px 16px 6px; + letter-spacing: 0.5px; + margin-bottom: 2px; } -#sidebar-wrapper .sidebar-list a, -#sidebar-wrapper .sidebar-main a { - display: block; - margin: 2px 8px; - padding: 10px 12px; - border-radius: var(--ots-radius-sm); +.ots-user-name { + font-size: 13px; + font-weight: 500; + color: var(--ots-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ots-user-profile-link { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 4px; + color: var(--ots-text-secondary); + text-decoration: none; transition: background var(--ots-transition), color var(--ots-transition); + font-size: 16px; + flex-shrink: 0; } -#sidebar-wrapper .sidebar-list a:hover, -#sidebar-wrapper .sidebar-main a:hover { +.ots-user-profile-link:hover { background: rgba(79, 140, 255, 0.12); color: var(--ots-primary); } -#sidebar-wrapper .sidebar { - padding: 14px 0; -} - -#sidebar-wrapper .sidebar-main a, -#sidebar-wrapper .sidebar-list a { - display: block; - padding: 10px 18px; - color: var(--ots-text); - border-left: 3px solid transparent; -} - #sidebar-wrapper .sidebar-main a:hover, #sidebar-wrapper .sidebar-list a:hover { background: rgba(79, 140, 255, 0.12); @@ -534,6 +740,22 @@ textarea:focus { .modal-content { border-radius: var(--ots-radius-lg); + background-color: var(--ots-surface-2) !important; +} + +.modal, +.modal-header, +.modal-body, +.modal-footer { + background-color: transparent !important; +} + +.modal-backdrop, +.modal-backdrop.show, +.modal-backdrop.in { + background-color: rgba(0, 0, 0, 0.3) !important; + backdrop-filter: blur(4px) !important; + opacity: 1 !important; } .modal-footer { diff --git a/custom/otssignange/css/override.css b/custom/otssignange/css/override.css index a933705..ad0d92e 100644 --- a/custom/otssignange/css/override.css +++ b/custom/otssignange/css/override.css @@ -55,8 +55,8 @@ body { padding: 0; display: flex; flex-direction: column; - overflow-y: auto; - z-index: 1000; + overflow: hidden; + z-index: 1200; } .ots-main { @@ -66,6 +66,11 @@ body { margin-left: 260px; } +.ots-main, +#page-wrapper { + position: relative; +} + .ots-content { flex: 1; padding: 32px; @@ -106,12 +111,13 @@ body { ============================================================================ */ .sidebar-header { - padding: 20px 16px; + padding: 18px 16px; border-bottom: 1px solid var(--color-border); display: flex; align-items: center; justify-content: space-between; gap: 12px; + flex-shrink: 0; } .brand-link { @@ -139,16 +145,18 @@ body { flex: 1; padding: 12px 0; overflow-y: auto; + min-height: 0; } .sidebar-nav { list-style: none; margin: 0; - padding: 12px 0 120px; + padding: 12px 0 140px; } .sidebar-nav li { display: block; + margin-top: 6px; } /* Compatibility: Xibo sidebar markup uses `sidebar-list`, `sidebar-main`, `sidebar-title`. @@ -157,10 +165,9 @@ body { .ots-sidebar li.sidebar-list > a, .ots-sidebar li.sidebar-main > a, .ots-sidebar li.sidebar-title > a { - display: grid; - grid-template-columns: 20px 1fr; + display: flex; align-items: center; - column-gap: 12px; + gap: 12px; padding: 8px 12px; color: #c8d5ee; text-decoration: none; @@ -171,7 +178,7 @@ body { border-left: 2px solid transparent; margin: 3px 10px; border-radius: 12px; - min-height: 40px; + min-height: 48px; line-height: 1.25; } @@ -192,15 +199,15 @@ body { } .ots-sidebar .ots-nav-icon { - width: 24px; - height: 24px; - display: flex; + width: 28px; + height: 28px; + display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 16px; color: currentColor; - justify-self: center; + margin-left: 0; } .ots-sidebar .ots-nav-text { @@ -210,6 +217,11 @@ body { line-height: 1.2; } +.sidebar-group-toggle .ots-nav-icon, +.ots-sidebar-nav .ots-nav-icon { + justify-self: start; +} + .ots-sidebar li.sidebar-title > a { display: block; font-size: 10px; @@ -223,6 +235,181 @@ body { line-height: 1; } +.brand-logo { + width: 32px; + height: 32px; + object-fit: contain; +} + +.brand-text { + color: var(--color-text-primary); + font-size: 18px; + font-weight: 600; + letter-spacing: 0.02em; +} + +.sidebar-header { + padding: 18px 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-shrink: 0; + height: 72px; + box-sizing: border-box; + position: sticky; + top: 0; + background: #08132a; + z-index: 1210; +} + +.sidebar-collapse-btn { + width: 32px; + height: 32px; + border-radius: 8px; + border: 0; + background: rgba(255, 255, 255, 0.08); + color: #d7e2f8; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); +} + +.sidebar-collapse-btn:hover { + background: rgba(255, 255, 255, 0.16); + color: #ffffff; +} + +.ots-sidebar.collapsed { + width: 88px; +} + +.ots-sidebar.collapsed .brand-text, +.ots-sidebar.collapsed .sidebar-group-toggle .ots-nav-text, +.ots-sidebar.collapsed .sidebar-list .ots-nav-text, +.ots-sidebar.collapsed .sidebar-submenu, +.ots-sidebar.collapsed .sidebar-group-caret, +.ots-sidebar.collapsed .user-details { + display: none !important; +} + +.ots-sidebar.collapsed .sidebar-group-toggle, +.ots-sidebar.collapsed .sidebar-list > a { + justify-content: center; + padding: 10px; +} + +/* Center icons when collapsed */ +.ots-sidebar.collapsed .ots-nav-icon { + justify-self: center !important; + margin-left: 0 !important; +} + +.ots-sidebar-collapsed #page-wrapper, +.ots-sidebar-collapsed .ots-main { + margin-left: 88px !important; +} + +.ots-sidebar-nav { + padding: 12px 0 140px; + padding-top: 8px; +} + +.sidebar-group { + margin-top: 6px; +} + +.sidebar-group-toggle { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + margin: 6px 10px; + border-radius: 12px; + color: #d7e2f8; + text-decoration: none; + background: rgba(255, 255, 255, 0.03); + transition: all var(--transition-fast); +} + +.sidebar-group-toggle:hover { + background: rgba(255, 255, 255, 0.08); + color: #ffffff; +} + +.sidebar-group-caret { + margin-left: auto; + font-size: 12px; + opacity: 0.8; +} + +.sidebar-submenu { + list-style: none; + margin: 4px 0 8px; + padding: 0 0 0 12px; + border-left: 1px solid rgba(255, 255, 255, 0.08); +} + +.sidebar-group.is-open .sidebar-group-caret { + transform: rotate(180deg); +} + +.sidebar-submenu .sidebar-list > a { + margin: 4px 10px 4px 18px; + background: rgba(255, 255, 255, 0.04); +} + +.sidebar-submenu .sidebar-list > a:hover { + background: rgba(255, 255, 255, 0.1); +} + +.sidebar-submenu .sidebar-list.active > a, +.sidebar-submenu .sidebar-list > a.active { + color: #0b1221; + background-color: #ffffff; + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); +} + +.sidebar-footer { + border-top: 1px solid rgba(255, 255, 255, 0.08); + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + background-color: rgba(15, 23, 42, 0.6); +} + +.sidebar-theme-toggle { + width: 36px; + height: 36px; + border-radius: 10px; + border: 0; + background: rgba(255, 255, 255, 0.08); + color: #ffffff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); +} + +.sidebar-theme-toggle:hover { + background: rgba(255, 255, 255, 0.16); +} + +.user-role { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--color-text-secondary); + margin-bottom: 4px; +} + .nav-section-divider { padding: 12px 16px 8px; margin-top: 8px; @@ -299,6 +486,7 @@ body { border-top: 1px solid var(--color-border); padding: 16px; background-color: rgba(59, 130, 246, 0.05); + flex-shrink: 0; } .sidebar-user { @@ -353,7 +541,6 @@ body { .ots-topbar { background-color: var(--color-surface-elevated); - border-bottom: 2px solid var(--color-border); padding: 10px 32px; display: flex; align-items: center; @@ -395,10 +582,13 @@ body { white-space: nowrap; transition: all var(--transition-fast); position: relative; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; } .ots-topbar .nav-link:hover { - background-color: rgba(59, 130, 246, 0.08); + background-color: rgba(59, 130, 246, 0.06); color: var(--color-primary); } @@ -430,8 +620,8 @@ body { border-radius: 8px; padding: 6px 0; margin-top: 4px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); - border: 1px solid var(--color-border); + box-shadow: none; + border: 0 !important; background-color: var(--color-surface); min-width: 180px; z-index: 1100; @@ -688,6 +878,99 @@ body { overflow: hidden; } +/* Make DataTables button collections compatible with OTS dropdown styling */ +.dt-buttons { + position: relative; + display: inline-block; +} + +.dt-button-collection { + position: absolute; + top: 100%; + right: 0; + margin-top: 6px; + display: none; + z-index: 1002; + min-width: 160px; + background-color: var(--color-surface-elevated); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.3); + overflow: hidden; + padding: 6px 0; +} + +.dt-buttons.active .dt-button-collection, +.dt-button-collection.active, +.dt-button-collection.show { + display: block; + animation: slideDown 150ms ease-out; +} + +/* Ensure collection items are visible and styled */ +.dt-button-collection .dt-button, +.dt-button-collection button, +.dt-button-collection a { + display: block; + width: 100%; + padding: 6px 12px; + color: var(--color-text-primary); + background: transparent; + text-decoration: none; + font-size: 13px; + text-align: left; + border: none; +} + +.dt-button-collection .dt-button:hover, +.dt-button-collection a:hover, +.dt-button-collection button:hover { + background-color: rgba(59, 130, 246, 0.06); + color: var(--color-primary); +} + +.dt-button-collection input[type="checkbox"] { + margin-right: 8px; + vertical-align: middle; +} + +.dt-button-collection .dt-button:focus, +.dt-button-collection a:focus, +.dt-button-collection button:focus { + outline: none; + box-shadow: none; +} + +/* DataTables sometimes nests a `div.dropdown-menu` inside the collection. + Ensure that inner dropdown-menu is visible when the collection is shown. */ +.dt-button-collection .dropdown-menu { + display: block !important; + position: static !important; + background: transparent !important; + border: none !important; + box-shadow: none !important; + padding: 0 !important; + min-width: 160px !important; +} + +.dt-button-collection .dropdown-menu .dropdown-item, +.dt-button-collection .dropdown-menu a, +.dt-button-collection .dropdown-menu .dt-button { + display: block !important; + padding: 6px 12px !important; + color: var(--color-text-primary) !important; + text-decoration: none !important; + background: transparent !important; + text-align: left !important; +} + +.dt-button-collection .dropdown-menu .dropdown-item:hover, +.dt-button-collection .dropdown-menu a:hover, +.dt-button-collection .dropdown-menu .dt-button:hover { + background-color: rgba(59, 130, 246, 0.06) !important; + color: var(--color-primary) !important; +} + .dropdown.active .dropdown-menu, .dropdown:focus-within .dropdown-menu { display: block; @@ -995,90 +1278,138 @@ body { background: linear-gradient(180deg, rgba(59, 130, 246, 0.06), rgba(59, 130, 246, 0.02)); border: 1px solid rgba(59, 130, 246, 0.18); box-shadow: 0 12px 26px rgba(8, 15, 30, 0.35); - display: flex; - flex-direction: column; - min-height: 360px; - align-items: stretch; + display: flex !important; + flex-direction: column !important; + min-height: 380px; + height: 380px; + align-items: stretch !important; + overflow: visible !important; +} + +.dashboard-chart-card > * { + width: 100% !important; + float: none !important; + clear: both !important; } .dashboard-chart-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - width: 100%; - box-sizing: border-box; - float: none; - flex-wrap: wrap; + display: flex !important; + flex-direction: column !important; + gap: 10px !important; + align-items: flex-start !important; + padding: 12px 16px !important; + box-sizing: border-box !important; + width: 100% !important; + background: transparent !important; + border: none !important; + float: none !important; + clear: both !important; position: static !important; inset: auto !important; + overflow: visible !important; + height: auto !important; + max-width: none !important; + flex: 0 0 auto !important; } -.dashboard-chart-card .widget-title, -.dashboard-chart-card .widget-body { - width: 100%; - float: none !important; - clear: both; - display: block !important; - position: static !important; +.panel .dashboard-chart-header { + padding: 12px 20px !important; } -.dashboard-chart-card .widget-title { - flex: 0 0 auto; -} - -.dashboard-chart-card .widget-body { - position: relative !important; -} - -.dashboard-chart-title { - display: flex; - align-items: center; - gap: 12px; +.dashboard-chart-info { + display: flex !important; + align-items: center !important; + gap: 10px !important; + flex: 0 0 auto !important; + min-width: 0 !important; } .dashboard-chart-icon { - width: 40px; - height: 40px; - border-radius: 12px; - background: rgba(59, 130, 246, 0.18); - color: var(--color-primary-light); - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 18px; - flex-shrink: 0; + width: 32px !important; + height: 32px !important; + border-radius: 10px !important; + background: rgba(59, 130, 246, 0.18) !important; + color: var(--color-primary-light) !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + font-size: 16px !important; + flex-shrink: 0 !important; + border: none !important; + padding: 0 !important; + margin: 0 !important; +} + +.dashboard-chart-meta { + display: flex !important; + flex-direction: column !important; + gap: 2px !important; + flex-shrink: 0 !important; } .dashboard-chart-heading { margin: 0; - font-size: 16px; + font-size: 15px; font-weight: 700; + line-height: 1.2; } .dashboard-chart-subtitle { - margin: 4px 0 0; - font-size: 12px; + margin: 0; + font-size: 11px; color: var(--color-text-secondary); + line-height: 1.2; } -.dashboard-chart-link { +.dashboard-chart-actions { + display: flex !important; + align-items: center !important; + gap: 12px !important; + width: 100% !important; + flex-shrink: 0 !important; + padding: 0 !important; + border: none !important; +} + +.dashboard-chart-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px; + border-radius: 999px; + background: rgba(15, 23, 42, 0.45); +} + +.dashboard-chart-toggle-button { + border: none; + background: transparent; + color: var(--color-text-secondary); font-size: 12px; font-weight: 600; - color: var(--color-primary); - text-decoration: none; - padding: 6px 10px; + width: 40px; + height: 34px; + padding: 0; border-radius: 999px; - border: 1px solid rgba(59, 130, 246, 0.24); - background: rgba(59, 130, 246, 0.12); - transition: all var(--transition-fast); - white-space: nowrap; + cursor: pointer; + transition: all 150ms ease; + display: inline-flex; + align-items: center; + justify-content: center; } -.dashboard-chart-link:hover { +.dashboard-chart-toggle-button:hover { color: var(--color-text-primary); - border-color: rgba(59, 130, 246, 0.5); +} + +.dashboard-chart-toggle-button.is-active { background: rgba(59, 130, 246, 0.22); + color: var(--color-text-primary); +} + +.dashboard-chart-toggle-button i { + font-size: 16px; + line-height: 1; + display: inline-block; } .dashboard-chart-body { @@ -1097,11 +1428,12 @@ body { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; padding: 12px; - height: clamp(220px, 32vh, 280px); + height: 200px; width: 100%; position: relative; overflow: hidden; flex: 1; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); } .dashboard-chart-canvas canvas { @@ -1118,6 +1450,103 @@ body { min-height: 0 !important; } +/* Enhanced chart container styling */ +.dashboard-chart-card { + transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + +.dashboard-chart-card:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25) !important; + transform: translateY(-2px); +} + +/* Chart action improvements */ +.dashboard-chart-actions { + gap: 16px !important; +} + +.dashboard-chart-link { + font-size: 13px; + font-weight: 500; + color: var(--color-primary-light); + text-decoration: none; + white-space: nowrap; + transition: all 150ms ease; + padding: 4px 8px; + border-radius: 6px; + display: inline-block; +} + +.dashboard-chart-link:hover { + color: var(--color-primary); + background: rgba(59, 130, 246, 0.1); +} + +/* Modern toggle improvements */ +.dashboard-chart-toggle { + background: rgba(15, 23, 42, 0.6); + border: 1px solid rgba(59, 130, 246, 0.2); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.dashboard-chart-toggle-button { + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid transparent; +} + +.dashboard-chart-toggle-button:hover { + background: rgba(59, 130, 246, 0.12); + border-color: rgba(59, 130, 246, 0.2); +} + +.dashboard-chart-toggle-button.is-active { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(59, 130, 246, 0.2)); + border-color: rgba(59, 130, 246, 0.3); + box-shadow: 0 0 12px rgba(59, 130, 246, 0.2); +} + +.dashboard-chart-toggle-button i { + transition: transform 200ms ease; +} + +.dashboard-chart-toggle-button:hover i { + transform: scale(1.15); +} + +/* Responsive chart cards */ +@media (max-width: 1200px) { + .dashboard-chart-canvas { + height: 180px; + } +} + +@media (max-width: 768px) { + .dashboard-chart-card { + max-height: none; + } + + .dashboard-chart-header { + flex-direction: row !important; + justify-content: space-between !important; + align-items: center !important; + gap: 12px !important; + } + + .dashboard-chart-info { + flex: 1; + min-width: 0; + } + + .dashboard-chart-actions { + flex-shrink: 0; + } + + .dashboard-chart-canvas { + height: 200px; + } +} + .panel { background-color: var(--color-surface); border: 1px solid var(--color-border); @@ -1329,8 +1758,8 @@ body .panel .panel-heading, align-items: center; justify-content: space-between; padding: 14px 16px; - border-bottom: 1px solid rgba(148, 163, 184, 0.2); - background: rgba(15, 23, 42, 0.3); + border-bottom: none; + background: transparent; } .ots-filter-title { @@ -1362,15 +1791,19 @@ body .panel .panel-heading, .ots-filter-content { padding: 16px; - max-height: 600px; - overflow: hidden; + max-height: none; + min-height: auto; + overflow: visible; transition: max-height 300ms ease-out, padding 300ms ease-out; + display: block; } .ots-filter-content.collapsed { max-height: 0; + min-height: 0; padding: 0 16px; overflow: hidden; + display: none; } .ots-filter-card .nav-tabs { @@ -1936,6 +2369,58 @@ body .panel .panel-heading, color: var(--color-text-primary); } +/* Minimal Icon-Only Buttons */ +.btn-icon { + width: 40px !important; + height: 40px !important; + padding: 0 !important; + border-radius: 8px !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + background-color: var(--color-surface-elevated) !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-secondary) !important; + font-size: 0 !important; + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1) !important; + cursor: pointer !important; + position: relative !important; + margin-left: 6px !important; + flex-shrink: 0 !important; +} + +.btn-icon:hover { + background-color: rgba(59, 130, 246, 0.12) !important; + border-color: rgba(59, 130, 246, 0.3) !important; + color: var(--color-primary-light) !important; + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1) !important; +} + +.btn-icon:active { + background-color: rgba(59, 130, 246, 0.2) !important; + border-color: rgba(59, 130, 246, 0.4) !important; + transform: scale(0.95); +} + +.btn-icon i, +.btn-icon svg { + font-size: 17px !important; + line-height: 1 !important; + display: inline-block !important; +} + +.btn-icon.btn-success { + background-color: rgba(34, 197, 94, 0.08) !important; + border-color: rgba(34, 197, 94, 0.2) !important; + color: rgb(34, 197, 94) !important; +} + +.btn-icon.btn-success:hover { + background-color: rgba(34, 197, 94, 0.15) !important; + border-color: rgba(34, 197, 94, 0.4) !important; + box-shadow: 0 2px 8px rgba(34, 197, 94, 0.1) !important; +} + /* ============================================================================ UTILITY CLASSES ============================================================================ */ @@ -2083,7 +2568,9 @@ canvas { #sidebar-wrapper .sidebar-list a, #sidebar-wrapper .sidebar-main a { - display: block; + display: flex; + align-items: center; + gap: 12px; margin: 2px 8px; padding: 10px 12px; border-radius: 8px; @@ -2383,17 +2870,34 @@ legend { /* Dropdowns, modals, and popovers */ .dropdown-menu, .dropdown-toggle, -.popover, -.modal, -.modal-content, -.modal-header, -.modal-body, -.modal-footer { +.popover { background-color: var(--color-surface) !important; color: var(--color-text-primary) !important; border: 1px solid var(--color-border) !important; } +.modal, +.modal-body, +.modal-footer { + background-color: transparent !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.modal-content { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.modal-backdrop, +.modal-backdrop.show, +.modal-backdrop.in { + background-color: rgba(0, 0, 0, 0.3) !important; + backdrop-filter: blur(4px) !important; + opacity: 1 !important; +} + .modal-header { background-color: var(--color-surface-elevated) !important; border-bottom: 1px solid var(--color-border) !important; @@ -2666,3 +3170,30 @@ a.text-muted:hover { .invisible { visibility: hidden !important; } + +/* ============================================================================ + FILTER FIELDS PADDING + ============================================================================ */ + +.FilterDiv.card-body { + padding-top: 20px !important; +} +/* ============================================================================ + LOGO WITH TEXT STYLING + ============================================================================ */ + +.navbar-brand.xibo-logo-container { + display: flex; + align-items: center; + gap: 12px; + padding: 0; + margin-right: 16px; +} + +.xibo-logo-text { + color: var(--color-text-primary); + font-size: 18px; + font-weight: 600; + margin: 0; + white-space: nowrap; +} \ No newline at end of file diff --git a/custom/otssignange/js/theme.js b/custom/otssignange/js/theme.js index c9a5e3f..effed8e 100644 --- a/custom/otssignange/js/theme.js +++ b/custom/otssignange/js/theme.js @@ -16,19 +16,51 @@ function initSidebarToggle() { const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]'); const sidebar = document.querySelector('.ots-sidebar'); + const closeBtn = document.querySelector('.ots-sidebar-close'); + const collapseBtn = document.querySelector('.sidebar-collapse-btn'); + const body = document.body; - if (!toggleBtn || !sidebar) return; + if (!sidebar) return; - toggleBtn.addEventListener('click', function(e) { - e.preventDefault(); - sidebar.classList.toggle('active'); - }); + // Handle sidebar close button + if (closeBtn) { + closeBtn.addEventListener('click', function(e) { + e.preventDefault(); + sidebar.classList.remove('active'); + }); + } + + if (toggleBtn) { + toggleBtn.addEventListener('click', function(e) { + e.preventDefault(); + sidebar.classList.toggle('active'); + }); + } + + if (collapseBtn) { + const isCollapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true'; + if (isCollapsed) { + sidebar.classList.add('collapsed'); + body.classList.add('ots-sidebar-collapsed'); + } + + collapseBtn.addEventListener('click', function(e) { + e.preventDefault(); + const nowCollapsed = !sidebar.classList.contains('collapsed'); + sidebar.classList.toggle('collapsed'); + body.classList.toggle('ots-sidebar-collapsed', nowCollapsed); + localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false'); + }); + } + + // Initialize sidebar section toggles + initSidebarSectionToggles(); // Close sidebar when clicking outside on mobile document.addEventListener('click', function(e) { if (window.innerWidth <= 768) { const isClickInsideSidebar = sidebar.contains(e.target); - const isClickOnToggle = toggleBtn.contains(e.target); + const isClickOnToggle = toggleBtn && toggleBtn.contains(e.target); if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) { sidebar.classList.remove('active'); @@ -37,27 +69,47 @@ }); } + /** + * Initialize sidebar section collapse/expand functionality + */ + function initSidebarSectionToggles() { + const groupToggles = document.querySelectorAll('.sidebar-group-toggle'); + + groupToggles.forEach(toggle => { + toggle.addEventListener('click', function(e) { + e.preventDefault(); + + const group = toggle.closest('.sidebar-group'); + const submenu = group ? group.querySelector('.sidebar-submenu') : null; + if (!submenu) return; + + const isOpen = group.classList.contains('is-open'); + group.classList.toggle('is-open', !isOpen); + toggle.setAttribute('aria-expanded', (!isOpen).toString()); + submenu.style.display = isOpen ? 'none' : 'block'; + }); + }); + } + /** * Initialize dropdown menus */ function initDropdowns() { const dropdowns = document.querySelectorAll('.dropdown'); - + dropdowns.forEach(dropdown => { - const button = dropdown.querySelector('.dropdown-menu'); - - if (!button) return; - + const toggle = dropdown.querySelector('.dropdown-toggle, [data-toggle="dropdown"], .dt-button'); const menu = dropdown.querySelector('.dropdown-menu'); - - // Toggle menu on button click - dropdown.addEventListener('click', function(e) { - if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]')) { - e.preventDefault(); - dropdown.classList.toggle('active'); - } + + if (!toggle || !menu) return; + + // Toggle menu on toggle click + toggle.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + dropdown.classList.toggle('active'); }); - + // Close menu when clicking outside document.addEventListener('click', function(e) { if (!dropdown.contains(e.target)) { @@ -65,6 +117,99 @@ } }); }); + // Support DataTables Buttons collections which are not wrapped by .dropdown + document.addEventListener('click', function(e) { + const btn = e.target.closest('.dt-button'); + + if (btn) { + e.preventDefault(); + e.stopPropagation(); + + const wrapper = btn.closest('.dt-buttons') || btn.parentElement; + + // close other open dt-buttons collections + document.querySelectorAll('.dt-buttons.active').forEach(w => { + if (w !== wrapper) w.classList.remove('active'); + }); + + wrapper.classList.toggle('active'); + + // If DataTables placed the collection on the body, find it and position it under the clicked button + const allCollections = Array.from(document.querySelectorAll('.dt-button-collection')); + let collection = wrapper.querySelector('.dt-button-collection') || allCollections.find(c => !wrapper.contains(c)); + + // If DataTables didn't create a collection element, create one as a fallback + if (!collection) { + collection = document.createElement('div'); + collection.className = 'dt-button-collection'; + // prefer to append near wrapper for positioning; fallback to body + (wrapper || document.body).appendChild(collection); + } + + if (collection) { + // hide other collections + allCollections.forEach(c => { if (c !== collection) { c.classList.remove('show'); c.style.display = 'none'; } }); + + const rect = btn.getBoundingClientRect(); + const top = rect.bottom + window.scrollY; + const left = rect.left + window.scrollX; + + collection.style.position = 'absolute'; + collection.style.top = `${top}px`; + collection.style.left = `${left}px`; + collection.style.display = 'block'; + collection.classList.add('show'); + // DEBUG: log collection contents + try { + console.log('dt-button-collection opened, children:', collection.children.length, collection); + } catch (err) {} + + // If the collection is empty or visually empty, build a fallback column list from the nearest table + const isEmpty = collection.children.length === 0 || collection.textContent.trim() === '' || collection.offsetHeight < 10; + if (isEmpty) { + try { + let table = btn.closest('table') || wrapper.querySelector('table') || document.querySelector('table'); + if (table && window.jQuery && jQuery.fn && jQuery.fn.dataTable && jQuery.fn.dataTable.isDataTable(table)) { + const dt = jQuery(table).DataTable(); + // clear existing + collection.innerHTML = ''; + const thead = table.querySelectorAll('thead th'); + thead.forEach((th, idx) => { + const text = (th.textContent || th.innerText || `Column ${idx+1}`).trim(); + const item = document.createElement('div'); + item.style.padding = '6px 12px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.gap = '8px'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.checked = dt.column(idx).visible(); + checkbox.addEventListener('change', function() { + dt.column(idx).visible(this.checked); + }); + const label = document.createElement('span'); + label.textContent = text; + label.style.color = 'var(--color-text-primary)'; + item.appendChild(checkbox); + item.appendChild(label); + collection.appendChild(item); + }); + console.log('Fallback: populated collection with', collection.children.length, 'items'); + } else { + console.log('Fallback: no DataTable instance found to populate column visibility'); + } + } catch (err) { + console.warn('Error building fallback column list', err); + } + } + } + + return; + } + + // click outside dt-button -> close any open collections + document.querySelectorAll('.dt-buttons.active').forEach(w => w.classList.remove('active')); + }); } /** diff --git a/custom/otssignange/views/about-page.twig b/custom/otssignange/views/about-page.twig new file mode 100644 index 0000000..553e903 --- /dev/null +++ b/custom/otssignange/views/about-page.twig @@ -0,0 +1,91 @@ +{# +/** + * Copyright (C) 2026 OTS Signs + * + * About page for OTS Signs. + */ +#} +{% extends "non-authed.twig" %} + +{% block title %}{{ "About"|trans }} | {% endblock %} + +{% block style %} + +{% endblock %} + +{% block header %}{% endblock %} +{% block contentClass %}{% endblock %} + +{% block content %} + +
+ {% trans "An" %} + Oribi Technology Services + {% trans "product." %} +
+{% trans "OTS Signs provides a compact, focused admin UI and proxy for Xibo CMS" %}
+ + {% set appVersion = version|default("dev") %} + {% set appEnvironment = appEnvironment|default(environment|default("local")) %} + {% set commitSha = revision|default("") %} + + + ++ {% trans "An" %} + Oribi Technology Services + {% trans "product." %} +
+{% trans "OTS Signs provides a compact, focused interface and hosting for Xibo CMS" %}
+ + {% set appVersion = version|default("dev") %} + {% set appEnvironment = appEnvironment|default(environment|default("local")) %} + {% set commitSha = revision|default("") %} + + + +