371 lines
17 KiB
TypeScript
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>
|
|
);
|
|
};
|