From 1e5ece9d132cc2d08e6116b550b9b97e5e50231b Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Thu, 6 Aug 2026 15:21:49 -0700 Subject: [PATCH] feature/AB#33850-FixedDTFooter --- .../NotificationsSettingGroup/Default.css | 22 +++- .../NotificationsSettingGroup/Default.js | 16 +++ .../Pages/ApplicationForms/Mapping.css | 50 ++++++--- .../Components/Notifications/Default.css | 104 +++++++++++++++++- .../Components/Notifications/Default.js | 51 +++++++++ 5 files changed, 216 insertions(+), 27 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css index 14ef7a32e3..f0b5497147 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css @@ -77,9 +77,23 @@ display: flex; flex-direction: row; align-items: flex-start; - min-height: 400px; + height: calc(100vh - 230px); + min-height: 0; gap: 0; - /* No fixed height — lets the DataTable footer and page footer remain visible */ +} + +#nav-tabContent.unt-tab-content { + display: flex; + flex-direction: column; + height: calc(100vh - 230px); + min-height: 0; +} + +#nav-tabContent.unt-tab-content > .tab-pane.show.active { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; } /* ── Left pane ────────────────────────────────────────────────────────────── */ @@ -101,7 +115,7 @@ border-right: 1px solid #ced4da; position: sticky; top: 10px; - height: calc(100vh - 190px); + height: calc(100vh - 230px); z-index: 10; transition: background 0.15s; } @@ -142,7 +156,7 @@ position: sticky; top: 10px; /* Explicit height (not max-height) so h-100 on the inner card resolves correctly */ - height: calc(100vh - 190px); + height: calc(100vh - 230px); } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js index 54e1360715..e9c3c1652c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js @@ -88,6 +88,7 @@ $(function () { if (templatesDataTable) { setTimeout(() => { templatesDataTable.columns.adjust().draw(); + resizeTemplatesScrollBody(); }, 0); } }); @@ -106,6 +107,21 @@ $(function () { } } + function resizeTemplatesScrollBody() { + if (!templatesDataTable) return; + const scrollResize = templatesDataTable.settings?.()[0]?._scrollResize; + if (scrollResize && typeof scrollResize._size === 'function') { + scrollResize._size(); + return; + } + + try { + templatesDataTable.columns.adjust(); + } catch (e) { + console.debug('Templates table column adjust failed:', e.message); + } + } + // ── Select2 Initialization ───────────────────────────────────────────── function initializeRecipientSelect() { $('#templateRecipientSelect').select2({ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css index 2c90349c2b..e30641dc33 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css @@ -10,6 +10,20 @@ width: auto; } +#nav-tabContent.unt-tab-content { + display: flex; + flex-direction: column; + height: calc(100vh - 230px); + min-height: 0; +} + +#nav-tabContent.unt-tab-content > .tab-pane.show.active { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; +} + .configuration-action-bar { align-items: center; border-bottom: 1px solid #dee2e6; @@ -129,24 +143,24 @@ tr:nth-child(even) {background-color: #f2f2f2;} position: relative; } -.buttons { - width: fit-content; - display: block; -} - -.ai-generate-btn:disabled, -.ai-generate-btn.disabled, -.ai-generate-btn[data-ai-shared-generating='1'], -.ai-generate-btn[data-ai-cooldown-active='1'], -.ai-generate-btn[data-ai-cooldown-checking='1'] { - cursor: not-allowed; - opacity: 0.55; - pointer-events: none; -} - -.buttons-div { - display: inline-flex; - padding: 20px; +.buttons { + width: fit-content; + display: block; +} + +.ai-generate-btn:disabled, +.ai-generate-btn.disabled, +.ai-generate-btn[data-ai-shared-generating='1'], +.ai-generate-btn[data-ai-cooldown-active='1'], +.ai-generate-btn[data-ai-cooldown-checking='1'] { + cursor: not-allowed; + opacity: 0.55; + pointer-events: none; +} + +.buttons-div { + display: inline-flex; + padding: 20px; margin: auto; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css index 2ed7dbd718..d6873d19c4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css @@ -4,6 +4,15 @@ background: #f8f9fb; font-family: var(--bs-body-font-family, 'BCSans', sans-serif); font-size: var(--bc-font-size, 1rem); + display: flex; + flex: 1 1 auto; + flex-direction: column; + height: 100%; + width: 100%; + min-width: 0; + min-height: 0; + position: relative; + overflow: hidden; } .notifications-widget .card { border: 0; } .notifications-widget .card .card-body { background: #fff; } @@ -69,11 +78,11 @@ min-width: 0; } -#notificationForm { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; +#notificationForm { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; } /* Modal styling */ @@ -94,6 +103,7 @@ .notification-modal-footer { flex-shrink: 0; + background-color: #f8f9fb; } /* Notification info note styling */ @@ -136,6 +146,90 @@ width: 100%; } +.notifications-widget .dt-container { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 auto; + overflow-y: hidden; + overflow-x: hidden; + width: 100%; + min-width: 0; + min-height: 0; +} + +.notifications-widget .dt-scroll-body { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} + +.notifications-widget .dt-scroll { + flex: 1 1 auto; + min-width: 0; + min-height: 0; +} + +.notifications-widget .dt-bootstrap5 > div.dt-layout-table { + flex-grow: 1; + overflow-y: auto; + min-height: 0; +} + + +.dt-unity-footer { + position: fixed; + bottom: 0px; + left: 0px; + width: 100%; + z-index: 99999; + background-color: rgb(255, 255, 255); + box-shadow: rgba(0, 0, 0, 0.1) 0px -2px 10px; + padding: 10px; + height: 60px; + display: flex; + visibility: visible; + opacity: 1; + overflow: visible; +} + +.notifications-widget .dt-unity-footer { + width: 100%; + display: flex; + position: fixed; + bottom: 0; + left: 0px; + height: 60px; + z-index: 999999; + background-color: white; + align-items: center; + justify-content: center; +} + +div.dt-container { + padding-bottom: 120px; + overflow: visible; +} + +body { + height: auto; + overflow: visible; + padding-bottom: 100px; +} + +:root { + height: auto; + overflow: visible; +} + +div.dt-scroll-body { + max-height: calc(-80px + 100vh); + height: calc(-80px + 100vh); + overflow: visible; + overflow-y: auto; + position: relative; +} + /* Notification table action buttons */ .notification-action-btn { margin-left: 10px; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index ee3fdc4b0b..edd18555fb 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -208,7 +208,10 @@ responseCallback, actionButtons: [], pagingEnabled: true, + scrollY: 'calc(100vh - 280px)', + scrollCollapse: true, reorderEnabled: false, + fixedHeaders: true, languageSetValues: {}, dynamicButtonContainerId: null, useNullPlaceholder: false, @@ -232,6 +235,51 @@ } } + // ScrollResize skips sizing while the table is hidden (e.g. an inactive Bootstrap + // tab pane), so nothing pins the pagination bar to the bottom until we recalculate + // it ourselves once the tab holding this widget becomes visible. + function resizeNotificationsScrollBody() { + if (!notificationsTable) return; + const scrollResize = notificationsTable.settings?.()[0]?._scrollResize; + if (scrollResize && typeof scrollResize._size === 'function') { + scrollResize._size(); + return; + } + try { + notificationsTable.columns.adjust(); + } catch (e) { + console.debug('Notifications table column adjust failed:', e.message); + } + } + + function bindTabVisibilityResize() { + document.addEventListener('shown.bs.tab', function (e) { + const target = e.target; + if (!target) return; + const isNotificationsTab = + target.id === 'nav-notifications-tab' || + target.getAttribute?.('data-bs-target') === '#nav-notifications'; + if (isNotificationsTab) { + resizeNotificationsScrollBody(); + } + }); + + const notificationsSection = document.getElementById('notifications-div'); + const notificationsMenuItem = document.getElementById('notifications-menu-item'); + const resizeWhenVisible = () => { + if (!notificationsSection?.offsetParent) return; + setTimeout(resizeNotificationsScrollBody, 0); + }; + + notificationsMenuItem?.addEventListener('click', resizeWhenVisible); + if (notificationsSection && typeof MutationObserver !== 'undefined') { + const observer = new MutationObserver(resizeWhenVisible); + observer.observe(notificationsSection, { attributes: true, attributeFilter: ['class', 'style'] }); + } + + window.addEventListener('resize', resizeWhenVisible); + } + function onCancelNotification(id) { if (!id) return; Swal.fire({ @@ -610,6 +658,9 @@ fetchTemplates().then(populateTemplates); initNotificationsTable(); + bindTabVisibilityResize(); + // In case the widget is already visible on load (not inside a hidden tab pane) + resizeNotificationsScrollBody(); // Attach save button listener (remove old one first to prevent duplicates) const saveBtn = document.getElementById('btn-save-notification');