From 10ab215396c33beaabb57f8d57adeeea699abd63 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Sat, 20 Jun 2026 08:04:37 -0400 Subject: [PATCH] Menu cleanup --- web/admin.html | 1 + web/atc.html | 1 + web/bulk-log.html | 1 + web/drone-requests.html | 1 + web/journal.html | 1 + web/movements.html | 3 +- web/reports.html | 3 +- web/topbar.js | 270 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 web/topbar.js diff --git a/web/admin.html b/web/admin.html index 45b041c..cc57109 100644 --- a/web/admin.html +++ b/web/admin.html @@ -6,6 +6,7 @@ PPR Admin Interface +
diff --git a/web/atc.html b/web/atc.html index 8b7a479..155568a 100644 --- a/web/atc.html +++ b/web/atc.html @@ -6,6 +6,7 @@ ATC Management Interface + +
diff --git a/web/drone-requests.html b/web/drone-requests.html index e853fd3..138daa1 100644 --- a/web/drone-requests.html +++ b/web/drone-requests.html @@ -209,6 +209,7 @@ } } +
diff --git a/web/journal.html b/web/journal.html index 1925c3d..dcad406 100644 --- a/web/journal.html +++ b/web/journal.html @@ -240,6 +240,7 @@ background: #1976D2; } +
diff --git a/web/movements.html b/web/movements.html index 4636c60..63c6ae3 100644 --- a/web/movements.html +++ b/web/movements.html @@ -358,6 +358,7 @@ min-width: 1200px; } +
@@ -1274,4 +1275,4 @@ } - \ No newline at end of file + diff --git a/web/reports.html b/web/reports.html index 19dc9c4..d78069f 100644 --- a/web/reports.html +++ b/web/reports.html @@ -358,6 +358,7 @@ min-width: 1200px; } +
@@ -1386,4 +1387,4 @@ document.addEventListener('DOMContentLoaded', initializePage); - \ No newline at end of file + diff --git a/web/topbar.js b/web/topbar.js new file mode 100644 index 0000000..0003adc --- /dev/null +++ b/web/topbar.js @@ -0,0 +1,270 @@ +(function () { + const actionMap = { + 'new-ppr': "openNewPPRModal", + 'book-out': "openLocalFlightModal", + 'book-in': "openBookInModal", + overflight: "openOverflightModal", + 'user-aircraft': "openUserAircraftModal", + 'user-management': "openUserManagementModal" + }; + + function injectTopbarStyles() { + if (document.getElementById('shared-topbar-styles')) return; + + const style = document.createElement('style'); + style.id = 'shared-topbar-styles'; + style.textContent = ` + .top-bar { + background: linear-gradient(135deg, #2c3e50, #3498db); + color: white; + padding: 0.5rem 2rem; + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + position: relative; + z-index: 100; + } + .top-bar .title { order: 2; flex: 1; text-align: center; } + .top-bar .title h1 { margin: 0; font-size: 1.5rem; } + .top-bar .menu-buttons { + order: 1; + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; + } + .top-bar .user-info { + order: 3; + font-size: 0.9rem; + opacity: 0.9; + display: flex; + align-items: center; + gap: 0.3rem; + } + .top-bar .btn { + padding: 0.7rem 1.5rem; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + text-decoration: none; + display: inline-block; + } + .top-bar .btn-success { background-color: #27ae60; color: white; } + .top-bar .btn-warning { background-color: #f39c12; color: white; } + .dropdown { position: relative; display: inline-block; } + .dropdown-toggle { white-space: nowrap; } + .dropdown-menu { + display: none; + position: absolute; + background-color: white; + min-width: 220px; + box-shadow: 0 8px 16px rgba(0,0,0,0.2); + border-radius: 5px; + z-index: 1000; + top: 100%; + left: 0; + margin-top: 0.5rem; + padding: 0; + } + .dropdown-menu.active { display: block; } + .dropdown-menu a { + color: #333; + padding: 0.75rem 1.2rem; + text-decoration: none; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + transition: background-color 0.2s ease; + white-space: nowrap; + } + .dropdown-menu a:hover { background-color: #f5f5f5; } + .dropdown-menu a:first-child { border-radius: 5px 5px 0 0; } + .dropdown-menu a:last-child { border-radius: 0 0 5px 5px; } + .shortcut { font-size: 0.8rem; color: #999; margin-left: 1rem; } + `; + document.head.appendChild(style); + } + + function actionLink(label, action, shortcut, id = '') { + const shortcutText = shortcut ? `(${shortcut})` : ''; + const idAttr = id ? ` id="${id}"` : ''; + const hidden = id === 'user-management-dropdown' ? ' style="display: none;"' : ''; + return `${label} ${shortcutText}`; + } + + function navLink(label, href) { + return `${label}`; + } + + function normalizeTopbar() { + const topbar = document.querySelector('.top-bar'); + if (!topbar || topbar.dataset.sharedTopbar === 'true') return; + + injectTopbarStyles(); + + const existingTitle = topbar.querySelector('#tower-title, .title h1, h1'); + const path = window.location.pathname.replace(/\/$/, '') || '/'; + const titleByPath = { + '/admin': 'โœˆ๏ธ Swansea Tower', + '/atc': 'โœˆ๏ธ Swansea Tower - ATC View', + '/reports': '๐Ÿ“Š PPR Reports', + '/movements': '๐Ÿ“ˆ Movements', + '/drone-requests': 'Drone Requests', + '/bulk-log': '๐Ÿงพ Bulk Flight Log', + '/journal': '๐Ÿ“” Journal Log' + }; + const titleText = titleByPath[path] || (existingTitle ? existingTitle.textContent.trim() : 'Tower Ops'); + const existingUser = topbar.querySelector('#current-user'); + const username = existingUser ? existingUser.textContent.trim() : 'Loading...'; + + topbar.dataset.sharedTopbar = 'true'; + topbar.innerHTML = ` +
+

${titleText}

+
+ + + `; + } + + function closeDropdowns(except = null) { + document.querySelectorAll('.dropdown-menu.active').forEach(menu => { + if (menu !== except) menu.classList.remove('active'); + }); + } + + function runAction(action) { + const fnName = actionMap[action]; + if (fnName && typeof window[fnName] === 'function') { + if (action === 'book-out') { + window[fnName]('LOCAL'); + } else { + window[fnName](); + } + return; + } + + window.location.href = `/admin?action=${encodeURIComponent(action)}`; + } + + function handleDeferredAction() { + const params = new URLSearchParams(window.location.search); + const action = params.get('action'); + if (!action) return; + + window.setTimeout(() => { + runAction(action); + const cleanUrl = window.location.pathname + window.location.hash; + window.history.replaceState({}, document.title, cleanUrl); + }, 300); + } + + async function updateRoleVisibility() { + const token = localStorage.getItem('ppr_access_token'); + const userManagement = document.getElementById('user-management-dropdown'); + const userAircraft = document.getElementById('user-aircraft-dropdown'); + + if (!token || (!userManagement && !userAircraft)) return; + + try { + const response = await fetch('/api/v1/auth/test-token', { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` } + }); + if (!response.ok) return; + + const user = await response.json(); + const role = (user.role || '').toUpperCase(); + if (role === 'ADMINISTRATOR') { + if (userManagement) userManagement.style.display = 'flex'; + if (userAircraft) userAircraft.style.display = 'flex'; + } else if (role === 'OPERATOR') { + if (userManagement) userManagement.style.display = 'none'; + if (userAircraft) userAircraft.style.display = 'flex'; + } else { + if (userManagement) userManagement.style.display = 'none'; + if (userAircraft) userAircraft.style.display = 'none'; + } + } catch (error) { + if (userManagement) userManagement.style.display = 'none'; + } + } + + document.addEventListener('click', event => { + const toggle = event.target.closest('.dropdown-toggle'); + const action = event.target.closest('[data-topbar-action]'); + const logout = event.target.closest('[data-topbar-logout]'); + + if (toggle) { + event.preventDefault(); + event.stopImmediatePropagation(); + const menu = toggle.parentElement.querySelector('.dropdown-menu'); + const willOpen = !menu.classList.contains('active'); + closeDropdowns(menu); + menu.classList.toggle('active', willOpen); + return; + } + + if (action) { + event.preventDefault(); + event.stopImmediatePropagation(); + closeDropdowns(); + runAction(action.dataset.topbarAction); + return; + } + + if (logout) { + event.preventDefault(); + event.stopImmediatePropagation(); + if (typeof window.logout === 'function') { + window.logout(); + } else { + localStorage.removeItem('ppr_access_token'); + localStorage.removeItem('ppr_username'); + localStorage.removeItem('ppr_token_expiry'); + window.location.href = '/admin'; + } + return; + } + + if (!event.target.closest('.dropdown')) { + closeDropdowns(); + } + }); + + document.addEventListener('DOMContentLoaded', () => { + normalizeTopbar(); + updateRoleVisibility(); + handleDeferredAction(); + }); +})();