From d711456b8882bcc4ab4c03ebbbe64d6a6a9c9d4a Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 27 Jul 2025 17:49:37 -0600 Subject: [PATCH] Full update to map css --- configs/homepage/logs/homepage.log | 2 + .../public/css/modules/apartment-popup.css | 235 +++ map/app/public/css/modules/base.css | 40 + map/app/public/css/modules/buttons.css | 87 + map/app/public/css/modules/cache-busting.css | 113 + map/app/public/css/modules/doc-search.css | 144 ++ map/app/public/css/modules/forms.css | 124 ++ map/app/public/css/modules/layout.css | 98 + map/app/public/css/modules/leaflet-custom.css | 184 ++ map/app/public/css/modules/map-controls.css | 72 + map/app/public/css/modules/mobile-ui.css | 154 ++ map/app/public/css/modules/modal.css | 83 + map/app/public/css/modules/notifications.css | 90 + map/app/public/css/modules/print.css | 13 + map/app/public/css/modules/qr-code.css | 74 + map/app/public/css/modules/responsive.css | 190 ++ .../css/modules/start-location-marker.css | 72 + map/app/public/css/style.css | 1844 +---------------- map/app/public/index.html | 4 + map/app/public/js/config.js | 30 +- map/app/public/js/external-layers.js | 28 +- map/app/public/js/location-manager.js | 14 +- map/app/public/js/main.js | 5 +- map/app/public/js/ui-controls.js | 46 +- map/app/routes/index.js | 6 + map/files-explainer.md | 66 +- 26 files changed, 1971 insertions(+), 1847 deletions(-) create mode 100644 map/app/public/css/modules/apartment-popup.css create mode 100644 map/app/public/css/modules/base.css create mode 100644 map/app/public/css/modules/buttons.css create mode 100644 map/app/public/css/modules/cache-busting.css create mode 100644 map/app/public/css/modules/doc-search.css create mode 100644 map/app/public/css/modules/forms.css create mode 100644 map/app/public/css/modules/layout.css create mode 100644 map/app/public/css/modules/leaflet-custom.css create mode 100644 map/app/public/css/modules/map-controls.css create mode 100644 map/app/public/css/modules/mobile-ui.css create mode 100644 map/app/public/css/modules/modal.css create mode 100644 map/app/public/css/modules/notifications.css create mode 100644 map/app/public/css/modules/print.css create mode 100644 map/app/public/css/modules/qr-code.css create mode 100644 map/app/public/css/modules/responsive.css create mode 100644 map/app/public/css/modules/start-location-marker.css diff --git a/configs/homepage/logs/homepage.log b/configs/homepage/logs/homepage.log index 3caeca4..731953f 100644 --- a/configs/homepage/logs/homepage.log +++ b/configs/homepage/logs/homepage.log @@ -701,3 +701,5 @@ at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-07-27T23:30:12.346Z] error: Error calling https://api.open-meteo.com/v1/forecast... +[2025-07-27T23:30:12.352Z] error: [ 500, [AggregateError: ] { code: 'ETIMEDOUT' } ] diff --git a/map/app/public/css/modules/apartment-popup.css b/map/app/public/css/modules/apartment-popup.css new file mode 100644 index 0000000..41dc4fb --- /dev/null +++ b/map/app/public/css/modules/apartment-popup.css @@ -0,0 +1,235 @@ +/* Apartment Building Popup Styles */ +.apartment-popup .leaflet-popup-content-wrapper { + background: white; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0,0,0,0.15); + border: 1px solid #e9ecef; + max-width: min(320px, calc(100vw - 40px)) !important; + min-width: min(280px, calc(100vw - 40px)) !important; + width: auto !important; +} + +.apartment-popup .leaflet-popup-content { + margin: 12px 18px; + line-height: 1.4; + max-width: 100%; + overflow: hidden; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.apartment-building-popup .suite-selector { + transition: all 0.2s ease; + font-weight: 500; + border: 2px solid #e9ecef; + background: white; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 16px; + padding-right: 32px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 100%; + max-width: 100%; + box-sizing: border-box; + font-size: 12px; + padding: 8px 32px 8px 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.apartment-building-popup .suite-selector:hover { + border-color: #ff6b35; + box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15); +} + +.apartment-building-popup .suite-selector:focus { + outline: none; + border-color: #ff6b35; + box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.1); +} + +.apartment-building-popup .suite-content { + transition: all 0.3s ease; + max-width: 100%; + overflow: hidden; + box-sizing: border-box; +} + +.apartment-building-popup .suite-details { + animation: fadeIn 0.3s ease; + word-wrap: break-word; + overflow-wrap: break-word; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } +} + +/* General apartment popup container constraints */ +.apartment-building-popup { + max-width: 100%; + box-sizing: border-box; + word-wrap: break-word; + overflow-wrap: break-word; + overflow: hidden; +} + +.apartment-building-popup * { + box-sizing: border-box; + max-width: 100%; +} + +/* Specific text overflow handling */ +.apartment-building-popup select option { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.apartment-building-popup .suite-details > div, +.apartment-building-popup.app-data .unit-details > div { + word-wrap: break-word; + overflow-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.3; +} + +/* Allow long addresses to wrap */ +.apartment-building-popup .building-header > div > div:first-child > div:first-child { + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; +} + +.apartment-building-popup .building-header { + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; + margin: -12px -18px 16px -18px !important; + padding: 16px 20px !important; + background: linear-gradient(135deg, #ff6b35, #f7931e); + color: white; + border-radius: 8px 8px 0 0; + position: relative; + left: 0; + right: 0; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.apartment-building-popup.app-data .building-header { + background: linear-gradient(135deg, #a02c8d, #ba6cdf) !important; +} + +.apartment-building-popup button { + width: 100%; + max-width: 100%; + box-sizing: border-box; + word-wrap: break-word; + white-space: normal; +} + +/* Ensure dropdown options don't cause overflow */ +.apartment-building-popup select option { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Mobile-specific Leaflet popup positioning fixes */ +@media (max-width: 768px) { + .leaflet-popup { + max-width: calc(100vw - 20px) !important; + } + + .leaflet-popup-content-wrapper { + max-width: 100% !important; + overflow: hidden; + } + + .leaflet-popup-content { + max-width: 100% !important; + overflow: hidden; + word-wrap: break-word; + overflow-wrap: break-word; + } + + /* Ensure popups don't go off-screen on mobile */ + .leaflet-popup-pane { + pointer-events: none; + } + + .leaflet-popup { + pointer-events: auto; + margin: 0 10px; + } +} + +@media (max-width: 480px) { + .leaflet-popup { + max-width: calc(100vw - 15px) !important; + margin: 0 7px; + } +} + +@media (max-width: 320px) { + .leaflet-popup { + max-width: calc(100vw - 10px) !important; + margin: 0 5px; + } +} + +/* Additional mobile button fixes for ultra-small screens */ +@media (max-width: 280px) { + .apartment-building-popup.app-data .unit-details div[style*="display: flex"] { + flex-direction: column !important; + gap: 2px !important; + } + + .apartment-building-popup.app-data .unit-details button { + flex: none !important; + width: 100% !important; + min-width: unset !important; + margin-bottom: 2px; + } + + .apartment-building-popup button { + font-size: 8px !important; + padding: 3px 5px !important; + line-height: 1.2; + } + + .apartment-building-popup .suite-selector, + .apartment-building-popup.app-data .unit-selector { + font-size: 8px !important; + padding: 4px 18px 4px 4px !important; + } +} + +/* Prevent text selection on popup elements for better mobile UX */ +.apartment-building-popup { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.apartment-building-popup input, +.apartment-building-popup select, +.apartment-building-popup textarea { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} diff --git a/map/app/public/css/modules/base.css b/map/app/public/css/modules/base.css new file mode 100644 index 0000000..247ea0c --- /dev/null +++ b/map/app/public/css/modules/base.css @@ -0,0 +1,40 @@ +/* CSS Variables for theming */ +:root { + --primary-color: #a02c8d; + --success-color: #27ae60; + --danger-color: #e74c3c; + --warning-color: #f39c12; + --secondary-color: #ba6cdf; + --dark-color: #2e053f; + --light-color: #efcef0; + --border-radius: 4px; + --transition: all 0.3s ease; + --header-height: 60px; + + /* Responsive width variables */ + --container-max-width: 100%; + --content-padding: 20px; + --mobile-padding: 15px; + + /* Breakpoints for consistency */ + --mobile-breakpoint: 768px; + --tablet-breakpoint: 1024px; + --desktop-breakpoint: 1200px; +} + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + color: var(--dark-color); + background-color: var(--light-color); + width: 100%; + overflow-x: hidden; /* Prevent horizontal scrolling */ +} diff --git a/map/app/public/css/modules/buttons.css b/map/app/public/css/modules/buttons.css new file mode 100644 index 0000000..33c803f --- /dev/null +++ b/map/app/public/css/modules/buttons.css @@ -0,0 +1,87 @@ +/* Buttons */ +.btn { + padding: 10px 16px; + border: none; + border-radius: var(--border-radius); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; + outline: none; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0,0,0,0.15); +} + +.btn:active { + transform: translateY(0); +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background-color: #2471a3; +} + +.btn-success { + background-color: var(--success-color); + color: white; +} + +.btn-success:hover { + background-color: #229954; +} + +.btn-secondary { + background-color: var(--secondary-color); + color: white; +} + +.btn-secondary:hover { + background-color: #7f8c8d; +} + +.btn-sm { + padding: 6px 12px; + font-size: 13px; +} + +.btn-danger { + background-color: var(--danger-color); + color: white; +} + +.btn-danger:hover { + background-color: #c0392b; +} + +/* Disabled button styles */ +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + background-color: #6c757d; +} + +.btn:disabled:hover { + background-color: #6c757d; + transform: none; +} + +.btn-primary:disabled { + background-color: #6c757d; + border-color: #6c757d; +} + +.btn-primary:disabled:hover { + background-color: #6c757d; + border-color: #6c757d; +} diff --git a/map/app/public/css/modules/cache-busting.css b/map/app/public/css/modules/cache-busting.css new file mode 100644 index 0000000..77ea20a --- /dev/null +++ b/map/app/public/css/modules/cache-busting.css @@ -0,0 +1,113 @@ +/* Cache Busting Update Notification Styles */ +.update-notification { + position: fixed !important; + top: 20px !important; + right: 20px !important; + background: linear-gradient(135deg, #4CAF50, #45a049) !important; + color: white !important; + padding: 15px !important; + border-radius: 8px !important; + box-shadow: 0 4px 20px rgba(0,0,0,0.3) !important; + z-index: 10000 !important; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; + animation: slideInFromRight 0.3s ease-out !important; + max-width: 350px !important; + border: 1px solid rgba(255,255,255,0.2) !important; +} + +@keyframes slideInFromRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.update-notification-content { + display: flex !important; + align-items: center !important; + gap: 10px !important; + flex-wrap: wrap !important; +} + +.update-message { + font-size: 14px !important; + font-weight: 500 !important; + flex: 1 !important; + min-width: 150px !important; +} + +.update-button { + background: rgba(255,255,255,0.2) !important; + border: 1px solid rgba(255,255,255,0.3) !important; + color: white !important; + padding: 8px 16px !important; + border-radius: 4px !important; + cursor: pointer !important; + font-size: 12px !important; + font-weight: 500 !important; + transition: background-color 0.2s ease !important; + white-space: nowrap !important; +} + +.update-button:hover { + background: rgba(255,255,255,0.3) !important; +} + +.update-dismiss { + background: none !important; + border: none !important; + color: white !important; + cursor: pointer !important; + font-size: 18px !important; + padding: 0 !important; + margin-left: 5px !important; + width: 24px !important; + height: 24px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 50% !important; + transition: background-color 0.2s ease !important; +} + +.update-dismiss:hover { + background: rgba(255,255,255,0.2) !important; +} + +/* Mobile responsive styles for update notification */ +@media (max-width: 768px) { + .update-notification { + top: 10px !important; + right: 10px !important; + left: 10px !important; + max-width: none !important; + padding: 12px !important; + } + + .update-notification-content { + flex-direction: column !important; + align-items: stretch !important; + gap: 8px !important; + } + + .update-message { + text-align: center !important; + min-width: auto !important; + } + + .update-button { + align-self: center !important; + min-width: 120px !important; + } + + .update-dismiss { + position: absolute !important; + top: 8px !important; + right: 8px !important; + margin: 0 !important; + } +} diff --git a/map/app/public/css/modules/doc-search.css b/map/app/public/css/modules/doc-search.css new file mode 100644 index 0000000..db5d5ce --- /dev/null +++ b/map/app/public/css/modules/doc-search.css @@ -0,0 +1,144 @@ +/* Documentation Search Styles */ +.docs-search-container { + position: relative; + flex: 0 1 400px; + margin: 0 1rem; + /* Remove z-index here - let it inherit from header */ +} + +.docs-search-wrapper { + position: relative; +} + +.docs-search-input { + width: 100%; + padding: 0.5rem 2.5rem 0.5rem 1rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + background: white; + transition: border-color 0.2s; +} + +.docs-search-input:focus { + outline: none; + border-color: #4CAF50; + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1); +} + +.docs-search-icon { + position: absolute; + right: 0.75rem; + top: 50%; + transform: translateY(-50%); + opacity: 0.5; + pointer-events: none; +} + +.docs-search-results { + position: absolute; + top: calc(100% + 0.5rem); + left: 0; + right: 0; + max-height: 60vh; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); + z-index: 10002; /* Just slightly higher than header */ + overflow: hidden; + display: flex; + flex-direction: column; +} + +.docs-search-results.hidden { + display: none; +} + +.docs-search-results-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-bottom: 1px solid #eee; + background: #f5f5f5; +} + +.results-count { + font-size: 12px; + color: #666; + font-weight: 500; +} + +.close-results { + background: none; + border: none; + font-size: 20px; + cursor: pointer; + color: #666; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s; +} + +.close-results:hover { + background-color: #e0e0e0; +} + +.docs-search-results-list { + overflow-y: auto; + flex: 1; +} + +.search-result { + display: block; + padding: 1rem; + border-bottom: 1px solid #eee; + text-decoration: none; + color: inherit; + transition: background-color 0.2s; +} + +.search-result:hover { + background-color: #f5f5f5; +} + +.search-result:last-child { + border-bottom: none; +} + +.search-result-title { + font-weight: 500; + color: #333; + margin-bottom: 0.25rem; +} + +.search-result-snippet { + font-size: 13px; + color: #666; + line-height: 1.4; + margin-bottom: 0.25rem; +} + +.search-result-snippet mark { + background-color: #ffeb3b; + color: inherit; + font-weight: 500; + padding: 0 2px; +} + +.search-result-path { + font-size: 11px; + color: #999; +} + +.no-results { + padding: 2rem; + text-align: center; + color: #666; +} diff --git a/map/app/public/css/modules/forms.css b/map/app/public/css/modules/forms.css new file mode 100644 index 0000000..9bd4f1e --- /dev/null +++ b/map/app/public/css/modules/forms.css @@ -0,0 +1,124 @@ +/* Form styles */ +.form-group { + margin-bottom: 15px; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: var(--dark-color); + font-size: 14px; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: var(--border-radius); + font-size: 14px; + transition: var(--transition); +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1); +} + +.form-group input.valid { + border-color: var(--success-color); +} + +.form-group input.invalid { + border-color: var(--danger-color); +} + +.form-group input[readonly] { + background-color: #f5f5f5; + cursor: not-allowed; +} + +.form-group select { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: var(--border-radius); + font-size: 14px; + transition: var(--transition); + background-color: white; + cursor: pointer; +} + +.form-group select:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1); +} + +.form-group input[type="checkbox"] { + width: auto; + margin-right: 8px; + cursor: pointer; +} + +.form-group label input[type="checkbox"] { + vertical-align: middle; +} + +.form-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #e0e0e0; +} + +/* Edit Footer Form */ +.edit-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: white; + border-top: 2px solid var(--primary-color); + box-shadow: 0 -2px 10px rgba(0,0,0,0.1); + z-index: 1500; + transition: transform 0.3s ease; + max-height: 60vh; + overflow-y: auto; +} + +.edit-footer.hidden { + transform: translateY(100%); +} + +.edit-footer-content { + padding: 20px; + max-width: 800px; + margin: 0 auto; +} + +.edit-footer-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid #e0e0e0; +} + +.edit-footer-header h2 { + margin: 0; + font-size: 20px; + color: var(--dark-color); +} diff --git a/map/app/public/css/modules/layout.css b/map/app/public/css/modules/layout.css new file mode 100644 index 0000000..c8dc550 --- /dev/null +++ b/map/app/public/css/modules/layout.css @@ -0,0 +1,98 @@ +/* App container */ +#app { + display: flex; + flex-direction: column; + height: 100vh; /* Fallback for older browsers */ + height: var(--app-height); + width: 100%; + max-width: 100vw; + position: relative; + overflow: hidden; /* Prevent content from overflowing */ +} + +/* Header */ +.header { + height: var(--header-height); + background-color: var(--dark-color); + color: white; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--content-padding); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + z-index: 10001; /* Increase from 1000 to be higher than map controls */ + position: relative; + width: 100%; + min-width: 0; /* Allow flex items to shrink */ + flex-shrink: 0; /* Don't shrink the header */ +} + +.header h1 { + font-size: 24px; + font-weight: 600; +} + +.header-actions { + display: flex; + align-items: center; + gap: 15px; +} + +.location-count { + background-color: rgba(255,255,255,0.1); + padding: 5px 15px; + border-radius: 20px; + font-size: 14px; +} + +/* User info in header */ +.user-info { + display: flex; + align-items: center; + gap: 15px; + padding: 0 15px; + border-right: 1px solid rgba(255,255,255,0.2); + margin-right: 15px; +} + +.user-email { + font-size: 14px; + color: rgba(255,255,255,0.9); +} + +/* Map container */ +#map-container { + position: relative; + width: 100%; + max-width: 100vw; + height: calc(100vh - var(--header-height)); + height: calc(var(--app-height) - var(--header-height)); + flex: 1; + overflow: hidden; +} + +#map { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + max-width: 100%; + background-color: #f0f0f0; +} + +/* Fullscreen styles */ +.fullscreen #map-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5000; +} + +.fullscreen .header { + display: none; +} diff --git a/map/app/public/css/modules/leaflet-custom.css b/map/app/public/css/modules/leaflet-custom.css new file mode 100644 index 0000000..5a04bae --- /dev/null +++ b/map/app/public/css/modules/leaflet-custom.css @@ -0,0 +1,184 @@ +/* Leaflet customizations */ +.leaflet-popup-content-wrapper { + border-radius: var(--border-radius); + box-shadow: 0 3px 10px rgba(0,0,0,0.2); +} + +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.5; +} + +.popup-content h3 { + margin: 0 0 10px 0; + color: var(--dark-color); + font-size: 16px; +} + +.popup-content p { + margin: 5px 0; + color: #666; + font-size: 14px; +} + +.popup-content .popup-meta { + font-size: 12px; + color: #999; + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid #eee; +} + +/* Popup actions section */ +.popup-content .popup-actions { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #eee; + display: flex; + gap: 10px; + justify-content: center; +} + +.popup-content .popup-actions .btn { + flex: 1; + max-width: 120px; +} + +/* Leaflet Circle Markers - Add this section */ +.leaflet-marker-icon { + background-color: transparent !important; + border: none !important; +} + +.leaflet-interactive { + cursor: pointer; +} + +/* Ensure circle markers are visible */ +path.leaflet-interactive { + stroke: #fff; + stroke-opacity: 1; + stroke-width: 2; + fill-opacity: 0.8; +} + +/* Fix for marker z-index */ +.leaflet-pane.leaflet-marker-pane { + z-index: 600; +} + +.leaflet-pane.leaflet-tooltip-pane { + z-index: 650; +} + +.leaflet-pane.leaflet-popup-pane { + z-index: 700; +} + +/* Ensure markers are above the map tiles */ +.leaflet-marker-pane svg { + position: relative; + z-index: 1; +} + +/* Force circle markers to be visible */ +.leaflet-overlay-pane svg { + z-index: 1; +} + +.leaflet-overlay-pane svg path { + cursor: pointer; + pointer-events: auto; +} + +/* Ensure SVG circle markers are rendered */ +.location-marker { + cursor: pointer !important; +} + +/* Override any conflicting styles */ +.leaflet-container path.leaflet-interactive { + stroke: #ffffff !important; + stroke-opacity: 1 !important; + stroke-width: 2px !important; + fill-opacity: 0.8 !important; +} + +/* Marker being moved */ +.location-marker.leaflet-drag-target { + cursor: move !important; +} + +/* Popup actions buttons spacing */ +.popup-actions { + display: flex; + gap: 5px; + margin-top: 10px; +} + +/* Pulsing animation for marker being moved */ +@keyframes pulse-marker { + 0% { + transform: scale(1); + opacity: 0.9; + } + 50% { + transform: scale(1.2); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0.9; + } +} + +/* Ensure marker animations work */ +.leaflet-overlay-pane svg path { + transform-origin: center; +} + +/* Move confirmation popup styles */ +.move-confirm-popup-wrapper .leaflet-popup-content-wrapper { + background: white; + border: 2px solid var(--primary-color); + box-shadow: 0 4px 12px rgba(0,0,0,0.2); +} + +.move-confirm-popup { + text-align: center; + padding: 10px; +} + +.move-confirm-popup h3 { + margin: 0 0 10px 0; + color: var(--dark-color); + font-size: 16px; +} + +.move-confirm-popup p { + margin: 0 0 15px 0; + color: #666; +} + +.move-confirm-popup .popup-actions { + display: flex; + gap: 10px; + justify-content: center; +} + +/* Cancel move button styles */ +#cancel-move-btn, +#mobile-cancel-move-btn { + background-color: var(--danger-color); + color: white; +} + +#cancel-move-btn:hover, +#mobile-cancel-move-btn:hover { + background-color: #c0392b; +} + +/* Ensure crosshairs are visible during move */ +.crosshair { + z-index: 1100; +} diff --git a/map/app/public/css/modules/map-controls.css b/map/app/public/css/modules/map-controls.css new file mode 100644 index 0000000..ebc53a6 --- /dev/null +++ b/map/app/public/css/modules/map-controls.css @@ -0,0 +1,72 @@ +/* Map controls */ +.map-controls { + position: absolute; + top: 20px; + right: 20px; + display: flex; + flex-direction: column; + gap: 10px; + z-index: 1000; +} + +/* Move Controls */ +.move-controls { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: 10000; + min-width: 300px; + text-align: center; +} + +/* Crosshair for location selection */ +.crosshair { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + z-index: 999; +} + +.crosshair.hidden { + display: none; +} + +.crosshair-x, +.crosshair-y { + position: absolute; + background-color: rgba(44, 90, 160, 0.8); +} + +.crosshair-x { + width: 40px; + height: 2px; + left: -20px; + top: -1px; +} + +.crosshair-y { + width: 2px; + height: 40px; + left: -1px; + top: -20px; +} + +.crosshair-info { + position: absolute; + top: 30px; + left: 50%; + transform: translateX(-50%); + background-color: rgba(44, 62, 80, 0.9); + color: white; + padding: 5px 10px; + border-radius: var(--border-radius); + font-size: 12px; + white-space: nowrap; +} diff --git a/map/app/public/css/modules/mobile-ui.css b/map/app/public/css/modules/mobile-ui.css new file mode 100644 index 0000000..5e4d5e7 --- /dev/null +++ b/map/app/public/css/modules/mobile-ui.css @@ -0,0 +1,154 @@ +/* Mobile dropdown menu */ +.mobile-dropdown { + position: relative; + display: none; +} + +.mobile-dropdown-toggle { + background: none; + border: none; + color: white; + font-size: 18px; + cursor: pointer; + padding: 8px; + border-radius: var(--border-radius); + transition: var(--transition); + display: flex; + align-items: center; + gap: 5px; +} + +.mobile-dropdown-toggle:hover { + background-color: rgba(255,255,255,0.1); +} + +.mobile-dropdown-content { + position: absolute; + top: 100%; + right: 0; + background-color: white; + color: var(--dark-color); + min-width: 250px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + border-radius: var(--border-radius); + overflow: hidden; + transform: translateY(-10px); + opacity: 0; + visibility: hidden; + transition: var(--transition); + z-index: 1001; +} + +.mobile-dropdown.active .mobile-dropdown-content { + transform: translateY(0); + opacity: 1; + visibility: visible; +} + +.mobile-dropdown-item { + padding: 12px 15px; + border-bottom: 1px solid #eee; + font-size: 14px; +} + +.mobile-dropdown-item:last-child { + border-bottom: none; + border-top: 1px solid #ddd; + background-color: #fff5f5; +} + +.mobile-dropdown-item:last-child button { + color: var(--danger-color); + font-weight: 500; +} + +.mobile-dropdown-item:last-child:hover { + background-color: #fee; +} + +.mobile-dropdown-item.location-info { + background-color: var(--primary-color); + color: white; + font-weight: 500; +} + +.mobile-dropdown-item.user-info { + background-color: var(--light-color); + color: var(--dark-color); +} + +/* Add logout button specific styles */ +.mobile-dropdown-item button { + background: none; + border: none; + color: inherit; + font-size: inherit; + cursor: pointer; + width: 100%; + text-align: left; + padding: 0; + font-family: inherit; +} + +.mobile-dropdown-item button:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* Logout button danger styling */ +.mobile-dropdown-item.logout-item { + border-top: 1px solid #eee; + background-color: #fee; +} + +.mobile-dropdown-item.logout-item button { + color: var(--danger-color); + font-weight: 500; +} + +.mobile-dropdown-item.logout-item:hover { + background-color: #fdd; +} + +/* Floating sidebar for mobile */ +.mobile-sidebar { + position: fixed; + top: 50%; + right: 10px; + transform: translateY(-50%); + background-color: white; + border-radius: var(--border-radius); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + display: none; + flex-direction: column; + gap: 5px; + padding: 8px; +} + +.mobile-sidebar .btn { + margin: 0; + min-width: 44px; + min-height: 44px; + padding: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; +} + +/* Active state for mobile buttons */ +.mobile-sidebar .btn.active { + background-color: var(--dark-color); + color: white; +} + +/* Active state for desktop map control buttons */ +.map-controls .btn.active { + background-color: var(--dark-color); + color: white; + border-color: var(--dark-color); +} + +.mobile-sidebar .btn:active { + transform: scale(0.95); +} diff --git a/map/app/public/css/modules/modal.css b/map/app/public/css/modules/modal.css new file mode 100644 index 0000000..025bdc3 --- /dev/null +++ b/map/app/public/css/modules/modal.css @@ -0,0 +1,83 @@ +/* Modal */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 3000; + animation: fadeIn 0.3s ease; +} + +.modal.hidden { + display: none; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.modal-content { + background-color: white; + border-radius: var(--border-radius); + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow: auto; + box-shadow: 0 4px 20px rgba(0,0,0,0.15); + animation: slideUp 0.3s ease; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(50px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal-header { + padding: 20px; + border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h2 { + font-size: 20px; + font-weight: 600; + color: var(--dark-color); +} + +.modal-close { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: var(--secondary-color); + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: var(--transition); +} + +.modal-close:hover { + background-color: var(--light-color); + color: var(--dark-color); +} + +.modal-body { + padding: 20px; +} diff --git a/map/app/public/css/modules/notifications.css b/map/app/public/css/modules/notifications.css new file mode 100644 index 0000000..409a46f --- /dev/null +++ b/map/app/public/css/modules/notifications.css @@ -0,0 +1,90 @@ +/* Status messages */ +.status-container { + position: fixed; + top: calc(var(--header-height) + 20px); + left: 50%; + transform: translateX(-50%); + z-index: 2000; + max-width: 400px; + width: 90%; +} + +.status-message { + padding: 12px 20px; + border-radius: var(--border-radius); + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 10px; + animation: slideIn 0.3s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.status-message.success { + background-color: var(--success-color); + color: white; +} + +.status-message.error { + background-color: var(--danger-color); + color: white; +} + +.status-message.warning { + background-color: var(--warning-color); + color: white; +} + +.status-message.info { + background-color: var(--primary-color); + color: white; +} + +/* Loading overlay */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255,255,255,0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 4000; +} + +.loading-overlay.hidden { + display: none; +} + +.spinner { + width: 50px; + height: 50px; + border: 3px solid var(--light-color); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-overlay p { + margin-top: 20px; + color: var(--dark-color); + font-size: 16px; +} diff --git a/map/app/public/css/modules/print.css b/map/app/public/css/modules/print.css new file mode 100644 index 0000000..3f1452b --- /dev/null +++ b/map/app/public/css/modules/print.css @@ -0,0 +1,13 @@ +/* Print styles */ +@media print { + .header, + .map-controls, + .status-container, + .modal { + display: none !important; + } + + #map-container { + height: 100vh; + } +} diff --git a/map/app/public/css/modules/qr-code.css b/map/app/public/css/modules/qr-code.css new file mode 100644 index 0000000..9a9f6be --- /dev/null +++ b/map/app/public/css/modules/qr-code.css @@ -0,0 +1,74 @@ +/* Search Result Item with QR Button */ +.search-result-item { + position: relative; + padding: 12px; + border-bottom: 1px solid #e0e0e0; +} + +.search-result-item:last-child { + border-bottom: none; +} + +.search-result-item .make-qr-btn { + position: absolute; + top: 12px; + right: 12px; + padding: 4px 8px; + font-size: 12px; +} + +/* QR Code Modal Styles */ +.qr-modal { + z-index: 11000; /* Ensure QR modal is above everything else */ +} + +.qr-modal-content { + max-width: 400px; + text-align: center; + z-index: 11001; /* Even higher for the content */ + position: relative; +} + +.qr-modal-body { + padding: 30px; +} + +.qr-code-image { + max-width: 256px; + height: auto; + margin: 0 auto 20px; + display: block; + border: 4px solid var(--light-color); + border-radius: var(--border-radius); +} + +.qr-code-info { + margin-top: 20px; +} + +.qr-code-info p { + margin-bottom: 10px; + color: var(--secondary-color); + font-size: 14px; +} + +.qr-code-url { + color: var(--primary-color); + text-decoration: none; + word-break: break-all; + font-size: 14px; +} + +.qr-code-url:hover { + text-decoration: underline; +} + +.qr-loading { + padding: 40px; +} + +/* Adjust search result layout to accommodate button */ +.docs-search-results-list .search-result { + display: block; + padding-right: 100px; /* Make room for QR button */ +} diff --git a/map/app/public/css/modules/responsive.css b/map/app/public/css/modules/responsive.css new file mode 100644 index 0000000..ce09a31 --- /dev/null +++ b/map/app/public/css/modules/responsive.css @@ -0,0 +1,190 @@ +/* Mobile responsiveness */ +@media (max-width: 768px) { + .docs-search-container { + display: block; + width: 100%; + margin: 10px 0; + padding: 0 0px; + z-index:10002/* Remove z-index */ + } + + .docs-search-wrapper { + width: 100%; + } + .docs-search-input { + width: 100%; + font-size: 16px; + padding: 0.75rem 2.5rem 0.75rem 1rem; + } + .docs-search-results { + position: fixed !important; + top: var(--header-height, 60px); + left: 0 !important; + right: 0 !important; + width: 100vw !important; + max-width: 100vw !important; + min-width: 0 !important; + border-radius: 0 0 12px 12px; + z-index: 10002; /* Same as desktop */ + margin: 0; + box-shadow: 0 8px 24px rgba(0,0,0,0.25); + } + + .docs-search-results-list { + max-height: 50vh; + overflow-y: auto; + } +} + +/* Desktop styles - show normal layout */ +@media (min-width: 769px) { + .mobile-dropdown { + display: none; + } + + .mobile-sidebar { + display: none; + } + + .header-actions { + display: flex; + } + + .user-info, + .location-count { + display: flex; + } + + .map-controls { + display: flex; + } + + /* Show shifts button on desktop */ + .header-actions a[href="/shifts.html"] { + display: inline-flex !important; + } + + .btn span.btn-icon { + margin-right: 5px; + } +} + +/* Hide desktop elements on mobile */ +@media (max-width: 768px) { + /* Update root variables for mobile */ + :root { + --content-padding: var(--mobile-padding); + --header-height: 50px; /* Smaller header on mobile */ + } + + .header { + padding: 0 var(--mobile-padding); + height: var(--header-height); + min-height: 50px; + flex-wrap: nowrap; + } + + .header h1 { + font-size: 18px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; + } + + .header-actions { + display: none; + } + + /* Hide any floating shifts button on mobile - but NOT the one in dropdown */ + .header-actions a[href="/shifts.html"] { + display: none !important; + } + + .mobile-dropdown { + display: block; + flex-shrink: 0; + } + + .mobile-sidebar { + display: flex; + position: fixed; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: auto; + max-width: 60px; + flex-direction: column; + box-sizing: border-box; + } + + .map-controls { + display: none; + } + + /* Hide user info and location count on desktop header for mobile */ + .user-info, + .location-count { + display: none; + } + + /* Adjust modal for mobile */ + .modal-content { + width: 95%; + max-width: calc(100vw - 20px); + margin: 10px; + box-sizing: border-box; + } + + .form-row { + grid-template-columns: 1fr; + } + + /* Adjust edit footer for mobile */ + .edit-footer-content { + padding: 15px; + } + + .edit-footer-header h2 { + font-size: 18px; + } +} + +/* Mobile responsiveness for QR button */ +@media (max-width: 768px) { + .search-result-item .make-qr-btn { + position: static; + display: block; + width: 100%; + margin-top: 10px; + } + + .docs-search-results-list .search-result { + padding-right: 12px; + } + + .qr-modal-content { + width: 95%; + max-width: 350px; + z-index: 11001; /* Maintain high z-index on mobile */ + } + + .qr-modal { + z-index: 11000; /* Maintain high z-index on mobile */ + } +} + +/* Mobile-friendly popup buttons */ +@media (max-width: 768px) { + .popup-content .popup-actions .btn { + padding: 10px 12px; + font-size: 14px; + min-height: 44px; /* Ensure touch-friendly size */ + } + + .move-confirm-popup .popup-actions .btn { + min-width: 100px; + min-height: 44px; + } +} diff --git a/map/app/public/css/modules/start-location-marker.css b/map/app/public/css/modules/start-location-marker.css new file mode 100644 index 0000000..d4f38b0 --- /dev/null +++ b/map/app/public/css/modules/start-location-marker.css @@ -0,0 +1,72 @@ +/* Distinctive start location marker styles */ +.start-location-custom-marker { + z-index: 2000 !important; +} + +.start-location-marker-wrapper { + position: relative; + width: 48px; + height: 48px; +} + +.start-location-marker-pin { + position: absolute; + width: 48px; + height: 48px; + background: #ff4444; + border-radius: 50% 50% 50% 0; + transform: rotate(-45deg); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + border: 3px solid white; + animation: bounce-marker 2s ease-in-out infinite; +} + +.start-location-marker-inner { + transform: rotate(45deg); + width: 24px; + height: 24px; +} + +.start-location-marker-pulse { + position: absolute; + width: 48px; + height: 48px; + border-radius: 50%; + background: rgba(255, 68, 68, 0.3); + animation: pulse-ring 2s ease-out infinite; +} + +@keyframes bounce-marker { + 0%, 100% { + transform: rotate(-45deg) translateY(0); + } + 50% { + transform: rotate(-45deg) translateY(-5px); + } +} + +@keyframes pulse-ring { + 0% { + transform: scale(0.5); + opacity: 1; + } + 100% { + transform: scale(2); + opacity: 0; + } +} + +/* Enhanced popup for start location */ +.start-location-popup-enhanced .leaflet-popup-content-wrapper { + padding: 0; + overflow: hidden; + border: none; + box-shadow: 0 5px 20px rgba(0,0,0,0.3); +} + +.start-location-popup-enhanced .leaflet-popup-content { + margin: 0; +} diff --git a/map/app/public/css/style.css b/map/app/public/css/style.css index aa762f9..9d0cbc3 100644 --- a/map/app/public/css/style.css +++ b/map/app/public/css/style.css @@ -1,1828 +1,16 @@ -/* CSS Variables for theming */ -:root { - --primary-color: #a02c8d; - --success-color: #27ae60; - --danger-color: #e74c3c; - --warning-color: #f39c12; - --secondary-color: #ba6cdf; - --dark-color: #2e053f; - --light-color: #efcef0; - --border-radius: 4px; - --transition: all 0.3s ease; - --header-height: 60px; - - /* Responsive width variables */ - --container-max-width: 100%; - --content-padding: 20px; - --mobile-padding: 15px; - - /* Breakpoints for consistency */ - --mobile-breakpoint: 768px; - --tablet-breakpoint: 1024px; - --desktop-breakpoint: 1200px; -} - -/* Reset and base styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; - color: var(--dark-color); - background-color: var(--light-color); - width: 100%; - overflow-x: hidden; /* Prevent horizontal scrolling */ -} - -/* App container */ -#app { - display: flex; - flex-direction: column; - height: 100vh; /* Fallback for older browsers */ - height: var(--app-height); - width: 100%; - max-width: 100vw; - position: relative; - overflow: hidden; /* Prevent content from overflowing */ -} - -/* Header */ -.header { - height: var(--header-height); - background-color: var(--dark-color); - color: white; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 var(--content-padding); - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - z-index: 10001; /* Increase from 1000 to be higher than map controls */ - position: relative; - width: 100%; - min-width: 0; /* Allow flex items to shrink */ - flex-shrink: 0; /* Don't shrink the header */ -} - -.header h1 { - font-size: 24px; - font-weight: 600; -} - -.header-actions { - display: flex; - align-items: center; - gap: 15px; -} - -.location-count { - background-color: rgba(255,255,255,0.1); - padding: 5px 15px; - border-radius: 20px; - font-size: 14px; -} - -/* User info in header */ -.user-info { - display: flex; - align-items: center; - gap: 15px; - padding: 0 15px; - border-right: 1px solid rgba(255,255,255,0.2); - margin-right: 15px; -} - -.user-email { - font-size: 14px; - color: rgba(255,255,255,0.9); -} - -/* Map container */ -#map-container { - position: relative; - width: 100%; - max-width: 100vw; - height: calc(100vh - var(--header-height)); - height: calc(var(--app-height) - var(--header-height)); - flex: 1; - overflow: hidden; -} - -#map { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - height: 100%; - max-width: 100%; - background-color: #f0f0f0; -} - -/* Map controls */ -.map-controls { - position: absolute; - top: 20px; - right: 20px; - display: flex; - flex-direction: column; - gap: 10px; - z-index: 1000; -} - -/* Move Controls */ -.move-controls { - position: fixed; - top: 20px; - left: 50%; - transform: translateX(-50%); - background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: 10000; - min-width: 300px; - text-align: center; -} - -/* Mobile-specific move controls */ -@media (max-width: 768px) { - .move-controls { - top: auto; - bottom: 20px; - left: 10px; - right: 10px; - transform: none; - width: auto; - min-width: unset; - max-width: 100%; - padding: 15px; - z-index: 10001; /* Ensure it's on top */ - background: white; /* Add background */ - box-shadow: 0 -2px 10px rgba(0,0,0,0.1); /* Add shadow for visibility */ - } - - .move-controls-content h3 { - font-size: 18px; - margin-bottom: 8px; - } - - .move-controls-content p { - font-size: 14px; - margin: 3px 0; - } - - .move-controls-actions { - flex-direction: column; - gap: 8px; - margin-top: 12px; - } - - .move-controls-actions .btn { - width: 100%; - padding: 12px 16px; - font-size: 16px; - } -} - -/* Buttons */ -.btn { - padding: 10px 16px; - border: none; - border-radius: var(--border-radius); - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; - outline: none; -} - -.btn:hover { - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.15); -} - -.btn:active { - transform: translateY(0); -} - -.btn-primary { - background-color: var(--primary-color); - color: white; -} - -.btn-primary:hover { - background-color: #2471a3; -} - -.btn-success { - background-color: var(--success-color); - color: white; -} - -.btn-success:hover { - background-color: #229954; -} - -.btn-secondary { - background-color: var(--secondary-color); - color: white; -} - -.btn-secondary:hover { - background-color: #7f8c8d; -} - -.btn-sm { - padding: 6px 12px; - font-size: 13px; -} - -.btn-danger { - background-color: var(--danger-color); - color: white; -} - -.btn-danger:hover { - background-color: #c0392b; -} - -/* Disabled button styles */ -.btn:disabled { - opacity: 0.6; - cursor: not-allowed; - background-color: #6c757d; -} - -.btn:disabled:hover { - background-color: #6c757d; - transform: none; -} - -.btn-primary:disabled { - background-color: #6c757d; - border-color: #6c757d; -} - -.btn-primary:disabled:hover { - background-color: #6c757d; - border-color: #6c757d; -} - -/* Crosshair for location selection */ -.crosshair { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - pointer-events: none; - z-index: 999; -} - -.crosshair.hidden { - display: none; -} - -.crosshair-x, -.crosshair-y { - position: absolute; - background-color: rgba(44, 90, 160, 0.8); -} - -.crosshair-x { - width: 40px; - height: 2px; - left: -20px; - top: -1px; -} - -.crosshair-y { - width: 2px; - height: 40px; - left: -1px; - top: -20px; -} - -.crosshair-info { - position: absolute; - top: 30px; - left: 50%; - transform: translateX(-50%); - background-color: rgba(44, 62, 80, 0.9); - color: white; - padding: 5px 10px; - border-radius: var(--border-radius); - font-size: 12px; - white-space: nowrap; -} - -/* Status messages */ -.status-container { - position: fixed; - top: calc(var(--header-height) + 20px); - left: 50%; - transform: translateX(-50%); - z-index: 2000; - max-width: 400px; - width: 90%; -} - -.status-message { - padding: 12px 20px; - border-radius: var(--border-radius); - margin-bottom: 10px; - display: flex; - align-items: center; - gap: 10px; - animation: slideIn 0.3s ease; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.status-message.success { - background-color: var(--success-color); - color: white; -} - -.status-message.error { - background-color: var(--danger-color); - color: white; -} - -.status-message.warning { - background-color: var(--warning-color); - color: white; -} - -.status-message.info { - background-color: var(--primary-color); - color: white; -} - -/* Modal */ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0,0,0,0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 3000; - animation: fadeIn 0.3s ease; -} - -.modal.hidden { - display: none; -} - -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -.modal-content { - background-color: white; - border-radius: var(--border-radius); - width: 90%; - max-width: 500px; - max-height: 90vh; - overflow: auto; - box-shadow: 0 4px 20px rgba(0,0,0,0.15); - animation: slideUp 0.3s ease; -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(50px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.modal-header { - padding: 20px; - border-bottom: 1px solid #e0e0e0; - display: flex; - justify-content: space-between; - align-items: center; -} - -.modal-header h2 { - font-size: 20px; - font-weight: 600; - color: var(--dark-color); -} - -.modal-close { - background: none; - border: none; - font-size: 24px; - cursor: pointer; - color: var(--secondary-color); - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - transition: var(--transition); -} - -.modal-close:hover { - background-color: var(--light-color); - color: var(--dark-color); -} - -.modal-body { - padding: 20px; -} - -/* Edit Footer Form */ -.edit-footer { - position: fixed; - bottom: 0; - left: 0; - right: 0; - background-color: white; - border-top: 2px solid var(--primary-color); - box-shadow: 0 -2px 10px rgba(0,0,0,0.1); - z-index: 1500; - transition: transform 0.3s ease; - max-height: 60vh; - overflow-y: auto; -} - -.edit-footer.hidden { - transform: translateY(100%); -} - -.edit-footer-content { - padding: 20px; - max-width: 800px; - margin: 0 auto; -} - -.edit-footer-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; - padding-bottom: 15px; - border-bottom: 1px solid #e0e0e0; -} - -.edit-footer-header h2 { - margin: 0; - font-size: 20px; - color: var(--dark-color); -} - -/* Form styles */ -.form-group { - margin-bottom: 15px; -} - -.form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 15px; -} - -.form-group label { - display: block; - margin-bottom: 5px; - font-weight: 500; - color: var(--dark-color); - font-size: 14px; -} - -.form-group input, -.form-group textarea { - width: 100%; - padding: 8px 12px; - border: 1px solid #ddd; - border-radius: var(--border-radius); - font-size: 14px; - transition: var(--transition); -} - -.form-group input:focus, -.form-group textarea:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1); -} - -.form-group input.valid { - border-color: var(--success-color); -} - -.form-group input.invalid { - border-color: var(--danger-color); -} - -.form-group input[readonly] { - background-color: #f5f5f5; - cursor: not-allowed; -} - -.form-group select { - width: 100%; - padding: 8px 12px; - border: 1px solid #ddd; - border-radius: var(--border-radius); - font-size: 14px; - transition: var(--transition); - background-color: white; - cursor: pointer; -} - -.form-group select:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1); -} - -.form-group input[type="checkbox"] { - width: auto; - margin-right: 8px; - cursor: pointer; -} - -.form-group label input[type="checkbox"] { - vertical-align: middle; -} - -.form-actions { - display: flex; - gap: 10px; - justify-content: flex-end; - margin-top: 20px; - padding-top: 20px; - border-top: 1px solid #e0e0e0; -} - -/* Loading overlay */ -.loading-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(255,255,255,0.9); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - z-index: 4000; -} - -.loading-overlay.hidden { - display: none; -} - -.spinner { - width: 50px; - height: 50px; - border: 3px solid var(--light-color); - border-top-color: var(--primary-color); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -.loading-overlay p { - margin-top: 20px; - color: var(--dark-color); - font-size: 16px; -} - -/* Leaflet customizations */ -.leaflet-popup-content-wrapper { - border-radius: var(--border-radius); - box-shadow: 0 3px 10px rgba(0,0,0,0.2); -} - -.leaflet-popup-content { - margin: 13px 19px; - line-height: 1.5; -} - -.popup-content h3 { - margin: 0 0 10px 0; - color: var(--dark-color); - font-size: 16px; -} - -.popup-content p { - margin: 5px 0; - color: #666; - font-size: 14px; -} - -.popup-content .popup-meta { - font-size: 12px; - color: #999; - margin-top: 10px; - padding-top: 10px; - border-top: 1px solid #eee; -} - -/* Popup actions section */ -.popup-content .popup-actions { - margin-top: 15px; - padding-top: 15px; - border-top: 1px solid #eee; - display: flex; - gap: 10px; - justify-content: center; -} - -.popup-content .popup-actions .btn { - flex: 1; - max-width: 120px; -} - -/* Distinctive start location marker styles */ -.start-location-custom-marker { - z-index: 2000 !important; -} - -.start-location-marker-wrapper { - position: relative; - width: 48px; - height: 48px; -} - -.start-location-marker-pin { - position: absolute; - width: 48px; - height: 48px; - background: #ff4444; - border-radius: 50% 50% 50% 0; - transform: rotate(-45deg); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - display: flex; - align-items: center; - justify-content: center; - border: 3px solid white; - animation: bounce-marker 2s ease-in-out infinite; -} - -.start-location-marker-inner { - transform: rotate(45deg); - width: 24px; - height: 24px; -} - -.start-location-marker-pulse { - position: absolute; - width: 48px; - height: 48px; - border-radius: 50%; - background: rgba(255, 68, 68, 0.3); - animation: pulse-ring 2s ease-out infinite; -} - -@keyframes bounce-marker { - 0%, 100% { - transform: rotate(-45deg) translateY(0); - } - 50% { - transform: rotate(-45deg) translateY(-5px); - } -} - -@keyframes pulse-ring { - 0% { - transform: scale(0.5); - opacity: 1; - } - 100% { - transform: scale(2); - opacity: 0; - } -} - -/* Enhanced popup for start location */ -.start-location-popup-enhanced .leaflet-popup-content-wrapper { - padding: 0; - overflow: hidden; - border: none; - box-shadow: 0 5px 20px rgba(0,0,0,0.3); -} - -.start-location-popup-enhanced .leaflet-popup-content { - margin: 0; -} - -/* Mobile dropdown menu */ -.mobile-dropdown { - position: relative; - display: none; -} - -.mobile-dropdown-toggle { - background: none; - border: none; - color: white; - font-size: 18px; - cursor: pointer; - padding: 8px; - border-radius: var(--border-radius); - transition: var(--transition); - display: flex; - align-items: center; - gap: 5px; -} - -.mobile-dropdown-toggle:hover { - background-color: rgba(255,255,255,0.1); -} - -.mobile-dropdown-content { - position: absolute; - top: 100%; - right: 0; - background-color: white; - color: var(--dark-color); - min-width: 250px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - border-radius: var(--border-radius); - overflow: hidden; - transform: translateY(-10px); - opacity: 0; - visibility: hidden; - transition: var(--transition); - z-index: 1001; -} - -.mobile-dropdown.active .mobile-dropdown-content { - transform: translateY(0); - opacity: 1; - visibility: visible; -} - -.mobile-dropdown-item { - padding: 12px 15px; - border-bottom: 1px solid #eee; - font-size: 14px; -} - -.mobile-dropdown-item:last-child { - border-bottom: none; - border-top: 1px solid #ddd; - background-color: #fff5f5; -} - -.mobile-dropdown-item:last-child button { - color: var(--danger-color); - font-weight: 500; -} - -.mobile-dropdown-item:last-child:hover { - background-color: #fee; -} - -.mobile-dropdown-item.location-info { - background-color: var(--primary-color); - color: white; - font-weight: 500; -} - -.mobile-dropdown-item.user-info { - background-color: var(--light-color); - color: var(--dark-color); -} - -/* Add logout button specific styles */ -.mobile-dropdown-item button { - background: none; - border: none; - color: inherit; - font-size: inherit; - cursor: pointer; - width: 100%; - text-align: left; - padding: 0; - font-family: inherit; -} - -.mobile-dropdown-item button:hover { - background-color: rgba(0, 0, 0, 0.05); -} - -/* Logout button danger styling */ -.mobile-dropdown-item.logout-item { - border-top: 1px solid #eee; - background-color: #fee; -} - -.mobile-dropdown-item.logout-item button { - color: var(--danger-color); - font-weight: 500; -} - -.mobile-dropdown-item.logout-item:hover { - background-color: #fdd; -} - -/* Floating sidebar for mobile */ -.mobile-sidebar { - position: fixed; - top: 50%; - right: 10px; - transform: translateY(-50%); - background-color: white; - border-radius: var(--border-radius); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - z-index: 1000; - display: none; - flex-direction: column; - gap: 5px; - padding: 8px; -} - -.mobile-sidebar .btn { - margin: 0; - min-width: 44px; - min-height: 44px; - padding: 12px; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; -} - -/* Active state for mobile buttons */ -.mobile-sidebar .btn.active { - background-color: var(--dark-color); - color: white; -} - -/* Active state for desktop map control buttons */ -.map-controls .btn.active { - background-color: var(--dark-color); - color: white; - border-color: var(--dark-color); -} - -.mobile-sidebar .btn:active { - transform: scale(0.95); -} - -/* Documentation Search Styles */ -.docs-search-container { - position: relative; - flex: 0 1 400px; - margin: 0 1rem; - /* Remove z-index here - let it inherit from header */ -} - -.docs-search-wrapper { - position: relative; -} - -.docs-search-input { - width: 100%; - padding: 0.5rem 2.5rem 0.5rem 1rem; - border: 1px solid #ddd; - border-radius: 4px; - font-size: 14px; - background: white; - transition: border-color 0.2s; -} - -.docs-search-input:focus { - outline: none; - border-color: #4CAF50; - box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1); -} - -.docs-search-icon { - position: absolute; - right: 0.75rem; - top: 50%; - transform: translateY(-50%); - opacity: 0.5; - pointer-events: none; -} - -.docs-search-results { - position: absolute; - top: calc(100% + 0.5rem); - left: 0; - right: 0; - max-height: 60vh; - background: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); - z-index: 10002; /* Just slightly higher than header */ - overflow: hidden; - display: flex; - flex-direction: column; -} - -.docs-search-results.hidden { - display: none; -} - -.docs-search-results-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem 1rem; - border-bottom: 1px solid #eee; - background: #f5f5f5; -} - -.results-count { - font-size: 12px; - color: #666; - font-weight: 500; -} - -.close-results { - background: none; - border: none; - font-size: 20px; - cursor: pointer; - color: #666; - padding: 0; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: background-color 0.2s; -} - -.close-results:hover { - background-color: #e0e0e0; -} - -.docs-search-results-list { - overflow-y: auto; - flex: 1; -} - -.search-result { - display: block; - padding: 1rem; - border-bottom: 1px solid #eee; - text-decoration: none; - color: inherit; - transition: background-color 0.2s; -} - -.search-result:hover { - background-color: #f5f5f5; -} - -.search-result:last-child { - border-bottom: none; -} - -.search-result-title { - font-weight: 500; - color: #333; - margin-bottom: 0.25rem; -} - -.search-result-snippet { - font-size: 13px; - color: #666; - line-height: 1.4; - margin-bottom: 0.25rem; -} - -.search-result-snippet mark { - background-color: #ffeb3b; - color: inherit; - font-weight: 500; - padding: 0 2px; -} - -.search-result-path { - font-size: 11px; - color: #999; -} - -.no-results { - padding: 2rem; - text-align: center; - color: #666; -} - -/* Search Result Item with QR Button */ -.search-result-item { - position: relative; - padding: 12px; - border-bottom: 1px solid #e0e0e0; -} - -.search-result-item:last-child { - border-bottom: none; -} - -.search-result-item .make-qr-btn { - position: absolute; - top: 12px; - right: 12px; - padding: 4px 8px; - font-size: 12px; -} - -/* QR Code Modal Styles */ -.qr-modal { - z-index: 11000; /* Ensure QR modal is above everything else */ -} - -.qr-modal-content { - max-width: 400px; - text-align: center; - z-index: 11001; /* Even higher for the content */ - position: relative; -} - -.qr-modal-body { - padding: 30px; -} - -.qr-code-image { - max-width: 256px; - height: auto; - margin: 0 auto 20px; - display: block; - border: 4px solid var(--light-color); - border-radius: var(--border-radius); -} - -.qr-code-info { - margin-top: 20px; -} - -.qr-code-info p { - margin-bottom: 10px; - color: var(--secondary-color); - font-size: 14px; -} - -.qr-code-url { - color: var(--primary-color); - text-decoration: none; - word-break: break-all; - font-size: 14px; -} - -.qr-code-url:hover { - text-decoration: underline; -} - -.qr-loading { - padding: 40px; -} - -/* Adjust search result layout to accommodate button */ -.docs-search-results-list .search-result { - display: block; - padding-right: 100px; /* Make room for QR button */ -} - -/* Mobile responsiveness for QR button */ -@media (max-width: 768px) { - .search-result-item .make-qr-btn { - position: static; - display: block; - width: 100%; - margin-top: 10px; - } - - .docs-search-results-list .search-result { - padding-right: 12px; - } - - .qr-modal-content { - width: 95%; - max-width: 350px; - z-index: 11001; /* Maintain high z-index on mobile */ - } - - .qr-modal { - z-index: 11000; /* Maintain high z-index on mobile */ - } -} - -/* Mobile responsiveness */ -@media (max-width: 768px) { - .docs-search-container { - display: block; - width: 100%; - margin: 10px 0; - padding: 0 0px; - z-index:10002/* Remove z-index */ - } - - .docs-search-wrapper { - width: 100%; - } - .docs-search-input { - width: 100%; - font-size: 16px; - padding: 0.75rem 2.5rem 0.75rem 1rem; - } - .docs-search-results { - position: fixed !important; - top: var(--header-height, 60px); - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 0 !important; - border-radius: 0 0 12px 12px; - z-index: 10002; /* Same as desktop */ - margin: 0; - box-shadow: 0 8px 24px rgba(0,0,0,0.25); - } - - .docs-search-results-list { - max-height: 50vh; - overflow-y: auto; - } -} - -/* Desktop styles - show normal layout */ -@media (min-width: 769px) { - .mobile-dropdown { - display: none; - } - - .mobile-sidebar { - display: none; - } - - .header-actions { - display: flex; - } - - .user-info, - .location-count { - display: flex; - } - - .map-controls { - display: flex; - } - - /* Show shifts button on desktop */ - .header-actions a[href="/shifts.html"] { - display: inline-flex !important; - } - - .btn span.btn-icon { - margin-right: 5px; - } -} - -/* Hide desktop elements on mobile */ -@media (max-width: 768px) { - /* Update root variables for mobile */ - :root { - --content-padding: var(--mobile-padding); - --header-height: 50px; /* Smaller header on mobile */ - } - - .header { - padding: 0 var(--mobile-padding); - height: var(--header-height); - min-height: 50px; - flex-wrap: nowrap; - } - - .header h1 { - font-size: 18px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; - min-width: 0; - } - - .header-actions { - display: none; - } - - /* Hide any floating shifts button on mobile - but NOT the one in dropdown */ - .header-actions a[href="/shifts.html"] { - display: none !important; - } - - .mobile-dropdown { - display: block; - flex-shrink: 0; - } - - .mobile-sidebar { - display: flex; - position: fixed; - right: 10px; - top: 50%; - transform: translateY(-50%); - width: auto; - max-width: 60px; - flex-direction: column; - box-sizing: border-box; - } - - .map-controls { - display: none; - } - - /* Hide user info and location count on desktop header for mobile */ - .user-info, - .location-count { - display: none; - } - - /* Adjust modal for mobile */ - .modal-content { - width: 95%; - max-width: calc(100vw - 20px); - margin: 10px; - box-sizing: border-box; - } - - .form-row { - grid-template-columns: 1fr; - } - - /* Adjust edit footer for mobile */ - .edit-footer-content { - padding: 15px; - } - - .edit-footer-header h2 { - font-size: 18px; - } -} - -/* Fullscreen styles */ -.fullscreen #map-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5000; -} - -.fullscreen .header { - display: none; -} - -/* Print styles */ -@media print { - .header, - .map-controls, - .status-container, - .modal { - display: none !important; - } - - #map-container { - height: 100vh; - } -} - -/* Leaflet Circle Markers - Add this section */ -.leaflet-marker-icon { - background-color: transparent !important; - border: none !important; -} - -.leaflet-interactive { - cursor: pointer; -} - -/* Ensure circle markers are visible */ -path.leaflet-interactive { - stroke: #fff; - stroke-opacity: 1; - stroke-width: 2; - fill-opacity: 0.8; -} - -/* Fix for marker z-index */ -.leaflet-pane.leaflet-marker-pane { - z-index: 600; -} - -.leaflet-pane.leaflet-tooltip-pane { - z-index: 650; -} - -.leaflet-pane.leaflet-popup-pane { - z-index: 700; -} - -/* Ensure markers are above the map tiles */ -.leaflet-marker-pane svg { - position: relative; - z-index: 1; -} - -/* Force circle markers to be visible */ -.leaflet-overlay-pane svg { - z-index: 1; -} - -.leaflet-overlay-pane svg path { - cursor: pointer; - pointer-events: auto; -} - -/* Ensure SVG circle markers are rendered */ -.location-marker { - cursor: pointer !important; -} - -/* Override any conflicting styles */ -.leaflet-container path.leaflet-interactive { - stroke: #ffffff !important; - stroke-opacity: 1 !important; - stroke-width: 2px !important; - fill-opacity: 0.8 !important; -} - -/* Marker being moved */ -.location-marker.leaflet-drag-target { - cursor: move !important; -} - -/* Popup actions buttons spacing */ -.popup-actions { - display: flex; - gap: 5px; - margin-top: 10px; -} - -/* Pulsing animation for marker being moved */ -@keyframes pulse-marker { - 0% { - transform: scale(1); - opacity: 0.9; - } - 50% { - transform: scale(1.2); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 0.9; - } -} - -/* Ensure marker animations work */ -.leaflet-overlay-pane svg path { - transform-origin: center; -} - -/* Move confirmation popup styles */ -.move-confirm-popup-wrapper .leaflet-popup-content-wrapper { - background: white; - border: 2px solid var(--primary-color); - box-shadow: 0 4px 12px rgba(0,0,0,0.2); -} - -.move-confirm-popup { - text-align: center; - padding: 10px; -} - -.move-confirm-popup h3 { - margin: 0 0 10px 0; - color: var(--dark-color); - font-size: 16px; -} - -.move-confirm-popup p { - margin: 0 0 15px 0; - color: #666; -} - -.move-confirm-popup .popup-actions { - display: flex; - gap: 10px; - justify-content: center; -} - -/* Cancel move button styles */ -#cancel-move-btn, -#mobile-cancel-move-btn { - background-color: var(--danger-color); - color: white; -} - -#cancel-move-btn:hover, -#mobile-cancel-move-btn:hover { - background-color: #c0392b; -} - -/* Ensure crosshairs are visible during move */ -.crosshair { - z-index: 1100; -} - -/* Mobile-friendly popup buttons */ -@media (max-width: 768px) { - .popup-content .popup-actions .btn { - padding: 10px 12px; - font-size: 14px; - min-height: 44px; /* Ensure touch-friendly size */ - } - - .move-confirm-popup .popup-actions .btn { - min-width: 100px; - min-height: 44px; - } -} - -/* Cache Busting Update Notification Styles */ -.update-notification { - position: fixed !important; - top: 20px !important; - right: 20px !important; - background: linear-gradient(135deg, #4CAF50, #45a049) !important; - color: white !important; - padding: 15px !important; - border-radius: 8px !important; - box-shadow: 0 4px 20px rgba(0,0,0,0.3) !important; - z-index: 10000 !important; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; - animation: slideInFromRight 0.3s ease-out !important; - max-width: 350px !important; - border: 1px solid rgba(255,255,255,0.2) !important; -} - -@keyframes slideInFromRight { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -.update-notification-content { - display: flex !important; - align-items: center !important; - gap: 10px !important; - flex-wrap: wrap !important; -} - -.update-message { - font-size: 14px !important; - font-weight: 500 !important; - flex: 1 !important; - min-width: 150px !important; -} - -.update-button { - background: rgba(255,255,255,0.2) !important; - border: 1px solid rgba(255,255,255,0.3) !important; - color: white !important; - padding: 8px 16px !important; - border-radius: 4px !important; - cursor: pointer !important; - font-size: 12px !important; - font-weight: 500 !important; - transition: background-color 0.2s ease !important; - white-space: nowrap !important; -} - -.update-button:hover { - background: rgba(255,255,255,0.3) !important; -} - -.update-dismiss { - background: none !important; - border: none !important; - color: white !important; - cursor: pointer !important; - font-size: 18px !important; - padding: 0 !important; - margin-left: 5px !important; - width: 24px !important; - height: 24px !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 50% !important; - transition: background-color 0.2s ease !important; -} - -.update-dismiss:hover { - background: rgba(255,255,255,0.2) !important; -} - -/* Mobile responsive styles for update notification */ -@media (max-width: 768px) { - .update-notification { - top: 10px !important; - right: 10px !important; - left: 10px !important; - max-width: none !important; - padding: 12px !important; - } - - .update-notification-content { - flex-direction: column !important; - align-items: stretch !important; - gap: 8px !important; - } - - .update-message { - text-align: center !important; - min-width: auto !important; - } - - .update-button { - align-self: center !important; - min-width: 120px !important; - } - - .update-dismiss { - position: absolute !important; - top: 8px !important; - right: 8px !important; - margin: 0 !important; - } -} - -/* Apartment Building Popup Styles */ -.apartment-popup .leaflet-popup-content-wrapper { - background: white; - border-radius: 8px; - box-shadow: 0 8px 32px rgba(0,0,0,0.15); - border: 1px solid #e9ecef; - max-width: min(320px, calc(100vw - 40px)) !important; - min-width: min(280px, calc(100vw - 40px)) !important; - width: auto !important; -} - -.apartment-popup .leaflet-popup-content { - margin: 12px 18px; - line-height: 1.4; - max-width: 100%; - overflow: hidden; - word-wrap: break-word; - overflow-wrap: break-word; -} - -.apartment-building-popup .suite-selector { - transition: all 0.2s ease; - font-weight: 500; - border: 2px solid #e9ecef; - background: white; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 8px center; - background-size: 16px; - padding-right: 32px; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - width: 100%; - max-width: 100%; - box-sizing: border-box; - font-size: 12px; - padding: 8px 32px 8px 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.apartment-building-popup .suite-selector:hover { - border-color: #ff6b35; - box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15); -} - -.apartment-building-popup .suite-selector:focus { - outline: none; - border-color: #ff6b35; - box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.1); -} - -.apartment-building-popup .suite-content { - transition: all 0.3s ease; - max-width: 100%; - overflow: hidden; - box-sizing: border-box; -} - -.apartment-building-popup .suite-details { - animation: fadeIn 0.3s ease; - word-wrap: break-word; - overflow-wrap: break-word; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(5px); } - to { opacity: 1; transform: translateY(0); } -} - -/* General apartment popup container constraints */ -.apartment-building-popup { - max-width: 100%; - box-sizing: border-box; - word-wrap: break-word; - overflow-wrap: break-word; - overflow: hidden; -} - -.apartment-building-popup * { - box-sizing: border-box; - max-width: 100%; -} - -/* Specific text overflow handling */ -.apartment-building-popup select option { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.apartment-building-popup .suite-details > div, -.apartment-building-popup.app-data .unit-details > div { - word-wrap: break-word; - overflow-wrap: break-word; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.3; -} - -/* Allow long addresses to wrap */ -.apartment-building-popup .building-header > div > div:first-child > div:first-child { - word-wrap: break-word; - overflow-wrap: break-word; - white-space: normal; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; -} - -.apartment-building-popup .building-header { - overflow: hidden; - text-overflow: ellipsis; - box-sizing: border-box; - margin: -12px -18px 16px -18px !important; - padding: 16px 20px !important; - background: linear-gradient(135deg, #ff6b35, #f7931e); - color: white; - border-radius: 8px 8px 0 0; - position: relative; - left: 0; - right: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); -} - -.apartment-building-popup.app-data .building-header { - background: linear-gradient(135deg, #a02c8d, #ba6cdf) !important; -} - -.apartment-building-popup button { - width: 100%; - max-width: 100%; - box-sizing: border-box; - word-wrap: break-word; - white-space: normal; -} - -/* Ensure dropdown options don't cause overflow */ -.apartment-building-popup select option { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Mobile-specific Leaflet popup positioning fixes */ -@media (max-width: 768px) { - .leaflet-popup { - max-width: calc(100vw - 20px) !important; - } - - .leaflet-popup-content-wrapper { - max-width: 100% !important; - overflow: hidden; - } - - .leaflet-popup-content { - max-width: 100% !important; - overflow: hidden; - word-wrap: break-word; - overflow-wrap: break-word; - } - - /* Ensure popups don't go off-screen on mobile */ - .leaflet-popup-pane { - pointer-events: none; - } - - .leaflet-popup { - pointer-events: auto; - margin: 0 10px; - } -} - -@media (max-width: 480px) { - .leaflet-popup { - max-width: calc(100vw - 15px) !important; - margin: 0 7px; - } -} - -@media (max-width: 320px) { - .leaflet-popup { - max-width: calc(100vw - 10px) !important; - margin: 0 5px; - } -} - -/* Additional mobile button fixes for ultra-small screens */ -@media (max-width: 280px) { - .apartment-building-popup.app-data .unit-details div[style*="display: flex"] { - flex-direction: column !important; - gap: 2px !important; - } - - .apartment-building-popup.app-data .unit-details button { - flex: none !important; - width: 100% !important; - min-width: unset !important; - margin-bottom: 2px; - } - - .apartment-building-popup button { - font-size: 8px !important; - padding: 3px 5px !important; - line-height: 1.2; - } - - .apartment-building-popup .suite-selector, - .apartment-building-popup.app-data .unit-selector { - font-size: 8px !important; - padding: 4px 18px 4px 4px !important; - } -} - -/* Prevent text selection on popup elements for better mobile UX */ -.apartment-building-popup { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.apartment-building-popup input, -.apartment-building-popup select, -.apartment-building-popup textarea { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; -} +@import url("modules/base.css"); +@import url("modules/layout.css"); +@import url("modules/buttons.css"); +@import url("modules/map-controls.css"); +@import url("modules/modal.css"); +@import url("modules/forms.css"); +@import url("modules/notifications.css"); +@import url("modules/leaflet-custom.css"); +@import url("modules/start-location-marker.css"); +@import url("modules/mobile-ui.css"); +@import url("modules/doc-search.css"); +@import url("modules/qr-code.css"); +@import url("modules/responsive.css"); +@import url("modules/print.css"); +@import url("modules/cache-busting.css"); +@import url("modules/apartment-popup.css"); diff --git a/map/app/public/index.html b/map/app/public/index.html index e99a2ae..199e19d 100644 --- a/map/app/public/index.html +++ b/map/app/public/index.html @@ -44,6 +44,10 @@
+ + 🖥️ + Homepage + 📅 View Shifts diff --git a/map/app/public/js/config.js b/map/app/public/js/config.js index ed4f45f..4265d25 100644 --- a/map/app/public/js/config.js +++ b/map/app/public/js/config.js @@ -5,5 +5,33 @@ export const CONFIG = { DEFAULT_ZOOM: parseInt(document.querySelector('meta[name="default-zoom"]')?.content) || 11, REFRESH_INTERVAL: 30000, // 30 seconds MAX_ZOOM: 20, - MIN_ZOOM: 2 + MIN_ZOOM: 2, + domain: null // Will be loaded dynamically }; + +// Load domain configuration from server +export async function loadDomainConfig() { + try { + const response = await fetch('/api/config/domain'); + if (response.ok) { + const data = await response.json(); + CONFIG.domain = data.domain; + updateHomepageLinks(); + } else { + console.error('Failed to load domain config:', response.status); + } + } catch (error) { + console.error('Error loading domain config:', error); + } +} + +// Update homepage links with the configured domain +function updateHomepageLinks() { + if (CONFIG.domain) { + const homepageUrl = `https://homepage.${CONFIG.domain}`; + const homepageLink = document.getElementById('homepage-link'); + if (homepageLink) { + homepageLink.href = homepageUrl; + } + } +} diff --git a/map/app/public/js/external-layers.js b/map/app/public/js/external-layers.js index dbdb6d1..e5122dc 100644 --- a/map/app/public/js/external-layers.js +++ b/map/app/public/js/external-layers.js @@ -428,8 +428,8 @@ function setupApartmentPopupListeners(props) { // Close the popup first map.closePopup(); - // Open the add modal with pre-filled data - openAddModal(lat, lng); + // Open the add modal but prevent the automatic address lookup + openAddModal(lat, lng, false); // Pre-fill the form with Edmonton data, focusing on the selected suite setTimeout(() => { @@ -470,8 +470,8 @@ function setupSingleUnitPopupListeners() { // Close the popup first map.closePopup(); - // Open the add modal with pre-filled data - openAddModal(lat, lng); + // Open the add modal but prevent the automatic address lookup + openAddModal(lat, lng, false); // Pre-fill the form with Edmonton data setTimeout(() => { @@ -494,8 +494,14 @@ function setupSingleUnitPopupListeners() { /** * Pre-fills the add location form with city data */ -function prefillAddForm(data) { +async function prefillAddForm(data) { try { + // Import UI controls dynamically to avoid circular dependencies + const { resetAddressConfirmation } = await import('./ui-controls.js'); + + // First, reset any existing state + resetAddressConfirmation('add'); + // Basic form fields if (data.address) { const addressField = document.getElementById('location-address'); @@ -514,8 +520,8 @@ function prefillAddForm(data) { // Notes field with additional context const notesField = document.getElementById('location-notes'); - if (notesField && data.notes) { - let notes = data.notes; + if (notesField) { + let notes = data.notes || ''; // Add neighborhood information if available if (data.neighborhood && data.neighborhood.trim()) { @@ -537,7 +543,7 @@ function prefillAddForm(data) { notes += `\nSuite: ${data.suite}`; } - notesField.value = notes; + notesField.value = notes.trim(); } // Set coordinates (already set by openAddModal, but ensure they're correct) @@ -555,7 +561,11 @@ function prefillAddForm(data) { geoField.value = `${data.lat.toFixed(8)};${data.lng.toFixed(8)}`; } - console.log('Form pre-filled with city data:', data); + // Mark the address as confirmed since it's coming from a trusted source + const { setAddressConfirmed } = await import('./ui-controls.js'); + setAddressConfirmed('add', true); + + console.log('Form pre-filled and confirmed with city data:', data); } catch (error) { console.error('Error pre-filling form:', error); diff --git a/map/app/public/js/location-manager.js b/map/app/public/js/location-manager.js index 783169f..dffe2dc 100644 --- a/map/app/public/js/location-manager.js +++ b/map/app/public/js/location-manager.js @@ -470,7 +470,7 @@ export function closeAddModal() { document.getElementById('location-form').reset(); } -export function openAddModal(lat, lng) { +export function openAddModal(lat, lng, performLookup = true) { const modal = document.getElementById('add-modal'); const latInput = document.getElementById('location-lat'); const lngInput = document.getElementById('location-lng'); @@ -493,11 +493,13 @@ export function openAddModal(lat, lng) { // Show modal modal.classList.remove('hidden'); - // Trigger custom event for auto address lookup - const autoLookupEvent = new CustomEvent('autoAddressLookup', { - detail: { mode: 'add', lat, lng } - }); - document.dispatchEvent(autoLookupEvent); + // Conditionally perform the auto address lookup + if (performLookup) { + const autoLookupEvent = new CustomEvent('autoAddressLookup', { + detail: { mode: 'add', lat, lng } + }); + document.dispatchEvent(autoLookupEvent); + } } // Replace the startMovingMarker function diff --git a/map/app/public/js/main.js b/map/app/public/js/main.js index ace0d29..67022ca 100644 --- a/map/app/public/js/main.js +++ b/map/app/public/js/main.js @@ -1,5 +1,5 @@ // Main application entry point -import { CONFIG } from './config.js'; +import { CONFIG, loadDomainConfig } from './config.js'; import { hideLoading, showStatus, setViewportDimensions } from './utils.js'; import { checkAuth } from './auth.js'; import { initializeMap } from './map-manager.js'; @@ -24,6 +24,9 @@ document.addEventListener('DOMContentLoaded', async () => { console.log('DOM loaded, initializing application...'); try { + // Load domain configuration first + await loadDomainConfig(); + // First check authentication await checkAuth(); diff --git a/map/app/public/js/ui-controls.js b/map/app/public/js/ui-controls.js index 750485d..7d23599 100644 --- a/map/app/public/js/ui-controls.js +++ b/map/app/public/js/ui-controls.js @@ -20,6 +20,50 @@ export function getAddressConfirmationState() { }; } +/** + * Sets the address confirmation state and updates the UI accordingly. + * @param {'add' | 'edit'} mode - The form mode. + * @param {boolean} confirmed - The confirmation state to set. + */ +export function setAddressConfirmed(mode, confirmed) { + const button = mode === 'add' ? + document.getElementById('confirm-address-add-btn') : + document.getElementById('confirm-address-edit-btn'); + + const saveButton = mode === 'add' ? + document.getElementById('save-location-btn') : + document.getElementById('save-edit-location-btn'); + + if (mode === 'add') { + isAddressConfirmed = confirmed; + } else { + isEditAddressConfirmed = confirmed; + } + + if (confirmed) { + if (button) { + button.textContent = '✅ Address Confirmed'; + button.classList.remove('btn-secondary'); + button.classList.add('btn-success'); + button.disabled = false; + } + if (saveButton) { + saveButton.disabled = false; + } + } else { + // This is essentially what resetAddressConfirmation does + if (button) { + button.textContent = '📍 Confirm Address'; + button.classList.remove('btn-success'); + button.classList.add('btn-secondary'); + button.disabled = false; + } + if (saveButton) { + saveButton.disabled = true; + } + } +} + // Add logout function async function logout() { try { @@ -532,7 +576,7 @@ export function setupEventListeners() { if (marker) { startMovingMarker(locationData, marker); - } else { + } else { console.error('Could not find marker for location:', locationData); showStatus('Could not find marker for this location', 'error'); } diff --git a/map/app/routes/index.js b/map/app/routes/index.js index b934bde..dc2bb48 100644 --- a/map/app/routes/index.js +++ b/map/app/routes/index.js @@ -35,6 +35,12 @@ module.exports = (app) => { // Public config endpoint app.get('/api/config/start-location', require('../controllers/settingsController').getPublicStartLocation); + // Domain config endpoint (public) + app.get('/api/config/domain', (req, res) => { + const config = require('../config'); + res.json({ domain: config.domain }); + }); + // QR code routes (authenticated) app.use('/api/qr', requireAuth, qrRoutes); diff --git a/map/files-explainer.md b/map/files-explainer.md index 5bfd473..c7d8f07 100644 --- a/map/files-explainer.md +++ b/map/files-explainer.md @@ -98,13 +98,77 @@ Admin panel HTML page for managing start location, walk sheet, shift management, CSS styles specific to the admin panel UI. +# app/public/css/modules/apartment-popup.css + +Styles for the apartment building popup, ensuring it is responsive and user-friendly. + +# app/public/css/modules/base.css + +Contains base styles, including CSS variables for theming, resets, and default body styles. + +# app/public/css/modules/buttons.css + +Defines styles for all button types, states (hover, disabled), and variants (primary, danger, etc.). + +# app/public/css/modules/cache-busting.css + +Styles for the cache busting update notification that prompts users to refresh the page. + +# app/public/css/modules/doc-search.css + +Styles for the documentation search component in the header. + +# app/public/css/modules/forms.css + +Styles for form elements, input fields, and the slide-up edit footer. + +# app/public/css/modules/layout.css + +Defines the main application layout, including the header, app container, and map container. + +# app/public/css/modules/leaflet-custom.css + +Customizations for the Leaflet.js library, including popups and marker styles. + +# app/public/css/modules/map-controls.css + +Styles for map controls, such as the crosshairs and move controls. + +# app/public/css/modules/mobile-ui.css + +Styles for mobile-specific UI elements like the mobile dropdown menu and floating sidebar. + +# app/public/css/modules/modal.css + +Styles for all modal dialogs used throughout the application. + +# app/public/css/modules/notifications.css + +Styles for status messages and the global loading overlay. + +# app/public/css/modules/print.css + +Print-specific styles to ensure the map prints correctly. + +# app/public/css/modules/qr-code.css + +Styles for the QR code generation modal and related components. + +# app/public/css/modules/responsive.css + +Contains all media queries for responsive design, adapting the layout for different screen sizes. + +# app/public/css/modules/start-location-marker.css + +Defines the animated styles for the distinctive start location marker. + # app/public/css/shifts.css CSS styles for the volunteer shifts page, including grid view, calendar view, and view toggle functionality. # app/public/css/style.css -The style.css file is the base .css file for the application. It is referenced in many of the .html files. +Main stylesheet that imports all modular CSS files from the `public/css/modules/` directory. It is referenced in all HTML files to load the application's styles. # app/public/favicon.ico