diff --git a/src/app/monitoring/page.tsx b/src/app/monitoring/page.tsx index bb5cfc8..d0b7c3f 100644 --- a/src/app/monitoring/page.tsx +++ b/src/app/monitoring/page.tsx @@ -49,6 +49,8 @@ interface VehicleSidebarProps { currentTheme: any; carLocations: CarLocation[]; currentPositions: CurrentCarPosition[]; + showRoute: boolean; + setShowRoute: React.Dispatch>; } function VehicleSidebar({ @@ -57,7 +59,9 @@ function VehicleSidebar({ toggleCarSelection, currentTheme, carLocations, - currentPositions + currentPositions, + showRoute, + setShowRoute }: VehicleSidebarProps) { const [searchTerm, setSearchTerm] = useState(''); const [selectedVehicle, setSelectedVehicle] = useState(null); @@ -81,14 +85,26 @@ function VehicleSidebar({
-
- -

- 차량 목록 - - {vehicles.length}대 - -

+
+
+ +

+ 차량 목록 + + {vehicles.length}대 + +

+
+
@@ -190,6 +206,12 @@ function VehicleSidebar({ ); } +interface RouteGroup { + carId: string; + color: string; + points: { lat: number; lng: number }[]; +} + export default function MonitoringPage() { const router = useRouter(); const { isAuthenticated } = useAuthStore(); @@ -197,8 +219,11 @@ export default function MonitoringPage() { const [wsConnected, setWsConnected] = useState(false); const [carLocations, setCarLocations] = useState([]); const [currentPositions, setCurrentPositions] = useState([]); + const [showRoute, setShowRoute] = useState(false); + const [routePoints, setRoutePoints] = useState([]); const wsRef = useRef(null); - const [companyId, setCompanyId] = useState('2'); + //Todo: 회사 아이디 받아오기 + const [companyId, setCompanyId] = useState('1'); const [selectedCars, setSelectedCars] = useState([]); const [showAllCars, setShowAllCars] = useState(true); const animationRef = useRef(null); @@ -210,10 +235,10 @@ export default function MonitoringPage() { latitude: 36.5, longitude: 127.5, zoom: 7, - followVehicle: false, + followVehicle: false, + userSetPosition: true // 사용자가 직접 설정한 위치인지 여부 }); - const [lastDragTime, setLastDragTime] = useState(0); const dragCooldownMs = 5000; @@ -229,15 +254,12 @@ export default function MonitoringPage() { ]); const handleMapDrag = (center: {lat: number, lng: number}, zoom: number) => { - - setLastDragTime(Date.now()); - - setMapSettings(prev => ({ ...prev, latitude: center.lat, longitude: center.lng, - zoom: zoom + zoom: zoom, + userSetPosition: true })); }; @@ -251,7 +273,8 @@ export default function MonitoringPage() { const toggleVehicleFollow = () => { setMapSettings(prev => ({ ...prev, - followVehicle: !prev.followVehicle + followVehicle: !prev.followVehicle, + userSetPosition: false })); }; @@ -411,6 +434,35 @@ export default function MonitoringPage() { } }; + // 경로 포인트 업데이트 함수 + const updateRoutePoints = () => { + if (!showRoute) { + setRoutePoints([]); + return; + } + + // 각 차량별로 경로를 그룹화하여 저장 + const groupedRoutes = currentPositions.map(pos => { + const vehicle = carLocations.find(v => v.carId === pos.carId); + if (vehicle) { + return { + carId: pos.carId, + color: pos.color, + points: vehicle.locations + .slice(0, pos.currentIndex + 1) + .map(loc => ({ + lat: loc.latitude, + lng: loc.longitude + })) + }; + } + return null; + }).filter((route): route is RouteGroup => route !== null); + + setRoutePoints(groupedRoutes); + }; + + // 차량 선택 토글 함수 수정 const toggleCarSelection = (carId: string) => { setSelectedCars(prev => { if (prev.includes(carId)) { @@ -421,6 +473,16 @@ export default function MonitoringPage() { }); }; + // 차량 위치가 업데이트될 때마다 경로도 업데이트 + useEffect(() => { + updateRoutePoints(); + }, [currentPositions, showRoute]); + + // 경로 보기/숨기기 토글 시 경로 업데이트 + useEffect(() => { + updateRoutePoints(); + }, [showRoute]); + const visibleMarkers = currentPositions .filter(pos => showAllCars || selectedCars.includes(pos.carId)) .map(pos => ({ @@ -435,16 +497,12 @@ export default function MonitoringPage() { } })); + // 차량 위치가 업데이트될 때마다 지도 위치 업데이트 useEffect(() => { if (!mapSettings.followVehicle) { return; } - const timeSinceDrag = Date.now() - lastDragTime; - if (timeSinceDrag < dragCooldownMs) { - return; - } - if (selectedCars.length === 1) { const selectedCar = currentPositions.find(pos => pos.carId === selectedCars[0]); if (selectedCar) { @@ -452,11 +510,12 @@ export default function MonitoringPage() { ...prev, latitude: selectedCar.currentLocation.latitude, longitude: selectedCar.currentLocation.longitude, - zoom: 15 + zoom: 15, + userSetPosition: false })); } } - }, [currentPositions, selectedCars, mapSettings.followVehicle, lastDragTime]); + }, [currentPositions, selectedCars, mapSettings.followVehicle]); const handleSubscribe = () => { if (wsRef.current && companyId && wsConnected) { @@ -536,7 +595,6 @@ export default function MonitoringPage() {
-
-
@@ -624,6 +683,9 @@ export default function MonitoringPage() { markers={visibleMarkers} onMapDrag={handleMapDrag} allowDrag={true} + showRoute={showRoute} + routePoints={routePoints} + followVehicle={mapSettings.followVehicle} />
@@ -640,4 +702,4 @@ export default function MonitoringPage() {
); -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/components/map/CarMap.tsx b/src/components/map/CarMap.tsx index 3f87e19..d5d700e 100644 --- a/src/components/map/CarMap.tsx +++ b/src/components/map/CarMap.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from 'react'; -import { NAVER_CLIENT_ID } from '@/lib/env'; interface RoutePoint { lat: number; @@ -17,6 +16,12 @@ interface MarkerData { onClick?: (vehicleId: string) => void; } +interface RouteGroup { + carId: string; + color: string; + points: { lat: number; lng: number }[]; +} + interface NaverMapProps { // 기본 지도 속성 latitude?: number; @@ -28,9 +33,14 @@ interface NaverMapProps { // 마커 관련 속성 markers?: MarkerData[]; + // 경로 관련 속성 + showRoute?: boolean; + routePoints?: RouteGroup[]; + // 단순화된 속성들 allowDrag?: boolean; // 드래그 허용 (기본값: true) onMapDrag?: (center: {lat: number, lng: number}, zoom: number) => void; // 지도 드래그 콜백 + followVehicle?: boolean; // 차량 추적 여부 } declare global { @@ -66,9 +76,8 @@ const loadNaverScript = (): Promise => { const script = document.createElement('script'); script.id = 'naver-map-script'; script.type = 'text/javascript'; - // 환경변수에서 CLIENT ID 가져오기 - const clientId = NAVER_CLIENT_ID || 'xefwc1thif'; + const clientId = process.env.NEXT_PUBLIC_NAVER_CLIENT_ID; script.src = `https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=${clientId}`; @@ -95,12 +104,16 @@ export default function CarMap({ width = '100%', zoom = 15, markers = [], + showRoute = false, + routePoints = [], allowDrag = true, - onMapDrag + onMapDrag, + followVehicle = false }: NaverMapProps) { const mapContainerRef = useRef(null); const mapInstance = useRef(null); const markersRef = useRef([]); + const routeLinesRef = useRef<{[key: string]: any}>({}); // 각 차량의 경로를 개별적으로 관리 const [isRefReady, setIsRefReady] = useState(false); const [isMapLoaded, setIsMapLoaded] = useState(false); const [error, setError] = useState(null); @@ -331,6 +344,56 @@ export default function CarMap({ } }, [markers]); + // 경로 업데이트 효과 + useEffect(() => { + if (mapInstance.current && window.naver && showRoute && routePoints.length > 0) { + // 기존 경로 제거 + Object.values(routeLinesRef.current).forEach(line => { + if (line) line.setMap(null); + }); + routeLinesRef.current = {}; + + // 새 경로 생성 + const drawRoutes = () => { + routePoints.forEach(routeGroup => { + const path = routeGroup.points.map(point => + new window.naver.maps.LatLng(point.lat, point.lng) + ); + + routeLinesRef.current[routeGroup.carId] = new window.naver.maps.Polyline({ + path: path, + strokeColor: routeGroup.color, + strokeWeight: 3, + strokeOpacity: 0.8, + map: mapInstance.current + }); + }); + }; + + drawRoutes(); + } else { + // 경로 표시가 꺼졌을 때 모든 경로 제거 + Object.values(routeLinesRef.current).forEach(line => { + if (line) line.setMap(null); + }); + routeLinesRef.current = {}; + } + }, [showRoute, routePoints, isMapLoaded]); + + // 차량 추적 효과 + useEffect(() => { + if (mapInstance.current && window.naver && followVehicle && markers.length > 0) { + const selectedMarker = markers.find(marker => marker.isSelected); + if (selectedMarker) { + // 선택된 차량의 위치로 지도 중심 이동 + const position = new window.naver.maps.LatLng(selectedMarker.lat, selectedMarker.lng); + mapInstance.current.setCenter(position); + // 줌 레벨은 고정 (기본값 15) + mapInstance.current.setZoom(15); + } + } + }, [followVehicle, markers, isMapLoaded]); + if (error) { return (
diff --git a/src/components/map/NaverMap.tsx b/src/components/map/NaverMap.tsx index 7842d46..559f6af 100644 --- a/src/components/map/NaverMap.tsx +++ b/src/components/map/NaverMap.tsx @@ -132,7 +132,7 @@ const loadNaverScript = (): Promise => { script.type = 'text/javascript'; // 환경변수에서 CLIENT ID 가져오기 - const clientId = NAVER_CLIENT_ID || 'xefwc1thif'; + const clientId = process.env.NEXT_PUBLIC_NAVER_CLIENT_ID; console.log(`NaverMap: 사용 clientId=${clientId}`); diff --git a/src/components/vehicles/RouteMap.tsx b/src/components/vehicles/RouteMap.tsx new file mode 100644 index 0000000..e69de29