diff --git a/src/app/monitoring/page.tsx b/src/app/monitoring/page.tsx index d0b7c3f..aec7bf9 100644 --- a/src/app/monitoring/page.tsx +++ b/src/app/monitoring/page.tsx @@ -1,9 +1,10 @@ 'use client'; -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useAuthStore } from '@/lib/authStore'; import { useTheme } from '@/contexts/ThemeContext'; +import { useVehicleStore, Vehicle } from '@/lib/vehicleStore'; import { MinusIcon, PlusIcon, @@ -51,6 +52,8 @@ interface VehicleSidebarProps { currentPositions: CurrentCarPosition[]; showRoute: boolean; setShowRoute: React.Dispatch>; + selectedVehicleDetails: Vehicle | null; + storeVehicles: Vehicle[]; } function VehicleSidebar({ @@ -61,7 +64,9 @@ function VehicleSidebar({ carLocations, currentPositions, showRoute, - setShowRoute + setShowRoute, + selectedVehicleDetails, + storeVehicles }: VehicleSidebarProps) { const [searchTerm, setSearchTerm] = useState(''); const [selectedVehicle, setSelectedVehicle] = useState(null); @@ -81,6 +86,57 @@ function VehicleSidebar({ const selectedVehicleFullData = selectedVehicle ? carLocations.find(car => car.carId === selectedVehicle) : null; + const getStateDisplay = (state: string | null | undefined) => { + if (!state) return { + text: "알 수 없음", + color: "text-gray-500", + bgColor: "bg-gray-100 dark:bg-gray-700" + }; + + switch (state) { + case "RUNNING": + return { + text: "운행 중", + color: "text-green-600 dark:text-green-400", + bgColor: "bg-green-100 dark:bg-green-900/30" + }; + case "STOPPED": + return { + text: "정지", + color: "text-red-600 dark:text-red-400", + bgColor: "bg-red-100 dark:bg-red-900/30" + }; + case "NOT_REGISTERED": + return { + text: "미등록", + color: "text-yellow-600 dark:text-yellow-400", + bgColor: "bg-yellow-100 dark:bg-yellow-900/30" + }; + default: + return { + text: "알 수 없음", + color: "text-gray-500", + bgColor: "bg-gray-100 dark:bg-gray-700" + }; + } + }; + + const vehicleState = selectedVehicleDetails?.carState; + const stateDisplay = getStateDisplay(vehicleState); + + useEffect(() => { + if (selectedVehicle) { + console.log('선택된 차량:', selectedVehicle); + console.log('차량 상세 정보:', selectedVehicleDetails); + console.log('스토어의 모든 차량:', storeVehicles); + + const matchingVehicle = storeVehicles.find(v => + v.id === selectedVehicle || v.mdn === selectedVehicle + ); + console.log('스토어에서 찾은 차량:', matchingVehicle); + } + }, [selectedVehicle, selectedVehicleDetails, storeVehicles]); + return (
@@ -170,9 +226,16 @@ function VehicleSidebar({
-
+
+
+
차량 상태
+
+ {stateDisplay.text} +
+
+
-
현재 위치
+
현재 위치
위도: @@ -184,8 +247,9 @@ function VehicleSidebar({
-
-
시간
+ +
+
시간
{new Date(selectedVehicleData.currentLocation.timestamp).toLocaleTimeString()}
@@ -222,7 +286,6 @@ export default function MonitoringPage() { const [showRoute, setShowRoute] = useState(false); const [routePoints, setRoutePoints] = useState([]); const wsRef = useRef(null); - //Todo: 회사 아이디 받아오기 const [companyId, setCompanyId] = useState('1'); const [selectedCars, setSelectedCars] = useState([]); const [showAllCars, setShowAllCars] = useState(true); @@ -236,7 +299,7 @@ export default function MonitoringPage() { longitude: 127.5, zoom: 7, followVehicle: false, - userSetPosition: true // 사용자가 직접 설정한 위치인지 여부 + userSetPosition: true }); const [lastDragTime, setLastDragTime] = useState(0); @@ -253,6 +316,10 @@ export default function MonitoringPage() { { name: '대전', latitude: 36.3504, longitude: 127.3845, zoom: 11 } ]); + const { vehicles: storeVehicles, fetchVehicles } = useVehicleStore(); + + const [selectedVehicleDetails, setSelectedVehicleDetails] = useState(null); + const handleMapDrag = (center: {lat: number, lng: number}, zoom: number) => { setMapSettings(prev => ({ ...prev, @@ -325,12 +392,9 @@ export default function MonitoringPage() { } }, [carLocations]); - // 연결 및 데이터 수신 처리 useEffect(() => { connectWebSocket(); - // 테스트 데이터 생성은 제거 (직접 입력 기능으로 대체) - return () => { if (wsRef.current) { wsRef.current.close(); @@ -341,7 +405,6 @@ export default function MonitoringPage() { }; }, []); - // WebSocket 연결 상태가 변경될 때 자동 구독 useEffect(() => { if (wsConnected && companyId) { console.log('WebSocket 연결됨, 자동 구독 시도:', companyId); @@ -434,14 +497,12 @@ 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) { @@ -462,23 +523,27 @@ export default function MonitoringPage() { setRoutePoints(groupedRoutes); }; - // 차량 선택 토글 함수 수정 - const toggleCarSelection = (carId: string) => { + const toggleCarSelection = useCallback((carId: string) => { setSelectedCars(prev => { if (prev.includes(carId)) { + setSelectedVehicleDetails(null); return prev.filter(id => id !== carId); } else { - return [...prev, carId]; + const vehicleInfo = storeVehicles.find(v => v.id === carId || v.mdn === carId); + setSelectedVehicleDetails(vehicleInfo || null); + return [carId]; } }); - }; + }, [storeVehicles]); + + useEffect(() => { + fetchVehicles(); + }, [fetchVehicles]); - // 차량 위치가 업데이트될 때마다 경로도 업데이트 useEffect(() => { updateRoutePoints(); }, [currentPositions, showRoute]); - // 경로 보기/숨기기 토글 시 경로 업데이트 useEffect(() => { updateRoutePoints(); }, [showRoute]); @@ -497,7 +562,6 @@ export default function MonitoringPage() { } })); - // 차량 위치가 업데이트될 때마다 지도 위치 업데이트 useEffect(() => { if (!mapSettings.followVehicle) { return; @@ -606,6 +670,8 @@ export default function MonitoringPage() { currentPositions={currentPositions} showRoute={showRoute} setShowRoute={setShowRoute} + selectedVehicleDetails={selectedVehicleDetails} + storeVehicles={storeVehicles} />
diff --git a/src/components/logs/VehicleLogDetailSlidePanel.tsx b/src/components/logs/VehicleLogDetailSlidePanel.tsx index 2c894a4..24b7f5b 100644 --- a/src/components/logs/VehicleLogDetailSlidePanel.tsx +++ b/src/components/logs/VehicleLogDetailSlidePanel.tsx @@ -57,16 +57,30 @@ export default function VehicleLogDetailSlidePanel({ isOpen, onClose, log, onDel formattedEndTime ) as RouteResponse; - if (routeData && routeData.route && routeData.route.length > 0) { - const points = routeData.route.map((point: RoutePoint) => ({ - lat: point.latitude, - lng: point.longitude, - timestamp: point.timestamp - })); - setRoutePoints(points); - } else { - setRoutePoints([]); - } + // 응답 구조에 맞게 경로 데이터 추출 및 중복 제거 + const points = routeData.data?.route + ? (() => { + // 중복 제거: 같은 타임스탬프의 위치 데이터는 한 번만 사용 + const uniquePoints = []; + const seenTimestamps = new Set(); + + for (const point of routeData.data.route) { + if (!seenTimestamps.has(point.timestamp)) { + uniquePoints.push({ + lat: point.latitude, + lng: point.longitude, + timestamp: point.timestamp + }); + seenTimestamps.add(point.timestamp); + } + } + + console.log('중복 제거 후 위치 포인트:', uniquePoints.length, '개'); + return uniquePoints; + })() + : []; + + setRoutePoints(points); } catch (error) { setRoutePoints([]); } finally { diff --git a/src/components/vehicles/VehicleDetailSlidePanel.tsx b/src/components/vehicles/VehicleDetailSlidePanel.tsx index 2e6b498..d1859f3 100644 --- a/src/components/vehicles/VehicleDetailSlidePanel.tsx +++ b/src/components/vehicles/VehicleDetailSlidePanel.tsx @@ -38,12 +38,12 @@ export default function VehicleDetailSlidePanel({ isOpen, onClose, vehicle }: Ve setIsLoadingPosition(true); try { - const position = await fetchLatestPosition(vehicle.mdn); - if (position) { + const response = await fetchLatestPosition(vehicle.mdn); + if (response && response.data) { setLatestPosition({ - latitude: position.latitude, - longitude: position.longitude, - timestamp: position.timestamp + latitude: response.data.latitude, + longitude: response.data.longitude, + timestamp: response.data.timestamp }); } else { setLatestPosition(null); @@ -493,14 +493,24 @@ export default function VehicleDetailSlidePanel({ isOpen, onClose, vehicle }: Ve
- + {latestPosition && latestPosition.latitude && latestPosition.longitude ? ( + + ) : ( +
+
+

+ 현재 차량 위치 정보가 없습니다 +

+
+
+ )}
diff --git a/src/lib/api.ts b/src/lib/api.ts index c07c546..9a58d1d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -110,10 +110,14 @@ export const fetchApi = async (endpoint: string, queryParams?: Record { try { const response = await fetch(`${API_BASE_URL}/api/gps/position?mdn=${mdn}`); diff --git a/src/pages/api/vehicle/position.ts b/src/pages/api/vehicle/position.ts new file mode 100644 index 0000000..e69de29