Full update to map css
This commit is contained in:
parent
994440a2fd
commit
d711456b88
@ -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' } ]
|
||||
|
||||
235
map/app/public/css/modules/apartment-popup.css
Normal file
235
map/app/public/css/modules/apartment-popup.css
Normal 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;
|
||||
}
|
||||
40
map/app/public/css/modules/base.css
Normal file
40
map/app/public/css/modules/base.css
Normal 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 */
|
||||
}
|
||||
87
map/app/public/css/modules/buttons.css
Normal file
87
map/app/public/css/modules/buttons.css
Normal 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;
|
||||
}
|
||||
113
map/app/public/css/modules/cache-busting.css
Normal file
113
map/app/public/css/modules/cache-busting.css
Normal 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;
|
||||
}
|
||||
}
|
||||
144
map/app/public/css/modules/doc-search.css
Normal file
144
map/app/public/css/modules/doc-search.css
Normal 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;
|
||||
}
|
||||
124
map/app/public/css/modules/forms.css
Normal file
124
map/app/public/css/modules/forms.css
Normal 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);
|
||||
}
|
||||
98
map/app/public/css/modules/layout.css
Normal file
98
map/app/public/css/modules/layout.css
Normal 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;
|
||||
}
|
||||
184
map/app/public/css/modules/leaflet-custom.css
Normal file
184
map/app/public/css/modules/leaflet-custom.css
Normal 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;
|
||||
}
|
||||
72
map/app/public/css/modules/map-controls.css
Normal file
72
map/app/public/css/modules/map-controls.css
Normal 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;
|
||||
}
|
||||
154
map/app/public/css/modules/mobile-ui.css
Normal file
154
map/app/public/css/modules/mobile-ui.css
Normal 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);
|
||||
}
|
||||
83
map/app/public/css/modules/modal.css
Normal file
83
map/app/public/css/modules/modal.css
Normal 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;
|
||||
}
|
||||
90
map/app/public/css/modules/notifications.css
Normal file
90
map/app/public/css/modules/notifications.css
Normal 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;
|
||||
}
|
||||
13
map/app/public/css/modules/print.css
Normal file
13
map/app/public/css/modules/print.css
Normal file
@ -0,0 +1,13 @@
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.header,
|
||||
.map-controls,
|
||||
.status-container,
|
||||
.modal {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#map-container {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
74
map/app/public/css/modules/qr-code.css
Normal file
74
map/app/public/css/modules/qr-code.css
Normal 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 */
|
||||
}
|
||||
190
map/app/public/css/modules/responsive.css
Normal file
190
map/app/public/css/modules/responsive.css
Normal 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;
|
||||
}
|
||||
}
|
||||
72
map/app/public/css/modules/start-location-marker.css
Normal file
72
map/app/public/css/modules/start-location-marker.css
Normal 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
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user