feat(aerial): 위성 요청 취소 기능 추가

- SatRequest status에 '취소' 상태 추가
- 필터 탭에 '취소' 추가
- 대기/촬영중 상태 모두 취소 가능 (confirm 팝업)
- 취소된 요청은 빨간 ✕ 배지 + 투명도 60%
- satRequests를 상태(state)로 관리하여 실시간 상태 변경

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nan Kyung Lee 2026-03-17 10:16:31 +09:00
부모 0c4bfb2f24
커밋 00e7a3e70a

파일 보기

@ -14,7 +14,7 @@ interface SatRequest {
requestDate: string requestDate: string
expectedReceive: string expectedReceive: string
resolution: string resolution: string
status: '촬영중' | '대기' | '완료' status: '촬영중' | '대기' | '완료' | '취소'
provider?: string provider?: string
purpose?: string purpose?: string
requester?: string requester?: string
@ -85,6 +85,7 @@ const SAT_MAP_STYLE: StyleSpecification = {
type SatModalPhase = 'none' | 'provider' | 'blacksky' | 'up42' type SatModalPhase = 'none' | 'provider' | 'blacksky' | 'up42'
export function SatelliteRequest() { export function SatelliteRequest() {
const [requests, setRequests] = useState(satRequests)
const [statusFilter, setStatusFilter] = useState('전체') const [statusFilter, setStatusFilter] = useState('전체')
const [modalPhase, setModalPhase] = useState<SatModalPhase>('none') const [modalPhase, setModalPhase] = useState<SatModalPhase>('none')
const [selectedRequest, setSelectedRequest] = useState<SatRequest | null>(null) const [selectedRequest, setSelectedRequest] = useState<SatRequest | null>(null)
@ -125,13 +126,14 @@ export function SatelliteRequest() {
if (modalPhase === 'up42') loadSatPasses() if (modalPhase === 'up42') loadSatPasses()
}, [modalPhase, loadSatPasses]) }, [modalPhase, loadSatPasses])
const allRequests = showMoreCompleted ? satRequests : satRequests.filter(r => r.status !== '완료' || r.id === 'SAT-003') const allRequests = showMoreCompleted ? requests : requests.filter(r => (r.status !== '완료' && r.status !== '취소') || r.id === 'SAT-003')
const filtered = allRequests.filter(r => { const filtered = allRequests.filter(r => {
if (statusFilter === '전체') return true if (statusFilter === '전체') return true
if (statusFilter === '대기') return r.status === '대기' if (statusFilter === '대기') return r.status === '대기'
if (statusFilter === '진행') return r.status === '촬영중' if (statusFilter === '진행') return r.status === '촬영중'
if (statusFilter === '완료') return r.status === '완료' if (statusFilter === '완료') return r.status === '완료'
if (statusFilter === '취소') return r.status === '취소'
return true return true
}) })
@ -144,6 +146,9 @@ export function SatelliteRequest() {
if (s === '대기') return ( if (s === '대기') return (
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-semibold font-korean" style={{ background: 'rgba(59,130,246,.15)', border: '1px solid rgba(59,130,246,.3)', color: 'var(--blue)' }}> </span> <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-semibold font-korean" style={{ background: 'rgba(59,130,246,.15)', border: '1px solid rgba(59,130,246,.3)', color: 'var(--blue)' }}> </span>
) )
if (s === '취소') return (
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-semibold font-korean" style={{ background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', color: 'var(--red)' }}> </span>
)
return ( return (
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-semibold font-korean" style={{ background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)', color: 'var(--green)' }}> </span> <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-semibold font-korean" style={{ background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)', color: 'var(--green)' }}> </span>
) )
@ -156,7 +161,7 @@ export function SatelliteRequest() {
{ value: '0.5m', label: '최고 해상도', color: 'var(--cyan)' }, { value: '0.5m', label: '최고 해상도', color: 'var(--cyan)' },
] ]
const filters = ['전체', '대기', '진행', '완료'] const filters = ['전체', '대기', '진행', '완료', '취소']
const up42Filtered = up42Satellites.filter(s => s.type === up42SubTab) const up42Filtered = up42Satellites.filter(s => s.type === up42SubTab)
@ -231,7 +236,7 @@ export function SatelliteRequest() {
gridTemplateColumns: '60px 1fr 100px 100px 120px 80px 90px', gridTemplateColumns: '60px 1fr 100px 100px 120px 80px 90px',
borderColor: 'rgba(255,255,255,.04)', borderColor: 'rgba(255,255,255,.04)',
background: selectedRequest?.id === r.id ? 'rgba(99,102,241,.06)' : r.status === '촬영중' ? 'rgba(234,179,8,.03)' : 'transparent', background: selectedRequest?.id === r.id ? 'rgba(99,102,241,.06)' : r.status === '촬영중' ? 'rgba(234,179,8,.03)' : 'transparent',
opacity: r.status === '완료' ? 0.7 : 1, opacity: (r.status === '완료' || r.status === '취소') ? 0.6 : 1,
}} }}
> >
<div className="text-[11px] font-mono text-text-2">{r.id}</div> <div className="text-[11px] font-mono text-text-2">{r.id}</div>
@ -266,8 +271,18 @@ export function SatelliteRequest() {
{r.status === '완료' && ( {r.status === '완료' && (
<button className="px-3 py-1.5 text-[10px] font-semibold font-korean rounded border cursor-pointer hover:bg-bg-hover transition-colors" style={{ background: 'rgba(34,197,94,.08)', borderColor: 'rgba(34,197,94,.2)', color: 'var(--green)' }}>📥 </button> <button className="px-3 py-1.5 text-[10px] font-semibold font-korean rounded border cursor-pointer hover:bg-bg-hover transition-colors" style={{ background: 'rgba(34,197,94,.08)', borderColor: 'rgba(34,197,94,.2)', color: 'var(--green)' }}>📥 </button>
)} )}
{r.status === '대기' && ( {(r.status === '대기' || r.status === '촬영중') && (
<button className="px-3 py-1.5 text-[10px] font-semibold font-korean rounded border cursor-pointer hover:bg-bg-hover transition-colors" style={{ background: 'rgba(239,68,68,.08)', borderColor: 'rgba(239,68,68,.2)', color: 'var(--red)' }}> </button> <button
onClick={(e) => {
e.stopPropagation()
if (confirm(`${r.id} (${r.zone}) 위성 촬영 요청을 취소하시겠습니까?`)) {
setRequests(prev => prev.map(req => req.id === r.id ? { ...req, status: '취소' as const } : req))
setSelectedRequest(null)
}
}}
className="px-3 py-1.5 text-[10px] font-semibold font-korean rounded border cursor-pointer hover:bg-bg-hover transition-colors"
style={{ background: 'rgba(239,68,68,.08)', borderColor: 'rgba(239,68,68,.2)', color: 'var(--red)' }}
> </button>
)} )}
</div> </div>
</div> </div>