import React, { useState, useCallback } from 'react' import { View, Text, FlatList, TouchableOpacity, StyleSheet, Alert, RefreshControl } from 'react-native' import { useFocusEffect } from 'expo-router' import { COLORS } from '../../constants/Config' import { getAISOCEvents } from '../../services/api' const SEV_COLOR: Record = { CRITICAL: COLORS.danger, HIGH: '#F97316', MEDIUM: COLORS.warning, LOW: COLORS.success, } export default function AISOCScreen() { const [events, setEvents] = useState([]) const [loading, setLoading] = useState(false) const [expanded, setExpanded] = useState>(new Set()) const load = useCallback(async () => { setLoading(true) try { const r = await getAISOCEvents(); setEvents(r.data?.items ?? r.data ?? []) } catch { setEvents([]) } finally { setLoading(false) } }, []) useFocusEffect(useCallback(() => { load() }, [load])) const toggle = (id: number) => { const next = new Set(expanded) next.has(id) ? next.delete(id) : next.add(id) setExpanded(next) } const resolve = (item: any) => { Alert.alert('대응 완료', `"${item.event_type ?? '인시던트'}"을 대응 완료로 표시하시겠습니까?`, [ { text: '취소', style: 'cancel' }, { text: '완료', onPress: () => { setEvents(prev => prev.map(e => e.id === item.id ? { ...e, status: 'resolved' } : e)) }}, ]) } const renderItem = ({ item }: { item: any }) => { const sev = (item.severity ?? item.grade ?? 'MEDIUM').toUpperCase() const isOpen = expanded.has(item.id) return ( toggle(item.id)}> {sev} {item.event_type ?? item.title ?? '보안 이벤트'} {item.status ?? 'open'} {item.detected_at?.slice(0, 16).replace('T', ' ') ?? item.created_at?.slice(0, 16).replace('T', ' ')} {isOpen && ( {item.description ?? item.detail ?? '-'} {item.status !== 'resolved' && ( resolve(item)}> 대응 완료 )} )} ) } return ( String(i.id ?? Math.random())} renderItem={renderItem} refreshControl={} ListEmptyComponent={보안 인시던트가 없습니다.} contentContainerStyle={{ padding: 12 }} /> ) } const s = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.bg }, card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, cardResolved: { opacity: 0.6 }, row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4 }, sevBadge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, sevText: { fontSize: 10, color: '#fff', fontWeight: '800' }, eventType: { flex: 1, fontSize: 13, fontWeight: '600', color: COLORS.text }, status: { fontSize: 11, color: COLORS.muted }, time: { fontSize: 11, color: COLORS.muted }, detail: { marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: COLORS.border }, detailText: { fontSize: 13, color: COLORS.text, lineHeight: 20, marginBottom: 10 }, resolveBtn: { backgroundColor: COLORS.success, borderRadius: 6, padding: 8, alignItems: 'center' }, resolveBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, })