- Expo SDK 54, expo-router, NativeWind - 7 screens: login, tasks, calendar, goals, chat, settings, tabs - API client (api.hasdo.info), SecureStore auth, AuthContext - Tab navigation: Ukoly, Kalendar, Cile, Chat, Nastaveni - Pull-to-refresh, FAB, group colors, priority dots - Calendar grid with task dots - AI chat with keyboard avoiding - Web export verified (780 modules, 1.5MB bundle) - Bundle ID: info.hasdo.taskteam Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
4.6 KiB
TypeScript
163 lines
4.6 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
FlatList,
|
|
StyleSheet,
|
|
RefreshControl,
|
|
Alert,
|
|
} from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useAuthContext } from '../../lib/AuthContext';
|
|
import * as api from '../../lib/api';
|
|
|
|
export default function GoalsScreen() {
|
|
const { token } = useAuthContext();
|
|
const [goals, setGoals] = useState<any[]>([]);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
const loadGoals = useCallback(async () => {
|
|
if (!token) return;
|
|
try {
|
|
const res = await api.getGoals(token);
|
|
setGoals(res.data || []);
|
|
} catch (err: any) {
|
|
Alert.alert('Chyba', err.message);
|
|
}
|
|
}, [token]);
|
|
|
|
useEffect(() => {
|
|
loadGoals();
|
|
}, [loadGoals]);
|
|
|
|
const onRefresh = useCallback(async () => {
|
|
setRefreshing(true);
|
|
await loadGoals();
|
|
setRefreshing(false);
|
|
}, [loadGoals]);
|
|
|
|
const renderGoal = ({ item }: { item: any }) => {
|
|
const progress = item.progress || 0;
|
|
const total = item.target || 100;
|
|
const pct = Math.min(Math.round((progress / total) * 100), 100);
|
|
|
|
return (
|
|
<View style={styles.goalCard}>
|
|
<View style={styles.goalHeader}>
|
|
<View style={styles.goalIcon}>
|
|
<Ionicons
|
|
name={pct >= 100 ? 'trophy' : 'flag-outline'}
|
|
size={24}
|
|
color={pct >= 100 ? '#F59E0B' : '#3B82F6'}
|
|
/>
|
|
</View>
|
|
<View style={styles.goalInfo}>
|
|
<Text style={styles.goalTitle}>{item.title}</Text>
|
|
{item.description && (
|
|
<Text style={styles.goalDesc} numberOfLines={2}>
|
|
{item.description}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
<Text style={styles.goalPct}>{pct}%</Text>
|
|
</View>
|
|
|
|
<View style={styles.progressBarBg}>
|
|
<View
|
|
style={[
|
|
styles.progressBarFill,
|
|
{
|
|
width: `${pct}%`,
|
|
backgroundColor: pct >= 100 ? '#22C55E' : pct >= 50 ? '#3B82F6' : '#F59E0B',
|
|
},
|
|
]}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.goalFooter}>
|
|
<Text style={styles.goalStat}>
|
|
{progress} / {total}
|
|
</Text>
|
|
{item.deadline && (
|
|
<Text style={styles.goalDeadline}>
|
|
<Ionicons name="time-outline" size={12} color="#94A3B8" />{' '}
|
|
{new Date(item.deadline).toLocaleDateString('cs-CZ')}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<FlatList
|
|
data={goals}
|
|
keyExtractor={(item) => item.id?.toString()}
|
|
renderItem={renderGoal}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
}
|
|
contentContainerStyle={styles.listContent}
|
|
ListEmptyComponent={
|
|
<View style={styles.empty}>
|
|
<Ionicons name="trophy-outline" size={48} color="#CBD5E1" />
|
|
<Text style={styles.emptyText}>Zatim zadne cile</Text>
|
|
<Text style={styles.emptySubtext}>
|
|
Vytvorte cile ve webove aplikaci
|
|
</Text>
|
|
</View>
|
|
}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: '#F8FAFC' },
|
|
listContent: { padding: 12, paddingBottom: 20 },
|
|
goalCard: {
|
|
backgroundColor: '#fff',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
marginBottom: 12,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 3,
|
|
elevation: 2,
|
|
},
|
|
goalHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
|
|
goalIcon: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 10,
|
|
backgroundColor: '#EFF6FF',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
goalInfo: { flex: 1, marginLeft: 12 },
|
|
goalTitle: { fontSize: 16, fontWeight: '600', color: '#1E293B' },
|
|
goalDesc: { fontSize: 13, color: '#64748B', marginTop: 2 },
|
|
goalPct: { fontSize: 18, fontWeight: '700', color: '#3B82F6' },
|
|
progressBarBg: {
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: '#E2E8F0',
|
|
},
|
|
progressBarFill: {
|
|
height: 8,
|
|
borderRadius: 4,
|
|
},
|
|
goalFooter: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
marginTop: 8,
|
|
},
|
|
goalStat: { fontSize: 12, color: '#64748B' },
|
|
goalDeadline: { fontSize: 12, color: '#94A3B8' },
|
|
empty: { alignItems: 'center', marginTop: 60 },
|
|
emptyText: { color: '#94A3B8', fontSize: 16, marginTop: 12 },
|
|
emptySubtext: { color: '#CBD5E1', fontSize: 13, marginTop: 4 },
|
|
});
|