ship-gis/src/components/ship/ReplayLegend.tsx
htlee 6e3ad9e0d8 chore: JavaScript → TypeScript 전환 완료 (77개 파일)
JS/JSX 77개 파일을 TS/TSX로 전환하고 JS 원본을 삭제.
- stores 7개, map core 6개, hooks 4개 등 전체 모듈 전환
- TypeScript strict 모드, OL/Deck.gl 타입 적용
- .gitignore에서 TS/TSX 무시 규칙 제거
- pre-commit hook: .js,.jsx → .ts,.tsx 확장자 변경
- tsc --noEmit 0 에러, ESLint 0 에러, yarn build 성공

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 10:28:27 +09:00

149 lines
4.5 KiB
TypeScript

/**
* 리플레이 전용 범례 컴포넌트
* - 리플레이 모드에서 선종별 카운트 표시
* - ShipLegend 디자인 재사용, replayStore 연동
*/
import { memo } from 'react';
import { shallow } from 'zustand/shallow';
import useReplayStore from '../../replay/stores/replayStore';
import {
SIGNAL_KIND_CODE_FISHING,
SIGNAL_KIND_CODE_KCGV,
SIGNAL_KIND_CODE_PASSENGER,
SIGNAL_KIND_CODE_CARGO,
SIGNAL_KIND_CODE_TANKER,
SIGNAL_KIND_CODE_GOV,
SIGNAL_KIND_CODE_NORMAL,
SIGNAL_KIND_CODE_BUOY,
} from '../../types/constants';
import './ShipLegend.scss';
// 선박 종류별 SVG 아이콘
import fishingIcon from '../../assets/img/shipKindIcons/fishing.svg';
import passIcon from '../../assets/img/shipKindIcons/pass.svg';
import cargoIcon from '../../assets/img/shipKindIcons/cargo.svg';
import hazardIcon from '../../assets/img/shipKindIcons/hazard.svg';
import govIcon from '../../assets/img/shipKindIcons/gov.svg';
import kcgvIcon from '../../assets/img/shipKindIcons/kcgv.svg';
import bouyIcon from '../../assets/img/shipKindIcons/bouy.svg';
import etcIcon from '../../assets/img/shipKindIcons/etc.svg';
/**
* 선박 종류 코드 -> 아이콘 매핑
*/
const SHIP_KIND_ICONS: Record<string, string> = {
[SIGNAL_KIND_CODE_FISHING]: fishingIcon,
[SIGNAL_KIND_CODE_KCGV]: kcgvIcon,
[SIGNAL_KIND_CODE_PASSENGER]: passIcon,
[SIGNAL_KIND_CODE_CARGO]: cargoIcon,
[SIGNAL_KIND_CODE_TANKER]: hazardIcon,
[SIGNAL_KIND_CODE_GOV]: govIcon,
[SIGNAL_KIND_CODE_NORMAL]: etcIcon,
[SIGNAL_KIND_CODE_BUOY]: bouyIcon,
};
interface LegendItemConfig {
code: string;
label: string;
}
/**
* 범례 항목 설정
*/
const LEGEND_ITEMS: LegendItemConfig[] = [
{ code: SIGNAL_KIND_CODE_FISHING, label: '어선' },
{ code: SIGNAL_KIND_CODE_PASSENGER, label: '여객선' },
{ code: SIGNAL_KIND_CODE_CARGO, label: '화물선' },
{ code: SIGNAL_KIND_CODE_TANKER, label: '유조선' },
{ code: SIGNAL_KIND_CODE_GOV, label: '관공선' },
{ code: SIGNAL_KIND_CODE_KCGV, label: '경비함정' },
{ code: SIGNAL_KIND_CODE_BUOY, label: '어망/부이' },
{ code: SIGNAL_KIND_CODE_NORMAL, label: '기타' },
];
interface ReplayLegendItemProps {
code: string;
label: string;
count: number;
icon: string;
isVisible: boolean;
onToggle: (code: string) => void;
}
/**
* 리플레이 범례 항목
*/
const ReplayLegendItem = memo(function ReplayLegendItem({ code, label, count, icon, isVisible, onToggle }: ReplayLegendItemProps) {
const isBuoy = code === SIGNAL_KIND_CODE_BUOY;
return (
<li className={`legend-item ${!isVisible ? 'disabled' : ''}`}>
<div className="legend-info" onClick={() => onToggle(code)}>
<span className="legend-icon">
<img
src={icon}
alt={label}
style={{ transform: isBuoy ? 'rotate(0deg)' : 'rotate(45deg)' }}
/>
</span>
<span className="legend-label">{label}</span>
</div>
<span className="legend-count">{count}</span>
</li>
);
});
/**
* 리플레이 전용 범례 컴포넌트
*/
const ReplayLegend = memo(function ReplayLegend() {
const { replayShipCounts, replayTotalCount, shipKindCodeFilter } =
useReplayStore(
(state: {
replayShipCounts: Record<string, number>;
replayTotalCount: number;
shipKindCodeFilter: Set<string>;
}) => ({
replayShipCounts: state.replayShipCounts,
replayTotalCount: state.replayTotalCount,
shipKindCodeFilter: state.shipKindCodeFilter,
}),
shallow
);
const toggleShipKindCode = useReplayStore((state: { toggleShipKindCode: (code: string) => void }) => state.toggleShipKindCode);
return (
<article className="ship-legend">
{/* 헤더 */}
<div className="legend-header">
<div className="legend-title">
<span> </span>
</div>
</div>
{/* 선박 종류별 목록 */}
<ul className="legend-list">
{LEGEND_ITEMS.map((item) => (
<ReplayLegendItem
key={item.code}
code={item.code}
label={item.label}
count={replayShipCounts[item.code] || 0}
icon={SHIP_KIND_ICONS[item.code]}
isVisible={shipKindCodeFilter.has(item.code)}
onToggle={toggleShipKindCode}
/>
))}
</ul>
{/* 푸터 - 전체 카운트 */}
<div className="legend-footer">
<span></span>
<span className="total-count">{replayTotalCount}</span>
</div>
</article>
);
});
export default ReplayLegend;