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 */}
-

+
+

+
+ 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]);