import React, { useState, useCallback, useEffect } from 'react' import { View, Text, FlatList, TextInput, TouchableOpacity, Modal, StyleSheet, Share, RefreshControl, ScrollView, } from 'react-native' import { useFocusEffect } from 'expo-router' import { COLORS } from '../../constants/Config' import { getKBList, getKBDetail } from '../../services/api' import { useKBBookmark } from '../../hooks/useKBBookmark' import { useOffline } from '../../contexts/OfflineContext' import MarkdownViewer from '../../components/MarkdownViewer' const CATEGORIES = ['전체', '서버', '네트워크', '보안', 'CSAP', '기타'] export default function KBBrowserScreen() { const [items, setItems] = useState([]) const [query, setQuery] = useState('') const [cat, setCat] = useState('전체') const [loading, setLoading] = useState(false) const [detail, setDetail] = useState(null) const { isBookmarked, toggle } = useKBBookmark() const { isOffline, getCache, setCache } = useOffline() const load = useCallback(async () => { setLoading(true) try { if (isOffline) { const cached = await getCache('kb_list') if (cached) { setItems(cached as any[]); return } } const r = await getKBList(query || undefined) const data = r.data?.items ?? r.data ?? [] setItems(data) await setCache('kb_list', data) } catch { const cached = await getCache('kb_list') if (cached) setItems(cached as any[]) } finally { setLoading(false) } }, [query, isOffline]) useFocusEffect(useCallback(() => { load() }, [load])) const openDetail = async (id: number) => { try { const r = await getKBDetail(id) setDetail(r.data) } catch { setDetail({ id, title: '상세 로드 실패', content: '네트워크 오류가 발생했습니다.' }) } } const shareKB = async () => { if (!detail) return await Share.share({ message: `[GUARDiA KB] ${detail.title}\n\n${(detail.content ?? '').slice(0, 200)}...` }) } const filtered = items.filter(i => cat === '전체' || (i.category ?? '') === cat) return ( {/* 검색 */} {isOffline && 오프라인} {/* 카테고리 */} {CATEGORIES.map(c => ( setCat(c)}> {c} ))} {/* 목록 */} String(i.id ?? i.kb_id)} refreshControl={} ListEmptyComponent={검색 결과가 없습니다.} contentContainerStyle={{ padding: 12 }} renderItem={({ item }) => ( openDetail(item.id ?? item.kb_id)}> {item.title} {item.category ?? '기타'} 조회 {item.view_count ?? 0} toggle(item.id ?? item.kb_id)} style={s.bookmark}> {isBookmarked(item.id ?? item.kb_id) ? '⭐' : '☆'} )} /> {/* 상세 모달 */} setDetail(null)}> ← 닫기 공유 {detail?.title} ) } const s = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.bg }, searchBar: { flexDirection: 'row', alignItems: 'center', padding: 10, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border, gap: 8 }, searchInput: { flex: 1, backgroundColor: COLORS.bg, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 14, color: COLORS.text }, offlineBadge: { backgroundColor: COLORS.warning, borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, offlineText: { fontSize: 10, color: '#fff', fontWeight: '700' }, catBar: { backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, catChip: { paddingHorizontal: 14, paddingVertical: 8, marginHorizontal: 4 }, catChipActive: { borderBottomWidth: 2, borderBottomColor: COLORS.accent }, catText: { fontSize: 13, color: COLORS.muted }, catTextActive: { color: COLORS.accent, fontWeight: '700' }, card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, cardRow: { flexDirection: 'row', alignItems: 'flex-start' }, title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginBottom: 6 }, metaRow: { flexDirection: 'row', alignItems: 'center', gap: 8 }, chip: { fontSize: 11, backgroundColor: COLORS.light, color: COLORS.blue, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 }, meta: { fontSize: 12, color: COLORS.muted }, bookmark: { paddingLeft: 8 }, empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, modalContainer: { flex: 1, backgroundColor: '#fff' }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, borderBottomWidth: 1, borderBottomColor: COLORS.border }, back: { fontSize: 15, color: COLORS.accent, fontWeight: '600' }, shareBtn: { fontSize: 15, color: COLORS.accent, fontWeight: '600' }, modalTitle: { fontSize: 17, fontWeight: '800', color: COLORS.text, padding: 16, paddingBottom: 0 }, })