Files
task-team/mobile/app/(tabs)/goals.tsx
Admin db81100b5b React Native Expo app — full mobile client
- 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>
2026-03-29 15:09:35 +00:00

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 },
});