import { useState, useEffect } from 'react'; import type { Incident } from './IncidentsLeftPanel'; import { fetchIncidentMedia, fetchIncidentAerialMedia, getMediaImageUrl, } from '../services/incidentsApi'; import type { MediaInfo, AerialMediaItem } from '../services/incidentsApi'; type MediaTab = 'all' | 'photo' | 'video' | 'satellite' | 'cctv'; const MEDIA_TABS: { id: MediaTab; label: string; icon: string }[] = [ { id: 'all', label: '์ „์ฒด', icon: '' }, { id: 'photo', label: '์‚ฌ์ง„', icon: '๐Ÿ“ท' }, { id: 'video', label: '์˜์ƒ', icon: '๐ŸŽฌ' }, { id: 'satellite', label: '์œ„์„ฑ', icon: '๐Ÿ›ฐ' }, { id: 'cctv', label: 'CCTV', icon: '๐Ÿ“น' }, ]; function str(obj: Record | null, key: string, fallback = 'โ€”'): string { if (!obj || obj[key] == null) return fallback; return String(obj[key]); } function num(obj: Record | null, key: string, fallback = 0): number { if (!obj || obj[key] == null) return fallback; return Number(obj[key]); } function bool(obj: Record | null, key: string): boolean { if (!obj || obj[key] == null) return false; return Boolean(obj[key]); } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• MediaModal โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ export function MediaModal({ incident, onClose }: { incident: Incident; onClose: () => void }) { const [activeTab, setActiveTab] = useState('all'); const [selectedCam, setSelectedCam] = useState(0); const [media, setMedia] = useState(null); const [aerialImages, setAerialImages] = useState([]); const [selectedImageIdx, setSelectedImageIdx] = useState(0); useEffect(() => { fetchIncidentMedia(parseInt(incident.id)).then(setMedia); fetchIncidentAerialMedia(parseInt(incident.id)).then(setAerialImages); }, [incident.id]); // Timeline dots (UI constant) const timelineDots = [ { pct: 5, color: '#ef4444', label: incident.time }, { pct: 30, color: '#ef4444', label: '' }, { pct: 42, color: '#ef4444', label: '' }, { pct: 58, color: '#f59e0b', label: '' }, { pct: 78, color: '#ef4444', label: '' }, { pct: 95, color: '#6b7280', label: '' }, ]; if (!media) { return (
{ if (e.target === e.currentTarget) onClose(); }} className="fixed inset-0 z-[10000] flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(6px)' }} >
ํ˜„์žฅ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
); } const total = (media.photoCnt ?? 0) + (media.videoCnt ?? 0) + (media.satCnt ?? 0) + (media.cctvCnt ?? 0) + aerialImages.length; const showPhoto = activeTab === 'all' || activeTab === 'photo'; const showVideo = activeTab === 'all' || activeTab === 'video'; const showSat = activeTab === 'all' || activeTab === 'satellite'; const showCctv = activeTab === 'all' || activeTab === 'cctv'; return (
{ if (e.target === e.currentTarget) onClose(); }} className="fixed inset-0 z-[10000] flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(6px)' }} >
{/* โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
๐Ÿ“‹
ํ˜„์žฅ์ •๋ณด โ€” {incident.name}
{incident.name} ยท {incident.date} ยท ์‚ฌ์ง„ {media.photoCnt} / ์˜์ƒ {media.videoCnt} / ์œ„์„ฑ {media.satCnt} / CCTV {media.cctvCnt}
{/* Tabs */}
{MEDIA_TABS.map((t) => ( ))}
{/* Upload */} {/* Close */} โœ•
{/* โ”€โ”€ Timeline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
TIMELINE
{timelineDots.map((d, i) => (
))}
โ— ์ดˆ๊ธฐ โ— ๋Œ€์‘ โ— ์ข…๋ฃŒ
{/* โ”€โ”€ 2x2 Grid Content โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
{/* โ”€โ”€ Q1: ํ˜„์žฅ์‚ฌ์ง„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} {showPhoto && (
{/* Section header */}
๐Ÿ“ท ํ˜„์žฅ์‚ฌ์ง„ โ€”{' '} {aerialImages.length > 0 ? `${aerialImages.length}์žฅ` : str(media.photoMeta, 'title', 'ํ˜„์žฅ ์‚ฌ์ง„')}
{aerialImages.length > 1 && ( <> setSelectedImageIdx((p) => Math.max(0, p - 1))} /> setSelectedImageIdx((p) => Math.min(aerialImages.length - 1, p + 1)) } /> )}
{/* Photo content */}
{aerialImages.length > 0 ? ( <> {aerialImages[selectedImageIdx].orgnlNm { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).nextElementSibling?.classList.remove( 'hidden', ); }} />
๐Ÿ“ท
์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค
{aerialImages.length > 1 && ( <> )}
{selectedImageIdx + 1} / {aerialImages.length}
) : (
๐Ÿ“ท
{incident.name.replace('์œ ๋ฅ˜์œ ์ถœ', '์œ ์ถœ ํ˜„์žฅ').replace('์˜ค์—ผ', 'ํ˜„์žฅ')} ํ•ด์ƒ ์‚ฌ์ง„
{str(media.photoMeta, 'date')} ยท {str(media.photoMeta, 'by')}
)}
{/* Thumbnails */}
{aerialImages.length > 0 ? ( <>
{aerialImages.map((img, i) => (
setSelectedImageIdx(i)} > {img.orgnlNm { const el = e.target as HTMLImageElement; el.style.display = 'none'; }} />
))}
๐Ÿ“ท ์‚ฌ์ง„ {aerialImages.length}์žฅ {aerialImages[selectedImageIdx]?.takngDtm ? ` ยท ${new Date(aerialImages[selectedImageIdx].takngDtm!).toLocaleDateString('ko-KR')}` : ''} {aerialImages[selectedImageIdx]?.orgnlNm ?? ''}
) : ( <>
{Array.from({ length: Math.min(num(media.photoMeta, 'thumbCount'), 7) }).map( (_, i) => (
๐Ÿ“ท
), )}
๐Ÿ“ท ์‚ฌ์ง„ {num(media.photoMeta, 'thumbCount')}์žฅ ยท{' '} {str(media.photoMeta, 'stage')} ๐Ÿ”— R&D ์—ฐ๊ณ„
)}
)} {/* โ”€โ”€ Q2: ๋“œ๋ก  ์˜์ƒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} {showVideo && (
๐ŸŽฌ ๋“œ๋ก  ์˜์ƒ โ€” {str(media.droneMeta, 'title', '๋“œ๋ก  ์˜์ƒ')}
โ— REC
๐ŸŽฌ
๋“œ๋ก  ํ•ญ๊ณต ์ดฌ์˜ ์˜์ƒ
{str(media.droneMeta, 'device')} ยท {str(media.droneMeta, 'alt')} ๊ณ ๋„
{/* Video controls */}
โฎ
โ–ถ
โญ 02:34 / {str(media.droneMeta, 'duration')}
๐ŸŽฌ ์˜์ƒ {num(media.droneMeta, 'videoCount')}๊ฑด ยท {str(media.droneMeta, 'stage')}
๐Ÿ“‚ ์ „์ฒด๋ณด๊ธฐ ๐Ÿ”— R&D ์—ฐ๊ณ„
)} {/* โ”€โ”€ Q3: ์œ„์„ฑ์˜์ƒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} {showSat && (
๐Ÿ›ฐ ์œ„์„ฑ์˜์ƒ โ€” {str(media.satMeta, 'title', '์œ„์„ฑ์˜์ƒ')}
{str(media.satMeta, 'detection') !== 'โ€”' && (
{str(media.satMeta, 'detection')}
๐Ÿ›ฐ
{str(media.satMeta, 'title', '์œ„์„ฑ์˜์ƒ')} ์œ„์„ฑ์˜์ƒ
{str(media.satMeta, 'date')} ยท ํ•ด์ƒ๋„ {str(media.satMeta, 'resolution')}
)} {str(media.satMeta, 'detection') === 'โ€”' && (
๐Ÿ›ฐ
์œ„์„ฑ์˜์ƒ ์—†์Œ
)}
{num(media.satMeta, 'thumbCount') > 0 && (
{Array.from({ length: num(media.satMeta, 'thumbCount') }).map((_, i) => (
๐Ÿ›ฐ
))}
)}
๐Ÿ›ฐ ์œ„์„ฑ {num(media.satMeta, 'thumbCount')}์žฅ ยท {str(media.satMeta, 'sensor')} ๐Ÿ” ํŽธ์ง‘/์ธก ๋น„๊ต
)} {/* โ”€โ”€ Q4: CCTV โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} {showCctv && (
๐Ÿ“น CCTV โ€” {str(media.cctvMeta, 'title', 'CCTV')}
{bool(media.cctvMeta, 'live') && ( โ— LIVE )}
{bool(media.cctvMeta, 'live') && (
โ— LIVE {new Date().toLocaleTimeString('ko-KR', { hour12: false })}
)}
๐Ÿ“น
{str(media.cctvMeta, 'title', 'CCTV').replace('#', 'CCTV #')}
{str(media.cctvMeta, 'ptz')} ยท {str(media.cctvMeta, 'angle')} ยท{' '} {bool(media.cctvMeta, 'live') ? '์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ' : '๋…นํ™” ์˜์ƒ'}
{/* CAM buttons */}
{Array.from({ length: num(media.cctvMeta, 'camCount') }).map((_, i) => ( ))}
๐Ÿ“น CCTV {num(media.cctvMeta, 'camCount')}์ฑ„๋„ ยท{' '} {str(media.cctvMeta, 'location')}
๐Ÿ”ด ๋…นํ™”์˜์ƒ ๐ŸŽฅ PTZ
)}
{/* โ”€โ”€ Bottom Bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
๐Ÿ“ท ์‚ฌ์ง„{' '} {aerialImages.length > 0 ? aerialImages.length : (media.photoCnt ?? 0)} ๐ŸŽฌ ์˜์ƒ {media.videoCnt ?? 0} ๐Ÿ›ฐ ์œ„์„ฑ {media.satCnt ?? 0} ๐Ÿ“น CCTV {media.cctvCnt ?? 0} ๐Ÿ“Ž ์ด {total}๊ฑด
); } function NavBtn({ label, onClick }: { label: string; onClick?: () => void }) { return ( ); } function BottomBtn({ icon, label, bg, bd, fg, }: { icon: string; label: string; bg: string; bd: string; fg: string; }) { return ( ); }