diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 4088f3bb..c272dc86 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -84,6 +84,13 @@ export default function TabLayout() {
tabBarIcon: ({ focused }) => ,
}}
/>
+ ,
+ }}
+ />
= {
+ CRITICAL: '#ef4444', WARNING: '#f59e0b', NORMAL: '#22c55e', NO_DATA: '#94a3b8',
+}
+
+export default function InsightsScreen() {
+ const { token } = useAuth()
+ const [weekly, setWeekly] = useState(null)
+ const [anomaly, setAnomaly] = useState(null)
+ const [predict, setPredict] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [refreshing, setRefreshing] = useState(false)
+
+ useEffect(() => { load() }, [])
+
+ async function load() {
+ setLoading(true)
+ try {
+ const [w, a, p] = await Promise.all([
+ apiClient.get('/api/insights/weekly', token),
+ apiClient.get('/api/insights/anomalies', token),
+ apiClient.get('/api/predict/sla-breach', token),
+ ])
+ setWeekly(w); setAnomaly(a); setPredict(p)
+ } catch { /* ์ค๋ฅ ๋ฌด์ */ }
+ setLoading(false); setRefreshing(false)
+ }
+
+ function onRefresh() { setRefreshing(true); load() }
+
+ if (loading) return (
+
+
+
+ )
+
+ return (
+ }>
+ {/* ์ด์ ๊ฐ์ง ์๋ฆผ */}
+ {(anomaly?.anomalies?.length || 0) > 0 && (
+
+ โ ๏ธ ์ด์ ๊ฐ์ง {anomaly!.anomalies.length}๊ฑด
+ {anomaly!.anomalies.map((a: any, i: number) => (
+ {a.message}
+ ))}
+
+ )}
+
+ {/* ํต๊ณ ์นด๋ */}
+
+ {[
+ { label: '์ ๊ท SR', val: weekly?.stats?.total || 0, color: COLORS.primary },
+ { label: '์๋ฃ์จ', val: `${weekly?.stats?.completion_rate || 0}%`, color: '#22c55e' },
+ { label: '๋ฏธ์ฒ๋ฆฌ', val: weekly?.stats?.open || 0, color: '#ef4444' },
+ ].map(item => (
+
+ {item.val}
+ {item.label}
+
+ ))}
+
+
+ {/* SLA ์์ธก */}
+ {predict && (
+
+ ๐ SLA ์๋ฐ ์์ธก (7์ผ)
+
+ {Math.round((predict.breach_probability_7d || 0) * 100)}%
+
+ ํ์ฌ SLA: {predict.current_rate || 0}%
+ {predict.insight ? {predict.insight} : null}
+
+ )}
+
+ {/* AI ์ฃผ๊ฐ ์ธ์ฌ์ดํธ */}
+ {weekly?.ai_insight && (
+
+ ๐ค AI ์ฃผ๊ฐ ์ธ์ฌ์ดํธ
+ {weekly.ai_insight}
+
+ )}
+
+ {/* ์์ ์นดํ
๊ณ ๋ฆฌ */}
+ {(weekly?.top_categories?.length || 0) > 0 && (
+
+ ๐ SR ์์ ์นดํ
๊ณ ๋ฆฌ
+ {weekly!.top_categories.map((c: any, i: number) => (
+
+ {c.category}
+
+
+
+ {c.count}๊ฑด
+
+ ))}
+
+ )}
+
+ )
+}
+
+const s = StyleSheet.create({
+ scroll: { flex: 1, backgroundColor: '#f8fafc' },
+ center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+ alertCard: { margin: 12, padding: 14, backgroundColor: '#fef2f2', borderRadius: 12, borderLeftWidth: 4, borderLeftColor: '#ef4444' },
+ alertTitle: { fontWeight: '700', fontSize: 14, color: '#7f1d1d', marginBottom: 4 },
+ alertMsg: { fontSize: 12, color: '#991b1b', marginTop: 3 },
+ statsRow: { flexDirection: 'row', padding: 12, gap: 8 },
+ statCard: { flex: 1, backgroundColor: '#fff', borderRadius: 12, padding: 14, alignItems: 'center', borderTopWidth: 3, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 4, elevation: 2 },
+ statVal: { fontSize: 24, fontWeight: '800' },
+ statLabel: { fontSize: 11, color: '#64748b', marginTop: 2 },
+ card: { margin: 12, marginTop: 0, backgroundColor: '#fff', borderRadius: 12, padding: 16, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 4, elevation: 2 },
+ cardTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b', marginBottom: 10 },
+ bigNum: { fontSize: 36, fontWeight: '800' },
+ subText: { fontSize: 12, color: '#64748b', marginTop: 2 },
+ insightText: { fontSize: 13, lineHeight: 20, color: '#374151', marginTop: 8 },
+ categoryRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 8 },
+ categoryName: { fontSize: 12, width: 80, color: '#374151' },
+ barBg: { flex: 1, backgroundColor: '#f1f5f9', borderRadius: 3, height: 6 },
+ barFill: { height: 6, backgroundColor: COLORS.primary, borderRadius: 3 },
+ categoryCount:{ fontSize: 11, color: '#64748b', width: 30, textAlign: 'right' },
+})