import React, { useCallback, useState } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import Animated, { FadeInDown, LinearTransition } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Haptics from 'expo-haptics'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, Divider, SectionLabel } from '@/components/ui/primitives'; import { RecurringExpenseRow } from '@/components/expense-row'; import { Colors, Spacing, Typography } from '@/constants/theme'; import type { RecurringExpense } from '@/types'; import { createRecurringExpense, deleteRecurringExpense, getRecurringExpenses, toggleRecurringExpense, updateRecurringExpense, } from '@/services/api'; import { useAuth } from '@/contexts/AuthContext'; import { ActionModal } from '@/components/ui/action-modal'; import { OptionsModal } from '@/components/ui/options-modal'; import { useFocusEffect } from 'expo-router'; import { useIsFocused } from "expo-router/react-navigation"; export default function RecurringExpensesScreen() { const insets = useSafeAreaInsets(); const { user } = useAuth(); const [expenses, setExpenses] = useState([]); const [showForm, setShowForm] = useState(false); const [loading, setLoading] = useState(true); const [name, setName] = useState(''); const [amount, setAmount] = useState(''); const [expenseType, setExpenseType] = useState<'expense' | 'income'>('expense'); const [dueDay, setDueDay] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [expenseToDelete, setExpenseToDelete] = useState(null); const [selectedExpense, setSelectedExpense] = useState(null); const [editingExpenseId, setEditingExpenseId] = useState(null); const isFocused = useIsFocused(); const fetchExpenses = useCallback(async () => { if (!user) return; try { setLoading(true); const data = await getRecurringExpenses(user.id); setExpenses(data); } catch (err) { Alert.alert('Erro', 'Não foi possível carregar os gastos.'); } finally { setLoading(false); } }, [user]); useFocusEffect( useCallback(() => { fetchExpenses(); }, [fetchExpenses]) ); const activeTotal = expenses .filter((e) => e.is_active) .reduce((sum, e) => { return sum + (e.type === 'income' ? -e.amount : e.amount); }, 0); const isTotalIncome = activeTotal < 0; const absTotal = Math.abs(activeTotal); const formattedTotal = absTotal.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', }); async function handleToggle(id: number) { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setExpenses((prev) => prev.map((e) => (e.id === id ? { ...e, is_active: !e.is_active } : e)), ); try { if (!user) return; await toggleRecurringExpense(user.id, id); } catch (err) { setExpenses((prev) => prev.map((e) => (e.id === id ? { ...e, is_active: !e.is_active } : e)), ); Alert.alert('Erro', 'Não foi possível alterar o status do gasto.'); } } async function handleAdd() { const amountNum = parseFloat(amount.replace(',', '.')); if (!name.trim() || isNaN(amountNum) || amountNum <= 0) { Alert.alert('Dados inválidos', 'Preencha o nome e um valor válido.'); return; } try { if (!user) return; setIsSubmitting(true); if (editingExpenseId) { const updated = await updateRecurringExpense(user.id, editingExpenseId, { name: name.trim(), amount: amountNum, category: 'outros', type: expenseType, due_day: dueDay ? parseInt(dueDay, 10) : null, }); setExpenses((prev) => prev.map((e) => e.id === editingExpenseId ? updated : e)); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } else { const newExpense = await createRecurringExpense(user.id, { name: name.trim(), amount: amountNum, category: 'outros', type: expenseType, due_day: dueDay ? parseInt(dueDay, 10) : null, is_active: true, starts_at: new Date().toISOString().split('T')[0], ends_at: null, }); setExpenses((prev) => [...prev, newExpense]); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } setName(''); setAmount(''); setExpenseType('expense'); setDueDay(''); setShowForm(false); setEditingExpenseId(null); } catch (err) { Alert.alert('Erro', 'Não foi possível salvar o lançamento.'); } finally { setIsSubmitting(false); } } function handleDelete(id: number) { setExpenseToDelete(id); setSelectedExpense(null); } function handleEdit(expense: RecurringExpense) { setName(expense.name); setAmount(expense.amount.toString()); setExpenseType(expense.type); setDueDay(expense.due_day ? expense.due_day.toString() : ''); setEditingExpenseId(expense.id); setSelectedExpense(null); setShowForm(true); } async function confirmDelete() { if (!user || expenseToDelete === null) return; Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); try { await deleteRecurringExpense(user.id, expenseToDelete); setExpenses((prev) => prev.filter((e) => e.id !== expenseToDelete)); } catch (err) { Alert.alert('Erro', 'Não foi possível remover o gasto.'); } finally { setExpenseToDelete(null); } } return ( {isFocused && ( Lançamentos Fixos Saldo líquido:{' '} {isTotalIncome ? '+ ' : (activeTotal > 0 ? '- ' : '')}{formattedTotal} { if (showForm) { setEditingExpenseId(null); setName(''); setAmount(''); setDueDay(''); } setShowForm((v) => !v); }} accessibilityLabel="Adicionar lançamento fixo" accessibilityRole="button" > {showForm ? '✕' : '+'} {showForm && ( setExpenseType('expense')} > Despesa setExpenseType('income')} > Receita