111 lines
5.0 KiB
TypeScript
111 lines
5.0 KiB
TypeScript
import React, { useState, useCallback } from 'react'
|
|
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, RefreshControl } from 'react-native'
|
|
import { useFocusEffect } from 'expo-router'
|
|
import { COLORS } from '../../constants/Config'
|
|
import { getMyStats } from '../../services/api'
|
|
|
|
type Period = 'this_month' | 'last_month' | 'total'
|
|
|
|
export default function MyStatsScreen() {
|
|
const [data, setData] = useState<any>(null)
|
|
const [period, setPeriod] = useState<Period>('this_month')
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true)
|
|
try { const r = await getMyStats(); setData(r.data) }
|
|
catch { setData(null) }
|
|
finally { setLoading(false) }
|
|
}, [])
|
|
|
|
useFocusEffect(useCallback(() => { load() }, [load]))
|
|
|
|
const p = data?.[period] ?? data?.this_month ?? {}
|
|
const total = data?.total ?? 0
|
|
|
|
return (
|
|
<ScrollView style={s.container} refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />}>
|
|
|
|
{/* 기간 탭 */}
|
|
<View style={s.tabs}>
|
|
{([['this_month','이번달'], ['last_month','지난달'], ['total','전체']] as [Period, string][]).map(([k, label]) => (
|
|
<TouchableOpacity key={k} style={[s.tab, period===k && s.tabActive]} onPress={() => setPeriod(k)}>
|
|
<Text style={[s.tabText, period===k && s.tabTextActive]}>{label}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
|
|
{/* 수치 카드 */}
|
|
<View style={s.grid}>
|
|
<StatCard label="생성" value={period === 'total' ? total : p.created ?? 0} color={COLORS.blue} />
|
|
<StatCard label="완료" value={p.completed ?? 0} color={COLORS.success} />
|
|
<StatCard label="완료율" value={`${p.rate ?? 0}%`} color={COLORS.accent} />
|
|
</View>
|
|
|
|
{/* 가로 바 비교 */}
|
|
{data?.this_month && data?.last_month && (
|
|
<>
|
|
<Text style={s.sectionTitle}>이번달 vs 지난달</Text>
|
|
{[
|
|
{ label: '생성', a: data.this_month.created ?? 0, b: data.last_month.created ?? 0 },
|
|
{ label: '완료', a: data.this_month.completed ?? 0, b: data.last_month.completed ?? 0 },
|
|
].map(({ label, a, b }) => {
|
|
const max = Math.max(a, b, 1)
|
|
return (
|
|
<View key={label} style={s.compareRow}>
|
|
<Text style={s.compareLabel}>{label}</Text>
|
|
<View style={s.barWrap}>
|
|
<View style={[s.barFill, { width: `${(a/max)*100}%`, backgroundColor: COLORS.accent }]} />
|
|
<Text style={s.barVal}>{a}</Text>
|
|
</View>
|
|
<View style={[s.barWrap, { marginTop: 4 }]}>
|
|
<View style={[s.barFill, { width: `${(b/max)*100}%`, backgroundColor: COLORS.muted }]} />
|
|
<Text style={s.barVal}>{b}</Text>
|
|
</View>
|
|
</View>
|
|
)
|
|
})}
|
|
<View style={s.legend}>
|
|
<View style={[s.legendDot, { backgroundColor: COLORS.accent }]} /><Text style={s.legendText}>이번달</Text>
|
|
<View style={[s.legendDot, { backgroundColor: COLORS.muted, marginLeft: 12 }]} /><Text style={s.legendText}>지난달</Text>
|
|
</View>
|
|
</>
|
|
)}
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
function StatCard({ label, value, color }: { label: string; value: string | number; color: string }) {
|
|
return (
|
|
<View style={[c.card, { borderTopColor: color }]}>
|
|
<Text style={[c.value, { color }]}>{value}</Text>
|
|
<Text style={c.label}>{label}</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const c = StyleSheet.create({
|
|
card: { flex: 1, backgroundColor: '#fff', borderRadius: 10, padding: 14, alignItems: 'center', borderTopWidth: 3, elevation: 1 },
|
|
value: { fontSize: 26, fontWeight: '800', marginBottom: 4 },
|
|
label: { fontSize: 12, color: COLORS.muted },
|
|
})
|
|
|
|
const s = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: COLORS.bg },
|
|
tabs: { flexDirection: 'row', backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border },
|
|
tab: { flex: 1, paddingVertical: 12, alignItems: 'center' },
|
|
tabActive: { borderBottomWidth: 2, borderBottomColor: COLORS.accent },
|
|
tabText: { fontSize: 13, color: COLORS.muted },
|
|
tabTextActive:{ color: COLORS.accent, fontWeight: '700' },
|
|
grid: { flexDirection: 'row', gap: 8, padding: 12 },
|
|
sectionTitle: { fontSize: 15, fontWeight: '700', color: COLORS.text, paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6 },
|
|
compareRow: { paddingHorizontal: 12, marginBottom: 10 },
|
|
compareLabel: { fontSize: 13, color: COLORS.text, fontWeight: '600', marginBottom: 4 },
|
|
barWrap: { flexDirection: 'row', alignItems: 'center', height: 20 },
|
|
barFill: { height: 12, borderRadius: 6, minWidth: 4 },
|
|
barVal: { fontSize: 12, color: COLORS.text, marginLeft: 6 },
|
|
legend: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingBottom: 16 },
|
|
legendDot: { width: 10, height: 10, borderRadius: 5 },
|
|
legendText: { fontSize: 12, color: COLORS.muted, marginLeft: 4 },
|
|
})
|