enhanced preorder map

This commit is contained in:
2025-09-18 12:55:26 +02:00
parent b69bfc3afe
commit f05ff28394
4 changed files with 384 additions and 57 deletions
+140 -2
View File
@@ -29,7 +29,7 @@
.tt-map-top-controls {
position: absolute;
top: 10px;
left: 55px; /* Moved to avoid overlapping with zoom controls */
left: 10px;
z-index: 401;
}
@@ -44,7 +44,7 @@
/* Container for built-in map buttons (zoom, layer toggle) */
.tt-map-builtin-controls {
position: absolute;
top: 10px;
top: 60px; /* Pushed down to make space for the search bar */
right: 10px;
z-index: 401;
display: flex;
@@ -74,4 +74,142 @@
.popup-loader {
text-align: center;
padding: 10px;
}
/* --- NEW MAP SEARCH STYLES --- */
.tt-map-search-wrapper {
position: absolute;
top: 10px;
right: 10px;
z-index: 402;
width: 320px;
max-width: calc(100% - 20px);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.tt-map-search-input-container {
position: relative;
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0,0,0,0.1);
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
padding: 0.25rem;
height: 42px;
}
.tt-map-search-input {
width: 100%;
height: 100%;
padding: 0 30px 0 32px;
border: none;
background-color: transparent;
font-size: 14px;
outline: none;
}
.tt-map-search-input-container .search-icon {
position: absolute;
left: 12px;
color: #888;
pointer-events: none;
}
.tt-map-search-input-container .clear-icon {
position: absolute;
right: 8px;
background: none;
border: none;
cursor: pointer;
color: #aaa;
font-size: 16px;
padding: 5px;
}
.tt-map-search-input-container .clear-icon:hover {
color: #333;
}
.tt-map-search-results {
list-style: none;
padding: 0;
margin: 8px 0 0 0;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
}
.tt-map-search-results .result-item,
.tt-map-search-results .result-item-info {
padding: 10px 15px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: background-color 0.15s;
font-size: 14px;
}
.tt-map-search-results .result-item:hover {
background-color: #f5f5f5;
}
.tt-map-search-results .result-item:not(:last-child) {
border-bottom: 1px solid #eee;
}
.tt-map-search-results .result-item i {
color: #6c757d;
}
.tt-map-search-results .result-item-info {
color: #6c757d;
cursor: default;
}
/* Fade animation for the results list */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
/* --- NEW CUSTOM MARKER STYLE --- */
.custom-map-marker {
background-color: rgba(0, 123, 255, 0.9);
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.9);
display: flex;
justify-content: center;
align-items: center;
animation: pulse 1.5s infinite;
}
.custom-map-marker i {
color: white;
font-size: 12px;
}
@keyframes pulse {
0% {
transform: scale(0.9);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(0, 123, 255, 0);
}
100% {
transform: scale(0.9);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0);
}
}
+121
View File
@@ -27,6 +27,12 @@ Vue.component('tt-map', {
internalLoading: true,
scriptsLoaded: false,
showSettings: false,
searchQuery: '',
searchResults: [],
isSearchLoading: false,
showSearchResults: false,
searchDebounce: null,
selectedMarker: null, // To hold the temporary marker
};
},
computed: {
@@ -248,6 +254,80 @@ Vue.component('tt-map', {
if (this.map) {
this.map.invalidateSize();
}
},
searchLocations() {
if (this.searchDebounce) clearTimeout(this.searchDebounce);
if (this.searchQuery.length < 3) {
this.searchResults = [];
this.showSearchResults = true;
return;
}
this.isSearchLoading = true;
this.searchDebounce = setTimeout(async () => {
try {
const response = await axios.get('/Geocoding/autocomplete', {
params: { q: this.searchQuery }
});
this.searchResults = response.data;
} catch (error) {
console.error("Geocoding search failed:", error);
this.searchResults = [];
} finally {
this.isSearchLoading = false;
}
}, 350);
},
selectLocation(location) {
this.searchQuery = location.text;
this.showSearchResults = false;
const [lat, lng] = location.value.split(',').map(Number);
if (this.map && !isNaN(lat) && !isNaN(lng)) {
this.map.flyTo([lat, lng], 17, { duration: 0.5 });
if (this.selectedMarker) {
this.selectedMarker.remove();
}
const customIcon = L.divIcon({
html: `<div class="custom-map-marker"><i class="fas fa-map-marker-alt"></i></div>`,
className: '', // Important to keep it empty
iconSize: [24, 24],
iconAnchor: [12, 12],
popupAnchor: [0, -14]
});
this.selectedMarker = L.marker([lat, lng], { icon: customIcon }).addTo(this.map)
.bindPopup(`<b>${location.text}</b>`)
.openPopup();
this.searchQuery = '';
this.searchResults = [];
setTimeout(() => {
if (this.selectedMarker) {
this.selectedMarker.remove();
this.selectedMarker = null;
}
}, 10000);
}
},
clearSearch() {
this.searchQuery = '';
this.searchResults = [];
this.showSearchResults = false;
if (this.selectedMarker) {
this.selectedMarker.remove();
this.selectedMarker = null;
}
},
handleSearchFocus() {
this.showSearchResults = true;
},
handleSearchBlur() {
setTimeout(() => {
this.showSearchResults = false;
}, 200);
}
},
watch: {
@@ -273,6 +353,47 @@ Vue.component('tt-map', {
</div>
<div ref="mapContainer" class="tt-map-container" :style="{ visibility: internalLoading ? 'hidden' : 'visible' }"></div>
<div class="tt-map-search-wrapper">
<div class="tt-map-search-input-container">
<i class="fas fa-search search-icon"></i>
<input
type="text"
class="tt-map-search-input"
placeholder="Adresse suchen..."
v-model="searchQuery"
@input="searchLocations"
@focus="handleSearchFocus"
@blur="handleSearchBlur"
autocomplete="off"
/>
<button v-if="searchQuery" @click="clearSearch" class="clear-icon">
<i class="fas fa-times"></i>
</button>
</div>
<transition name="fade">
<ul v-if="showSearchResults" class="tt-map-search-results">
<li v-if="isSearchLoading" class="result-item-info">
<i class="fas fa-spinner fa-spin"></i> Suche läuft...
</li>
<li v-else-if="searchQuery.length < 3" class="result-item-info">
Bitte mind. 3 Zeichen eingeben
</li>
<li v-else-if="searchResults.length === 0" class="result-item-info">
Keine Ergebnisse gefunden
</li>
<li
v-for="result in searchResults"
:key="result.value"
class="result-item"
@mousedown="selectLocation(result)"
>
<i class="fas fa-map-marker-alt"></i>
<span>{{ result.text }}</span>
</li>
</ul>
</transition>
</div>
<div class="tt-map-top-controls">
<slot name="tools"></slot>
</div>