Full update to map css

This commit is contained in:
admin 2025-07-27 17:49:37 -06:00
parent 994440a2fd
commit d711456b88
26 changed files with 1971 additions and 1847 deletions

View File

@ -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: <httpProxy> Error calling https://api.open-meteo.com/v1/forecast...
[2025-07-27T23:30:12.352Z] error: <httpProxy> [ 500, [AggregateError: ] { code: 'ETIMEDOUT' } ]

View File

@ -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;
}

View File

@ -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 */
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
/* Print styles */
@media print {
.header,
.map-controls,
.status-container,
.modal {
display: none !important;
}
#map-container {
height: 100vh;
}
}

View File

@ -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 */
}

View File

@ -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;
}
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +44,10 @@
</div>
<div class="header-actions">
<a href="#" id="homepage-link" class="btn btn-secondary">
<span class="btn-icon">🖥️</span>
<span class="btn-text">Homepage</span>
</a>
<a href="/shifts.html" class="btn btn-secondary">
<span class="btn-icon">📅</span>
<span class="btn-text">View Shifts</span>

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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');
}

View File

@ -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);

View File

@ -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