wing-ops/frontend/src/tabs/reports/components/OilSpreadMapPanel.tsx
leedano 9e51651fc7 Merge remote-tracking branch 'origin/develop' into feature/design-system-refactoring
# Conflicts:
#	docs/RELEASE-NOTES.md
#	frontend/src/common/components/map/MapView.tsx
#	frontend/src/tabs/incidents/components/DischargeZonePanel.tsx
#	frontend/src/tabs/incidents/components/IncidentsView.tsx
2026-04-07 18:02:57 +09:00

177 lines
5.8 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'];
capturedStep3: string | null;
capturedStep6: string | null;
onCaptureStep3: (dataUrl: string) => void;
onCaptureStep6: (dataUrl: string) => void;
onResetStep3: () => void;
onResetStep6: () => void;
}
interface MapSlotProps {
label: string;
step: number;
mapData: NonNullable<OilReportPayload['mapData']>;
captured: string | null;
onCapture: (dataUrl: string) => void;
onReset: () => void;
}
const MapSlot = ({ label, step, mapData, captured, onCapture, onReset }: MapSlotProps) => {
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);
};
return (
<div className="flex flex-col">
{/* 라벨 */}
<div className="flex items-center gap-1.5 mb-1.5">
<span
className="text-label-2 font-bold font-korean px-2 py-0.5 rounded"
style={{
background: 'color-mix(in srgb, var(--color-accent) 12%, transparent)',
color: 'var(--color-accent)',
border: '1px solid color-mix(in srgb, var(--color-accent) 25%, transparent)',
}}
>
{label}
</span>
</div>
{/* 지도 + 캡처 오버레이 */}
<div
className="relative rounded-lg border border-stroke overflow-hidden"
style={{ aspectRatio: '16/9' }}
>
<MapView
center={mapData.center}
zoom={mapData.zoom}
incidentCoord={{ lat: mapData.center[0], lon: mapData.center[1] }}
oilTrajectory={mapData.trajectory}
externalCurrentTime={step}
centerPoints={mapData.centerPoints}
showBeached={true}
showTimeLabel={true}
simulationStartTime={mapData.simulationStartTime || undefined}
mapCaptureRef={captureRef}
showOverlays={false}
/>
{captured && (
<div className="absolute top-2 right-2 z-10" style={{ width: '180px' }}>
<div
className="rounded-lg overflow-hidden"
style={{
border: '1px solid color-mix(in srgb, var(--color-accent) 50%, transparent)',
boxShadow: '0 4px 16px rgba(0,0,0,0.5)',
}}
>
<img src={captured} alt={`${label} 캡처`} className="w-full block" />
<div
className="flex items-center justify-between px-2 py-1"
style={{
background: 'rgba(15,23,42,0.85)',
borderTop: '1px solid color-mix(in srgb, var(--color-accent) 30%, transparent)',
}}
>
<span
className="text-caption font-korean font-semibold"
style={{ color: 'var(--color-accent)' }}
>
📷
</span>
<button
onClick={onReset}
className="text-caption font-korean hover:text-fg transition-colors"
style={{ color: 'rgba(148,163,184,0.8)' }}
>
</button>
</div>
</div>
</div>
)}
</div>
{/* 캡처 버튼 */}
<div className="flex items-center justify-between mt-1.5">
<p className="text-caption text-fg-disabled font-korean">
{captured ? 'PDF 출력 시 포함됩니다.' : '원하는 범위를 선택 후 캡처하세요.'}
</p>
<button
onClick={handleCapture}
disabled={isCapturing || !!captured}
className="px-2.5 py-1 text-label-2 font-semibold rounded transition-all font-korean flex items-center gap-1"
style={{
background: captured
? 'color-mix(in srgb, var(--color-accent) 6%, transparent)'
: 'color-mix(in srgb, var(--color-accent) 12%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-accent) 40%, transparent)',
color: captured
? 'color-mix(in srgb, var(--color-accent) 50%, transparent)'
: 'var(--color-accent)',
opacity: isCapturing ? 0.6 : 1,
cursor: captured ? 'default' : 'pointer',
}}
>
{captured ? '✓ 캡처됨' : '📷 캡처'}
</button>
</div>
</div>
);
};
const OilSpreadMapPanel = ({
mapData,
capturedStep3,
capturedStep6,
onCaptureStep3,
onCaptureStep6,
onResetStep3,
onResetStep6,
}: OilSpreadMapPanelProps) => {
if (!mapData) {
return (
<div className="w-full h-[200px] bg-bg-card border border-stroke rounded-lg flex items-center justify-center text-fg-disabled text-label-1 font-korean mb-4">
. .
</div>
);
}
return (
<div className="mb-4">
<div className="grid grid-cols-2 gap-4">
<MapSlot
label="3시간 후"
step={3}
mapData={mapData}
captured={capturedStep3}
onCapture={onCaptureStep3}
onReset={onResetStep3}
/>
<MapSlot
label="6시간 후"
step={6}
mapData={mapData}
captured={capturedStep6}
onCapture={onCaptureStep6}
onReset={onResetStep6}
/>
</div>
</div>
);
};
export default OilSpreadMapPanel;