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)} + /> + ))} +
    +
    + +
    +
    +
    + + {/* 패널 토글 버튼 */} +
    + ); +}