(activeUnitId: string | null = null)
| 71 | } |
| 72 | |
| 73 | private updateMarkers(activeUnitId: string | null = null) { |
| 74 | if (!this.map || !this.L) return; |
| 75 | const currentVehicles = this.fleetService.units(); |
| 76 | const L = this.L; |
| 77 | |
| 78 | const activeIds = new Set(currentVehicles.map(v => v.id)); |
| 79 | |
| 80 | // Remove stale markers |
| 81 | Object.keys(this.markers).forEach(id => { |
| 82 | if (!activeIds.has(id)) { |
| 83 | this.markers[id].remove(); |
| 84 | delete this.markers[id]; |
| 85 | } |
| 86 | }); |
| 87 | |
| 88 | // Add or update markers |
| 89 | currentVehicles.forEach(vehicle => { |
| 90 | const isActive = vehicle.id === activeUnitId; |
| 91 | const pingClass = vehicle.needsService ? 'animate-ping bg-error/50' : 'bg-primary/20'; |
| 92 | const colorClass = vehicle.needsService ? 'text-error' : 'text-primary'; |
| 93 | const scaleClass = isActive ? 'scale-125' : ''; |
| 94 | const shadowClass = isActive ? 'shadow-[0_0_15px_rgba(255,182,144,0.8)]' : ''; |
| 95 | |
| 96 | const markerHtml = ` |
| 97 | <div class="relative flex items-center justify-center w-8 h-8 ${scaleClass} transition-transform duration-300"> |
| 98 | <div class="absolute inset-0 rounded-full ${pingClass} ${shadowClass}"></div> |
| 99 | <svg class="w-6 h-6 ${colorClass} relative z-10 cursor-pointer" viewBox="0 0 24 24" fill="currentColor"> |
| 100 | <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/> |
| 101 | </svg> |
| 102 | </div> |
| 103 | `; |
| 104 | |
| 105 | const icon = L.divIcon({ |
| 106 | html: markerHtml, |
| 107 | className: 'custom-vehicle-marker', |
| 108 | iconSize: [32, 32], |
| 109 | iconAnchor: [16, 32] |
| 110 | }); |
| 111 | |
| 112 | if (this.markers[vehicle.id]) { |
| 113 | this.markers[vehicle.id].setLatLng([vehicle.lat, vehicle.lng]); |
| 114 | this.markers[vehicle.id].setIcon(icon); |
| 115 | } else { |
| 116 | const marker = L.marker([vehicle.lat, vehicle.lng], { icon }) |
| 117 | .addTo(this.map!) |
| 118 | .on('click', () => { |
| 119 | this.fleetService.setActiveUnit(vehicle.id); |
| 120 | // Optionally, we could update the sidebar's command terminal too by injecting Sidebar, |
| 121 | // but the clean architectural way is to have the terminal message also driven by FleetService. |
| 122 | // For now, setting the active unit triggers the sidebar selection highlight! |
| 123 | }); |
| 124 | this.markers[vehicle.id] = marker; |
| 125 | } |
| 126 | }); |
| 127 | } |
| 128 | |
| 129 | private initMap() { |
| 130 | if (!this.L) return; |
no test coverage detected