From e488075e36986bae3364907ef190d887debc3394 Mon Sep 17 00:00:00 2001 From: y2keui Date: Sat, 6 Jun 2026 22:38:06 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=B0=A8=EB=9F=89=20?= =?UTF-8?q?=EB=A7=88=EC=BB=A4=20=EC=8B=9C=EC=9D=B8=EC=84=B1=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/public/app.js | 37 +++++++-- apps/web/public/styles.css | 157 ++++++++++++++++++++++++++----------- 2 files changed, 143 insertions(+), 51 deletions(-) diff --git a/apps/web/public/app.js b/apps/web/public/app.js index e9893bf..39238bf 100644 --- a/apps/web/public/app.js +++ b/apps/web/public/app.js @@ -817,11 +817,25 @@ function updateMarkers({ fitBounds = false } = {}) { updateMarker(location); } + updateMarkerSelectionClasses(); + if (fitBounds && state.markers.size > 0) { fitMapToLocations(); } } +function updateMarkerSelectionClasses() { + for (const [driverId, marker] of state.markers) { + const element = marker.provider === "leaflet" + ? marker.marker.getElement?.() + : marker.element; + + if (element) { + element.classList.toggle("is-selected", state.selectedDriverId === driverId); + } + } +} + function updateInitialMapFit() { if (state.hasFittedInitialLocations || !state.map || !state.locations.size) return; @@ -889,12 +903,14 @@ function updateKakaoMarker(location) { content: markerElement, yAnchor: 1 }), + element: markerElement, currentPosition: targetPosition }; marker.overlay.setMap(state.map); state.markers.set(location.driverId, marker); } else { marker.overlay.setContent(markerElement); + marker.element = markerElement; animateMarkerPosition(marker, targetPosition); } } @@ -906,12 +922,13 @@ function updateLeafletMarker(location) { const leafletPosition = [targetPosition.latitude, targetPosition.longitude]; const existingMarker = state.markers.get(location.driverId); const markerHeading = resolveMarkerHeading(location, existingMarker?.currentPosition, targetPosition); + const selectedClass = state.selectedDriverId === location.driverId ? "is-selected" : ""; const icon = L.divIcon({ - className: `leaflet-truck-marker truck-marker ${status.key} ${vehicleType.className}`, + className: `leaflet-truck-marker truck-marker ${status.key} ${vehicleType.className} ${selectedClass}`, html: createTruckMarkerHtml(location, vehicleType, markerHeading), - iconSize: [86, 56], - iconAnchor: [43, 56], - popupAnchor: [0, -44] + iconSize: [106, 82], + iconAnchor: [53, 78], + popupAnchor: [0, -66] }); let marker = existingMarker; @@ -936,7 +953,8 @@ function updateLeafletMarker(location) { function createTruckMarkerElement(location, status, heading) { const vehicleType = getVehicleType(location); const markerElement = document.createElement("button"); - markerElement.className = `truck-marker ${status.key} ${vehicleType.className}`; + const selectedClass = state.selectedDriverId === location.driverId ? "is-selected" : ""; + markerElement.className = `truck-marker ${status.key} ${vehicleType.className} ${selectedClass}`; markerElement.type = "button"; markerElement.title = `${vehicleType.label} ${location.vehicleNo || location.driverId}`; markerElement.setAttribute("aria-label", `${vehicleType.label} ${location.vehicleNo || location.driverId} 위치 보기`); @@ -952,6 +970,7 @@ function createTruckMarkerHtml(location, vehicleType = getVehicleType(location), const label = location.vehicleNo ? location.vehicleNo.slice(-4) : location.driverId.slice(0, 4); const rotation = normalizeMarkerRotation(heading); return ` + @@ -1300,6 +1319,7 @@ function openMarkerActionPanel(location) { renderMarkerActionPanel(location); elements.markerActionPanel.classList.remove("is-hidden"); render(); + updateMarkerSelectionClasses(); } function hideMarkerActionPanel() { @@ -1313,6 +1333,7 @@ function closeMarkerActionPanel() { } state.selectedDriverId = ""; render(); + updateMarkerSelectionClasses(); } function renderMarkerActionPanel(location) { @@ -1374,6 +1395,7 @@ function showVehicleRoute(location) { clearInfoOverlay(); renderRouteFocusPanel(location); render(); + updateMarkerSelectionClasses(); if (!state.map || !state.mapProvider) return; @@ -1390,6 +1412,7 @@ function showVehicleCargo(location) { clearRouteOverlay(); renderCargoFocusPanel(location); render(); + updateMarkerSelectionClasses(); showInfoOverlay(location); } @@ -1400,6 +1423,7 @@ function showVehicleHistory(location) { clearInfoOverlay(); renderRouteFocusPanel(location); render(); + updateMarkerSelectionClasses(); renderRoutePreview(location, { fit: false }); loadLocationHistory(location); } @@ -1412,6 +1436,7 @@ function showVehicleCenter(location) { clearRouteOverlay(); renderCurrentLocationPanel(location); render(); + updateMarkerSelectionClasses(); if (state.map && state.mapProvider) { centerMapOnLocation(location, { zoom: true }); @@ -1484,6 +1509,7 @@ function focusLocation(location) { loadLocationHistory(location); renderRouteFocusPanel(location); render(); + updateMarkerSelectionClasses(); if (!state.map || !state.mapProvider) return; @@ -1553,6 +1579,7 @@ function closeHistoryDrawer() { clearInfoOverlay(); renderRouteFocusPanel(null); render(); + updateMarkerSelectionClasses(); } function showInfoOverlay(location) { diff --git a/apps/web/public/styles.css b/apps/web/public/styles.css index a022ecf..2c3a7d2 100644 --- a/apps/web/public/styles.css +++ b/apps/web/public/styles.css @@ -1053,98 +1053,155 @@ button:hover { --truck-color: var(--blue); --truck-cab: #1556b8; --truck-accent: rgba(255, 255, 255, 0.2); + --marker-status: var(--green); + --marker-outline: #ffffff; --truck-heading: 0deg; - --body-width: 42px; - --body-height: 24px; - --cab-width: 19px; + --body-width: 50px; + --body-height: 28px; + --cab-width: 22px; position: relative; display: grid; place-items: center; - width: 86px; - min-width: 86px; - height: 56px; - min-height: 56px; + width: 106px; + min-width: 106px; + height: 82px; + min-height: 82px; border: 0; padding: 0; background: transparent; color: #ffffff; - filter: drop-shadow(0 8px 13px rgba(16, 32, 48, 0.28)); + filter: + drop-shadow(0 2px 0 rgba(255, 255, 255, 0.92)) + drop-shadow(0 10px 16px rgba(16, 32, 48, 0.34)); font-family: inherit; cursor: pointer; transition: filter 0.18s ease, transform 0.18s ease; } -.truck-marker:hover { - filter: drop-shadow(0 10px 16px rgba(16, 32, 48, 0.34)); +.truck-marker:hover, +.truck-marker.is-selected { + z-index: 8; + filter: + drop-shadow(0 2px 0 rgba(255, 255, 255, 0.96)) + drop-shadow(0 13px 20px rgba(16, 32, 48, 0.44)); transform: translateY(-1px); } .truck-marker.stale { --truck-color: var(--amber); --truck-cab: #8f4307; + --marker-status: var(--amber); } .truck-marker.offline { --truck-color: var(--red); --truck-cab: #8f1d15; + --marker-status: var(--red); } .truck-marker.type-trailer { - --truck-color: #334155; - --truck-cab: #0f172a; - --body-width: 62px; - --body-height: 24px; + --truck-color: #7c3aed; + --truck-cab: #4c1d95; + --body-width: 70px; + --body-height: 27px; } .truck-marker.type-container { - --truck-color: #475569; - --truck-cab: #1e293b; - --body-width: 58px; - --body-height: 24px; + --truck-color: #f97316; + --truck-cab: #c2410c; + --body-width: 66px; + --body-height: 27px; --truck-accent: repeating-linear-gradient( 90deg, - rgba(255, 255, 255, 0.18) 0 2px, - transparent 2px 8px + rgba(255, 255, 255, 0.32) 0 2px, + transparent 2px 9px ); } .truck-marker.type-box, .truck-marker.type-wing { - --body-width: 48px; - --body-height: 25px; + --truck-color: #059669; + --truck-cab: #047857; + --body-width: 56px; + --body-height: 28px; } .truck-marker.type-van { - --truck-color: #0284c7; + --truck-color: #0ea5e9; --truck-cab: #0369a1; - --body-width: 34px; - --body-height: 21px; - --cab-width: 22px; + --body-width: 40px; + --body-height: 24px; + --cab-width: 25px; } .truck-marker.type-cold { - --truck-color: #0e7490; + --truck-color: #0891b2; --truck-cab: #155e75; - --body-width: 47px; - --body-height: 25px; + --body-width: 55px; + --body-height: 28px; --truck-accent: linear-gradient(135deg, rgba(255, 255, 255, 0.28), transparent 45%); } .truck-marker.stale { --truck-color: var(--amber); --truck-cab: #8f4307; + --marker-status: var(--amber); } .truck-marker.offline { --truck-color: var(--red); --truck-cab: #8f1d15; + --marker-status: var(--red); +} + +.truck-marker::after { + content: ""; + position: absolute; + top: 6px; + right: 14px; + z-index: 4; + width: 12px; + height: 12px; + border: 2px solid #ffffff; + border-radius: 50%; + background: var(--marker-status); + box-shadow: 0 2px 7px rgba(15, 23, 42, 0.32); + pointer-events: none; +} + +.truck-marker__halo { + position: absolute; + top: 5px; + left: 50%; + z-index: 0; + width: 98px; + height: 50px; + border: 2px solid rgba(15, 23, 42, 0.12); + border-radius: 999px; + background: + radial-gradient(circle at 48% 46%, rgba(255, 255, 255, 0.98) 0 54%, rgba(255, 255, 255, 0.82) 55% 100%); + box-shadow: + 0 0 0 2px rgba(255, 255, 255, 0.72), + 0 8px 18px rgba(15, 23, 42, 0.24); + transform: translateX(-50%); + pointer-events: none; +} + +.truck-marker.is-selected .truck-marker__halo { + border-color: rgba(31, 111, 235, 0.78); + box-shadow: + 0 0 0 3px rgba(255, 255, 255, 0.86), + 0 0 0 6px rgba(31, 111, 235, 0.22), + 0 10px 22px rgba(15, 23, 42, 0.28); } .truck-marker__vehicle { position: relative; + z-index: 2; display: inline-flex; align-items: flex-end; justify-content: center; + margin-bottom: 12px; padding-bottom: 8px; transform: rotate(var(--truck-heading)); transform-origin: 50% 44%; @@ -1156,19 +1213,20 @@ button:hover { content: ""; position: absolute; bottom: 1px; - width: 7px; - height: 7px; - border: 2px solid #ffffff; + width: 8px; + height: 8px; + border: 2px solid var(--marker-outline); border-radius: 50%; background: #17212b; + box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.14); } .truck-marker__vehicle::before { - left: 11px; + left: 12px; } .truck-marker__vehicle::after { - right: 12px; + right: 13px; } .truck-marker__body { @@ -1176,12 +1234,13 @@ button:hover { display: block; width: var(--body-width); height: var(--body-height); - border: 2px solid #ffffff; + border: 3px solid var(--marker-outline); border-right: 0; - border-radius: 5px 0 0 5px; + border-radius: 7px 0 0 6px; background: var(--truck-accent), var(--truck-color); + box-shadow: inset 0 -5px 0 rgba(15, 23, 42, 0.13); } .truck-marker.type-van .truck-marker__body { @@ -1191,19 +1250,20 @@ button:hover { .truck-marker__label { position: absolute; left: 50%; - bottom: 0; + bottom: 2px; + z-index: 3; display: inline-flex; align-items: center; gap: 4px; - max-width: 82px; - min-height: 18px; - padding: 2px 6px; - border: 1px solid rgba(255, 255, 255, 0.7); + max-width: 98px; + min-height: 21px; + padding: 2px 7px; + border: 2px solid rgba(255, 255, 255, 0.9); border-radius: 999px; - background: rgba(15, 23, 42, 0.88); - box-shadow: 0 4px 10px rgba(15, 23, 42, 0.22); + background: rgba(15, 23, 42, 0.94); + box-shadow: 0 5px 12px rgba(15, 23, 42, 0.3); color: #ffffff; - font-size: 10px; + font-size: 11px; font-weight: 900; line-height: 1.1; transform: translateX(-50%); @@ -1225,9 +1285,10 @@ button:hover { position: relative; width: var(--cab-width); height: calc(var(--body-height) - 3px); - border: 2px solid #ffffff; - border-radius: 0 6px 5px 0; + border: 3px solid var(--marker-outline); + border-radius: 0 8px 6px 0; background: var(--truck-cab); + box-shadow: inset 0 -4px 0 rgba(15, 23, 42, 0.14); } .truck-marker__cab::before { @@ -1246,6 +1307,10 @@ button:hover { background: transparent; } +.leaflet-truck-marker.is-selected { + z-index: 1000 !important; +} + .kakao-popup { min-width: 180px; padding: 12px;