import { useState, useCallback, useRef, useEffect } from 'react' import { fetchAerialMedia } from '../services/aerialApi' import type { AerialMediaItem } from '../services/aerialApi' // ── Helpers ── function formatDtm(dtm: string | null): string { if (!dtm) return '—' const d = new Date(dtm) return d.toISOString().slice(0, 16).replace('T', ' ') } const equipIcon = (t: string) => t === 'drone' ? '🛸' : t === 'plane' ? '✈' : '🛰' const equipTagCls = (t: string) => t === 'drone' ? 'bg-[rgba(59,130,246,0.12)] text-primary-blue' : t === 'plane' ? 'bg-[rgba(34,197,94,0.12)] text-status-green' : 'bg-[rgba(168,85,247,0.12)] text-primary-purple' const mediaTagCls = (t: string) => t === '영상' ? 'bg-[rgba(239,68,68,0.12)] text-status-red' : 'bg-[rgba(234,179,8,0.12)] text-status-yellow' const FilterBtn = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) => ( ) // ── Component ── export function MediaManagement() { const [mediaItems, setMediaItems] = useState([]) const [loading, setLoading] = useState(true) const [selectedIds, setSelectedIds] = useState>(new Set()) const [equipFilter, setEquipFilter] = useState('all') const [typeFilter, setTypeFilter] = useState>(new Set()) const [searchTerm, setSearchTerm] = useState('') const [sortBy, setSortBy] = useState('latest') const [showUpload, setShowUpload] = useState(false) const modalRef = useRef(null) const loadData = useCallback(async () => { setLoading(true) try { const items = await fetchAerialMedia() setMediaItems(items) } catch (err) { console.error('[aerial] 미디어 목록 조회 실패:', err) } finally { setLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) useEffect(() => { const handler = (e: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(e.target as Node)) { setShowUpload(false) } } if (showUpload) document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [showUpload]) const filtered = mediaItems.filter(f => { if (equipFilter !== 'all' && f.equipTpCd !== equipFilter) return false if (typeFilter.size > 0) { const isPhoto = f.mediaTpCd !== '영상' const isVideo = f.mediaTpCd === '영상' if (typeFilter.has('photo') && !isPhoto) return false if (typeFilter.has('video') && !isVideo) return false } if (searchTerm && !f.fileNm.toLowerCase().includes(searchTerm.toLowerCase())) return false return true }) const sorted = [...filtered].sort((a, b) => { if (sortBy === 'name') return a.fileNm.localeCompare(b.fileNm) if (sortBy === 'size') return parseFloat(b.fileSz ?? '0') - parseFloat(a.fileSz ?? '0') return (b.takngDtm ?? '').localeCompare(a.takngDtm ?? '') }) const toggleId = (id: number) => { setSelectedIds(prev => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) } const toggleAll = () => { if (selectedIds.size === sorted.length) { setSelectedIds(new Set()) } else { setSelectedIds(new Set(sorted.map(f => f.aerialMediaSn))) } } const toggleTypeFilter = (t: string) => { setTypeFilter(prev => { const next = new Set(prev) if (next.has(t)) { next.delete(t) } else { next.add(t) } return next }) } const droneCount = mediaItems.filter(f => f.equipTpCd === 'drone').length const planeCount = mediaItems.filter(f => f.equipTpCd === 'plane').length const satCount = mediaItems.filter(f => f.equipTpCd === 'satellite').length return (
{/* Filters */}
촬영 장비: setEquipFilter('all')} /> setEquipFilter('drone')} /> setEquipFilter('plane')} /> setEquipFilter('satellite')} /> 유형: toggleTypeFilter('photo')} /> toggleTypeFilter('video')} />
setSearchTerm(e.target.value)} className="px-3 py-1.5 bg-bg-0 border border-border rounded-sm text-text-1 font-korean text-[11px] outline-none w-40 focus:border-primary-cyan" />
{/* Summary Stats */}
{[ { icon: '📸', value: loading ? '…' : String(mediaItems.length), label: '총 파일', color: 'text-primary-cyan' }, { icon: '🛸', value: loading ? '…' : String(droneCount), label: '드론', color: 'text-text-1' }, { icon: '✈', value: loading ? '…' : String(planeCount), label: '유인항공기', color: 'text-text-1' }, { icon: '🛰', value: loading ? '…' : String(satCount), label: '위성', color: 'text-text-1' }, { icon: '💾', value: '—', label: '총 용량', color: 'text-text-1' }, ].map((s, i) => (
{s.icon}
{s.value}
{s.label}
))}
{/* File Table */}
{loading ? ( ) : sorted.map(f => ( toggleId(f.aerialMediaSn)} className={`border-b border-border/50 cursor-pointer transition-colors hover:bg-[rgba(255,255,255,0.02)] ${ selectedIds.has(f.aerialMediaSn) ? 'bg-[rgba(6,182,212,0.06)]' : '' }`} > ))}
0} onChange={toggleAll} className="accent-primary-blue" /> 사고명 위치 파일명 장비 유형 촬영일시 용량 해상도 📥
불러오는 중...
e.stopPropagation()}> toggleId(f.aerialMediaSn)} className="accent-primary-blue" /> {equipIcon(f.equipTpCd)} {f.acdntSn != null ? String(f.acdntSn) : '—'} {f.locDc ?? '—'} {f.fileNm} {f.equipNm} {f.mediaTpCd === '영상' ? '🎬' : '📷'} {f.mediaTpCd} {formatDtm(f.takngDtm)} {f.fileSz ?? '—'} {f.resolution ?? '—'} e.stopPropagation()}>
{/* Bottom Actions */}
선택된 파일: {selectedIds.size}
{/* Upload Modal */} {showUpload && (
📤 영상·사진 업로드
📁
파일을 드래그하거나 클릭하여 업로드
JPG, TIFF, GeoTIFF, MP4, MOV 지원 · 최대 2GB