마커 클릭 선택 패널 추가

This commit is contained in:
2026-06-06 22:06:16 +09:00
parent 5278b8cf06
commit 1bf346a898
3 changed files with 399 additions and 10 deletions

View File

@@ -14,6 +14,7 @@ const state = {
drivers: new Map(),
locations: new Map(),
selectedDriverId: "",
activeDetailMode: "",
vehicleSearch: "",
vehicleStatusFilter: "all",
eventSource: null,
@@ -51,6 +52,12 @@ const elements = {
routeFocusPanel: document.getElementById("routeFocusPanel"),
routeFocusTitle: document.getElementById("routeFocusTitle"),
routeFocusMeta: document.getElementById("routeFocusMeta"),
markerActionPanel: document.getElementById("markerActionPanel"),
markerActionEyebrow: document.getElementById("markerActionEyebrow"),
markerActionTitle: document.getElementById("markerActionTitle"),
markerActionMeta: document.getElementById("markerActionMeta"),
markerActionSummary: document.getElementById("markerActionSummary"),
markerActionCloseButton: document.getElementById("markerActionCloseButton"),
driverForm: document.getElementById("driverForm"),
driverNameInput: document.getElementById("driverNameInput"),
vehicleNoInput: document.getElementById("vehicleNoInput"),
@@ -245,6 +252,19 @@ function bindActions() {
elements.driversTab.addEventListener("click", () => setActiveView("drivers"));
elements.driverForm.addEventListener("submit", submitDriverForm);
elements.historyCloseButton.addEventListener("click", closeHistoryDrawer);
elements.markerActionCloseButton.addEventListener("click", closeMarkerActionPanel);
elements.markerActionPanel.addEventListener("click", (event) => {
const actionButton = event.target.closest("[data-marker-action]");
if (!actionButton) return;
const location = state.locations.get(state.selectedDriverId);
if (!location) {
closeMarkerActionPanel();
return;
}
runMarkerAction(actionButton.dataset.markerAction, location);
});
elements.vehicleSearchInput.addEventListener("input", () => {
state.vehicleSearch = elements.vehicleSearchInput.value.trim();
render();
@@ -390,10 +410,9 @@ function replaceLocations(locations) {
if (state.selectedDriverId) {
const selectedLocation = state.locations.get(state.selectedDriverId);
if (selectedLocation) {
renderRoutePreview(selectedLocation, { fit: false });
showInfoOverlay(selectedLocation);
renderRouteFocusPanel(selectedLocation);
refreshSelectedDetail(selectedLocation);
} else {
closeMarkerActionPanel();
clearRouteOverlay();
clearInfoOverlay();
renderRouteFocusPanel(null);
@@ -442,10 +461,8 @@ function applyLocationUpdates(locations) {
? state.locations.get(state.selectedDriverId)
: null;
if (selectedLocation && !elements.historyDrawer.classList.contains("is-hidden")) {
renderRoutePreview(selectedLocation, { fit: false });
showInfoOverlay(selectedLocation);
renderRouteFocusPanel(selectedLocation);
if (selectedLocation) {
refreshSelectedDetail(selectedLocation);
}
}
@@ -905,13 +922,13 @@ function updateLeafletMarker(location) {
marker: L.marker(leafletPosition, { icon, title: location.vehicleNo || location.driverId }),
currentPosition: targetPosition
};
marker.marker.on("click", () => focusLocation(location));
marker.marker.on("click", () => openMarkerActionPanel(location));
marker.marker.addTo(state.map);
state.markers.set(location.driverId, marker);
} else {
marker.marker.setIcon(icon);
marker.marker.off("click");
marker.marker.on("click", () => focusLocation(location));
marker.marker.on("click", () => openMarkerActionPanel(location));
animateMarkerPosition(marker, targetPosition);
}
}
@@ -924,7 +941,10 @@ function createTruckMarkerElement(location, status, heading) {
markerElement.title = `${vehicleType.label} ${location.vehicleNo || location.driverId}`;
markerElement.setAttribute("aria-label", `${vehicleType.label} ${location.vehicleNo || location.driverId} 위치 보기`);
markerElement.innerHTML = createTruckMarkerHtml(location, vehicleType, heading);
markerElement.addEventListener("click", () => focusLocation(location));
markerElement.addEventListener("click", (event) => {
event.stopPropagation();
openMarkerActionPanel(location);
});
return markerElement;
}
@@ -1117,6 +1137,8 @@ function centerMapOnLocation(location, options = {}) {
}
function renderRoutePreview(location, options = {}) {
if (!state.map || !state.mapProvider) return false;
clearRouteOverlay();
const routePath = getRoutePath(location);
@@ -1236,7 +1258,206 @@ function createDestinationMarkerHtml(location) {
`;
}
function refreshSelectedDetail(location) {
if (!elements.markerActionPanel.classList.contains("is-hidden")) {
renderMarkerActionPanel(location);
}
if (state.activeDetailMode === "route") {
renderRouteFocusPanel(location);
renderRoutePreview(location, { fit: false });
return;
}
if (state.activeDetailMode === "cargo") {
renderCargoFocusPanel(location);
showInfoOverlay(location);
return;
}
if (state.activeDetailMode === "history") {
renderRouteFocusPanel(location);
renderRoutePreview(location, { fit: false });
if (state.infoOverlay) {
showInfoOverlay(location);
}
return;
}
if (state.activeDetailMode === "center") {
renderCurrentLocationPanel(location);
showInfoOverlay(location);
}
}
function openMarkerActionPanel(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "menu";
elements.historyDrawer.classList.add("is-hidden");
clearRouteOverlay();
clearInfoOverlay();
renderRouteFocusPanel(null);
renderMarkerActionPanel(location);
elements.markerActionPanel.classList.remove("is-hidden");
render();
}
function hideMarkerActionPanel() {
elements.markerActionPanel.classList.add("is-hidden");
}
function closeMarkerActionPanel() {
hideMarkerActionPanel();
if (state.activeDetailMode === "menu") {
state.activeDetailMode = "";
}
state.selectedDriverId = "";
render();
}
function renderMarkerActionPanel(location) {
const status = getLocationStatus(location);
const vehicleType = getVehicleType(location);
const metrics = getRouteMetrics(location);
const driver = location.driverName || location.driverId;
const title = location.vehicleNo || location.driverId;
const distance = metrics ? formatDistance(metrics.remainingDistanceKm) : "-";
const eta = metrics ? formatEtaTime(metrics.estimatedArrivalAt) : "-";
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)
);
}
function createMarkerActionSummaryItem(label, value) {
const item = document.createElement("span");
const labelElement = document.createElement("b");
const valueElement = document.createElement("em");
labelElement.textContent = label;
valueElement.textContent = value || "-";
item.append(labelElement, valueElement);
return item;
}
function runMarkerAction(action, location) {
if (action === "route") {
showVehicleRoute(location);
return;
}
if (action === "cargo") {
showVehicleCargo(location);
return;
}
if (action === "history") {
showVehicleHistory(location);
return;
}
if (action === "center") {
showVehicleCenter(location);
}
}
function showVehicleRoute(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "route";
hideMarkerActionPanel();
elements.historyDrawer.classList.add("is-hidden");
clearInfoOverlay();
renderRouteFocusPanel(location);
render();
if (!state.map || !state.mapProvider) return;
if (!renderRoutePreview(location, { fit: true })) {
centerMapOnLocation(location, { zoom: true });
}
}
function showVehicleCargo(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "cargo";
hideMarkerActionPanel();
elements.historyDrawer.classList.add("is-hidden");
clearRouteOverlay();
renderCargoFocusPanel(location);
render();
showInfoOverlay(location);
}
function showVehicleHistory(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "history";
hideMarkerActionPanel();
clearInfoOverlay();
renderRouteFocusPanel(location);
render();
renderRoutePreview(location, { fit: false });
loadLocationHistory(location);
}
function showVehicleCenter(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "center";
hideMarkerActionPanel();
elements.historyDrawer.classList.add("is-hidden");
clearRouteOverlay();
renderCurrentLocationPanel(location);
render();
if (state.map && state.mapProvider) {
centerMapOnLocation(location, { zoom: true });
}
showInfoOverlay(location);
}
function setRouteFocusEyebrow(label) {
const eyebrow = elements.routeFocusPanel.querySelector(".section-eyebrow");
if (eyebrow) {
eyebrow.textContent = label;
}
}
function setRouteFocusContent(eyebrow, title, meta) {
setRouteFocusEyebrow(eyebrow);
elements.routeFocusPanel.classList.add("is-active");
elements.routeFocusTitle.textContent = title;
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 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("선택 경로");
if (!location) {
elements.routeFocusPanel.classList.remove("is-active");
elements.routeFocusTitle.textContent = "차량 선택";
@@ -1258,6 +1479,8 @@ function renderRouteFocusPanel(location) {
}
function focusLocation(location) {
state.activeDetailMode = "history";
hideMarkerActionPanel();
loadLocationHistory(location);
renderRouteFocusPanel(location);
render();
@@ -1272,6 +1495,8 @@ function focusLocation(location) {
async function loadLocationHistory(location) {
state.selectedDriverId = location.driverId;
state.activeDetailMode = "history";
hideMarkerActionPanel();
elements.historyDrawer.classList.remove("is-hidden");
elements.historyTitle.textContent = `${location.vehicleNo || location.driverId} 위치 이력`;
elements.historySummary.textContent = "조회 중";
@@ -1321,7 +1546,9 @@ function renderHistory(location, records) {
function closeHistoryDrawer() {
state.selectedDriverId = "";
state.activeDetailMode = "";
elements.historyDrawer.classList.add("is-hidden");
hideMarkerActionPanel();
clearRouteOverlay();
clearInfoOverlay();
renderRouteFocusPanel(null);
@@ -1329,6 +1556,8 @@ function closeHistoryDrawer() {
}
function showInfoOverlay(location) {
if (!state.map || !state.mapProvider) return;
if (shouldSuppressMapPopup()) {
clearInfoOverlay();
return;