import { useCallback, useEffect, useState } from 'react'; import type { ShipImageInfo } from '../../entities/shipImage/model/types'; import { fetchShipImagesByImo, toHighResUrl } from '../../entities/shipImage/api/fetchShipImages'; interface ShipImageModalProps { /** 갤러리에서 전달받은 전체 이미지 목록 (있으면 API 호출 생략) */ images?: ShipImageInfo[]; /** 시작 인덱스 */ initialIndex?: number; /** fallback: 첫 번째 이미지 경로 (images 없을 때) */ initialImagePath?: string; /** 전체 이미지 수 (images 없을 때 표시용) */ totalCount?: number; /** IMO — images 없을 때 API 호출용 */ imo?: number; vesselName?: string; onClose: () => void; } const ShipImageModal = ({ images: preloadedImages, initialIndex = 0, initialImagePath, totalCount, imo, vesselName, onClose, }: ShipImageModalProps) => { const [index, setIndex] = useState(initialIndex); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); // 전체 이미지 목록: preloaded가 있으면 그것을 사용, 없으면 API로 로드 const [fetchedImages, setFetchedImages] = useState(null); const needsFetch = !preloadedImages && !!imo && imo > 0; const [fetchingAll, setFetchingAll] = useState(needsFetch); const allImages = preloadedImages ?? fetchedImages; const total = allImages ? allImages.length : (totalCount ?? 1); const hasPrev = index > 0; const hasNext = index < total - 1; // 현재 이미지 URL 결정 (모달은 항상 고화질) const currentImageUrl = (() => { if (allImages && allImages[index]) return toHighResUrl(allImages[index].path); if (index === 0 && initialImagePath) return toHighResUrl(initialImagePath); return null; })(); // preloaded 없을 때만 API 호출 useEffect(() => { if (!needsFetch) return; const ac = new AbortController(); fetchShipImagesByImo(imo!, ac.signal).then((result) => { if (ac.signal.aborted) return; setFetchedImages(result.length > 0 ? result : null); setFetchingAll(false); }); return () => ac.abort(); }, [needsFetch, imo]); const goPrev = useCallback(() => { if (hasPrev) { setIndex((i) => i - 1); setLoading(true); setError(false); } }, [hasPrev]); const goNext = useCallback(() => { if (!hasNext) return; setIndex((i) => i + 1); setLoading(true); setError(false); }, [hasNext]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); else if (e.key === 'ArrowLeft') goPrev(); else if (e.key === 'ArrowRight') goNext(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose, goPrev, goNext]); // 현재 이미지 메타데이터 const currentMeta = allImages?.[index] ?? null; return (
e.stopPropagation()}> {/* 헤더 */}
{vesselName && {vesselName}} {total > 1 && {index + 1} / {total}}
{/* 이미지 영역 */}
{hasPrev && ( )}
{(loading || fetchingAll) && !error &&
} {error &&
이미지를 불러올 수 없습니다
} {currentImageUrl ? ( {vesselName setLoading(false)} onError={() => { setLoading(false); setError(true); }} /> ) : fetchingAll ? null : (
이미지를 불러올 수 없습니다
)}
{hasNext && ( )}
{/* 푸터 */}
{currentMeta?.copyright && {currentMeta.copyright}} {currentMeta?.date && {currentMeta.date}}
); }; export default ShipImageModal;