diff --git a/src/api/aisTargetApi.js b/src/api/aisTargetApi.js
index 043dc75a..5dc9a795 100644
--- a/src/api/aisTargetApi.js
+++ b/src/api/aisTargetApi.js
@@ -35,7 +35,8 @@ export async function searchAisTargets(minutes = 60) {
*/
export function aisTargetToFeature(aisTarget) {
const mmsi = String(aisTarget.mmsi || '');
- const signalKindCode = mapVesselTypeToKindCode(aisTarget.vesselType);
+ // 백엔드에서 signalKindCode를 직접 제공, 없으면 vesselType 기반 fallback
+ const signalKindCode = aisTarget.signalKindCode || mapVesselTypeToKindCode(aisTarget.vesselType);
return {
// 고유 식별자 (AIS 신호원 코드 + MMSI)
diff --git a/src/components/layout/Sidebar.jsx b/src/components/layout/Sidebar.jsx
index c099caad..ae643a36 100644
--- a/src/components/layout/Sidebar.jsx
+++ b/src/components/layout/Sidebar.jsx
@@ -3,6 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
import SideNav, { keyToPath, pathToKey } from './SideNav';
// 구현된 페이지
+import ShipFilterPanel from '../ship/ShipFilterPanel';
import ReplayPage from '../../pages/ReplayPage';
import AreaSearchPage from '../../areaSearch/components/AreaSearchPage';
@@ -49,7 +50,7 @@ export default function Sidebar() {
// 활성 키에 따른 패널 컴포넌트 렌더링
const renderPanel = () => {
const panelMap = {
- gnb1: null, // TODO: 필터/디스플레이 패널 재구현
+ gnb1: ,
gnb4: null, // TODO: 분석 패널
gnb5: null, // TODO: 타임라인 패널
gnb7: ,
diff --git a/src/components/ship/ShipFilterPanel.jsx b/src/components/ship/ShipFilterPanel.jsx
new file mode 100644
index 00000000..539d821c
--- /dev/null
+++ b/src/components/ship/ShipFilterPanel.jsx
@@ -0,0 +1,125 @@
+/**
+ * 선박 필터 패널
+ * - 선종별 표시/숨김 토글
+ * - 기존 DisplayComponent의 필터 탭을 민간화 버전으로 재구현
+ */
+import { useState, memo, useCallback } from 'react';
+import useShipStore from '../../stores/shipStore';
+import { SHIP_KIND_LIST } from '../../types/constants';
+
+/**
+ * 스위치 그룹 헤더 + 접이식 본문
+ */
+const SwitchGroup = memo(({ title, children, defaultOpen = true }) => {
+ const [isOpen, setIsOpen] = useState(defaultOpen);
+
+ return (
+
+
+
{title}
+
+
+ {children}
+
+
+ );
+});
+
+/**
+ * 개별 토글 스위치 (CSS 토글은 기존 common.css의 .switch 클래스 사용)
+ */
+const ToggleSwitch = memo(({ label, checked, onChange }) => (
+
+ {label}
+
+
+));
+
+/**
+ * 전체 ON/OFF 토글
+ */
+const AllToggle = memo(({ label, allChecked, onToggleAll }) => (
+
+ {label}
+
+
+));
+
+/**
+ * 선박 필터 패널 메인 컴포넌트
+ */
+export default function ShipFilterPanel({ isOpen, onToggle }) {
+ const kindVisibility = useShipStore((s) => s.kindVisibility);
+ const kindCounts = useShipStore((s) => s.kindCounts);
+ const toggleKindVisibility = useShipStore((s) => s.toggleKindVisibility);
+
+ // 선종 전체 토글
+ const allKindVisible = Object.values(kindVisibility).every(Boolean);
+ const handleToggleAllKind = useCallback(() => {
+ const nextValue = !allKindVisible;
+ SHIP_KIND_LIST.forEach(({ code }) => {
+ if (kindVisibility[code] !== nextValue) {
+ toggleKindVisibility(code);
+ }
+ });
+ }, [allKindVisible, kindVisibility, toggleKindVisibility]);
+
+ // 전체 선박 수
+ const totalCount = Object.values(kindCounts).reduce((sum, v) => sum + v, 0);
+
+ return (
+
+ {/* 탭 헤더 */}
+
+
+ {/* 필터 본문 */}
+
+
+
+
+ {/* 선종 필터 */}
+
+
+
+ {SHIP_KIND_LIST.map(({ code, label }) => (
+ toggleKindVisibility(code)}
+ />
+ ))}
+
+
+
+
+
+
+
+ {/* 패널 토글 버튼 */}
+
+
+ );
+}