import React, { useCallback, useState } from 'react'; import { ActivityIndicator, RefreshControl, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { Feather } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { FadeInDown, FadeInUp, LinearTransition, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Card, CurrencyDisplay, Divider, SectionLabel } from '@/components/ui/primitives'; import { ExpensesChart } from '@/components/ui/expenses-chart'; import { Colors, Spacing, Typography } from '@/constants/theme'; import { getDashboard } from '@/services/api'; import type { DashboardData } from '@/types'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter, useFocusEffect } from 'expo-router'; import { useIsFocused } from "expo-router/react-navigation"; const MONTH_NAMES = [ '', 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro', ]; export default function DashboardScreen() { const insets = useSafeAreaInsets(); const [refreshing, setRefreshing] = useState(false); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { user, logout } = useAuth(); const router = useRouter(); const isFocused = useIsFocused(); const [targetMonth, setTargetMonth] = useState(() => new Date().getMonth() + 1); const [targetYear, setTargetYear] = useState(() => new Date().getFullYear()); const fetchDashboard = useCallback(async () => { if (!user) return; try { setError(null); const res = await getDashboard(user.id, targetMonth, targetYear); setData(res); } catch (err) { setError('Não foi possível carregar os dados.'); } finally { setLoading(false); } }, [targetMonth, targetYear, user]); useFocusEffect( useCallback(() => { fetchDashboard(); }, [fetchDashboard]) ); async function handleRefresh() { setRefreshing(true); await fetchDashboard(); setRefreshing(false); } if (loading) { return ( ); } if (error || !data) { return ( {error} ); } const isPositive = data.balance.available >= 0; const monthName = MONTH_NAMES[data.period.month]; return ( {isFocused && ( } > Olá, {data.user.name} 👋 { let m = targetMonth - 1; let y = targetYear; if (m < 1) { m = 12; y -= 1; } setTargetMonth(m); setTargetYear(y); setLoading(true); }} style={styles.monthButton} > {monthName} {data.period.year} { let m = targetMonth + 1; let y = targetYear; if (m > 12) { m = 1; y += 1; } setTargetMonth(m); setTargetYear(y); setLoading(true); }} style={styles.monthButton} > { await logout(); router.replace('/login'); }} > 80 ? Colors.negative : Colors.primary, }, ]} /> {data.balance.expense_ratio.toFixed(1)}% do salário comprometido Bruto {data.salary.gross.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 0, })} → Líquido {data.salary.effective_net.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 0, })} INSS -{data.salary.inss_discount.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', })} IRRF -{data.salary.irrf_discount.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', })} {data.expenses.recurring_total < 0 ? '+ ' : (data.expenses.recurring_total > 0 ? '- ' : '')} {Math.abs(data.expenses.recurring_total).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 0, })} {data.expenses.one_time_total < 0 ? '+ ' : (data.expenses.one_time_total > 0 ? '- ' : '')} {Math.abs(data.expenses.one_time_total).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 0, })} )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background, }, centered: { justifyContent: 'center', alignItems: 'center', }, errorText: { ...Typography.body, color: Colors.negative, }, headerGradient: { position: 'absolute', top: 0, left: 0, right: 0, height: 200, }, scroll: { padding: Spacing.md, gap: Spacing.md, }, headerTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', paddingVertical: Spacing.sm, }, greeting: { flex: 1, }, logoutBtn: { padding: 8, backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 12, }, greetingText: { ...Typography.h2, color: Colors.textPrimary, }, periodText: { ...Typography.caption, color: Colors.textSecondary, textTransform: 'uppercase', letterSpacing: 1.5, }, monthSelector: { flexDirection: 'row', alignItems: 'center', marginTop: 6, gap: Spacing.xs, }, monthButton: { padding: 4, }, card: { gap: Spacing.sm, }, heroCard: { gap: Spacing.md, borderWidth: 1, }, heroPositive: { borderColor: `${Colors.positive}40`, }, heroNegative: { borderColor: `${Colors.negative}40`, }, heroBalance: { marginTop: Spacing.xs, }, ratioSection: { gap: Spacing.xs, }, ratioBar: { height: 4, backgroundColor: Colors.border, borderRadius: 2, overflow: 'hidden', }, ratioFill: { height: '100%', borderRadius: 2, }, ratioText: { ...Typography.caption, color: Colors.textMuted, }, salaryRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: Spacing.sm, }, salaryItem: { flex: 1, gap: 4, }, salaryLabel: { ...Typography.caption, color: Colors.textMuted, textTransform: 'uppercase', letterSpacing: 1, }, salaryValue: { ...Typography.h2, color: Colors.textPrimary, }, salaryArrow: { paddingHorizontal: Spacing.md, }, salaryArrowText: { ...Typography.h2, color: Colors.textMuted, }, taxRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, taxLabel: { ...Typography.body, color: Colors.textSecondary, }, taxValue: { ...Typography.body, color: Colors.negative, fontFamily: 'DMSans_600SemiBold', }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: Spacing.xs, }, totalBadge: { ...Typography.body, color: Colors.negative, fontFamily: 'DMSans_600SemiBold', }, rowDivider: { marginVertical: 0, }, });