import React,{useState,useEffect}from "react"; import{PieChart,Pie,Cell,ResponsiveContainer,Tooltip as RechartsTooltip,BarChart,Bar,XAxis,YAxis,CartesianGrid,Legend}from "recharts"; import{Heart,TrendingUp,TrendingDown,Calendar,DollarSign,Target,Plus,Trash2,Edit2,Save,PartyPopper,Tag,X,AlertTriangle,Zap}from "lucide-react"; // --- INTERFACES --- interface Transaction{id:string;date:string;description:string;category:string;amount:number;type:"despesa-fixa" | "despesa-variável" | "receita-fixa" | "receita-variável"}interface BudgetItem{id:string;description:string;category:string;estimatedAmount:number;type:"despesa-fixa" | "despesa-variável" | "receita-fixa" | "receita-variável"}interface Goal{id:string;name:string;target:number;current:number;deadline:string;priority:"baixa" | "média" | "alta"}interface CustomCategory{id:string;name:string;type:"despesa" | "receita"}// --- COMPONENTE PRINCIPAL --- const CoupleFinancialPlanner: React.FC = () =>{// --- ESTADOS GERAIS --- const [plannerName,setPlannerName] = useState<string>("Nosso Planner Financeiro");const [isEditingName,setIsEditingName] = useState<boolean>(false);const [activeTab,setActiveTab] = useState<"dashboard" | "transactions" | "budget" | "goals">("dashboard");// --- ESTADOS DE DADOS (COM LOCALSTORAGE) --- const [customCategories,setCustomCategories] = useState<CustomCategory[]>(() => {const saved = localStorage.getItem("customCategories"); return saved ? JSON.parse(saved) : [];});const [transactions,setTransactions] = useState<Transaction[]>(() => {const saved = localStorage.getItem("transactions"); return saved ? JSON.parse(saved) : [];});const [budgetItems,setBudgetItems] = useState<BudgetItem[]>(() => {const saved = localStorage.getItem("budgetItems"); return saved ? JSON.parse(saved) : [];});const [goals,setGoals] = useState<Goal[]>(() => {const saved = localStorage.getItem("goals"); return saved ? JSON.parse(saved) : [];});// --- ESTADOS DE FORMULÁRIOS --- // Transações const [newTransaction,setNewTransaction] = useState({date: "",description: "",category: "",amount: "",type: "despesa-fixa" as const});// Orçamento const [editingBudgetId,setEditingBudgetId] = useState<string | null>(null);const [newBudgetItem,setNewBudgetItem] = useState({description: "",category: "",estimatedAmount: "",type: "despesa-fixa" as const});// Nova Categoria const [showCategoryForm,setShowCategoryForm] = useState(false);const [newCategoryName,setNewCategoryName] = useState("");const [newCategoryType,setNewCategoryType] = useState<"despesa" | "receita">("despesa");// Metas const [newGoal,setNewGoal] = useState({name: "",target: "",current: "",deadline: "",priority: "média" as const});// Categorias Padrão const predefinedCategories: Record<string,string[]> ={despesa:["Moradia","Alimentação","Transporte","Saúde","Educação","Lazer","Vestuário","Contas Fixas","Outros"],receita: ["Salário","Freelance","Investimentos","Presentes","Outros"]}// --- EFEITOS (SALVAR) --- useEffect(() => {localStorage.setItem("transactions",JSON.stringify(transactions)); localStorage.setItem("budgetItems",JSON.stringify(budgetItems)); localStorage.setItem("goals",JSON.stringify(goals)); localStorage.setItem("customCategories",JSON.stringify(customCategories));},[transactions,budgetItems,goals,customCategories]);// --- FUNÇÕES AUXILIARES --- const formatCurrency = (value: number | string): string =>{return parseFloat(String(value) || "0").toLocaleString("pt-BR",{minimumFractionDigits: 2,maximumFractionDigits: 2})}const parseCurrency = (value: string): number =>{if (!value) return 0;return parseFloat(value.replace(/\./g,"").replace(",","."))}const getAllCategories = (fullType: string): string[] =>{const baseType = fullType.split("-")[0];const predefined = predefinedCategories[baseType] || [];const custom = customCategories.filter(c => c.type === baseType).map(c => c.name);return [...predefined,...custom]}// --- GAMIFICAÇÃO --- const getGamificationMessage = () =>{const budgetExpense = budgetItems.filter(i => i.type.startsWith("despesa")).reduce((acc,i) => acc + i.estimatedAmount,0);const totalDespesas = transactions.filter(t => t.type.startsWith("despesa")).reduce((sum,t) => sum + t.amount,0);if (budgetExpense === 0){return{message:"Configure seu orçamento para acompanhar seus gastos! 📊",color: "text-blue-600",bgColor: "bg-blue-50",icon: Zap}}const percentualGasto = (totalDespesas / budgetExpense) * 100;if (percentualGasto <= 80){return{message:"Você está indo muito bem! 🎉",color: "text-green-600",bgColor: "bg-green-50",icon: PartyPopper}}else if (percentualGasto <= 90){return{message:"Atenção! ⚠️",color: "text-yellow-600",bgColor: "bg-yellow-50",icon: AlertTriangle}}else if (percentualGasto <= 99){return{message:"Tome Cuidado! 😰",color: "text-orange-600",bgColor: "bg-orange-50",icon: AlertTriangle}}else{return{message:"Pare de gastar agora! 🚨",color: "text-red-600",bgColor: "bg-red-50",icon: AlertTriangle}}}// --- CÁLCULOS GERAIS --- const totalReceitas = transactions.filter(t => t.type.startsWith("receita")).reduce((sum,t) => sum + t.amount,0);const totalDespesas = transactions.filter(t => t.type.startsWith("despesa")).reduce((sum,t) => sum + t.amount,0);const saldo = totalReceitas - totalDespesas;// Dados Pizza (Despesas) const expensesByCategory = transactions.filter(t => t.type.startsWith("despesa")).reduce((acc: Record<string,number>,t) => {acc[t.category] = (acc[t.category] || 0) + t.amount; return acc;},{});const pieData = Object.entries(expensesByCategory).map(([name,value]) => ({name,value}));const PIE_COLORS = ["#FF6B9D","#C44569","#F97F51","#FEA47F","#786FA6","#778BEB","#63CDDA"];// Dados Barras (Metas) const goalsChartData = goals.map(g => ({name: g.name,Guardado: g.current,Falta: g.target - g.current > 0 ? g.target - g.current : 0,Alvo: g.target}));// Cálculos Orçamento const budgetRevenue = budgetItems.filter(i => i.type.startsWith("receita")).reduce((acc,i) => acc + i.estimatedAmount,0);const budgetExpense = budgetItems.filter(i => i.type.startsWith("despesa")).reduce((acc,i) => acc + i.estimatedAmount,0);const budgetBalance = budgetRevenue - budgetExpense;// Gamificação const gamification = getGamificationMessage();const GamificationIcon = gamification.icon;// --- ACTIONS --- const addCustomCategory = () =>{if (newCategoryName.trim()){setCustomCategories([...customCategories,{id: Date.now().toString(),name: newCategoryName,type: newCategoryType}]);setNewCategoryName("");setShowCategoryForm(false)}}const addTransaction = () =>{if (newTransaction.description && newTransaction.amount){setTransactions([...transactions,{id: Date.now().toString(),...newTransaction,amount: parseCurrency(newTransaction.amount)}]);setNewTransaction({...newTransaction,description: "",amount: ""})}}const deleteTransaction = (id: string) => setTransactions(transactions.filter(t => t.id !== id));const saveBudgetItem = () =>{if (newBudgetItem.description && newBudgetItem.estimatedAmount){const amount = typeof newBudgetItem.estimatedAmount === "string" ? parseCurrency(newBudgetItem.estimatedAmount) : newBudgetItem.estimatedAmount;if (editingBudgetId){setBudgetItems(budgetItems.map(item => item.id === editingBudgetId ? {...item,...newBudgetItem,estimatedAmount: amount} : item));setEditingBudgetId(null)}else{setBudgetItems([...budgetItems,{id: Date.now().toString(),...newBudgetItem,estimatedAmount: amount}])}setNewBudgetItem({description: "",category: "",estimatedAmount: "",type: "despesa-fixa"})}}const startEditingBudget = (item: BudgetItem) =>{setNewBudgetItem({description: item.description,category: item.category,estimatedAmount: item.estimatedAmount.toString(),type: item.type});setEditingBudgetId(item.id);window.scrollTo({top: 0,behavior: "smooth"})}const deleteBudgetItem = (id: string) => setBudgetItems(budgetItems.filter(i => i.id !== id));const addGoal = () =>{if (newGoal.name && newGoal.target){setGoals([...goals,{id: Date.now().toString(),...newGoal,target: parseCurrency(newGoal.target),current: parseCurrency(newGoal.current || "0")}]);setNewGoal({name: "",target: "",current: "",deadline: "",priority: "média"})}}return (<div className="min-h-screen bg-gradient-to-br from-pink-50 via-purple-50 to-blue-50 p-4 font-sans text-gray-800"> <div className="max-w-7xl mx-auto"> {} <header className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-6 flex flex-col md:flex-row items-center justify-between gap-4"> <div className="flex items-center gap-3"> <div className="p-3 bg-rose-100 rounded-xl"> <Heart className="text-rose-500" size={32} fill="currentColor" /> </div> <div> {isEditingName ? (<input type="text" value={plannerName} onChange={(e) => setPlannerName(e.target.value)} onBlur={() => setIsEditingName(false)} className="text-2xl font-bold border-b-2 border-rose-500 focus:outline-none" autoFocus />) : (<h1 onClick={() => setIsEditingName(true)} className="text-2xl font-bold cursor-pointer hover:text-rose-500">{plannerName}</h1>)} <p className="text-gray-500">Planejamento Financeiro</p> </div> </div> <div className="text-right bg-gray-50 px-6 py-2 rounded-xl border border-gray-100"> <p className="text-sm text-gray-500">Saldo Realizado</p> <p className={`text-2xl font-bold ${saldo >= 0 ? "text-green-600" : "text-red-600"}`}>R$ {formatCurrency(saldo)}</p> </div> </header> {} <div className="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6 p-2 overflow-x-auto"> <div className="flex gap-2 min-w-max"> {[{id: "dashboard",label: "Visão Geral",icon: TrendingUp},{id: "transactions",label: "Lançamentos",icon: DollarSign},{id: "budget",label: "Orçamento",icon: Calendar},{id: "goals",label: "Objetivos",icon: Target},].map((tab) => (<button key={tab.id} onClick={() => setActiveTab(tab.id as any)} className={`flex items-center gap-2 py-3 px-6 rounded-xl transition-all font-semibold ${activeTab === tab.id ? "bg-gradient-to-r from-pink-500 to-purple-500 text-white shadow-md" : "text-gray-600 hover:bg-gray-100"}`} > <tab.icon size={18} /> {tab.label} </button>))} </div> </div> {} {} {} {activeTab === "dashboard" && (<div className="space-y-6"> {} <div className={`${gamification.bgColor} p-5 rounded-2xl border-2 ${gamification.bgColor.replace("bg-","border-").replace("-50","-200")} flex items-center justify-center gap-3`}> <GamificationIcon className={gamification.color} size={32} /> <p className={`text-xl font-bold ${gamification.color}`}>{gamification.message}</p> </div> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> {} <div className="lg:col-span-1 bg-white p-6 rounded-2xl shadow-sm border border-gray-100 h-fit"> <h2 className="text-xl font-bold mb-6">Resumo do Mês</h2> <div className="space-y-4"> <div className="flex justify-between items-center p-4 bg-green-50 rounded-xl border border-green-100"> <div className="flex items-center gap-3"> <div className="p-2 bg-green-100 rounded-lg text-green-600"><TrendingUp size={20} /></div> <span className="text-gray-600 font-medium">Receitas</span> </div> <span className="text-green-600 font-bold text-lg">R$ {formatCurrency(totalReceitas)}</span> </div> <div className="flex justify-between items-center p-4 bg-red-50 rounded-xl border border-red-100"> <div className="flex items-center gap-3"> <div className="p-2 bg-red-100 rounded-lg text-red-600"><TrendingDown size={20} /></div> <span className="text-gray-600 font-medium">Despesas</span> </div> <span className="text-red-600 font-bold text-lg">R$ {formatCurrency(totalDespesas)}</span> </div> <div className="flex justify-between items-center p-4 bg-blue-50 rounded-xl border border-blue-100"> <div className="flex items-center gap-3"> <div className="p-2 bg-blue-100 rounded-lg text-blue-600"><DollarSign size={20} /></div> <span className="text-gray-600 font-medium">Resultado</span> </div> <span className={`font-bold text-lg ${saldo >= 0 ? "text-blue-600" : "text-red-600"}`}>R$ {formatCurrency(saldo)}</span> </div> </div> </div> {} <div className="lg:col-span-2 bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <h2 className="text-lg font-bold mb-4">Gastos por Categoria</h2> {pieData.length > 0 ? (<ResponsiveContainer width="100%" height={300}> <PieChart> <Pie data={pieData} cx="50%" cy="50%" labelLine={false} label={({name,percent}) => `${name} (${(percent * 100).toFixed(0)}%)`} outerRadius={100} fill="#8884d8" dataKey="value" > {pieData.map((entry,index) => (<Cell key={`cell-${index}`} fill={PIE_COLORS[index % PIE_COLORS.length]} />))} </Pie> <RechartsTooltip formatter={(value) => `R$ ${formatCurrency(Number(value))}`} /> </PieChart> </ResponsiveContainer>) : (<div className="flex flex-col items-center justify-center h-[300px] text-gray-400 bg-gray-50 rounded-xl border border-dashed border-gray-200"> <p>Sem dados suficientes para o gráfico</p> </div>)} </div> </div> {} <div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <div className="flex items-center gap-3 mb-4"> <div className="p-2 bg-purple-100 rounded-lg text-purple-600"> <Target size={20} /> </div> <h2 className="text-lg font-bold">Progresso das Minhas Metas</h2> </div> {goalsChartData.length > 0 ? (<ResponsiveContainer width="100%" height={300}> <BarChart data={goalsChartData} layout="vertical" margin={{top: 5,right: 30,left: 20,bottom: 5}} > <CartesianGrid strokeDasharray="3 3" horizontal={false} /> <XAxis type="number" tickFormatter={(val) => `R$${(val/1000).toFixed(0)}k`} /> <YAxis dataKey="name" type="category" width={120} /> <RechartsTooltip formatter={(value) => `R$ ${formatCurrency(Number(value))}`} /> <Legend /> <Bar dataKey="Guardado" stackId="a" fill="#10b981" radius={[0,4,4,0]} /> <Bar dataKey="Falta" stackId="a" fill="#e5e7eb" radius={[0,4,4,0]} /> </BarChart> </ResponsiveContainer>) : (<div className="flex flex-col items-center justify-center h-[200px] text-gray-400 bg-gray-50 rounded-xl border border-dashed border-gray-200"> <Target size={48} className="mb-3 opacity-30" /> <p className="font-medium">Cadastre suas metas para ver o progresso aqui</p> <p className="text-sm mt-1">Vá até a aba "Objetivos" para começar</p> </div>)} </div> </div>)} {} {} {} {activeTab === "budget" && (<div className="space-y-6"> {} <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="bg-white p-5 rounded-2xl shadow-sm border border-green-100 relative overflow-hidden"> <div className="absolute right-0 top-0 p-4 opacity-10"><TrendingUp size={60} /></div> <p className="text-gray-500 font-medium text-sm">Receita Prevista</p> <p className="text-2xl font-bold text-green-600 mt-1">R$ {formatCurrency(budgetRevenue)}</p> </div> <div className="bg-white p-5 rounded-2xl shadow-sm border border-red-100 relative overflow-hidden"> <div className="absolute right-0 top-0 p-4 opacity-10"><TrendingDown size={60} /></div> <p className="text-gray-500 font-medium text-sm">Despesa Prevista</p> <p className="text-2xl font-bold text-red-600 mt-1">R$ {formatCurrency(budgetExpense)}</p> </div> <div className="bg-white p-5 rounded-2xl shadow-sm border border-blue-100 relative overflow-hidden"> <div className="absolute right-0 top-0 p-4 opacity-10"><DollarSign size={60} /></div> <p className="text-gray-500 font-medium text-sm">Saldo Previsto</p> <p className={`text-2xl font-bold mt-1 ${budgetBalance >= 0 ? "text-blue-600" : "text-red-500"}`}>R$ {formatCurrency(budgetBalance)}</p> </div> </div> {} <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <div className="flex justify-between items-center mb-6"> <h2 className="text-xl font-bold text-gray-800"> {editingBudgetId ? "Editar Item" : "Adicionar ao Orçamento"} </h2> <button onClick={() => setShowCategoryForm(!showCategoryForm)} className="text-sm bg-indigo-50 text-indigo-600 px-3 py-1.5 rounded-lg font-medium hover:bg-indigo-100 flex items-center gap-1"> <Tag size={16} /> Nova Categoria </button> </div> {showCategoryForm && (<div className="mb-6 bg-indigo-50 p-4 rounded-xl border border-indigo-100 flex flex-col md:flex-row gap-3 items-end"> <div className="w-full"> <label className="text-xs text-indigo-800 font-bold ml-1">Nome</label> <input type="text" value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)} className="w-full p-2 rounded-lg border border-indigo-200 text-sm" /> </div> <div className="w-full md:w-48"> <label className="text-xs text-indigo-800 font-bold ml-1">Tipo</label> <select value={newCategoryType} onChange={e => setNewCategoryType(e.target.value as any)} className="w-full p-2 rounded-lg border border-indigo-200 text-sm"> <option value="despesa">Despesa</option> <option value="receita">Receita</option> </select> </div> <div className="flex gap-2"> <button onClick={addCustomCategory} className="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-bold">Salvar</button> <button onClick={() => setShowCategoryForm(false)} className="bg-white text-gray-500 px-3 py-2 rounded-lg text-sm font-bold border border-gray-200"><X size={16} /></button> </div> </div>)} <div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-end"> <div className="md:col-span-4"> <input type="text" placeholder="Descrição" className="w-full p-3 border border-gray-200 rounded-xl bg-gray-50" value={newBudgetItem.description} onChange={e => setNewBudgetItem({...newBudgetItem,description: e.target.value})} /> </div> <div className="md:col-span-2"> <select className="w-full p-3 border border-gray-200 rounded-xl bg-gray-50" value={newBudgetItem.type} onChange={e => setNewBudgetItem({...newBudgetItem,type: e.target.value as any})}> <option value="receita-fixa">Receita Fixa</option> <option value="receita-variável">Receita Variável</option> <option value="despesa-fixa">Despesa Fixa</option> <option value="despesa-variável">Despesa Variável</option> </select> </div> <div className="md:col-span-3"> <select className="w-full p-3 border border-gray-200 rounded-xl bg-gray-50" value={newBudgetItem.category} onChange={e => setNewBudgetItem({...newBudgetItem,category: e.target.value})}> <option value="">Categoria...</option> {getAllCategories(newBudgetItem.type).map(c => <option key={c} value={c}>{c}</option>)} </select> </div> <div className="md:col-span-2"> <input type="number" placeholder="Valor" className="w-full p-3 border border-gray-200 rounded-xl bg-gray-50" value={newBudgetItem.estimatedAmount} onChange={e => setNewBudgetItem({...newBudgetItem,estimatedAmount: e.target.value})} /> </div> <div className="md:col-span-1"> <button onClick={saveBudgetItem} className={`w-full p-3 rounded-xl font-bold text-white flex justify-center ${editingBudgetId ? "bg-blue-600" : "bg-rose-500"}`}> {editingBudgetId ? <Save size={20} /> : <Plus size={20} />} </button> </div> </div> </div> {} <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <div className="flex items-center gap-2 mb-4"> <div className="p-2 bg-green-100 rounded-lg text-green-600"><TrendingUp size={18} /></div> <h3 className="text-lg font-bold text-gray-700">Receitas Planejadas</h3> </div> <div className="space-y-3"> {budgetItems.filter(i => i.type.startsWith("receita")).map(item => (<div key={item.id} className="flex justify-between items-center p-3 border border-gray-100 rounded-xl hover:bg-green-50 group"> <div> <p className="font-bold text-gray-800">{item.description}</p> <p className="text-xs text-gray-500 bg-gray-100 px-2 rounded inline-block">{item.category}</p> </div> <div className="flex items-center gap-3"> <span className="font-bold text-green-600">R$ {formatCurrency(item.estimatedAmount)}</span> <div className="flex gap-1 opacity-0 group-hover:opacity-100"> <button onClick={() => startEditingBudget(item)} className="text-blue-500"><Edit2 size={16} /></button> <button onClick={() => deleteBudgetItem(item.id)} className="text-red-500"><Trash2 size={16} /></button> </div> </div> </div>))} </div> </div> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <div className="flex items-center gap-2 mb-4"> <div className="p-2 bg-red-100 rounded-lg text-red-600"><TrendingDown size={18} /></div> <h3 className="text-lg font-bold text-gray-700">Despesas Planejadas</h3> </div> <div className="space-y-3"> {budgetItems.filter(i => i.type.startsWith("despesa")).map(item => (<div key={item.id} className="flex justify-between items-center p-3 border border-gray-100 rounded-xl hover:bg-red-50 group"> <div> <p className="font-bold text-gray-800">{item.description}</p> <p className="text-xs text-gray-500 bg-gray-100 px-2 rounded inline-block">{item.category}</p> </div> <div className="flex items-center gap-3"> <span className="font-bold text-red-600">R$ {formatCurrency(item.estimatedAmount)}</span> <div className="flex gap-1 opacity-0 group-hover:opacity-100"> <button onClick={() => startEditingBudget(item)} className="text-blue-500"><Edit2 size={16} /></button> <button onClick={() => deleteBudgetItem(item.id)} className="text-red-500"><Trash2 size={16} /></button> </div> </div> </div>))} </div> </div> </div> </div>)} {} {} {} {activeTab === "transactions" && (<div className="space-y-6"> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <h2 className="text-xl font-bold text-gray-800 mb-6">Novo Lançamento</h2> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <input type="date" className="p-3 border rounded-xl bg-gray-50" value={newTransaction.date} onChange={e => setNewTransaction({...newTransaction,date: e.target.value})} /> <input type="text" placeholder="Descrição" className="p-3 border rounded-xl bg-gray-50" value={newTransaction.description} onChange={e => setNewTransaction({...newTransaction,description: e.target.value})} /> <input type="number" placeholder="Valor" className="p-3 border rounded-xl bg-gray-50" value={newTransaction.amount} onChange={e => setNewTransaction({...newTransaction,amount: e.target.value})} /> <select className="p-3 border rounded-xl bg-gray-50" value={newTransaction.type} onChange={e => setNewTransaction({...newTransaction,type: e.target.value as any})}> <option value="despesa-fixa">Despesa Fixa</option> <option value="despesa-variável">Despesa Variável</option> <option value="receita-fixa">Receita Fixa</option> <option value="receita-variável">Receita Variável</option> </select> <select className="p-3 border rounded-xl bg-gray-50 lg:col-span-3" value={newTransaction.category} onChange={e => setNewTransaction({...newTransaction,category: e.target.value})}> <option value="">Selecione Categoria</option> {getAllCategories(newTransaction.type).map(c => <option key={c} value={c}>{c}</option>)} </select> <button onClick={addTransaction} className="bg-rose-500 text-white p-3 rounded-xl font-bold hover:bg-rose-600 transition-colors flex items-center justify-center gap-2"> <Plus size={20} /> Adicionar </button> </div> </div> {} <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> {} <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <div className="flex items-center gap-2 mb-4 pb-3 border-b-2 border-green-100"> <div className="p-2 bg-green-100 rounded-lg text-green-600"><TrendingUp size={20} /></div> <h3 className="text-lg font-bold text-gray-800">Histórico de Receitas</h3> </div> <div className="space-y-3 max-h-[600px] overflow-y-auto"> {transactions.filter(t => t.type.includes("receita")).slice().reverse().map(t => (<div key={t.id} className="flex justify-between items-center p-4 border border-gray-100 rounded-xl hover:bg-green-50 transition-colors group"> <div className="flex-1"> <p className="font-bold text-gray-800">{t.description}</p> <p className="text-xs text-gray-500 mt-1"> <span className="bg-green-100 text-green-700 px-2 py-0.5 rounded mr-2">{t.category}</span> {new Date(t.date).toLocaleDateString("pt-BR")} </p> </div> <div className="flex items-center gap-3"> <span className="font-bold text-green-600 text-lg">+ R$ {formatCurrency(t.amount)}</span> <button onClick={() => deleteTransaction(t.id)} className="text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"> <Trash2 size={18}/> </button> </div> </div>))} {transactions.filter(t => t.type.includes("receita")).length === 0 && (<div className="flex flex-col items-center justify-center py-12 text-gray-400"> <TrendingUp size={48} className="mb-3 opacity-20" /> <p className="font-medium">Nenhuma receita lançada</p> <p className="text-sm mt-1">Adicione suas receitas acima</p> </div>)} </div> </div> {} <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <div className="flex items-center gap-2 mb-4 pb-3 border-b-2 border-red-100"> <div className="p-2 bg-red-100 rounded-lg text-red-600"><TrendingDown size={20} /></div> <h3 className="text-lg font-bold text-gray-800">Histórico de Despesas</h3> </div> <div className="space-y-3 max-h-[600px] overflow-y-auto"> {transactions.filter(t => t.type.includes("despesa")).slice().reverse().map(t => (<div key={t.id} className="flex justify-between items-center p-4 border border-gray-100 rounded-xl hover:bg-red-50 transition-colors group"> <div className="flex-1"> <p className="font-bold text-gray-800">{t.description}</p> <p className="text-xs text-gray-500 mt-1"> <span className="bg-red-100 text-red-700 px-2 py-0.5 rounded mr-2">{t.category}</span> {new Date(t.date).toLocaleDateString("pt-BR")} </p> </div> <div className="flex items-center gap-3"> <span className="font-bold text-red-600 text-lg">- R$ {formatCurrency(t.amount)}</span> <button onClick={() => deleteTransaction(t.id)} className="text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"> <Trash2 size={18}/> </button> </div> </div>))} {transactions.filter(t => t.type.includes("despesa")).length === 0 && (<div className="flex flex-col items-center justify-center py-12 text-gray-400"> <TrendingDown size={48} className="mb-3 opacity-20" /> <p className="font-medium">Nenhuma despesa lançada</p> <p className="text-sm mt-1">Adicione suas despesas acima</p> </div>)} </div> </div> </div> </div>)} {} {} {} {activeTab === "goals" && (<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <h2 className="text-xl font-bold text-gray-800 mb-6">Nova Meta</h2> <div className="space-y-4"> <input type="text" placeholder="Nome da Meta (Ex: Viagem)" className="w-full p-3 border rounded-xl bg-gray-50" value={newGoal.name} onChange={e => setNewGoal({...newGoal,name: e.target.value})} /> <input type="number" placeholder="Valor Alvo" className="w-full p-3 border rounded-xl bg-gray-50" value={newGoal.target} onChange={e => setNewGoal({...newGoal,target: e.target.value})} /> <input type="number" placeholder="Já guardado (Opcional)" className="w-full p-3 border rounded-xl bg-gray-50" value={newGoal.current} onChange={e => setNewGoal({...newGoal,current: e.target.value})} /> <button onClick={addGoal} className="w-full bg-rose-500 text-white p-3 rounded-xl font-bold">Criar Meta</button> </div> </div> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <h2 className="text-xl font-bold text-gray-800 mb-6">Minhas Metas</h2> <div className="space-y-6"> {goals.map(g => (<div key={g.id}> <div className="flex justify-between mb-1"> <span className="font-bold text-gray-700">{g.name}</span> <span className="text-sm text-gray-500">R$ {formatCurrency(g.current)} / R$ {formatCurrency(g.target)}</span> </div> <div className="w-full bg-gray-100 rounded-full h-3"> <div className="bg-rose-500 h-3 rounded-full transition-all duration-500" style={{width: `${Math.min((g.current / g.target) * 100,100)}%`}}></div> </div> </div>))} {goals.length === 0 && <p className="text-gray-400 text-center">Nenhuma meta cadastrada.</p>} </div> </div> </div>)} </div> </div>)}; export default CoupleFinancialPlanner;{}
