guardia-messenger/components/KBSuggest.tsx

94 lines
2.8 KiB
TypeScript

/**
* KBSuggest (#23) — SR 입력 중 실시간 KB 추천
*
* query(SR 제목 입력값) → debounce 600ms → GET /api/kb/?q={query}&limit=3
* 최대 3개 KB 카드 표시, 탭 → onOpen(KB id) 콜백 (상세 화면 이동).
*/
import { useState, useEffect, useRef } from 'react'
import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native'
import { COLORS, API_BASE } from '../constants/Config'
import { authFetch } from '../utils/auth'
interface KBItem {
id: number
title: string
summary?: string
category?: string
}
interface Props {
query: string
onOpen?: (id: number) => void
}
export function KBSuggest({ query, onOpen }: Props) {
const [items, setItems] = useState<KBItem[]>([])
const [loading, setLoading] = useState(false)
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
if (timer.current) clearTimeout(timer.current)
const q = query?.trim() ?? ''
if (q.length < 2) {
setItems([])
return
}
timer.current = setTimeout(async () => {
setLoading(true)
try {
const res = await authFetch(`${API_BASE}/api/kb/?q=${encodeURIComponent(q)}&limit=3`)
if (res.ok) {
const d = await res.json()
const list: KBItem[] = Array.isArray(d) ? d : d.items ?? d.results ?? []
setItems(list.slice(0, 3))
} else {
setItems([])
}
} catch {
setItems([])
} finally {
setLoading(false)
}
}, 600)
return () => {
if (timer.current) clearTimeout(timer.current)
}
}, [query])
if (!loading && items.length === 0) return null
return (
<View style={S.wrap}>
<Text style={S.title}>📚 </Text>
{loading ? (
<ActivityIndicator color={COLORS.accent} style={{ marginVertical: 6 }} />
) : (
items.map(kb => (
<Pressable key={kb.id} style={S.card} onPress={() => onOpen?.(kb.id)}>
<Text style={S.cardTitle} numberOfLines={1}>
{kb.title}
</Text>
{kb.summary ? (
<Text style={S.cardSummary} numberOfLines={2}>
{kb.summary}
</Text>
) : null}
{kb.category ? <Text style={S.cardCat}>{kb.category}</Text> : null}
</Pressable>
))
)}
</View>
)
}
export default KBSuggest
const S = StyleSheet.create({
wrap: { marginTop: 8 },
title: { fontSize: 12, fontWeight: '700', color: COLORS.muted, marginBottom: 6 },
card: { backgroundColor: COLORS.light, borderRadius: 10, padding: 11, marginBottom: 6 },
cardTitle: { fontSize: 13, fontWeight: '600', color: COLORS.blue },
cardSummary: { fontSize: 12, color: COLORS.text, marginTop: 3, lineHeight: 17 },
cardCat: { fontSize: 10, color: COLORS.muted, marginTop: 4 },
})