From fb4b0e4643dce4a002d5b9d063c36629d262e6b8 Mon Sep 17 00:00:00 2001 From: y2keui Date: Sun, 7 Jun 2026 02:23:32 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EC=A0=9C=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=EC=99=80=20=ED=99=94=EB=AC=BC=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/public/app.js | 116 +++++++++--------- apps/web/public/index.html | 30 ++--- apps/web/public/styles.css | 243 ++++++++++++++++++++++--------------- 3 files changed, 220 insertions(+), 169 deletions(-) diff --git a/apps/web/public/app.js b/apps/web/public/app.js index 6e4893d..3867935 100644 --- a/apps/web/public/app.js +++ b/apps/web/public/app.js @@ -33,6 +33,7 @@ const elements = { driversTab: document.getElementById("driversTab"), locationsPanel: document.getElementById("locationsPanel"), driversPanel: document.getElementById("driversPanel"), + mapPanel: document.querySelector(".map-panel"), vehicleList: document.getElementById("vehicleList"), mapMessage: document.getElementById("mapMessage"), totalCount: document.getElementById("totalCount"), @@ -1359,7 +1360,7 @@ function createDestinationMarkerElement(location) { function createDestinationMarkerHtml(location) { const metrics = getRouteMetrics(location); const destinationName = getDestinationName(location) || "목적지"; - const etaText = metrics ? formatEtaTime(metrics.estimatedArrivalAt) : "ETA"; + const etaText = metrics ? formatEtaTime(metrics.estimatedArrivalAt) : "도착시간 미정"; return ` 도착 @@ -1380,7 +1381,7 @@ function refreshSelectedDetail(location) { } if (state.activeDetailMode === "cargo") { - renderCargoFocusPanel(location); + hideRouteFocusPanel(); showInfoOverlay(location); return; } @@ -1436,14 +1437,14 @@ function renderMarkerActionPanel(location) { const distance = metrics ? formatDistance(metrics.remainingDistanceKm) : "-"; const eta = metrics ? formatEtaTime(metrics.estimatedArrivalAt) : "-"; - elements.markerActionEyebrow.textContent = "차량 선택"; + elements.markerActionEyebrow.textContent = "차량"; elements.markerActionTitle.textContent = `${title} · ${vehicleType.label}`; elements.markerActionMeta.textContent = [driver, status.label, formatSpeed(location.speed)].filter(Boolean).join(" · "); elements.markerActionSummary.replaceChildren( createMarkerActionSummaryItem("화물", getCargoSummary(location)), createMarkerActionSummaryItem("목적지", getRouteSummary(location)), createMarkerActionSummaryItem("남은 거리", distance), - createMarkerActionSummaryItem("도착 예정", eta) + createMarkerActionSummaryItem("도착 시간", eta) ); } @@ -1501,7 +1502,7 @@ function showVehicleCargo(location) { hideMarkerActionPanel(); elements.historyDrawer.classList.add("is-hidden"); clearRouteOverlay(); - renderCargoFocusPanel(location); + hideRouteFocusPanel(); render(); updateMarkerSelectionClasses(); showInfoOverlay(location); @@ -1549,35 +1550,27 @@ function setRouteFocusContent(eyebrow, title, meta) { elements.routeFocusMeta.textContent = meta; } -function renderCargoFocusPanel(location) { - const vehicleType = getVehicleType(location); - const product = getCargoName(location) || "미등록"; - const quantity = getCargoQuantity(location) || "수량 미등록"; - const weight = getCargoWeight(location) || "중량 미등록"; - const route = getRouteSummary(location); - setRouteFocusContent( - "화물 정보", - `${location.vehicleNo || location.driverId} · ${vehicleType.label}`, - `${product} · ${quantity} · ${weight} · ${route}` - ); +function hideRouteFocusPanel() { + elements.routeFocusPanel.classList.remove("is-active"); + setRouteFocusEyebrow("차량 정보"); + elements.routeFocusTitle.textContent = ""; + elements.routeFocusMeta.textContent = ""; } function renderCurrentLocationPanel(location) { const vehicleType = getVehicleType(location); const coordinate = `${formatCoordinate(location.latitude)}, ${formatCoordinate(location.longitude)}`; setRouteFocusContent( - "현재 위치", + "지도 중심", `${location.vehicleNo || location.driverId} · ${vehicleType.label}`, `${formatSpeed(location.speed)} · ${coordinate} · ${formatDateTime(location.receivedAt)}` ); } function renderRouteFocusPanel(location) { - setRouteFocusEyebrow("선택 경로"); + setRouteFocusEyebrow("차량 정보"); if (!location) { - elements.routeFocusPanel.classList.remove("is-active"); - elements.routeFocusTitle.textContent = "차량 선택"; - elements.routeFocusMeta.textContent = "선택하면 경로와 도착 예정 시간이 표시됩니다."; + hideRouteFocusPanel(); return; } @@ -1615,7 +1608,7 @@ async function loadLocationHistory(location) { state.activeDetailMode = "history"; hideMarkerActionPanel(); elements.historyDrawer.classList.remove("is-hidden"); - elements.historyTitle.textContent = `${location.vehicleNo || location.driverId} 위치 이력`; + elements.historyTitle.textContent = `${location.vehicleNo || location.driverId} 수신 이력`; elements.historySummary.textContent = "조회 중"; elements.historyList.replaceChildren(); @@ -1674,48 +1667,34 @@ function closeHistoryDrawer() { } function showInfoOverlay(location) { - if (!state.map || !state.mapProvider) return; + if (!elements.mapPanel) return; - if (shouldSuppressMapPopup()) { + if (shouldSuppressInfoPanel()) { clearInfoOverlay(); return; } const status = getLocationStatus(location); const content = createPopupElement(location, status); + const placement = getInfoPanelPlacement(location); - if (state.mapProvider === "leaflet") { - if (state.infoOverlay) { - state.infoOverlay - .setLatLng([location.latitude, location.longitude]) - .setContent(content); - return; - } - - state.infoOverlay = L.popup({ - closeButton: true, - offset: [0, -24] - }) - .setLatLng([location.latitude, location.longitude]) - .setContent(content) - .openOn(state.map); - return; + if (!(state.infoOverlay instanceof HTMLElement)) { + clearInfoOverlay(); + state.infoOverlay = document.createElement("section"); + state.infoOverlay.setAttribute("aria-label", "화물 상세"); + elements.mapPanel.append(state.infoOverlay); } - const position = new kakao.maps.LatLng(location.latitude, location.longitude); - - if (!state.infoOverlay) { - state.infoOverlay = new kakao.maps.CustomOverlay({ yAnchor: 1.35 }); - } - state.infoOverlay.setContent(content); - state.infoOverlay.setPosition(position); - state.infoOverlay.setMap(state.map); + state.infoOverlay.className = `map-info-panel ${placement}`; + state.infoOverlay.replaceChildren(content); } function clearInfoOverlay() { if (!state.infoOverlay) return; - if (typeof state.infoOverlay.setMap === "function") { + if (state.infoOverlay instanceof HTMLElement) { + state.infoOverlay.remove(); + } else if (typeof state.infoOverlay.setMap === "function") { state.infoOverlay.setMap(null); } else if (state.mapProvider === "leaflet" && state.map?.closePopup) { state.map.closePopup(state.infoOverlay); @@ -1724,14 +1703,43 @@ function clearInfoOverlay() { state.infoOverlay = null; } -function shouldSuppressMapPopup() { +function shouldSuppressInfoPanel() { return window.matchMedia("(max-width: 560px)").matches; } +function getInfoPanelPlacement(location) { + const center = getCurrentMapCenter(); + if (!center) return "is-top-right"; + + return location.longitude >= center.longitude ? "is-bottom-left" : "is-top-right"; +} + +function getCurrentMapCenter() { + if (!state.map || !state.mapProvider) return null; + + if (state.mapProvider === "kakao" && typeof state.map.getCenter === "function") { + const center = state.map.getCenter(); + return { + latitude: center.getLat(), + longitude: center.getLng() + }; + } + + if (state.mapProvider === "leaflet" && typeof state.map.getCenter === "function") { + const center = state.map.getCenter(); + return { + latitude: center.lat, + longitude: center.lng + }; + } + + return null; +} + function createPopupElement(location, status) { const vehicleType = getVehicleType(location); const container = document.createElement("div"); - container.className = "kakao-popup"; + container.className = "map-info-card"; const title = document.createElement("strong"); title.className = "popup-title"; @@ -1775,12 +1783,10 @@ function createRouteInfoElement(location) { return routeInfo; } - const source = firstString(location.routeSource, location.routingProvider) || "simulated"; routeInfo.append( createPopupRouteLine("남은 거리", formatDistance(metrics.remainingDistanceKm)), - createPopupRouteLine("예상 도착", formatEtaTime(metrics.estimatedArrivalAt)), - createPopupRouteLine("소요 시간", formatDuration(metrics.etaMinutes)), - createPopupRouteLine("경로", source === "simulated" ? "모의 경로" : source) + createPopupRouteLine("도착 시간", formatEtaTime(metrics.estimatedArrivalAt)), + createPopupRouteLine("남은 시간", formatDuration(metrics.etaMinutes)) ); return routeInfo; } diff --git a/apps/web/public/index.html b/apps/web/public/index.html index 552e9ba..53965d7 100644 --- a/apps/web/public/index.html +++ b/apps/web/public/index.html @@ -17,7 +17,7 @@
- 시범 관제 + 관제 데스크
@@ -41,7 +41,7 @@ - 샘플 전송 + 테스트 전송
@@ -49,12 +49,12 @@