diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 96d4f84..1362318 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -23,6 +23,10 @@ ### 수정 - 센서 API(/api/sensor/*) 인증 예외 처리 (공개 데이터) - 선박 모달 열 때마다 S&P Global 우선 탭 리셋 (MarineTraffic 포커스 유지 버그) +- S&P Global 사진 URL: IMO 기반 이미지 목록 API 연동 (잘못된 번호 패턴 제거) + +### 기타 +- 로그인 화면 KCG 로고에 DEMO 문구 오버레이 ## [2026-03-18.2] diff --git a/frontend/src/components/auth/LoginPage.tsx b/frontend/src/components/auth/LoginPage.tsx index 9742506..0703a47 100644 --- a/frontend/src/components/auth/LoginPage.tsx +++ b/frontend/src/components/auth/LoginPage.tsx @@ -104,7 +104,22 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => { > {/* Title */}
- KCG +
+ KCG + + DEMO + +

(); + +async function fetchSpgImages(imo: string): Promise { + if (spgImageCache.has(imo)) return spgImageCache.get(imo) || []; + try { + const res = await fetch(`/signal-batch/api/v1/shipimg/${imo}`); + if (!res.ok) throw new Error(`${res.status}`); + const data: SpgImageInfo[] = await res.json(); + spgImageCache.set(imo, data); + return data; + } catch { + spgImageCache.set(imo, null); + return []; + } +} + +function VesselPhoto({ mmsi, imo, shipImagePath }: VesselPhotoProps) { const localUrl = LOCAL_SHIP_PHOTOS[mmsi]; const hasSPGlobal = !!shipImagePath; - // 항상 S&P Global 우선 (모달 열릴 때마다 리셋) const [activeTab, setActiveTab] = useState(hasSPGlobal ? 'spglobal' : 'marinetraffic'); const [spgSlideIdx, setSpgSlideIdx] = useState(0); const [spgErrors, setSpgErrors] = useState>(new Set()); + const [spgImages, setSpgImages] = useState([]); - // 모달이 다른 선박으로 변경될 때 탭/슬라이드 리셋 + // 모달이 다른 선박으로 변경될 때 리셋 + 이미지 목록 조회 useEffect(() => { setActiveTab(hasSPGlobal ? 'spglobal' : 'marinetraffic'); setSpgSlideIdx(0); setSpgErrors(new Set()); - }, [mmsi, hasSPGlobal]); + setSpgImages([]); - // S&P Global slide URLs + if (imo && hasSPGlobal) { + fetchSpgImages(imo).then(setSpgImages); + } else if (shipImagePath) { + // IMO 없으면 shipImagePath 단일 이미지 사용 + setSpgImages([{ picId: 0, path: shipImagePath.replace(/_[12]\.\w+$/, ''), copyright: '', date: '' }]); + } + }, [mmsi, imo, hasSPGlobal, shipImagePath]); + + // S&P Global slide URLs: 각 이미지의 path + _2.jpg (원본) const spgUrls = useMemo( - () => shipImagePath ? buildSpgUrls(shipImagePath, shipImageCount ?? 1) : [], - [shipImagePath, shipImageCount], + () => spgImages.map(img => `${img.path}_2.jpg`), + [spgImages], ); const validSpgCount = spgUrls.length; @@ -188,7 +204,6 @@ function VesselPhoto({ mmsi, shipImagePath, shipImageCount }: VesselPhotoProps) }); useEffect(() => { - // 새 선박이면 캐시 확인 setMtPhoto(vesselPhotoCache.has(mmsi) ? vesselPhotoCache.get(mmsi) : undefined); }, [mmsi]);