wing-ops/frontend/src/tabs/reports/components/OilSpreadMapPanel.tsx
jeonghyo.k c4f11423aa feat(reports): 보고서 확산예측 지도 캡처 기능 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 18:23:42 +09:00

107 lines
4.2 KiB
TypeScript

import { useRef, useState } from 'react';
import { MapView } from '@common/components/map/MapView';
import type { OilReportPayload } from '@common/hooks/useSubMenu';
interface OilSpreadMapPanelProps {
mapData: OilReportPayload['mapData'];
capturedImage: string | null;
onCapture: (dataUrl: string) => void;
onReset: () => void;
}
const OilSpreadMapPanel = ({ mapData, capturedImage, onCapture, onReset }: OilSpreadMapPanelProps) => {
const captureRef = useRef<(() => Promise<string | null>) | null>(null);
const [isCapturing, setIsCapturing] = useState(false);
const handleCapture = async () => {
if (!captureRef.current) return;
setIsCapturing(true);
const dataUrl = await captureRef.current();
setIsCapturing(false);
if (dataUrl) {
onCapture(dataUrl);
}
};
if (!mapData) {
return (
<div className="w-full h-[280px] bg-bg-3 border border-border rounded-lg flex items-center justify-center text-text-3 text-[12px] font-korean mb-4">
. .
</div>
);
}
return (
<div className="mb-4">
{/* 지도 + 오버레이 컨테이너 — MapView 항상 마운트 유지 (deck.gl rAF race condition 방지) */}
<div className="relative w-full rounded-lg border border-border overflow-hidden" style={{ height: '620px' }}>
<MapView
center={mapData.center}
zoom={mapData.zoom}
incidentCoord={{ lat: mapData.center[0], lon: mapData.center[1] }}
oilTrajectory={mapData.trajectory}
externalCurrentTime={mapData.currentStep}
centerPoints={mapData.centerPoints}
showBeached={true}
showTimeLabel={true}
simulationStartTime={mapData.simulationStartTime || undefined}
mapCaptureRef={captureRef}
showOverlays={false}
/>
{/* 캡처 이미지 오버레이 — 우측 상단 */}
{capturedImage && (
<div className="absolute top-3 right-3 z-10" style={{ width: '220px' }}>
<div
className="rounded-lg overflow-hidden"
style={{ border: '1px solid rgba(6,182,212,0.5)', boxShadow: '0 4px 16px rgba(0,0,0,0.5)' }}
>
<img src={capturedImage} alt="확산예측 지도 캡처" className="w-full block" />
<div
className="flex items-center justify-between px-2.5 py-1.5"
style={{ background: 'rgba(15,23,42,0.85)', borderTop: '1px solid rgba(6,182,212,0.3)' }}
>
<span className="text-[10px] font-korean font-semibold" style={{ color: '#06b6d4' }}>
📷
</span>
<button
onClick={onReset}
className="text-[10px] font-korean hover:text-text-1 transition-colors"
style={{ color: 'rgba(148,163,184,0.8)' }}
>
</button>
</div>
</div>
</div>
)}
</div>
{/* 하단 안내 + 캡처 버튼 */}
<div className="flex items-center justify-between mt-2">
<p className="text-[10px] text-text-3 font-korean">
{capturedImage
? 'PDF 다운로드 시 캡처된 이미지가 포함됩니다.'
: '지도를 이동/확대하여 원하는 범위를 선택한 후 캡처하세요.'}
</p>
<button
onClick={handleCapture}
disabled={isCapturing || !!capturedImage}
className="px-3 py-1.5 text-[11px] font-semibold rounded transition-all font-korean flex items-center gap-1.5"
style={{
background: capturedImage ? 'rgba(6,182,212,0.06)' : 'rgba(6,182,212,0.12)',
border: '1px solid rgba(6,182,212,0.4)',
color: capturedImage ? 'rgba(6,182,212,0.5)' : '#06b6d4',
opacity: isCapturing ? 0.6 : 1,
cursor: capturedImage ? 'default' : 'pointer',
}}
>
{capturedImage ? '✓ 캡처됨' : '📷 이 범위로 캡처'}
</button>
</div>
</div>
);
};
export default OilSpreadMapPanel;