photum/pages/Finance.tsx
2025-11-25 13:56:11 -03:00

371 lines
17 KiB
TypeScript

import React, { useState } from 'react';
import { DollarSign, TrendingUp, TrendingDown, Calendar, Download, Filter, CreditCard, CheckCircle, Clock, XCircle } from 'lucide-react';
interface Transaction {
id: string;
type: 'income' | 'expense';
category: string;
description: string;
amount: number;
date: string;
status: 'paid' | 'pending' | 'overdue';
client?: string;
event?: string;
}
const MOCK_TRANSACTIONS: Transaction[] = [
{
id: '1',
type: 'income',
category: 'Formatura',
description: 'Pagamento Formatura Medicina UFPR',
amount: 8500.00,
date: '2025-12-01',
status: 'paid',
client: 'Ana Paula Silva',
event: 'Formatura Medicina UFPR'
},
{
id: '2',
type: 'income',
category: 'Casamento',
description: 'Sinal Casamento Maria & João',
amount: 3000.00,
date: '2025-12-05',
status: 'paid',
client: 'Maria Santos',
event: 'Casamento Maria & João'
},
{
id: '3',
type: 'expense',
category: 'Equipamento',
description: 'Manutenção Câmera Canon',
amount: 450.00,
date: '2025-12-03',
status: 'paid'
},
{
id: '4',
type: 'income',
category: 'Formatura',
description: 'Pagamento Formatura Direito PUC',
amount: 7200.00,
date: '2025-12-10',
status: 'pending',
client: 'Carlos Eduardo',
event: 'Formatura Direito PUC'
},
{
id: '5',
type: 'expense',
category: 'Transporte',
description: 'Combustível - Eventos Dezembro',
amount: 320.00,
date: '2025-12-08',
status: 'paid'
},
{
id: '6',
type: 'income',
category: 'Evento Corporativo',
description: 'Tech Summit 2026',
amount: 5500.00,
date: '2025-12-15',
status: 'pending',
client: 'TechCorp Ltda',
event: 'Tech Summit'
},
{
id: '7',
type: 'expense',
category: 'Software',
description: 'Assinatura Adobe Creative Cloud',
amount: 180.00,
date: '2025-12-01',
status: 'paid'
},
{
id: '8',
type: 'income',
category: 'Formatura',
description: 'Saldo Final Formatura Engenharia',
amount: 4500.00,
date: '2025-11-20',
status: 'overdue',
client: 'Roberto Mendes',
event: 'Formatura Engenharia UTFPR'
}
];
export const FinancePage: React.FC = () => {
const [filterType, setFilterType] = useState<'all' | 'income' | 'expense'>('all');
const [filterStatus, setFilterStatus] = useState<'all' | 'paid' | 'pending' | 'overdue'>('all');
const filteredTransactions = MOCK_TRANSACTIONS.filter(transaction => {
const matchesType = filterType === 'all' || transaction.type === filterType;
const matchesStatus = filterStatus === 'all' || transaction.status === filterStatus;
return matchesType && matchesStatus;
});
const totalIncome = MOCK_TRANSACTIONS
.filter(t => t.type === 'income' && t.status === 'paid')
.reduce((sum, t) => sum + t.amount, 0);
const totalExpense = MOCK_TRANSACTIONS
.filter(t => t.type === 'expense' && t.status === 'paid')
.reduce((sum, t) => sum + t.amount, 0);
const pendingIncome = MOCK_TRANSACTIONS
.filter(t => t.type === 'income' && (t.status === 'pending' || t.status === 'overdue'))
.reduce((sum, t) => sum + t.amount, 0);
const balance = totalIncome - totalExpense;
const getStatusIcon = (status: Transaction['status']) => {
switch (status) {
case 'paid':
return <CheckCircle size={16} className="text-green-600" />;
case 'pending':
return <Clock size={16} className="text-yellow-600" />;
case 'overdue':
return <XCircle size={16} className="text-red-600" />;
}
};
const getStatusColor = (status: Transaction['status']) => {
switch (status) {
case 'paid':
return 'bg-green-100 text-green-800';
case 'pending':
return 'bg-yellow-100 text-yellow-800';
case 'overdue':
return 'bg-red-100 text-red-800';
}
};
const getStatusLabel = (status: Transaction['status']) => {
switch (status) {
case 'paid':
return 'Pago';
case 'pending':
return 'Pendente';
case 'overdue':
return 'Atrasado';
}
};
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(value);
};
const formatDate = (dateString: string) => {
const date = new Date(dateString + 'T00:00:00');
return date.toLocaleDateString('pt-BR', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
};
return (
<div className="min-h-screen bg-gray-50 pt-32 pb-12">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div className="mb-8 flex items-center justify-between">
<div>
<h1 className="text-3xl font-serif font-bold text-brand-black mb-2">
Financeiro
</h1>
<p className="text-gray-600">
Acompanhe receitas, despesas e fluxo de caixa
</p>
</div>
<button className="flex items-center gap-2 px-4 py-2 bg-brand-gold text-white rounded-md hover:bg-[#a5bd2e] transition-colors font-medium">
<Download size={20} />
Exportar Relatório
</button>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-gray-600">Receitas</p>
<TrendingUp className="text-green-600" size={24} />
</div>
<p className="text-3xl font-bold text-green-600">{formatCurrency(totalIncome)}</p>
<p className="text-xs text-gray-500 mt-1">Pagamentos recebidos</p>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-gray-600">Despesas</p>
<TrendingDown className="text-red-600" size={24} />
</div>
<p className="text-3xl font-bold text-red-600">{formatCurrency(totalExpense)}</p>
<p className="text-xs text-gray-500 mt-1">Gastos do período</p>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-gray-600">Saldo</p>
<DollarSign className="text-brand-gold" size={24} />
</div>
<p className={`text-3xl font-bold ${balance >= 0 ? 'text-brand-gold' : 'text-red-600'}`}>
{formatCurrency(balance)}
</p>
<p className="text-xs text-gray-500 mt-1">Receitas - Despesas</p>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-gray-600">A Receber</p>
<Clock className="text-yellow-600" size={24} />
</div>
<p className="text-3xl font-bold text-yellow-600">{formatCurrency(pendingIncome)}</p>
<p className="text-xs text-gray-500 mt-1">Pagamentos pendentes</p>
</div>
</div>
{/* Filters */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex gap-2">
<button
onClick={() => setFilterType('all')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'all'
? 'bg-brand-gold text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Todas
</button>
<button
onClick={() => setFilterType('income')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'income'
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Receitas
</button>
<button
onClick={() => setFilterType('expense')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'expense'
? 'bg-red-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Despesas
</button>
</div>
<div className="flex gap-2">
<button
onClick={() => setFilterStatus('all')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'all'
? 'bg-brand-black text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Todos Status
</button>
<button
onClick={() => setFilterStatus('paid')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'paid'
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Pagos
</button>
<button
onClick={() => setFilterStatus('pending')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'pending'
? 'bg-yellow-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Pendentes
</button>
</div>
</div>
</div>
{/* Transactions List */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold">Transações</h2>
</div>
<div className="divide-y divide-gray-200">
{filteredTransactions.length === 0 ? (
<div className="p-12 text-center text-gray-500">
<CreditCard size={48} className="mx-auto mb-4 text-gray-300" />
<p>Nenhuma transação encontrada</p>
</div>
) : (
filteredTransactions.map((transaction) => (
<div
key={transaction.id}
className="p-6 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${transaction.type === 'income' ? 'bg-green-100' : 'bg-red-100'
}`}>
{transaction.type === 'income' ? (
<TrendingUp size={20} className="text-green-600" />
) : (
<TrendingDown size={20} className="text-red-600" />
)}
</div>
<div>
<h3 className="font-semibold text-brand-black">
{transaction.description}
</h3>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-gray-500">{transaction.category}</span>
{transaction.client && (
<>
<span className="text-xs text-gray-300"></span>
<span className="text-xs text-gray-500">{transaction.client}</span>
</>
)}
</div>
</div>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className={`text-lg font-bold ${transaction.type === 'income' ? 'text-green-600' : 'text-red-600'
}`}>
{transaction.type === 'income' ? '+' : '-'} {formatCurrency(transaction.amount)}
</p>
<div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
<Calendar size={12} />
{formatDate(transaction.date)}
</div>
</div>
<div className="flex items-center gap-2">
{getStatusIcon(transaction.status)}
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(transaction.status)}`}>
{getStatusLabel(transaction.status)}
</span>
</div>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
</div>
);
};