Files
carry-your-live/carry-your-live/app/(tabs)/stats.tsx
T
2026-08-23 11:36:17 +02:00

209 lines
6.7 KiB
TypeScript

import React, { useCallback } from 'react';
import { View, Text, StyleSheet, SafeAreaView, ScrollView } from 'react-native';
import { useFocusEffect } from 'expo-router';
import { Header } from '@/components/Header';
import { useStats } from '@/hooks/useStats';
import { useSettings, type ThemeColors } from '@/theme';
export default function StatsScreen() {
const { theme } = useSettings();
const stats = useStats();
useFocusEffect(
useCallback(() => {
return () => {};
}, [])
);
const maxDaily = Math.max(1, ...stats.daily.map((d) => d.count));
return (
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
<Header title="Stats" showLogo={true} />
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
<View style={[styles.card, { backgroundColor: theme.card }]}>
<Text style={[styles.cardTitle, { color: theme.textMuted }]}>Overview</Text>
<View style={styles.summaryGrid}>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: theme.accent }]}>{stats.totalCompleted}</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Completed</Text>
</View>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: theme.text }]}>{stats.completedLast7}</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Last 7 days</Text>
</View>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: theme.text }]}>{stats.completedLast30}</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Last 30 days</Text>
</View>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: theme.text }]}>{stats.completionRate}%</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Success rate</Text>
</View>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: theme.text }]}>{stats.currentStreak}</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Day streak</Text>
</View>
<View style={styles.summaryItem}>
<Text style={[styles.summaryValue, { color: stats.overdueCount > 0 ? theme.accent : theme.text }]}>
{stats.overdueCount}
</Text>
<Text style={[styles.summaryLabel, { color: theme.textMuted }]}>Overdue</Text>
</View>
</View>
</View>
<View style={[styles.card, { backgroundColor: theme.card }]}>
<Text style={[styles.cardTitle, { color: theme.textMuted }]}>Last 7 days</Text>
<View style={styles.chartRow}>
{stats.daily.map((d, i) => {
const height = (d.count / maxDaily) * BASE_HEIGHT;
return (
<View key={i} style={styles.chartCol}>
<View style={styles.chartBarTrack}>
<View
style={[
styles.chartBar,
{ height: Math.max(4, height), backgroundColor: d.count > 0 ? theme.accent : theme.cardAlt },
]}
/>
</View>
<Text style={[styles.chartLabel, { color: theme.textFaint }]}>{d.label}</Text>
</View>
);
})}
</View>
</View>
<View style={[styles.card, { backgroundColor: theme.card }]}>
<Text style={[styles.cardTitle, { color: theme.textMuted }]}>By tags</Text>
{stats.byCategory.length === 0 ? (
<Text style={[styles.emptyText, { color: theme.textFaint }]}>No completed tasks yet</Text>
) : (
stats.byCategory.map((c) => (
<View key={c.id} style={styles.row}>
<View style={[styles.dot, { backgroundColor: c.color }]} />
<Text style={[styles.rowLabel, { color: theme.text }]}>{c.name}</Text>
<Text style={[styles.rowValue, { color: theme.textMuted }]}>{c.count}</Text>
</View>
))
)}
</View>
<View style={[styles.card, { backgroundColor: theme.card }]}>
<Text style={[styles.cardTitle, { color: theme.textMuted }]}>By priority</Text>
{stats.byPriority.length === 0 ? (
<Text style={[styles.emptyText, { color: theme.textFaint }]}>No completed tasks yet</Text>
) : (
stats.byPriority.map((p) => (
<View key={p.priority} style={styles.row}>
<View style={[styles.dot, { backgroundColor: priorityColor(theme, p.priority) }]} />
<Text style={[styles.rowLabel, { color: theme.text }]}>{p.label}</Text>
<Text style={[styles.rowValue, { color: theme.textMuted }]}>{p.count}</Text>
</View>
))
)}
</View>
</ScrollView>
</SafeAreaView>
);
}
const BASE_HEIGHT = 90;
function priorityColor(theme: ThemeColors, priority: string): string {
switch (priority) {
case 'critical':
return theme.dangerStrong;
case 'high':
return theme.warning;
case 'low':
case 'medium':
return theme.success;
default:
return theme.textMuted;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
padding: 16,
paddingBottom: 32,
gap: 12,
},
card: {
borderRadius: 16,
padding: 16,
},
cardTitle: {
fontSize: 13,
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: 0.5,
marginBottom: 14,
},
summaryGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
},
summaryItem: {
width: '33.33%',
marginBottom: 16,
},
summaryValue: {
fontSize: 26,
fontWeight: '700',
},
summaryLabel: {
fontSize: 12,
marginTop: 2,
},
chartRow: {
flexDirection: 'row',
alignItems: 'flex-end',
gap: 8,
},
chartCol: {
flex: 1,
alignItems: 'center',
},
chartBarTrack: {
height: BASE_HEIGHT,
justifyContent: 'flex-end',
width: '100%',
},
chartBar: {
width: '100%',
borderRadius: 6,
minHeight: 4,
},
chartLabel: {
fontSize: 11,
marginTop: 6,
},
row: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
marginRight: 10,
},
rowLabel: {
flex: 1,
fontSize: 15,
},
rowValue: {
fontSize: 15,
fontWeight: '600',
},
emptyText: {
fontSize: 14,
},
});