dashboard: reimaginacao
Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
0a67f95eaf
commit
6300fb98a7
4 files changed
+151
-56
No files matched your search
@@ -1,53 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface Atendimento {
|
||||
id: string;
|
||||
hora: string;
|
||||
cliente: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const AtendimentosDia: React.FC = () => {
|
||||
const [atendimentos, setAtendimentos] = useState<Atendimento[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchAtendimentos() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const res = await fetch('/api/atendimentos-dia', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error('Erro ao buscar atendimentos do dia');
|
||||
const data = await res.json();
|
||||
setAtendimentos(data);
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
fetchAtendimentos();
|
||||
}, []);
|
||||
|
||||
if (loading) return <div>Carregando atendimentos...</div>;
|
||||
if (error) return <div className="text-red-600">{error}</div>;
|
||||
|
||||
return (
|
||||
<div className="border rounded p-4 shadow">
|
||||
<h3 className="font-semibold mb-2">Atendimentos do Dia</h3>
|
||||
<ul className="divide-y">
|
||||
{atendimentos.map((at) => (
|
||||
<li key={at.id} className="py-2 flex justify-between items-center">
|
||||
<span className="text-xs font-medium">{at.hora} - {at.cliente}</span>
|
||||
<span className={`px-2 py-1 rounded text-xs ${at.status === 'Realizado' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'}`}>{at.status}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { WorkflowTarefas } from './WorkflowTarefas';
|
||||
import { CalendarioAtendimentos } from './CalendarioAtendimentos';
|
||||
import { BenchmarkingEscritorio } from './BenchmarkingEscritorio';
|
||||
import { AtendimentosDia } from './AtendimentosDia';
|
||||
import { PainelFinanceiro } from './PainelFinanceiro';
|
||||
|
||||
export const DashboardLayout: React.FC = () => {
|
||||
|
||||
@@ -18,14 +18,13 @@ export const DashboardLayout: React.FC = () => {
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-bold">Calendário</h2>
|
||||
<CalendarioAtendimentos />
|
||||
<h2 className="text-xl font-bold">Atendimentos do Dia</h2>
|
||||
<AtendimentosDia />
|
||||
</div>
|
||||
|
||||
{/* Coluna 3: Benchmarking do Escritório */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-bold">Benchmarking do Escritório</h2>
|
||||
<BenchmarkingEscritorio />
|
||||
<PainelFinanceiro />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { useFinanceiroPainel } from '../../hooks/useFinanceiroPainel';
|
||||
import { CurrencyDollarIcon, ArrowDownCircleIcon, ArrowUpCircleIcon } from '@heroicons/react/24/solid';
|
||||
|
||||
export const PainelFinanceiro: React.FC = () => {
|
||||
const hoje = new Date();
|
||||
const [mes, setMes] = React.useState(hoje.getMonth() + 1);
|
||||
const [ano, setAno] = React.useState(hoje.getFullYear());
|
||||
const { data, isLoading, error } = useFinanceiroPainel(mes, ano);
|
||||
|
||||
const mesesNomes = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
|
||||
|
||||
return (
|
||||
<div className="border rounded-xl p-6 shadow-lg bg-white dark:bg-gray-900 flex flex-col gap-6 max-w-4xl mx-auto mt-8">
|
||||
<h2 className="text-lg font-bold text-gray-800 dark:text-gray-100 mb-2 flex items-center gap-2">
|
||||
<CurrencyDollarIcon className="h-6 w-6 text-green-600" /> Painel Financeiro
|
||||
</h2>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<button type="button" className="px-2 py-1 rounded bg-gray-100 dark:bg-gray-800" onClick={() => setMes(m => m === 1 ? 12 : m - 1)}><</button>
|
||||
<span className="font-semibold text-lg">{mesesNomes[mes - 1]}/{ano}</span>
|
||||
<button type="button" className="px-2 py-1 rounded bg-gray-100 dark:bg-gray-800" onClick={() => setMes(m => m === 12 ? 1 : m + 1)}>></button>
|
||||
<button type="button" className="px-2 py-1 rounded bg-gray-100 dark:bg-gray-800" onClick={() => setAno(a => a - 1)}>< Ano</button>
|
||||
<button type="button" className="px-2 py-1 rounded bg-gray-100 dark:bg-gray-800" onClick={() => setAno(a => a + 1)}>Ano ></button>
|
||||
</div>
|
||||
{isLoading && <div>Carregando dados financeiros...</div>}
|
||||
{error && <div className="text-red-600">Erro ao buscar dados financeiros</div>}
|
||||
{data && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div className="flex flex-col items-center bg-green-50 dark:bg-green-900 rounded-lg p-4">
|
||||
<CurrencyDollarIcon className="h-7 w-7 text-green-600 mb-1" />
|
||||
<span className="text-xs text-gray-500">Honorários a receber</span>
|
||||
<span className="text-lg font-bold text-green-700 dark:text-green-300">R$ {data.honorariosAReceber.toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center bg-blue-50 dark:bg-blue-900 rounded-lg p-4">
|
||||
<ArrowUpCircleIcon className="h-7 w-7 text-blue-600 mb-1" />
|
||||
<span className="text-xs text-gray-500">Honorários recebidos no mês</span>
|
||||
<span className="text-lg font-bold text-blue-700 dark:text-blue-300">R$ {data.honorariosRecebidosMes.toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center bg-red-50 dark:bg-red-900 rounded-lg p-4">
|
||||
<ArrowDownCircleIcon className="h-7 w-7 text-red-600 mb-1" />
|
||||
<span className="text-xs text-gray-500">Custas a pagar</span>
|
||||
<span className="text-lg font-bold text-red-700 dark:text-red-300">R$ {data.custasAPagar.toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center bg-teal-50 dark:bg-teal-900 rounded-lg p-4">
|
||||
<ArrowUpCircleIcon className="h-7 w-7 text-teal-600 mb-1" />
|
||||
<span className="text-xs text-gray-500">Custas pagas no mês</span>
|
||||
<span className="text-lg font-bold text-teal-700 dark:text-teal-300">R$ {data.custasPagasMes.toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2 text-green-700 dark:text-green-300">Honorários recebidos no mês</h3>
|
||||
<ul className="text-xs divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{data.detalhesHonorarios.length === 0 && <li className="text-gray-400">Nenhum recebimento registrado neste mês.</li>}
|
||||
{data.detalhesHonorarios.map((item, idx) => (
|
||||
<li key={idx} className="py-2 flex flex-col">
|
||||
<span className="font-bold">Contrato: {item.contrato.processoId}</span>
|
||||
<span>Parcela: {item.parcela.numeroParcela} - Valor: R$ {Number(item.parcela.valorPago || item.parcela.valor).toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
<span className="text-gray-500">Pago em: {item.parcela.dataPagamento ? new Date(item.parcela.dataPagamento).toLocaleDateString('pt-br') : '-'}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2 text-red-700 dark:text-red-300">Custas pagas no mês</h3>
|
||||
<ul className="text-xs divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{data.detalhesCustas.length === 0 && <li className="text-gray-400">Nenhuma custa paga neste mês.</li>}
|
||||
{data.detalhesCustas.map((c, idx) => (
|
||||
<li key={idx} className="py-2 flex flex-col">
|
||||
<span className="font-bold">Processo: {c.processoId}</span>
|
||||
<span>Tipo: {c.tipo} - Valor: R$ {Number(c.valor).toLocaleString('pt-br', { minimumFractionDigits: 2 })}</span>
|
||||
<span className="text-gray-500">Pago em: {c.dataPagamento ? new Date(c.dataPagamento).toLocaleDateString('pt-br') : '-'}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import api from '../services/api';
|
||||
|
||||
export interface FinanceiroResumo {
|
||||
honorariosAReceber: number;
|
||||
honorariosRecebidosMes: number;
|
||||
custasAPagar: number;
|
||||
custasPagasMes: number;
|
||||
detalhesHonorarios: any[];
|
||||
detalhesCustas: any[];
|
||||
}
|
||||
|
||||
// Hook para buscar dados financeiros do painel
|
||||
export function useFinanceiroPainel(mes: number, ano: number) {
|
||||
return useQuery<FinanceiroResumo>(['financeiroPainel', mes, ano], async () => {
|
||||
// Buscar honorários e parcelas
|
||||
const honorariosRes = await api.get('/honorarios');
|
||||
const honorarios = honorariosRes.data;
|
||||
let honorariosAReceber = 0;
|
||||
let honorariosRecebidosMes = 0;
|
||||
const detalhesHonorarios: any[] = [];
|
||||
honorarios.forEach((h: any) => {
|
||||
if (h.parcelas && Array.isArray(h.parcelas)) {
|
||||
h.parcelas.forEach((p: any) => {
|
||||
const dataPagamento = p.dataPagamento ? new Date(p.dataPagamento) : null;
|
||||
const vencimento = new Date(p.vencimento);
|
||||
if (p.status === 'PENDENTE' || p.status === 'VENCIDO') {
|
||||
honorariosAReceber += Number(p.valor);
|
||||
}
|
||||
if (p.status === 'PAGO' && dataPagamento && dataPagamento.getMonth() + 1 === mes && dataPagamento.getFullYear() === ano) {
|
||||
honorariosRecebidosMes += Number(p.valorPago || p.valor);
|
||||
detalhesHonorarios.push({ contrato: h, parcela: p });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Buscar custas
|
||||
const custasRes = await api.get('/custas-processuais');
|
||||
const custas = custasRes.data;
|
||||
let custasAPagar = 0;
|
||||
let custasPagasMes = 0;
|
||||
const detalhesCustas: any[] = [];
|
||||
custas.forEach((c: any) => {
|
||||
if (c.status === 'PENDENTE' || c.status === 'VENCIDO') {
|
||||
custasAPagar += Number(c.valor);
|
||||
}
|
||||
if (c.status === 'PAGO' && c.dataPagamento) {
|
||||
const dataPagamento = new Date(c.dataPagamento);
|
||||
if (dataPagamento.getMonth() + 1 === mes && dataPagamento.getFullYear() === ano) {
|
||||
custasPagasMes += Number(c.valor);
|
||||
detalhesCustas.push(c);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
honorariosAReceber,
|
||||
honorariosRecebidosMes,
|
||||
custasAPagar,
|
||||
custasPagasMes,
|
||||
detalhesHonorarios,
|
||||
detalhesCustas,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user