first commit
This commit is contained in:
commit
c9fb67e751
423 files changed
+53287
No files matched your search
@@ -0,0 +1,554 @@
|
||||
import { useState, lazy, Suspense, useEffect } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { formatCurrency, formatDate } from "../../utils/format";
|
||||
import { useCustasProcessuais } from "../../hooks/useCustasProcessuais";
|
||||
import { useHonorarios } from "../../hooks/useHonorarios";
|
||||
import { useProcessoContext } from "../../context/ProcessoContext";
|
||||
import api from "../../services/api"; // Adicionar esta importação
|
||||
|
||||
// Corrigir importações lazy
|
||||
const DialogCustaProcessual = lazy(() =>
|
||||
import("./dialogs/DialogCustaProcessual").then((module) => ({
|
||||
default: module.default,
|
||||
})),
|
||||
);
|
||||
const DialogHonorario = lazy(() =>
|
||||
import("./dialogs/DialogHonorario").then((module) => ({
|
||||
default: module.default,
|
||||
})),
|
||||
);
|
||||
const DialogParcela = lazy(() =>
|
||||
import("./dialogs/DialogParcela").then((module) => ({
|
||||
default: module.DialogParcela, // Usar .DialogParcela em vez de .default
|
||||
})),
|
||||
);
|
||||
|
||||
const getParcelaStatus = (parcela: any) => {
|
||||
if (parcela.status === "PAGO") return "PAGO";
|
||||
if (parcela.valorPago && parcela.valorPago > 0) return "PARCIALMENTE_PAGO";
|
||||
return parcela.status;
|
||||
};
|
||||
|
||||
export default function AbaFinanceiro() {
|
||||
const { processo, refreshProcesso } = useProcessoContext();
|
||||
|
||||
// Verificar se processo existe antes de usar
|
||||
useEffect(() => {
|
||||
if (!processo) {
|
||||
refreshProcesso();
|
||||
}
|
||||
}, [processo, refreshProcesso]);
|
||||
|
||||
// Restante do estado
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
const [custaDialogOpen, setCustaDialogOpen] = useState(false);
|
||||
const [honorarioDialogOpen, setHonorarioDialogOpen] = useState(false);
|
||||
const [editingCusta, setEditingCusta] = useState(null);
|
||||
const [editingHonorario, setEditingHonorario] = useState(null);
|
||||
const [confirmDelete, setConfirmDelete] = useState({
|
||||
show: false,
|
||||
type: null,
|
||||
id: null,
|
||||
});
|
||||
const [editingParcela, setEditingParcela] = useState<any>(null);
|
||||
const [parcelaDialogOpen, setParcelaDialogOpen] = useState(false);
|
||||
const [parcelaTipo, setParcelaTipo] = useState<"honorario" | "custa">(
|
||||
"honorario",
|
||||
);
|
||||
|
||||
// Inicializar hooks com ID do processo
|
||||
const { addCusta, updateCusta, deleteCusta } = useCustasProcessuais(
|
||||
processo?.id,
|
||||
);
|
||||
const { addHonorario, updateHonorario, deleteHonorario } = useHonorarios(
|
||||
processo?.id,
|
||||
);
|
||||
|
||||
// Se não houver processo, mostrar loading
|
||||
if (!processo) {
|
||||
return <div className="flex justify-center p-8">Carregando...</div>;
|
||||
}
|
||||
|
||||
const handleSaveCusta = async (data: any) => {
|
||||
try {
|
||||
if (editingCusta) {
|
||||
await updateCusta({ id: editingCusta.id, data });
|
||||
} else {
|
||||
await addCusta(data);
|
||||
}
|
||||
setCustaDialogOpen(false);
|
||||
setEditingCusta(null);
|
||||
await refreshProcesso(); // Use refreshProcesso instead of window.location.reload()
|
||||
} catch (error) {
|
||||
console.error("Erro ao salvar custa:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveHonorario = async (data: any) => {
|
||||
try {
|
||||
if (editingHonorario) {
|
||||
await updateHonorario({ id: editingHonorario.id, data });
|
||||
} else {
|
||||
await addHonorario(data);
|
||||
}
|
||||
setHonorarioDialogOpen(false);
|
||||
setEditingHonorario(null);
|
||||
await refreshProcesso(); // Use refreshProcesso instead of window.location.reload()
|
||||
} catch (error) {
|
||||
console.error("Erro ao salvar honorário:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
if (confirmDelete.type === "custa") {
|
||||
await deleteCusta(confirmDelete.id);
|
||||
} else if (confirmDelete.type === "honorario") {
|
||||
await deleteHonorario(confirmDelete.id);
|
||||
}
|
||||
setConfirmDelete({ show: false, type: null, id: null });
|
||||
await refreshProcesso(); // Use refreshProcesso instead of window.location.reload()
|
||||
} catch (error) {
|
||||
console.error("Erro ao excluir:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateParcela = async (data: any) => {
|
||||
try {
|
||||
if (parcelaTipo === "honorario") {
|
||||
// Remover /api do início pois já está configurado no baseURL do axios
|
||||
await api.put(`/honorarios/parcelas/${editingParcela.id}`, {
|
||||
status: data.status,
|
||||
dataPagamento: data.dataPagamento,
|
||||
valorPago: Number(data.valorPago),
|
||||
comprovante: data.comprovante,
|
||||
observacoes: data.observacoes,
|
||||
});
|
||||
}
|
||||
|
||||
setParcelaDialogOpen(false);
|
||||
setEditingParcela(null);
|
||||
await refreshProcesso();
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar parcela:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadgeClasses = (status: string) => {
|
||||
const baseClasses = "px-2 py-1 text-xs font-medium rounded-full";
|
||||
switch (status) {
|
||||
case "PENDENTE":
|
||||
return `${baseClasses} bg-yellow-100 text-yellow-800`;
|
||||
case "PAGO":
|
||||
return `${baseClasses} bg-green-100 text-green-800`;
|
||||
case "VENCIDO":
|
||||
return `${baseClasses} bg-red-100 text-red-800`;
|
||||
case "CANCELADO":
|
||||
return `${baseClasses} bg-gray-100 text-gray-800`;
|
||||
default:
|
||||
return `${baseClasses} bg-gray-100 text-gray-800`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="py-4">
|
||||
<Tab.Group>
|
||||
<Tab.List className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
|
||||
<Tab
|
||||
className={({ selected }) =>
|
||||
`w-full rounded-lg py-2.5 text-sm font-medium leading-5 ${
|
||||
selected
|
||||
? "bg-white text-blue-700 shadow"
|
||||
: "text-gray-600 hover:bg-white/[0.12] hover:text-blue-600"
|
||||
}`
|
||||
}
|
||||
>
|
||||
Custas Processuais
|
||||
</Tab>
|
||||
<Tab
|
||||
className={({ selected }) =>
|
||||
`w-full rounded-lg py-2.5 text-sm font-medium leading-5 ${
|
||||
selected
|
||||
? "bg-white text-blue-700 shadow"
|
||||
: "text-gray-600 hover:bg-white/[0.12] hover:text-blue-600"
|
||||
}`
|
||||
}
|
||||
>
|
||||
Honorários
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
|
||||
<Tab.Panels className="mt-4">
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Custas Processuais
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingCusta(null);
|
||||
setCustaDialogOpen(true);
|
||||
}}
|
||||
className="inline-flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Nova Custa
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{processo.custas?.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Tipo
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Descrição
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Valor
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Ações
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{processo.custas.map((custa) => (
|
||||
<tr key={custa.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{custa.tipo.replace(/_/g, " ")}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{custa.descricao}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 text-right">
|
||||
{formatCurrency(custa.valor)}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<span className={getStatusBadgeClasses(custa.status)}>
|
||||
{custa.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-right">
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingCusta(custa);
|
||||
setCustaDialogOpen(true);
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setConfirmDelete({
|
||||
show: true,
|
||||
type: "custa",
|
||||
id: custa.id,
|
||||
})
|
||||
}
|
||||
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
Nenhuma custa processual registrada
|
||||
</div>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900">Honorários</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingHonorario(null);
|
||||
setHonorarioDialogOpen(true);
|
||||
}}
|
||||
className="inline-flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Novo Honorário
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{processo.honorarios?.map((honorario) => (
|
||||
<div
|
||||
key={honorario.id}
|
||||
className="mb-6 p-4 bg-white rounded-lg shadow"
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<h4 className="text-lg font-medium text-gray-900">
|
||||
{honorario.formaPagamento === "VISTA"
|
||||
? "Pagamento à Vista"
|
||||
: honorario.formaPagamento === "PARCELADO"
|
||||
? `Parcelado em ${honorario.numeroParcelas}x`
|
||||
: "Percentual da Causa"}
|
||||
</h4>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className={getStatusBadgeClasses(honorario.status)}>
|
||||
{honorario.status}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingHonorario(honorario);
|
||||
setHonorarioDialogOpen(true);
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setConfirmDelete({
|
||||
show: true,
|
||||
type: "honorario",
|
||||
id: honorario.id,
|
||||
})
|
||||
}
|
||||
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
{honorario.valorFixo
|
||||
? `Valor: ${formatCurrency(honorario.valorFixo)}`
|
||||
: `Percentual: ${honorario.percentualCausa}%`}
|
||||
</p>
|
||||
{honorario.formaPagamento === "PARCELADO" && (
|
||||
<>
|
||||
<p className="text-sm text-gray-700">
|
||||
Entrada: {formatCurrency(honorario.valorEntrada)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-700">
|
||||
Parcelas: {honorario.numeroParcelas}x de{" "}
|
||||
{formatCurrency(honorario.valorParcela)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<p className="text-sm text-gray-500">
|
||||
Vencimento: {formatDate(honorario.dataVencimento)}
|
||||
</p>
|
||||
</div>
|
||||
{honorario.parcelas && (
|
||||
<div className="mt-4 overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Parcela
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Valor
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Vencimento
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Ações
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Valor Pago
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{honorario.parcelas.map((parcela) => (
|
||||
<tr key={parcela.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{parcela.numeroParcela}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 text-right">
|
||||
{formatCurrency(parcela.valor)}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{formatDate(parcela.vencimento)}
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<span
|
||||
className={getStatusBadgeClasses(
|
||||
parcela.status,
|
||||
)}
|
||||
>
|
||||
{parcela.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-right">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingParcela(parcela);
|
||||
setParcelaTipo("honorario");
|
||||
setParcelaDialogOpen(true);
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap text-xs text-gray-500">
|
||||
{parcela.valorPago > 0 &&
|
||||
parcela.valorPago < parcela.valor &&
|
||||
`Pago: ${formatCurrency(parcela.valorPago)}`}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
|
||||
{confirmDelete.show && (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center">
|
||||
<div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75"></div>
|
||||
<div className="relative inline-block p-6 bg-white rounded-lg text-left shadow-xl">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0">
|
||||
<TrashIcon className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Confirmar exclusão
|
||||
</h3>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
Tem certeza que deseja excluir este{" "}
|
||||
{confirmDelete.type === "custa"
|
||||
? "registro de custa"
|
||||
: "honorário"}
|
||||
? Esta ação não pode ser desfeita.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Excluir
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setConfirmDelete({ show: false, type: null, id: null })
|
||||
}
|
||||
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 sm:mt-0 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Suspense
|
||||
fallback={<div className="flex justify-center p-8">Carregando...</div>}
|
||||
>
|
||||
{custaDialogOpen && (
|
||||
<DialogCustaProcessual
|
||||
key="custa-dialog" // Adicionar key única
|
||||
open={custaDialogOpen}
|
||||
onClose={() => {
|
||||
setCustaDialogOpen(false);
|
||||
setEditingCusta(null);
|
||||
}}
|
||||
onSave={handleSaveCusta}
|
||||
editingCusta={editingCusta}
|
||||
/>
|
||||
)}
|
||||
|
||||
{honorarioDialogOpen && (
|
||||
<DialogHonorario
|
||||
key="honorario-dialog" // Adicionar key única
|
||||
open={honorarioDialogOpen}
|
||||
onClose={() => {
|
||||
setHonorarioDialogOpen(false);
|
||||
setEditingHonorario(null);
|
||||
}}
|
||||
onSave={handleSaveHonorario}
|
||||
editingHonorario={editingHonorario}
|
||||
/>
|
||||
)}
|
||||
|
||||
{parcelaDialogOpen && (
|
||||
<DialogParcela
|
||||
key="parcela-dialog" // Adicionar key única
|
||||
isOpen={parcelaDialogOpen}
|
||||
onClose={() => {
|
||||
setParcelaDialogOpen(false);
|
||||
setEditingParcela(null);
|
||||
}}
|
||||
onSave={handleUpdateParcela}
|
||||
parcela={editingParcela}
|
||||
tipo={parcelaTipo}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { useProcessoContext } from "../../context/ProcessoContext";
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
|
||||
export function AbaInformacoes() {
|
||||
const { processo } = useProcessoContext();
|
||||
|
||||
if (!processo) {
|
||||
return <div>Carregando...</div>;
|
||||
}
|
||||
|
||||
// Encontrar próxima audiência
|
||||
const proximaAudiencia = processo.dadosDatajud?.movimentos?.find((mov) => {
|
||||
const isAudiencia =
|
||||
mov.nome.toLowerCase().includes("audiência") &&
|
||||
mov.complementosTabelados?.some((c) => c.nome === "designada");
|
||||
if (!isAudiencia) return false;
|
||||
|
||||
const dataAudiencia = new Date(mov.dataHora);
|
||||
return dataAudiencia > new Date();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-xl p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
||||
Informações do Processo
|
||||
</h3>
|
||||
|
||||
{/* Informações Básicas */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem label="Número do Processo" value={processo.numero} />
|
||||
<InfoItem label="Natureza" value={processo.natureza} />
|
||||
<InfoItem label="Classe" value={processo.classeProcessual} />
|
||||
<InfoItem label="Órgão Julgador" value={processo.orgaoJulgador} />
|
||||
<InfoItem
|
||||
label="Tribunal"
|
||||
value={`${processo.tribunal} (${processo.tribunalUF})`}
|
||||
/>
|
||||
<InfoItem
|
||||
label="Valor da Causa"
|
||||
value={
|
||||
processo.valorCausa
|
||||
? new Intl.NumberFormat("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
}).format(processo.valorCausa)
|
||||
: "Não informado"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dados do DataJud */}
|
||||
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
|
||||
<h3 className="text-md font-medium text-gray-900 mb-4">
|
||||
Dados do DataJud
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem
|
||||
label="Classe"
|
||||
value={processo.dadosDatajud?.classe?.nome}
|
||||
/>
|
||||
<InfoItem
|
||||
label="Data de Ajuizamento"
|
||||
value={
|
||||
processo.dataAjuizamento
|
||||
? format(new Date(processo.dataAjuizamento), "dd/MM/yyyy", {
|
||||
locale: ptBR,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<InfoItem label="Grau" value={processo.dadosDatajud?.grau} />
|
||||
<InfoItem
|
||||
label="Formato"
|
||||
value={processo.dadosDatajud?.formato?.nome}
|
||||
/>
|
||||
<InfoItem
|
||||
label="Sistema"
|
||||
value={processo.dadosDatajud?.sistema?.nome}
|
||||
/>
|
||||
<InfoItem
|
||||
label="Órgão Julgador"
|
||||
value={processo.dadosDatajud?.orgaoJulgador?.nome}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Assuntos do DataJud */}
|
||||
{processo.dadosDatajud?.assuntos &&
|
||||
processo.dadosDatajud.assuntos.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2">
|
||||
Assuntos
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{processo.dadosDatajud.assuntos.map((assunto) => (
|
||||
<span
|
||||
key={assunto.codigo}
|
||||
className="inline-flex items-center px-3 py-1 rounded-full text-sm
|
||||
bg-blue-50 text-blue-700 border border-blue-200"
|
||||
>
|
||||
{assunto.nome}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Próxima Audiência */}
|
||||
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
|
||||
<h4 className="font-medium text-gray-900 mb-2">Próxima Audiência</h4>
|
||||
{proximaAudiencia ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-900">
|
||||
<span className="font-medium">Tipo:</span>{" "}
|
||||
{proximaAudiencia.nome}
|
||||
</p>
|
||||
<p className="text-sm text-gray-900">
|
||||
<span className="font-medium">Data:</span>{" "}
|
||||
{format(
|
||||
new Date(proximaAudiencia.dataHora),
|
||||
"dd/MM/yyyy 'às' HH:mm",
|
||||
{ locale: ptBR },
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">Nenhuma audiência agendada</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoItem({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: string | null | undefined;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{label}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{value || "Não informado"}</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useProcessoContext } from "../../context/ProcessoContext";
|
||||
import { useProcessoMovimentacoes } from "../../hooks/useProcessoMovimentacoes";
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { ClockIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export default function AbaMovimentacoes() {
|
||||
const { processo } = useProcessoContext();
|
||||
const { movimentacoes, loading, error } = useProcessoMovimentacoes(processo);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center p-8">Carregando movimentações...</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-8 text-red-600">
|
||||
<span>Erro ao carregar movimentações</span>
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6">
|
||||
<div className="flow-root">
|
||||
<ul role="list" className="-mb-8">
|
||||
{movimentacoes.map((movimentacao, idx) => {
|
||||
// Criar um identificador único usando todas as informações disponíveis
|
||||
const movId = `${movimentacao.codigo}-${movimentacao.nome.slice(0, 10)}-${movimentacao.dataHora}-${idx}`;
|
||||
|
||||
return (
|
||||
<li key={movId}>
|
||||
<div className="relative pb-8">
|
||||
{idx !== movimentacoes.length - 1 && (
|
||||
<span
|
||||
className="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className="relative flex space-x-3">
|
||||
<div>
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-50">
|
||||
{movimentacao.complementosTabelados?.length > 0 ? (
|
||||
<DocumentIcon className="h-5 w-5 text-blue-600" />
|
||||
) : (
|
||||
<ClockIcon className="h-5 w-5 text-blue-600" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{movimentacao.nome}
|
||||
</p>
|
||||
{movimentacao.complementosTabelados?.map(
|
||||
(complemento) => (
|
||||
<p
|
||||
key={complemento.codigo}
|
||||
className="mt-0.5 text-sm text-gray-500"
|
||||
>
|
||||
{complemento.nome}
|
||||
</p>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className="whitespace-nowrap text-right text-sm text-gray-500">
|
||||
{format(
|
||||
new Date(movimentacao.dataHora),
|
||||
"dd/MM/yyyy 'às' HH:mm",
|
||||
{
|
||||
locale: ptBR,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import React from "react";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FormSelect, FormInput } from "../forms";
|
||||
|
||||
const TIPOS_AUDIENCIA = [
|
||||
{ value: "INSTRUCAO", label: "Instrução" },
|
||||
{ value: "INSTRUCAO_JULGAMENTO", label: "Instrução e Julgamento" },
|
||||
{ value: "CONCILIACAO", label: "Conciliação" },
|
||||
{ value: "PERICIA", label: "Perícia" },
|
||||
{ value: "ESTUDO_PSICOPEDAGOGICO", label: "Estudo Psicopedagógico" },
|
||||
{ value: "MEDIACAO", label: "Mediação" },
|
||||
];
|
||||
|
||||
interface AudienciaModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: any) => void;
|
||||
editingAudiencia?: any;
|
||||
isFinalizacao?: boolean; // Nova prop
|
||||
}
|
||||
|
||||
export const AudienciaModal: React.FC<AudienciaModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
editingAudiencia,
|
||||
isFinalizacao = false,
|
||||
}) => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: editingAudiencia || {},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="w-full max-w-md bg-white rounded-lg p-6">
|
||||
<Dialog.Title className="text-lg font-medium mb-4">
|
||||
{isFinalizacao
|
||||
? "Finalizar Audiência"
|
||||
: editingAudiencia
|
||||
? "Editar Audiência"
|
||||
: "Nova Audiência"}
|
||||
</Dialog.Title>
|
||||
|
||||
<form onSubmit={handleSubmit(onSave)} className="space-y-4">
|
||||
{!isFinalizacao ? (
|
||||
<>
|
||||
<FormSelect
|
||||
label="Tipo de Audiência"
|
||||
{...register("tipo")}
|
||||
options={TIPOS_AUDIENCIA}
|
||||
required
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Data e Hora"
|
||||
type="datetime-local"
|
||||
{...register("data")}
|
||||
required
|
||||
/>
|
||||
|
||||
<FormInput label="Local" {...register("local")} required />
|
||||
|
||||
<FormSelect
|
||||
label="Modalidade"
|
||||
{...register("modalidade")}
|
||||
options={[
|
||||
{ value: "PRESENCIAL", label: "Presencial" },
|
||||
{ value: "VIRTUAL", label: "Virtual" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Link (para audiência virtual)"
|
||||
{...register("linkVirtual")}
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Observações"
|
||||
{...register("observacoes")}
|
||||
as="textarea"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-gray-50 p-4 rounded-lg mb-4">
|
||||
<h3 className="font-medium text-gray-900">
|
||||
Informações da Audiência
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Tipo: {editingAudiencia?.tipo}
|
||||
<br />
|
||||
Data: {new Date(editingAudiencia?.data).toLocaleString()}
|
||||
<br />
|
||||
Local: {editingAudiencia?.local}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FormInput
|
||||
label="Relatório da Audiência"
|
||||
{...register("relatorio")}
|
||||
as="textarea"
|
||||
rows={6}
|
||||
placeholder="Descreva o que ocorreu na audiência..."
|
||||
required
|
||||
/>
|
||||
|
||||
<FormSelect
|
||||
label="Status Final"
|
||||
{...register("status")}
|
||||
options={[
|
||||
{ value: "REALIZADA", label: "Realizada" },
|
||||
{ value: "CANCELADA", label: "Cancelada" },
|
||||
{ value: "REDESIGNADA", label: "Redesignada" },
|
||||
]}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-50 rounded-lg"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className={`px-4 py-2 text-sm text-white rounded-lg
|
||||
${
|
||||
isFinalizacao
|
||||
? "bg-green-600 hover:bg-green-700"
|
||||
: "bg-blue-600 hover:bg-blue-700"
|
||||
}`}
|
||||
>
|
||||
{isFinalizacao ? "Finalizar" : "Salvar"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,262 @@
|
||||
import {
|
||||
Dialog,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { TipoCustaProcessual, StatusPagamento } from "@prisma/client";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import ptBR from "date-fns/locale/pt-BR";
|
||||
|
||||
interface DialogCustaProcessualProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: any) => Promise<void>;
|
||||
editingCusta?: any;
|
||||
}
|
||||
|
||||
export function DialogCustaProcessual({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
editingCusta,
|
||||
}: DialogCustaProcessualProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
tipo: "",
|
||||
descricao: "",
|
||||
valor: "",
|
||||
percentual: "",
|
||||
status: "PENDENTE",
|
||||
vencimento: null as Date | null,
|
||||
dataPagamento: null as Date | null,
|
||||
observacoes: "",
|
||||
numeroParcelas: 1,
|
||||
parcelaAtual: 1,
|
||||
comprovante: "",
|
||||
});
|
||||
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
|
||||
// Atualizar form quando o editingCusta muda
|
||||
useEffect(() => {
|
||||
if (editingCusta) {
|
||||
setFormData({
|
||||
tipo: editingCusta.tipo || "",
|
||||
descricao: editingCusta.descricao || "",
|
||||
valor: editingCusta.valor?.toString() || "",
|
||||
percentual: editingCusta.percentual?.toString() || "",
|
||||
status: editingCusta.status || "PENDENTE",
|
||||
vencimento: editingCusta.vencimento
|
||||
? new Date(editingCusta.vencimento)
|
||||
: null,
|
||||
dataPagamento: editingCusta.dataPagamento
|
||||
? new Date(editingCusta.dataPagamento)
|
||||
: null,
|
||||
observacoes: editingCusta.observacoes || "",
|
||||
numeroParcelas: editingCusta.numeroParcelas || 1,
|
||||
parcelaAtual: editingCusta.parcelaAtual || 1,
|
||||
comprovante: editingCusta.comprovante || "",
|
||||
});
|
||||
} else {
|
||||
// Reset para os valores padrão
|
||||
setFormData({
|
||||
tipo: "",
|
||||
descricao: "",
|
||||
valor: "",
|
||||
percentual: "",
|
||||
status: "PENDENTE",
|
||||
vencimento: null,
|
||||
dataPagamento: null,
|
||||
observacoes: "",
|
||||
numeroParcelas: 1,
|
||||
parcelaAtual: 1,
|
||||
comprovante: "",
|
||||
});
|
||||
}
|
||||
}, [editingCusta, open]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormError(null);
|
||||
|
||||
try {
|
||||
// Validar campos obrigatórios
|
||||
if (!formData.tipo) {
|
||||
setFormError("Selecione o tipo da custa");
|
||||
return;
|
||||
}
|
||||
if (!formData.descricao) {
|
||||
setFormError("Informe a descrição da custa");
|
||||
return;
|
||||
}
|
||||
if (!formData.valor) {
|
||||
setFormError("Informe o valor da custa");
|
||||
return;
|
||||
}
|
||||
|
||||
// Preparar dados
|
||||
const preparedData = {
|
||||
...formData,
|
||||
valor: Number(formData.valor),
|
||||
percentual: formData.percentual ? Number(formData.percentual) : null,
|
||||
numeroParcelas: Number(formData.numeroParcelas),
|
||||
parcelaAtual: Number(formData.parcelaAtual),
|
||||
};
|
||||
|
||||
await onSave(preparedData);
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao salvar custa:", error);
|
||||
setFormError(error.message || "Erro ao salvar custa. Tente novamente.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ p: 3 }}>
|
||||
<h2 className="text-lg font-semibold mb-4">
|
||||
{editingCusta ? "Editar Custa Processual" : "Nova Custa Processual"}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Tipo"
|
||||
value={formData.tipo}
|
||||
onChange={(e) => setFormData({ ...formData, tipo: e.target.value })}
|
||||
required
|
||||
>
|
||||
{Object.values(TipoCustaProcessual).map((tipo) => (
|
||||
<MenuItem key={tipo} value={tipo}>
|
||||
{tipo.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Descrição"
|
||||
value={formData.descricao}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, descricao: e.target.value })
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor"
|
||||
type="number"
|
||||
value={formData.valor}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valor: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual (se aplicável)"
|
||||
type="number"
|
||||
value={formData.percentual}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentual: e.target.value })
|
||||
}
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ptBR}
|
||||
>
|
||||
<DatePicker
|
||||
label="Vencimento"
|
||||
value={formData.vencimento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, vencimento: date })
|
||||
}
|
||||
sx={{ width: "100%", mb: 2 }}
|
||||
/>
|
||||
|
||||
{formData.status === "PAGO" && (
|
||||
<DatePicker
|
||||
label="Data de Pagamento"
|
||||
value={formData.dataPagamento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, dataPagamento: date })
|
||||
}
|
||||
sx={{ width: "100%" }}
|
||||
/>
|
||||
)}
|
||||
</LocalizationProvider>
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Status"
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, status: e.target.value })
|
||||
}
|
||||
required
|
||||
>
|
||||
{Object.values(StatusPagamento).map((status) => (
|
||||
<MenuItem key={status} value={status}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Número de Parcelas"
|
||||
type="number"
|
||||
value={formData.numeroParcelas}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
numeroParcelas: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
inputProps={{ min: 1 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Observações"
|
||||
multiline
|
||||
rows={3}
|
||||
value={formData.observacoes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, observacoes: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formError && (
|
||||
<Box sx={{ mt: 2, p: 2, bgcolor: "error.light", borderRadius: 1 }}>
|
||||
<Typography color="error">{formError}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "flex-end", gap: 2 }}
|
||||
>
|
||||
<Button onClick={onClose}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Salvar
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default DialogCustaProcessual;
|
||||
@@ -0,0 +1,347 @@
|
||||
import {
|
||||
Dialog,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { FormaPagamento, StatusPagamento } from "@prisma/client";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import ptBR from "date-fns/locale/pt-BR";
|
||||
import { IHonorario, IHonorarioCreate } from "../types/financeiro";
|
||||
|
||||
interface DialogHonorarioProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: Partial<IHonorarioCreate>) => Promise<void>;
|
||||
editingHonorario?: IHonorario;
|
||||
}
|
||||
|
||||
export function DialogHonorario({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
editingHonorario,
|
||||
}: DialogHonorarioProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
formaPagamento: "VISTA",
|
||||
valorFixo: "",
|
||||
percentualCausa: "",
|
||||
percentualExito: "",
|
||||
valorEntrada: "",
|
||||
numeroParcelas: 1,
|
||||
valorParcela: "",
|
||||
diaVencimento: 10,
|
||||
status: "PENDENTE",
|
||||
dataVencimento: new Date(),
|
||||
observacoes: "",
|
||||
});
|
||||
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
|
||||
// Atualizar form quando o editingHonorario muda
|
||||
useEffect(() => {
|
||||
if (editingHonorario) {
|
||||
setFormData({
|
||||
formaPagamento: editingHonorario.formaPagamento,
|
||||
valorFixo: editingHonorario.valorFixo || "",
|
||||
percentualCausa: editingHonorario.percentualCausa || "",
|
||||
percentualExito: editingHonorario.percentualExito || "",
|
||||
valorEntrada: editingHonorario.valorEntrada || "",
|
||||
numeroParcelas: editingHonorario.numeroParcelas || 1,
|
||||
valorParcela: editingHonorario.valorParcela || "",
|
||||
diaVencimento: editingHonorario.diaVencimento || 10,
|
||||
status: editingHonorario.status,
|
||||
dataVencimento: editingHonorario.dataVencimento
|
||||
? new Date(editingHonorario.dataVencimento)
|
||||
: new Date(),
|
||||
observacoes: editingHonorario.observacoes || "",
|
||||
});
|
||||
} else {
|
||||
// Reset para os valores padrão
|
||||
setFormData({
|
||||
formaPagamento: "VISTA",
|
||||
valorFixo: "",
|
||||
percentualCausa: "",
|
||||
percentualExito: "",
|
||||
valorEntrada: "",
|
||||
numeroParcelas: 1,
|
||||
valorParcela: "",
|
||||
diaVencimento: 10,
|
||||
status: "PENDENTE",
|
||||
dataVencimento: new Date(),
|
||||
observacoes: "",
|
||||
});
|
||||
}
|
||||
}, [editingHonorario, open]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormError(null);
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
const validationError = validateFormData(formData);
|
||||
if (validationError) {
|
||||
setFormError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Converter strings numéricas
|
||||
const preparedData = {
|
||||
...formData,
|
||||
valorFixo: formData.valorFixo ? Number(formData.valorFixo) : null,
|
||||
percentualCausa: formData.percentualCausa
|
||||
? Number(formData.percentualCausa)
|
||||
: null,
|
||||
percentualExito: formData.percentualExito
|
||||
? Number(formData.percentualExito)
|
||||
: null,
|
||||
valorEntrada: formData.valorEntrada
|
||||
? Number(formData.valorEntrada)
|
||||
: null,
|
||||
valorParcela: formData.valorParcela
|
||||
? Number(formData.valorParcela)
|
||||
: null,
|
||||
};
|
||||
|
||||
await onSave(preparedData);
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao salvar honorário:", error);
|
||||
setFormError(
|
||||
error.message || "Erro ao salvar honorário. Tente novamente.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validateFormData = (data: any): string | null => {
|
||||
if (!data.formaPagamento) {
|
||||
return "Forma de pagamento é obrigatória";
|
||||
}
|
||||
|
||||
switch (data.formaPagamento) {
|
||||
case "VISTA":
|
||||
if (!data.valorFixo) {
|
||||
return "Informe o valor do honorário";
|
||||
}
|
||||
break;
|
||||
case "PERCENTUAL_CAUSA":
|
||||
if (!data.percentualCausa) {
|
||||
return "Informe o percentual da causa";
|
||||
}
|
||||
break;
|
||||
case "PERCENTUAL_EXITO":
|
||||
if (!data.percentualExito) {
|
||||
return "Informe o percentual do êxito";
|
||||
}
|
||||
break;
|
||||
case "PARCELADO":
|
||||
case "ENTRADA_MAIS_PARCELAS":
|
||||
if (!data.valorParcela) {
|
||||
return "Informe o valor da parcela";
|
||||
}
|
||||
if (!data.numeroParcelas || data.numeroParcelas < 1) {
|
||||
return "Informe o número de parcelas";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ p: 3 }}>
|
||||
<h2 className="text-lg font-semibold mb-4">
|
||||
{editingHonorario ? "Editar Honorário" : "Novo Honorário"}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Forma de Pagamento</InputLabel>
|
||||
<Select
|
||||
value={formData.formaPagamento}
|
||||
label="Forma de Pagamento"
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, formaPagamento: e.target.value })
|
||||
}
|
||||
>
|
||||
{Object.values(FormaPagamento).map((forma) => (
|
||||
<MenuItem key={forma} value={forma}>
|
||||
{forma.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{formData.formaPagamento === "VISTA" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor"
|
||||
type="number"
|
||||
value={formData.valorFixo}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorFixo: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formData.formaPagamento === "PERCENTUAL_CAUSA" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual da Causa"
|
||||
type="number"
|
||||
value={formData.percentualCausa}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentualCausa: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formData.formaPagamento === "PERCENTUAL_EXITO" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual do Êxito"
|
||||
type="number"
|
||||
value={formData.percentualExito}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentualExito: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(formData.formaPagamento === "PARCELADO" ||
|
||||
formData.formaPagamento === "ENTRADA_MAIS_PARCELAS") && (
|
||||
<>
|
||||
{formData.formaPagamento === "ENTRADA_MAIS_PARCELAS" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor da Entrada"
|
||||
type="number"
|
||||
value={formData.valorEntrada}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorEntrada: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
)}
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Número de Parcelas"
|
||||
type="number"
|
||||
value={formData.numeroParcelas}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
numeroParcelas: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
required
|
||||
inputProps={{ min: "1" }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor da Parcela"
|
||||
type="number"
|
||||
value={formData.valorParcela}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorParcela: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Dia do Vencimento"
|
||||
type="number"
|
||||
value={formData.diaVencimento}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
diaVencimento: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
inputProps={{ min: 1, max: 31 }}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Status"
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, status: e.target.value })
|
||||
}
|
||||
required
|
||||
>
|
||||
{Object.values(StatusPagamento).map((status) => (
|
||||
<MenuItem key={status} value={status}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ptBR}
|
||||
>
|
||||
<DatePicker
|
||||
label="Data de Vencimento"
|
||||
value={formData.dataVencimento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, dataVencimento: date || new Date() })
|
||||
}
|
||||
sx={{ width: "100%" }}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Observações"
|
||||
multiline
|
||||
rows={3}
|
||||
value={formData.observacoes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, observacoes: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formError && (
|
||||
<Box sx={{ mt: 2, p: 2, bgcolor: "error.light", borderRadius: 1 }}>
|
||||
<Typography color="error">{formError}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "flex-end", gap: 2 }}
|
||||
>
|
||||
<Button onClick={onClose}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Salvar
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default DialogHonorario;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { useProcessoMovimentacoes } from "../../hooks/useProcessoMovimentacoes";
|
||||
|
||||
interface MovimentacoesTimelineProps {
|
||||
numeroProcesso: string;
|
||||
tribunal: string;
|
||||
}
|
||||
|
||||
export function MovimentacoesTimeline({
|
||||
numeroProcesso,
|
||||
tribunal,
|
||||
}: MovimentacoesTimelineProps) {
|
||||
const { movimentacoes, loading, error } = useProcessoMovimentacoes(
|
||||
numeroProcesso,
|
||||
tribunal,
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center p-4">Carregando movimentações...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center p-4 text-red-600">
|
||||
Erro ao carregar movimentações
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!movimentacoes?.length) {
|
||||
return (
|
||||
<div className="text-center p-4">Nenhuma movimentação encontrada</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flow-root">
|
||||
<ul role="list" className="-mb-8">
|
||||
{movimentacoes.map((mov, idx) => (
|
||||
<li key={`${mov.codigo}-${mov.dataHora}`}>
|
||||
<div className="relative pb-8">
|
||||
{idx !== movimentacoes.length - 1 ? (
|
||||
<span className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200" />
|
||||
) : null}
|
||||
<div className="relative flex space-x-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100">
|
||||
<span className="text-xs text-gray-500">{mov.codigo}</span>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
|
||||
<div>
|
||||
<p className="text-sm text-gray-900">{mov.nome}</p>
|
||||
{mov.complementosTabelados?.map((comp: any) => (
|
||||
<p key={comp.codigo} className="text-sm text-gray-500">
|
||||
{comp.nome}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<div className="whitespace-nowrap text-right text-sm text-gray-500">
|
||||
{format(new Date(mov.dataHora), "dd/MM/yyyy 'às' HH:mm", {
|
||||
locale: ptBR,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { useState } from "react";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { useClientes } from "../../hooks/useClientes";
|
||||
import { FormSelect, FormInput } from "../forms";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
interface ParteProcessoModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: any) => void;
|
||||
tipo: "AUTOR" | "REU";
|
||||
editingParte?: any;
|
||||
}
|
||||
|
||||
export const ParteProcessoModal: React.FC<ParteProcessoModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
tipo,
|
||||
editingParte,
|
||||
}) => {
|
||||
const { register, handleSubmit, setValue, watch } = useForm({
|
||||
defaultValues: editingParte || {
|
||||
tipo, // Já estava aqui, mas precisamos garantir que está sendo enviado
|
||||
principal: false,
|
||||
},
|
||||
});
|
||||
const { clientes } = useClientes();
|
||||
const [useExistingClient, setUseExistingClient] = useState(
|
||||
!!editingParte?.clienteId,
|
||||
);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const filteredClientes = clientes.filter(
|
||||
(cliente) =>
|
||||
cliente.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
cliente.cpfCnpj.includes(searchTerm),
|
||||
);
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
const parteData = {
|
||||
...data,
|
||||
tipo, // Garantir que o tipo está sendo enviado
|
||||
nome: useExistingClient
|
||||
? clientes.find((c) => c.id === data.clienteId)?.nome
|
||||
: data.nome,
|
||||
cpfCnpj: useExistingClient
|
||||
? clientes.find((c) => c.id === data.clienteId)?.cpfCnpj
|
||||
: data.cpfCnpj,
|
||||
clienteId: useExistingClient ? data.clienteId : undefined,
|
||||
principal: data.principal || false,
|
||||
};
|
||||
|
||||
console.log("Enviando dados:", parteData); // Debug
|
||||
onSave(parteData);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||
{/* ... dialog backdrop ... */}
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="w-full max-w-md bg-white rounded-lg p-6">
|
||||
<Dialog.Title className="text-lg font-medium mb-4">
|
||||
Adicionar {tipo === "AUTOR" ? "Autor" : "Réu"}
|
||||
</Dialog.Title>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
{/* Adicionar campo oculto para o tipo */}
|
||||
<input type="hidden" {...register("tipo")} value={tipo} />
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useExistingClient}
|
||||
onChange={(e) => setUseExistingClient(e.target.checked)}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<label className="text-sm">Usar cliente existente</label>
|
||||
</div>
|
||||
|
||||
{useExistingClient ? (
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Buscar Cliente
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||
placeholder="Digite nome ou CPF/CNPJ..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<div className="mt-2 max-h-60 overflow-y-auto border border-gray-200 rounded-lg">
|
||||
{filteredClientes.map((cliente) => (
|
||||
<label
|
||||
key={cliente.id}
|
||||
className="flex items-center p-2 hover:bg-gray-50 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
{...register("clienteId")}
|
||||
value={cliente.id}
|
||||
className="mr-2"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium">{cliente.nome}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{cliente.cpfCnpj}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<FormInput label="Nome" {...register("nome")} required />
|
||||
<FormInput label="CPF/CNPJ" {...register("cpfCnpj")} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormInput
|
||||
label="Observações"
|
||||
{...register("observacoes")}
|
||||
as="textarea"
|
||||
/>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-50 rounded-lg"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 text-sm text-white bg-blue-600 hover:bg-blue-700 rounded-lg"
|
||||
>
|
||||
Salvar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
Whitespace-only changes.
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { Processo } from '../../types/entities';
|
||||
|
||||
interface ProcessoCardProps {
|
||||
processo: Processo;
|
||||
}
|
||||
|
||||
const resultadoLabel = {
|
||||
NAO_JULGADO: 'Não Julgado',
|
||||
PROCEDENTE: 'Procedente',
|
||||
PARCIALMENTE_PROCEDENTE: 'Parcialmente Procedente',
|
||||
IMPROCEDENTE: 'Improcedente',
|
||||
SEM_ANALISE_MERITO: 'Sem Análise de Mérito'
|
||||
};
|
||||
|
||||
const resultadoColor = {
|
||||
NAO_JULGADO: 'bg-gray-100 text-gray-800',
|
||||
PROCEDENTE: 'bg-green-100 text-green-800',
|
||||
PARCIALMENTE_PROCEDENTE: 'bg-yellow-100 text-yellow-800',
|
||||
IMPROCEDENTE: 'bg-red-100 text-red-800',
|
||||
SEM_ANALISE_MERITO: 'bg-gray-100 text-gray-800'
|
||||
};
|
||||
|
||||
export const ProcessoCard: React.FC<ProcessoCardProps> = ({ processo }) => {
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
{processo.numero}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{processo.cliente.nome}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{processo.nomeContraparte}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{processo.natureza}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{processo.status}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{processo.valorCausa?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{new Date(processo.dataCriacao).toLocaleDateString('pt-BR')}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{new Date(processo.dataAtualizacao).toLocaleDateString('pt-BR')}
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${resultadoColor[processo.resultado]}`}>
|
||||
{resultadoLabel[processo.resultado]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,646 @@
|
||||
import React, { useState, useEffect, lazy, Suspense } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useProcessos } from "../../hooks/useProcessos";
|
||||
import api from "../../services/api"; // Adicionar esta importação
|
||||
import { Tab, Dialog } from "@headlessui/react";
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ClockIcon,
|
||||
DocumentTextIcon,
|
||||
UserGroupIcon,
|
||||
BanknotesIcon, // Alterado de BalanceIcon para BanknotesIcon
|
||||
CalendarIcon,
|
||||
PencilIcon,
|
||||
ExclamationTriangleIcon,
|
||||
PlusIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ProcessoModal } from "./ProcessoModal.tsx";
|
||||
import { formatNumeroProcesso, formatCPF } from "../../utils/formatters";
|
||||
import { ParteProcessoModal } from "./ParteProcessoModal";
|
||||
import { usePartesProcesso } from "../../hooks/usePartesProcesso";
|
||||
import { useAudiencias } from "../../hooks/useAudiencias";
|
||||
import { AudienciaModal } from "./AudienciaModal";
|
||||
import AbaFinanceiro from "./AbaFinanceiro";
|
||||
import { ProcessoProvider } from "../../context/ProcessoContext";
|
||||
|
||||
// Adicionar lazy import para AbaMovimentacoes
|
||||
const AbaMovimentacoes = lazy(() => import("./AbaMovimentacoes"));
|
||||
|
||||
function classNames(...classes: string[]) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: "geral", name: "Geral", icon: DocumentTextIcon },
|
||||
{ id: "partes", name: "Partes", icon: UserGroupIcon },
|
||||
{ id: "movimentacoes", name: "Movimentações", icon: ClockIcon },
|
||||
{ id: "audiencias", name: "Audiências", icon: CalendarIcon },
|
||||
{ id: "financeiro", name: "Financeiro", icon: BanknotesIcon }, // Atualizado aqui também
|
||||
];
|
||||
|
||||
export const ProcessoDetalhes: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { getProcesso, isLoading, deleteProcesso } = useProcessos();
|
||||
const [processo, setProcesso] = useState<Processo | null>(null);
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [editingParte, setEditingParte] = useState<any>(null);
|
||||
const [parteModalConfig, setParteModalConfig] = useState<{
|
||||
isOpen: boolean;
|
||||
tipo: "AUTOR" | "REU" | null;
|
||||
}>({ isOpen: false, tipo: null });
|
||||
const [isAudienciaModalOpen, setIsAudienciaModalOpen] = useState(false);
|
||||
const [editingAudiencia, setEditingAudiencia] = useState<any>(null);
|
||||
const [isFinalizacao, setIsFinalizacao] = useState(false); // Movido para cá
|
||||
|
||||
// Carregar processo com partes
|
||||
useEffect(() => {
|
||||
const loadProcesso = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const data = await getProcesso(id);
|
||||
// Só atualizar se os dados forem diferentes
|
||||
if (JSON.stringify(data) !== JSON.stringify(processo)) {
|
||||
setProcesso(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar processo:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadProcesso();
|
||||
}, [id, getProcesso]); // Remover processo da dependency list
|
||||
|
||||
const { addParte, updateParte, deleteParte } = usePartesProcesso(id!);
|
||||
|
||||
const handleSaveParte = async (data: any) => {
|
||||
try {
|
||||
if (editingParte) {
|
||||
await updateParte({ id: editingParte.id, data });
|
||||
} else {
|
||||
await addParte(data);
|
||||
}
|
||||
setParteModalConfig({ isOpen: false, tipo: null });
|
||||
setEditingParte(null);
|
||||
// Recarregar processo após salvar
|
||||
const updatedProcesso = await getProcesso(id!);
|
||||
setProcesso(updatedProcesso);
|
||||
} catch (error) {
|
||||
console.error("Erro ao salvar parte:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteParte = async (parteId: string) => {
|
||||
if (window.confirm("Tem certeza que deseja remover esta parte?")) {
|
||||
try {
|
||||
await deleteParte(parteId);
|
||||
} catch (error) {
|
||||
console.error("Erro ao excluir parte:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { addAudiencia, updateAudiencia, deleteAudiencia } = useAudiencias(id!);
|
||||
|
||||
const handleSaveAudiencia = async (data: any) => {
|
||||
try {
|
||||
if (editingAudiencia) {
|
||||
await updateAudiencia({ id: editingAudiencia.id, data });
|
||||
} else {
|
||||
await addAudiencia(data);
|
||||
}
|
||||
setIsAudienciaModalOpen(false);
|
||||
setEditingAudiencia(null);
|
||||
// Recarregar processo após salvar
|
||||
const updatedProcesso = await getProcesso(id!);
|
||||
setProcesso(updatedProcesso);
|
||||
} catch (error) {
|
||||
console.error("Erro ao salvar audiência:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAudiencia = async (audienciaId: string) => {
|
||||
if (window.confirm("Tem certeza que deseja remover esta audiência?")) {
|
||||
try {
|
||||
await deleteAudiencia(audienciaId);
|
||||
// Recarregar processo
|
||||
const updatedProcesso = await getProcesso(id!);
|
||||
setProcesso(updatedProcesso);
|
||||
} catch (error) {
|
||||
console.error("Erro ao excluir audiência:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="animate-pulse">Carregando...</div>;
|
||||
}
|
||||
|
||||
if (!processo) {
|
||||
return <div>Processo não encontrado</div>;
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteProcesso.mutateAsync(id!);
|
||||
navigate("/processos");
|
||||
} catch (error) {
|
||||
console.error("Erro ao excluir processo:", error);
|
||||
alert("Erro ao excluir processo");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={() => navigate("/processos")}
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5 text-gray-500" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-gray-900">
|
||||
{formatNumeroProcesso(processo.numero)}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">{processo.natureza}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
{/* Status Badge */}
|
||||
<span
|
||||
className={classNames(
|
||||
"px-3 py-1 rounded-full text-sm font-medium",
|
||||
processo.status === "INICIAL"
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: processo.status === "EM_ANDAMENTO"
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: processo.status === "SUSPENSO"
|
||||
? "bg-gray-100 text-gray-800"
|
||||
: processo.status === "CONCLUSO"
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800",
|
||||
)}
|
||||
>
|
||||
{processo.status}
|
||||
</span>
|
||||
|
||||
{/* Adicionar o botão de resultado aqui */}
|
||||
<select
|
||||
value={processo.resultado || 'NAO_JULGADO'}
|
||||
onChange={async (e) => {
|
||||
try {
|
||||
await api.patch(`/processos/${processo.id}/resultado`, {
|
||||
resultado: e.target.value
|
||||
});
|
||||
// Recarregar dados após atualização
|
||||
const updated = await getProcesso(processo.id);
|
||||
setProcesso(updated);
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar resultado:', error);
|
||||
alert('Erro ao atualizar resultado do processo');
|
||||
}
|
||||
}}
|
||||
className="rounded-lg border-gray-300 text-sm"
|
||||
>
|
||||
<option value="NAO_JULGADO">Não Julgado</option>
|
||||
<option value="PROCEDENTE">Procedente</option>
|
||||
<option value="PARCIALMENTE_PROCEDENTE">Parcialmente Procedente</option>
|
||||
<option value="IMPROCEDENTE">Improcedente</option>
|
||||
<option value="SEM_ANALISE_MERITO">Sem Análise de Mérito</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* Adicionar botão de edição */}
|
||||
<button
|
||||
onClick={() => setIsEditModalOpen(true)}
|
||||
className="flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4 mr-2" />
|
||||
Editar Processo
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
className="flex items-center px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4 mr-2" />
|
||||
Excluir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
{processo && (
|
||||
<ProcessoProvider processoId={processo.id}>
|
||||
<Tab.Group selectedIndex={selectedTab} onChange={setSelectedTab}>
|
||||
<Tab.List className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
className={({ selected }) =>
|
||||
classNames(
|
||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5",
|
||||
"flex items-center justify-center",
|
||||
selected
|
||||
? "bg-white text-blue-700 shadow"
|
||||
: "text-gray-600 hover:bg-white/[0.12] hover:text-blue-600",
|
||||
)
|
||||
}
|
||||
>
|
||||
<tab.icon className="h-4 w-4 mr-2" />
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
|
||||
<Tab.Panels className="mt-2">
|
||||
{/* Informações Gerais */}
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<dl className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
Número do Processo
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{formatNumeroProcesso(processo.numero)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
Natureza
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{processo.natureza}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
Órgão Julgador
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{processo.orgaoJulgador}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
Valor da Causa
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{new Intl.NumberFormat("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
}).format(processo.valorCausa || 0)}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Tab.Panel>
|
||||
|
||||
{/* Partes do Processo */}
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<div className="space-y-8">
|
||||
{/* Autores */}
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Autores
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingParte(null);
|
||||
setParteModalConfig({ isOpen: true, tipo: "AUTOR" });
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
+ Adicionar Autor
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{processo.partes
|
||||
?.filter((p) => p.tipo === "AUTOR")
|
||||
.map((parte) => (
|
||||
<div
|
||||
key={parte.id}
|
||||
className="flex justify-between items-center p-4 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<div>
|
||||
<h4 className="font-medium">{parte.nome}</h4>
|
||||
{parte.cpfCnpj && (
|
||||
<p className="text-sm text-gray-500">
|
||||
{formatCPF(parte.cpfCnpj)}
|
||||
</p>
|
||||
)}
|
||||
{parte.observacoes && (
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{parte.observacoes}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingParte(parte);
|
||||
setParteModalConfig({
|
||||
isOpen: true,
|
||||
tipo: "AUTOR",
|
||||
});
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteParte(parte.id)}
|
||||
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Réus */}
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Réus
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingParte(null);
|
||||
setParteModalConfig({ isOpen: true, tipo: "REU" });
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
+ Adicionar Réu
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{processo.partes
|
||||
?.filter((p) => p.tipo === "REU")
|
||||
.map((parte) => (
|
||||
<div
|
||||
key={parte.id}
|
||||
className="flex justify-between items-center p-4 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<div>
|
||||
<h4 className="font-medium">{parte.nome}</h4>
|
||||
{parte.cpfCnpj && (
|
||||
<p className="text-sm text-gray-500">
|
||||
{formatCPF(parte.cpfCnpj)}
|
||||
</p>
|
||||
)}
|
||||
{parte.observacoes && (
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{parte.observacoes}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingParte(parte);
|
||||
setParteModalConfig({
|
||||
isOpen: true,
|
||||
tipo: "REU",
|
||||
});
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteParte(parte.id)}
|
||||
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ParteProcessoModal
|
||||
isOpen={parteModalConfig.isOpen}
|
||||
onClose={() =>
|
||||
setParteModalConfig({ isOpen: false, tipo: null })
|
||||
}
|
||||
onSave={handleSaveParte}
|
||||
tipo={parteModalConfig.tipo}
|
||||
editingParte={editingParte}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
|
||||
{/* Movimentações */}
|
||||
<Tab.Panel className="bg-white rounded-xl">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex justify-center p-8">Carregando...</div>
|
||||
}
|
||||
>
|
||||
<AbaMovimentacoes />
|
||||
</Suspense>
|
||||
</Tab.Panel>
|
||||
|
||||
{/* Audiências */}
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<div className="space-y-4">
|
||||
{/* Header com botão de adicionar */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Audiências
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingAudiencia(null);
|
||||
setIsAudienciaModalOpen(true);
|
||||
}}
|
||||
className="flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Nova Audiência
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lista de Audiências */}
|
||||
{processo.audiencias && processo.audiencias.length > 0 ? (
|
||||
<div className="divide-y divide-gray-200">
|
||||
{processo.audiencias.map((audiencia) => (
|
||||
<div key={audiencia.id} className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-lg font-medium text-gray-900">
|
||||
{audiencia.tipo}
|
||||
</h4>
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<p>
|
||||
Data:{" "}
|
||||
{new Date(audiencia.data).toLocaleString()}
|
||||
</p>
|
||||
<p>Local: {audiencia.local}</p>
|
||||
{audiencia.modalidade === "VIRTUAL" && (
|
||||
<p>
|
||||
Link:{" "}
|
||||
<a
|
||||
href={audiencia.linkVirtual}
|
||||
className="text-blue-600 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Acessar
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{audiencia.relatorio && (
|
||||
<div className="mt-2 p-3 bg-gray-50 rounded-lg">
|
||||
<p className="font-medium text-sm">
|
||||
Relatório:
|
||||
</p>
|
||||
<p className="text-sm mt-1">
|
||||
{audiencia.relatorio}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Finalizada em:{" "}
|
||||
{new Date(
|
||||
audiencia.dataFinalizacao,
|
||||
).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{audiencia.status === "AGENDADA" && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingAudiencia(audiencia);
|
||||
setIsAudienciaModalOpen(true);
|
||||
setIsFinalizacao(true);
|
||||
}}
|
||||
className="px-3 py-1 text-sm text-green-600 hover:bg-green-50 rounded-lg border border-green-600"
|
||||
>
|
||||
Finalizar
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingAudiencia(audiencia);
|
||||
setIsAudienciaModalOpen(true);
|
||||
}}
|
||||
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleDeleteAudiencia(audiencia.id)
|
||||
}
|
||||
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500 text-center py-4">
|
||||
Nenhuma audiência agendada
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
|
||||
{/* Financeiro */}
|
||||
<Tab.Panel className="bg-white rounded-xl p-6">
|
||||
<ProcessoProvider processoId={processo?.id}>
|
||||
<AbaFinanceiro />
|
||||
</ProcessoProvider>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</ProcessoProvider>
|
||||
)}
|
||||
|
||||
{/* Modal de Edição */}
|
||||
<ProcessoModal
|
||||
isOpen={isEditModalOpen}
|
||||
processo={processo}
|
||||
onClose={() => setIsEditModalOpen(false)}
|
||||
onSuccess={() => {
|
||||
setIsEditModalOpen(false);
|
||||
// Recarregar dados após edição
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Modal de Confirmação de Exclusão */}
|
||||
<Dialog
|
||||
open={isDeleteModalOpen}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
className="relative z-50"
|
||||
>
|
||||
<div
|
||||
className="fixed inset-0 bg-black/30 backdrop-blur-sm"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="mx-auto max-w-sm rounded-lg bg-white p-6 shadow-xl">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationTriangleIcon className="h-10 w-10 text-red-500" />
|
||||
</div>
|
||||
<div>
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">
|
||||
Confirmar exclusão
|
||||
</Dialog.Title>
|
||||
<Dialog.Description className="mt-2 text-sm text-gray-500">
|
||||
Tem certeza que deseja excluir este processo? Esta ação não
|
||||
poderá ser desfeita.
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsDeleteModalOpen(false)}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700"
|
||||
>
|
||||
Excluir
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
{/* Adicionar Modal de Audiência */}
|
||||
<AudienciaModal
|
||||
isOpen={isAudienciaModalOpen}
|
||||
onClose={() => {
|
||||
setIsAudienciaModalOpen(false);
|
||||
setEditingAudiencia(null);
|
||||
setIsFinalizacao(false);
|
||||
}}
|
||||
onSave={handleSaveAudiencia}
|
||||
editingAudiencia={editingAudiencia}
|
||||
isFinalizacao={isFinalizacao}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Processo } from '../../types/entities';
|
||||
|
||||
interface ProcessoFormProps {
|
||||
onSubmit: (data: Partial<Processo>) => void;
|
||||
initialData?: Partial<Processo>;
|
||||
}
|
||||
|
||||
const resultadoOptions = [
|
||||
{ value: 'NAO_JULGADO', label: 'Não Julgado' },
|
||||
{ value: 'PROCEDENTE', label: 'Procedente' },
|
||||
{ value: 'PARCIALMENTE_PROCEDENTE', label: 'Parcialmente Procedente' },
|
||||
{ value: 'IMPROCEDENTE', label: 'Improcedente' },
|
||||
{ value: 'SEM_ANALISE_MERITO', label: 'Sem Análise de Mérito' }
|
||||
];
|
||||
|
||||
export const ProcessoForm: React.FC<ProcessoFormProps> = ({ onSubmit, initialData }) => {
|
||||
const { register, handleSubmit, reset } = useForm<Partial<Processo>>({
|
||||
defaultValues: initialData,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Número do Processo
|
||||
</label>
|
||||
<input
|
||||
{...register('numero')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Natureza
|
||||
</label>
|
||||
<select
|
||||
{...register('natureza')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
<option value="CIVEL">Cível</option>
|
||||
<option value="CRIMINAL">Criminal</option>
|
||||
<option value="PREVIDENCIARIO">Previdenciário</option>
|
||||
<option value="TRABALHISTA">Trabalhista</option>
|
||||
<option value="FAMILIA">Família</option>
|
||||
<option value="TRIBUTARIO">Tributário</option>
|
||||
<option value="ADMINISTRATIVO">Administrativo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
{...register('status')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
<option value="INICIAL">Inicial</option>
|
||||
<option value="EM_ANDAMENTO">Em Andamento</option>
|
||||
<option value="SUSPENSO">Suspenso</option>
|
||||
<option value="RECURSO">Recurso</option>
|
||||
<option value="CONCLUSO">Concluso</option>
|
||||
<option value="ARQUIVADO">Arquivado</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Resultado do Julgamento
|
||||
</label>
|
||||
<select
|
||||
{...register('resultado')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
{resultadoOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Cliente
|
||||
</label>
|
||||
<input
|
||||
{...register('clienteId')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Valor da Causa
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
{...register('valorCausa')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Órgão Julgador
|
||||
</label>
|
||||
<input
|
||||
{...register('orgaoJulgador')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Nome da Contraparte
|
||||
</label>
|
||||
<input
|
||||
{...register('nomeContraparte')}
|
||||
className="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => reset()}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg"
|
||||
>
|
||||
Salvar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { DatajudProcesso } from "../../types/datajud";
|
||||
|
||||
interface ProcessoInfoProps {
|
||||
data: DatajudProcesso;
|
||||
}
|
||||
|
||||
export const ProcessoInfo = ({ data }: ProcessoInfoProps) => {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem label="Classe" value={data.classe.nome} />
|
||||
<InfoItem
|
||||
label="Data de Ajuizamento"
|
||||
value={format(new Date(data.dataAjuizamento), "dd/MM/yyyy", {
|
||||
locale: ptBR,
|
||||
})}
|
||||
/>
|
||||
<InfoItem label="Grau" value={data.grau} />
|
||||
<InfoItem label="Formato" value={data.formato.nome} />
|
||||
<InfoItem label="Sistema" value={data.sistema.nome} />
|
||||
<InfoItem label="Órgão Julgador" value={data.orgaoJulgador.nome} />
|
||||
</div>
|
||||
|
||||
{data.assuntos.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h4 className="font-medium text-gray-700 mb-2">Assuntos</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{data.assuntos.map((assunto) => (
|
||||
<span
|
||||
key={assunto.codigo}
|
||||
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
|
||||
>
|
||||
{assunto.nome}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function InfoItem({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{label}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{value}</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProcessoInfo;
|
||||
@@ -0,0 +1,443 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Dialog, Transition, Combobox } from "@headlessui/react";
|
||||
import {
|
||||
XMarkIcon,
|
||||
MagnifyingGlassIcon,
|
||||
CheckIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Processo, NaturezaProcesso, TipoParte } from "../../types/entities";
|
||||
import { useProcessos } from "../../hooks/useProcessos";
|
||||
import { useClientes } from "../../hooks/useClientes";
|
||||
import { FormInput, FormSelect, FormCheckbox } from "../forms";
|
||||
import axios from "axios";
|
||||
import { UF_BRASIL, TRIBUNAIS } from "../../utils/constants/tribunais";
|
||||
import { useDatajud } from "../../hooks/useDatajud";
|
||||
import { ProcessoInfo } from "./ProcessoInfo"; // Adicionar import
|
||||
import { DatajudProcesso } from "../../types/datajud";
|
||||
|
||||
interface ProcessoModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
processo: Processo | null;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
interface OrgaoJulgadorOption {
|
||||
id: string;
|
||||
nome: string;
|
||||
}
|
||||
|
||||
interface OrgaoSugestao {
|
||||
id: string;
|
||||
nome: string;
|
||||
}
|
||||
|
||||
export const ProcessoModal: React.FC<ProcessoModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
processo,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [copiarOrgaoJulgador, setCopiarOrgaoJulgador] = useState(false);
|
||||
const { register, handleSubmit, setValue, watch, reset } = useForm();
|
||||
const { createProcesso, updateProcesso } = useProcessos();
|
||||
const { clientes } = useClientes(); // Adicionar hook de clientes
|
||||
const [clienteSearch, setClienteSearch] = useState("");
|
||||
const [selectedOrgao, setSelectedOrgao] =
|
||||
useState<OrgaoJulgadorOption | null>(null);
|
||||
const [orgaos, setOrgaos] = useState<string[]>([]);
|
||||
const [orgaoQuery, setOrgaoQuery] = useState("");
|
||||
const [selectedUF, setSelectedUF] = useState(processo?.tribunalUF || "");
|
||||
const [availableTribunais, setAvailableTribunais] = useState<any[]>([]);
|
||||
const { consultarProcesso } = useDatajud();
|
||||
const [dadosDatajud, setDadosDatajud] = useState<DatajudProcesso | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const orgaoJulgador = watch("orgaoJulgador");
|
||||
|
||||
useEffect(() => {
|
||||
if (copiarOrgaoJulgador) {
|
||||
setValue("orgaoOrigem", orgaoJulgador);
|
||||
}
|
||||
}, [orgaoJulgador, copiarOrgaoJulgador, setValue]);
|
||||
|
||||
// Adicionar useEffect para popular form quando editando
|
||||
useEffect(() => {
|
||||
if (isOpen && processo) {
|
||||
console.log("Populando formulário com:", processo);
|
||||
Object.keys(processo).forEach((key) => {
|
||||
if (
|
||||
key !== "id" &&
|
||||
key !== "dataCriacao" &&
|
||||
key !== "dataAtualizacao"
|
||||
) {
|
||||
setValue(key, processo[key]);
|
||||
}
|
||||
});
|
||||
if (processo.orgaoJulgadorRef) {
|
||||
setSelectedOrgao(processo.orgaoJulgadorRef);
|
||||
setValue("orgaoJulgador", processo.orgaoJulgadorRef.nome);
|
||||
}
|
||||
// Definir UF e tribunal ao editar
|
||||
setSelectedUF(processo.tribunalUF || "");
|
||||
setValue("tribunal", processo.tribunal || "");
|
||||
} else {
|
||||
reset({
|
||||
numero: "",
|
||||
natureza: "",
|
||||
tipoParteCliente: "",
|
||||
valorCausa: "",
|
||||
orgaoJulgador: "",
|
||||
orgaoOrigem: "",
|
||||
nomeContraparte: "",
|
||||
clienteId: "",
|
||||
tribunalUF: "",
|
||||
tribunal: "",
|
||||
});
|
||||
setSelectedUF("");
|
||||
}
|
||||
}, [isOpen, processo, setValue, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Tentar buscar órgãos existentes
|
||||
const loadOrgaos = async () => {
|
||||
try {
|
||||
const response = await axios.get<string[]>("/api/processos/orgaos");
|
||||
if (Array.isArray(response.data)) {
|
||||
setOrgaos(response.data.filter(Boolean));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar órgãos:", error);
|
||||
setOrgaos([]); // Em caso de erro, inicializar com array vazio
|
||||
}
|
||||
};
|
||||
loadOrgaos();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedUF) {
|
||||
setAvailableTribunais(TRIBUNAIS[selectedUF] || []);
|
||||
} else {
|
||||
setAvailableTribunais([]);
|
||||
}
|
||||
}, [selectedUF]);
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
const processData = {
|
||||
...data,
|
||||
numero: data.numero.replace(/\D/g, ""),
|
||||
tribunalUF: selectedUF,
|
||||
tribunal: data.tribunal,
|
||||
dadosDatajud: dadosDatajud,
|
||||
valorCausa: data.valorCausa ? Number(data.valorCausa) : undefined,
|
||||
};
|
||||
|
||||
if (processo) {
|
||||
await updateProcesso.mutateAsync({
|
||||
id: processo.id,
|
||||
data: processData
|
||||
});
|
||||
} else {
|
||||
await createProcesso.mutateAsync(processData);
|
||||
}
|
||||
|
||||
onSuccess?.();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Erro ao salvar processo:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Filtrar clientes baseado na busca
|
||||
const filteredClientes = clientes.filter(
|
||||
(cliente) =>
|
||||
cliente.nome.toLowerCase().includes(clienteSearch.toLowerCase()) ||
|
||||
cliente.cpfCnpj.includes(clienteSearch),
|
||||
);
|
||||
|
||||
// Filtrar órgãos baseado na busca
|
||||
const filteredOrgaos =
|
||||
orgaoQuery === ""
|
||||
? orgaos
|
||||
: orgaos.filter((orgao) =>
|
||||
orgao.toLowerCase().includes(orgaoQuery.toLowerCase()),
|
||||
);
|
||||
|
||||
const handleOrgaoSelect = (orgao: OrgaoJulgadorOption | null) => {
|
||||
setSelectedOrgao(orgao);
|
||||
if (orgao) {
|
||||
setValue("orgaoJulgador", orgao.nome);
|
||||
setValue("orgaoJulgadorId", orgao.id);
|
||||
}
|
||||
};
|
||||
|
||||
// Função para buscar dados do DataJud
|
||||
const buscarDadosDatajud = async (numero: string, tribunal: string) => {
|
||||
try {
|
||||
const dados = await consultarProcesso(numero, tribunal);
|
||||
if (dados) {
|
||||
setDadosDatajud(dados);
|
||||
// Preencher campos do formulário
|
||||
setValue("orgaoJulgador", dados.orgaoJulgador.nome);
|
||||
setValue("natureza", inferirNatureza(dados.classe.nome));
|
||||
// Outros campos conforme necessário
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar dados do DataJud:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Função auxiliar para inferir natureza do processo
|
||||
const inferirNatureza = (classeNome: string): string => {
|
||||
const nome = classeNome.toLowerCase();
|
||||
if (nome.includes("cível")) return "CIVEL";
|
||||
if (nome.includes("criminal")) return "CRIMINAL";
|
||||
if (nome.includes("trabalh")) return "TRABALHISTA";
|
||||
// ... outras regras
|
||||
return "CIVEL";
|
||||
};
|
||||
|
||||
// Adicionar evento no campo de número do processo
|
||||
useEffect(() => {
|
||||
const numero = watch("numero");
|
||||
const tribunal = watch("tribunal");
|
||||
|
||||
if (numero?.length >= 20 && tribunal) {
|
||||
// Número CNJ completo
|
||||
buscarDadosDatajud(numero, tribunal);
|
||||
}
|
||||
}, [watch("numero"), watch("tribunal")]);
|
||||
|
||||
return (
|
||||
<Transition show={isOpen} as={React.Fragment}>
|
||||
<Dialog onClose={onClose} className="relative z-50">
|
||||
{/* ...existing modal backdrop code... */}
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<Dialog.Panel className="mx-auto max-w-4xl w-full bg-white rounded-xl shadow-2xl">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<Dialog.Title className="text-2xl font-bold text-gray-900">
|
||||
{processo ? "Editar Processo" : "Novo Processo"}
|
||||
</Dialog.Title>
|
||||
<button onClick={onClose}>
|
||||
<XMarkIcon className="h-6 w-6 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Substituir o FormSelect por uma busca customizada */}
|
||||
<div className="col-span-2 space-y-2">
|
||||
<label className="block text-sm font-semibold text-gray-700">
|
||||
Cliente <span className="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
{/* Campo de busca */}
|
||||
<div className="relative">
|
||||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar cliente por nome ou CPF..."
|
||||
className="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
value={clienteSearch}
|
||||
onChange={(e) => setClienteSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Lista de clientes filtrada */}
|
||||
<div className="mt-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg">
|
||||
{filteredClientes.length > 0 ? (
|
||||
<div className="divide-y divide-gray-200">
|
||||
{filteredClientes.map((cliente) => (
|
||||
<label
|
||||
key={cliente.id}
|
||||
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
{...register("clienteId", {
|
||||
required: "Cliente é obrigatório",
|
||||
})}
|
||||
value={cliente.id}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
|
||||
/>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{cliente.nome}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
CPF: {cliente.cpfCnpj}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
Nenhum cliente encontrado
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormInput
|
||||
label="Número do Processo"
|
||||
{...register("numero")}
|
||||
placeholder="0000000-00.0000.000.0000"
|
||||
required
|
||||
/>
|
||||
<FormSelect
|
||||
label="Natureza"
|
||||
{...register("natureza")}
|
||||
options={Object.values(NaturezaProcesso).map((value) => ({
|
||||
value,
|
||||
label: value.charAt(0) + value.slice(1).toLowerCase(),
|
||||
}))}
|
||||
required
|
||||
/>
|
||||
<FormSelect
|
||||
label="Tipo de Parte"
|
||||
{...register("tipoParteCliente")}
|
||||
options={Object.values(TipoParte).map((value) => ({
|
||||
value,
|
||||
label: value.charAt(0) + value.slice(1).toLowerCase(),
|
||||
}))}
|
||||
required
|
||||
/>
|
||||
<FormInput
|
||||
label="Valor da Causa"
|
||||
type="number"
|
||||
step="0.01"
|
||||
{...register("valorCausa")}
|
||||
/>
|
||||
<FormSelect
|
||||
label="UF do Tribunal"
|
||||
value={selectedUF || ""} // Garantir que nunca seja null
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedUF(value);
|
||||
setValue("tribunal", ""); // Limpar tribunal ao mudar UF
|
||||
}}
|
||||
required
|
||||
options={UF_BRASIL}
|
||||
/>
|
||||
|
||||
<FormSelect
|
||||
label="Tribunal"
|
||||
value={watch("tribunal") || ""} // Garantir que nunca seja null
|
||||
onChange={(e) => setValue("tribunal", e.target.value)}
|
||||
required
|
||||
options={availableTribunais}
|
||||
disabled={!selectedUF}
|
||||
/>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
||||
Órgão Julgador <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500"
|
||||
{...register("orgaoJulgador", { required: true })}
|
||||
onChange={(e) => {
|
||||
setValue("orgaoJulgador", e.target.value);
|
||||
setOrgaoQuery(e.target.value);
|
||||
}}
|
||||
autoComplete="off"
|
||||
placeholder="Digite o órgão julgador..."
|
||||
/>
|
||||
{/* Lista de sugestões */}
|
||||
{orgaoQuery && (
|
||||
<ul className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-auto">
|
||||
{filteredOrgaos.map((orgao, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="px-4 py-2 hover:bg-blue-50 cursor-pointer"
|
||||
onClick={() => {
|
||||
setValue("orgaoJulgador", orgao);
|
||||
setOrgaoQuery("");
|
||||
}}
|
||||
>
|
||||
{orgao}
|
||||
</li>
|
||||
))}
|
||||
{filteredOrgaos.length === 0 && (
|
||||
<li className="px-4 py-2 text-gray-500 italic">
|
||||
Pressione Enter para adicionar "{orgaoQuery}"
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<FormInput
|
||||
label="Órgão de Origem"
|
||||
{...register("orgaoOrigem")}
|
||||
disabled={copiarOrgaoJulgador}
|
||||
/>
|
||||
<FormCheckbox
|
||||
label="Mesmo que Órgão Julgador"
|
||||
checked={copiarOrgaoJulgador}
|
||||
onChange={(e) =>
|
||||
setCopiarOrgaoJulgador(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FormInput
|
||||
label="Nome da Contraparte"
|
||||
{...register("nomeContraparte")}
|
||||
required
|
||||
/>
|
||||
<FormSelect
|
||||
label="Status de Atividade"
|
||||
{...register("statusAtividade")}
|
||||
options={[
|
||||
{ value: "ATIVO", label: "Ativo" },
|
||||
{ value: "BAIXADO", label: "Baixado" },
|
||||
{ value: "SUSPENSO", label: "Suspenso" },
|
||||
]}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{dadosDatajud && (
|
||||
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
|
||||
<h3 className="text-sm font-medium text-blue-800 mb-2">
|
||||
Dados encontrados no DataJud
|
||||
</h3>
|
||||
<ProcessoInfo data={dadosDatajud} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-3 pt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
{processo ? "Salvar" : "Criar"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
import React from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import api from '../../services/api';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { Processo } from '../../types/entities';
|
||||
|
||||
const resultadoOptions = [
|
||||
{ value: 'NAO_JULGADO', label: 'Não Julgado' },
|
||||
{ value: 'PROCEDENTE', label: 'Procedente' },
|
||||
{ value: 'PARCIALMENTE_PROCEDENTE', label: 'Parcialmente Procedente' },
|
||||
{ value: 'IMPROCEDENTE', label: 'Improcedente' },
|
||||
{ value: 'SEM_ANALISE_MERITO', label: 'Sem Análise de Mérito' }
|
||||
];
|
||||
|
||||
const getResultadoLabel = (valor: string) => {
|
||||
return resultadoOptions.find(opt => opt.value === valor)?.label || valor;
|
||||
};
|
||||
|
||||
const getResultadoColor = (resultado: string) => {
|
||||
switch (resultado) {
|
||||
case 'PROCEDENTE': return 'bg-green-100 text-green-800';
|
||||
case 'PARCIALMENTE_PROCEDENTE': return 'bg-yellow-100 text-yellow-800';
|
||||
case 'IMPROCEDENTE': return 'bg-red-100 text-red-800';
|
||||
case 'SEM_ANALISE_MERITO': return 'bg-gray-100 text-gray-800';
|
||||
default: return 'bg-blue-100 text-blue-800';
|
||||
}
|
||||
};
|
||||
|
||||
export const ProcessoView: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const { data: processo, refetch, isLoading, isError } = useQuery<Processo>(
|
||||
['processo', id],
|
||||
async () => {
|
||||
const response = await api.get(`/processos/${id}`); // remover /api
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Carregando...</div>;
|
||||
}
|
||||
|
||||
if (isError || !processo) {
|
||||
return <div>Erro ao carregar processo. Processo não encontrado.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Status e Resultado do Processo */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white">Status do Processo</h3>
|
||||
|
||||
{/* Botão de Julgamento */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm text-gray-500">Processo julgado?</span>
|
||||
<select
|
||||
value={processo.resultado}
|
||||
onChange={async (e) => {
|
||||
try {
|
||||
await api.patch(`/processos/${processo.id}/resultado`, {
|
||||
resultado: e.target.value
|
||||
});
|
||||
refetch();
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar resultado:', error);
|
||||
alert('Erro ao atualizar resultado do processo');
|
||||
}
|
||||
}}
|
||||
className="rounded-lg border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
{resultadoOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm font-medium text-gray-500">Status:</span>
|
||||
<span className="px-2 py-1 text-sm rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
{processo.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm font-medium text-gray-500">Resultado:</span>
|
||||
<span className={`px-2 py-1 text-sm rounded-full ${getResultadoColor(processo.resultado)}`}>
|
||||
{getResultadoLabel(processo.resultado)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resto das informações do processo */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Processo {processo.numero}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Cliente</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{processo.cliente.nome}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Contraparte</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{processo.nomeContraparte}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Natureza</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{processo.natureza}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Status</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{processo.status}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Valor da Causa</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{processo.valorCausa?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Data de Criação</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{format(new Date(processo.dataCriacao), 'dd/MM/yyyy', { locale: ptBR })}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Última Atualização</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">{format(new Date(processo.dataAtualizacao), 'dd/MM/yyyy', { locale: ptBR })}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-medium mb-4">Resultado</h3>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className={`px-2 py-1 text-sm rounded-full ${getResultadoColor(processo.resultado)}`}>
|
||||
{getResultadoLabel(processo.resultado)}
|
||||
</span>
|
||||
<select
|
||||
value={processo.resultado}
|
||||
onChange={async (e) => {
|
||||
try {
|
||||
await api.patch(`/processos/${processo.id}/resultado`, {
|
||||
resultado: e.target.value
|
||||
});
|
||||
refetch(); // Atualizar dados do processo
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar resultado:', error);
|
||||
alert('Erro ao atualizar resultado do processo');
|
||||
}
|
||||
}}
|
||||
className="rounded-lg border-gray-300 text-sm"
|
||||
>
|
||||
{resultadoOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Processo } from "../../types/entities";
|
||||
import { useProcessos } from "../../hooks/useProcessos";
|
||||
import { formatNumeroProcesso } from "../../utils/formatters";
|
||||
import { DocumentTextIcon, ClockIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
interface ProcessosListProps {
|
||||
searchTerm: string;
|
||||
onEdit: (processo: Processo) => void;
|
||||
}
|
||||
|
||||
const resultadoLabel = {
|
||||
NAO_JULGADO: 'Não Julgado',
|
||||
PROCEDENTE: 'Procedente',
|
||||
PARCIALMENTE_PROCEDENTE: 'Parcialmente Procedente',
|
||||
IMPROCEDENTE: 'Improcedente',
|
||||
SEM_ANALISE_MERITO: 'Sem Análise de Mérito'
|
||||
};
|
||||
|
||||
const resultadoColor = {
|
||||
NAO_JULGADO: 'bg-gray-100 text-gray-800',
|
||||
PROCEDENTE: 'bg-green-100 text-green-800',
|
||||
PARCIALMENTE_PROCEDENTE: 'bg-yellow-100 text-yellow-800',
|
||||
IMPROCEDENTE: 'bg-red-100 text-red-800',
|
||||
SEM_ANALISE_MERITO: 'bg-gray-100 text-gray-800'
|
||||
};
|
||||
|
||||
const getResultadoColor = (resultado: string) => {
|
||||
return resultadoColor[resultado as keyof typeof resultadoColor] || 'bg-gray-100 text-gray-800';
|
||||
};
|
||||
|
||||
export const ProcessosList: React.FC<ProcessosListProps> = ({
|
||||
searchTerm,
|
||||
onEdit,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { processos, isLoading } = useProcessos();
|
||||
|
||||
const filteredProcessos = processos.filter(
|
||||
(processo) =>
|
||||
processo.numero.includes(searchTerm) ||
|
||||
processo.nomeContraparte
|
||||
?.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
const getResultadoDisplay = (processo: Processo) => {
|
||||
if (!processo.resultado || !resultadoLabel[processo.resultado as keyof typeof resultadoLabel]) {
|
||||
return (
|
||||
<span className="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800">
|
||||
Não Julgado
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${getResultadoColor(processo.resultado)}`}>
|
||||
{resultadoLabel[processo.resultado as keyof typeof resultadoLabel]}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="animate-pulse">Carregando...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase"
|
||||
>
|
||||
Processo
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase"
|
||||
>
|
||||
Natureza
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase"
|
||||
>
|
||||
Parte
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase"
|
||||
>
|
||||
Próximos Prazos
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
|
||||
>
|
||||
Resultado
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{filteredProcessos.map((processo) => (
|
||||
<tr
|
||||
key={processo.id}
|
||||
onClick={() => navigate(`/processos/${processo.id}`)}
|
||||
className="hover:bg-gray-50 cursor-pointer"
|
||||
>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<DocumentTextIcon className="h-10 w-10 text-gray-400" />
|
||||
<div className="ml-4">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{formatNumeroProcesso(processo.numero)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{processo.orgaoJulgador}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-500">
|
||||
{processo.natureza}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-900">
|
||||
{processo.tipoParteCliente === "AUTOR" ? "Autor" : "Réu"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{processo.nomeContraparte}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span
|
||||
className={`px-2 py-1 text-xs rounded-full ${
|
||||
processo.statusAtividade === "ATIVO"
|
||||
? "bg-green-100 text-green-800"
|
||||
: processo.statusAtividade === "BAIXADO"
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: processo.statusAtividade === "SUSPENSO"
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
}`}
|
||||
>
|
||||
{processo.statusAtividade}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{processo.diligencias?.length > 0 ? (
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<ClockIcon className="h-4 w-4 mr-1" />
|
||||
{processo.diligencias.length} prazos
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-gray-500">Sem prazos</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{getResultadoDisplay(processo)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import React, { useState } from "react";
|
||||
import { PlusIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { ProcessosList } from "./ProcessosList";
|
||||
import { ProcessoModal } from "./ProcessoModal";
|
||||
import { Processo } from "../../types/entities";
|
||||
|
||||
export const ProcessosPage: React.FC = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedProcesso, setSelectedProcesso] = useState<Processo | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar processos..."
|
||||
className="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-white
|
||||
focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedProcesso(null);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg
|
||||
hover:bg-blue-700 dark:hover:bg-blue-500 transition-colors"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5 mr-2" />
|
||||
Novo Processo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lista de Processos */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
{/* ...headers... */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{/* ...rows... */}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ProcessosList
|
||||
searchTerm={searchTerm}
|
||||
onEdit={(processo) => {
|
||||
setSelectedProcesso(processo);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<ProcessoModal
|
||||
isOpen={isModalOpen}
|
||||
processo={selectedProcesso}
|
||||
onClose={() => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedProcesso(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,262 @@
|
||||
import {
|
||||
Dialog,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { TipoCustaProcessual, StatusPagamento } from "@prisma/client";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import ptBR from "date-fns/locale/pt-BR";
|
||||
|
||||
interface DialogCustaProcessualProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: any) => Promise<void>;
|
||||
editingCusta?: any;
|
||||
}
|
||||
|
||||
export function DialogCustaProcessual({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
editingCusta,
|
||||
}: DialogCustaProcessualProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
tipo: "",
|
||||
descricao: "",
|
||||
valor: "",
|
||||
percentual: "",
|
||||
status: "PENDENTE",
|
||||
vencimento: null as Date | null,
|
||||
dataPagamento: null as Date | null,
|
||||
observacoes: "",
|
||||
numeroParcelas: 1,
|
||||
parcelaAtual: 1,
|
||||
comprovante: "",
|
||||
});
|
||||
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
|
||||
// Atualizar form quando o editingCusta muda
|
||||
useEffect(() => {
|
||||
if (editingCusta) {
|
||||
setFormData({
|
||||
tipo: editingCusta.tipo || "",
|
||||
descricao: editingCusta.descricao || "",
|
||||
valor: editingCusta.valor?.toString() || "",
|
||||
percentual: editingCusta.percentual?.toString() || "",
|
||||
status: editingCusta.status || "PENDENTE",
|
||||
vencimento: editingCusta.vencimento
|
||||
? new Date(editingCusta.vencimento)
|
||||
: null,
|
||||
dataPagamento: editingCusta.dataPagamento
|
||||
? new Date(editingCusta.dataPagamento)
|
||||
: null,
|
||||
observacoes: editingCusta.observacoes || "",
|
||||
numeroParcelas: editingCusta.numeroParcelas || 1,
|
||||
parcelaAtual: editingCusta.parcelaAtual || 1,
|
||||
comprovante: editingCusta.comprovante || "",
|
||||
});
|
||||
} else {
|
||||
// Reset para os valores padrão
|
||||
setFormData({
|
||||
tipo: "",
|
||||
descricao: "",
|
||||
valor: "",
|
||||
percentual: "",
|
||||
status: "PENDENTE",
|
||||
vencimento: null,
|
||||
dataPagamento: null,
|
||||
observacoes: "",
|
||||
numeroParcelas: 1,
|
||||
parcelaAtual: 1,
|
||||
comprovante: "",
|
||||
});
|
||||
}
|
||||
}, [editingCusta, open]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormError(null);
|
||||
|
||||
try {
|
||||
// Validar campos obrigatórios
|
||||
if (!formData.tipo) {
|
||||
setFormError("Selecione o tipo da custa");
|
||||
return;
|
||||
}
|
||||
if (!formData.descricao) {
|
||||
setFormError("Informe a descrição da custa");
|
||||
return;
|
||||
}
|
||||
if (!formData.valor) {
|
||||
setFormError("Informe o valor da custa");
|
||||
return;
|
||||
}
|
||||
|
||||
// Preparar dados
|
||||
const preparedData = {
|
||||
...formData,
|
||||
valor: Number(formData.valor),
|
||||
percentual: formData.percentual ? Number(formData.percentual) : null,
|
||||
numeroParcelas: Number(formData.numeroParcelas),
|
||||
parcelaAtual: Number(formData.parcelaAtual),
|
||||
};
|
||||
|
||||
await onSave(preparedData);
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao salvar custa:", error);
|
||||
setFormError(error.message || "Erro ao salvar custa. Tente novamente.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ p: 3 }}>
|
||||
<h2 className="text-lg font-semibold mb-4">
|
||||
{editingCusta ? "Editar Custa Processual" : "Nova Custa Processual"}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Tipo"
|
||||
value={formData.tipo}
|
||||
onChange={(e) => setFormData({ ...formData, tipo: e.target.value })}
|
||||
required
|
||||
>
|
||||
{Object.values(TipoCustaProcessual).map((tipo) => (
|
||||
<MenuItem key={tipo} value={tipo}>
|
||||
{tipo.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Descrição"
|
||||
value={formData.descricao}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, descricao: e.target.value })
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor"
|
||||
type="number"
|
||||
value={formData.valor}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valor: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual (se aplicável)"
|
||||
type="number"
|
||||
value={formData.percentual}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentual: e.target.value })
|
||||
}
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ptBR}
|
||||
>
|
||||
<DatePicker
|
||||
label="Vencimento"
|
||||
value={formData.vencimento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, vencimento: date })
|
||||
}
|
||||
sx={{ width: "100%", mb: 2 }}
|
||||
/>
|
||||
|
||||
{formData.status === "PAGO" && (
|
||||
<DatePicker
|
||||
label="Data de Pagamento"
|
||||
value={formData.dataPagamento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, dataPagamento: date })
|
||||
}
|
||||
sx={{ width: "100%" }}
|
||||
/>
|
||||
)}
|
||||
</LocalizationProvider>
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Status"
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, status: e.target.value })
|
||||
}
|
||||
required
|
||||
>
|
||||
{Object.values(StatusPagamento).map((status) => (
|
||||
<MenuItem key={status} value={status}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Número de Parcelas"
|
||||
type="number"
|
||||
value={formData.numeroParcelas}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
numeroParcelas: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
inputProps={{ min: 1 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Observações"
|
||||
multiline
|
||||
rows={3}
|
||||
value={formData.observacoes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, observacoes: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formError && (
|
||||
<Box sx={{ mt: 2, p: 2, bgcolor: "error.light", borderRadius: 1 }}>
|
||||
<Typography color="error">{formError}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "flex-end", gap: 2 }}
|
||||
>
|
||||
<Button onClick={onClose}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Salvar
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default DialogCustaProcessual;
|
||||
@@ -0,0 +1,353 @@
|
||||
import {
|
||||
Dialog,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { FormaPagamento, StatusPagamento } from "@prisma/client";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import ptBR from "date-fns/locale/pt-BR";
|
||||
import { IHonorario, IHonorarioCreate } from "../types/financeiro";
|
||||
|
||||
interface DialogHonorarioProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: Partial<IHonorarioCreate>) => Promise<void>;
|
||||
editingHonorario?: IHonorario;
|
||||
}
|
||||
|
||||
const DialogHonorario: React.FC<DialogHonorarioProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
editingHonorario,
|
||||
}) => {
|
||||
const [formData, setFormData] = useState({
|
||||
formaPagamento: "VISTA",
|
||||
valorFixo: "",
|
||||
percentualCausa: "",
|
||||
percentualExito: "",
|
||||
valorEntrada: "",
|
||||
numeroParcelas: 1,
|
||||
valorParcela: "",
|
||||
diaVencimento: 10,
|
||||
status: "PENDENTE",
|
||||
dataVencimento: new Date(),
|
||||
observacoes: "",
|
||||
});
|
||||
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
|
||||
// Atualizar form quando o editingHonorario muda
|
||||
useEffect(() => {
|
||||
if (editingHonorario) {
|
||||
setFormData({
|
||||
formaPagamento: editingHonorario.formaPagamento,
|
||||
valorFixo: editingHonorario.valorFixo || "",
|
||||
percentualCausa: editingHonorario.percentualCausa || "",
|
||||
percentualExito: editingHonorario.percentualExito || "",
|
||||
valorEntrada: editingHonorario.valorEntrada || "",
|
||||
numeroParcelas: editingHonorario.numeroParcelas || 1,
|
||||
valorParcela: editingHonorario.valorParcela || "",
|
||||
diaVencimento: editingHonorario.diaVencimento || 10,
|
||||
status: editingHonorario.status,
|
||||
dataVencimento: editingHonorario.dataVencimento
|
||||
? new Date(editingHonorario.dataVencimento)
|
||||
: new Date(),
|
||||
observacoes: editingHonorario.observacoes || "",
|
||||
});
|
||||
} else {
|
||||
// Reset para os valores padrão
|
||||
setFormData({
|
||||
formaPagamento: "VISTA",
|
||||
valorFixo: "",
|
||||
percentualCausa: "",
|
||||
percentualExito: "",
|
||||
valorEntrada: "",
|
||||
numeroParcelas: 1,
|
||||
valorParcela: "",
|
||||
diaVencimento: 10,
|
||||
status: "PENDENTE",
|
||||
dataVencimento: new Date(),
|
||||
observacoes: "",
|
||||
});
|
||||
}
|
||||
}, [editingHonorario, open]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormError(null);
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
const validationError = validateFormData(formData);
|
||||
if (validationError) {
|
||||
setFormError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Converter strings numéricas
|
||||
const preparedData = {
|
||||
...formData,
|
||||
valorFixo: formData.valorFixo ? Number(formData.valorFixo) : null,
|
||||
percentualCausa: formData.percentualCausa
|
||||
? Number(formData.percentualCausa)
|
||||
: null,
|
||||
percentualExito: formData.percentualExito
|
||||
? Number(formData.percentualExito)
|
||||
: null,
|
||||
valorEntrada: formData.valorEntrada
|
||||
? Number(formData.valorEntrada)
|
||||
: null,
|
||||
valorParcela: formData.valorParcela
|
||||
? Number(formData.valorParcela)
|
||||
: null,
|
||||
};
|
||||
|
||||
await onSave(preparedData);
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao salvar honorário:", error);
|
||||
setFormError(
|
||||
error.message || "Erro ao salvar honorário. Tente novamente.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validateFormData = (data: any): string | null => {
|
||||
if (!data.formaPagamento) {
|
||||
return "Forma de pagamento é obrigatória";
|
||||
}
|
||||
|
||||
switch (data.formaPagamento) {
|
||||
case "VISTA":
|
||||
if (!data.valorFixo) {
|
||||
return "Informe o valor do honorário";
|
||||
}
|
||||
break;
|
||||
case "PERCENTUAL_CAUSA":
|
||||
if (!data.percentualCausa) {
|
||||
return "Informe o percentual da causa";
|
||||
}
|
||||
break;
|
||||
case "PERCENTUAL_EXITO":
|
||||
if (!data.percentualExito) {
|
||||
return "Informe o percentual do êxito";
|
||||
}
|
||||
break;
|
||||
case "PARCELADO":
|
||||
case "ENTRADA_MAIS_PARCELAS":
|
||||
if (!data.valorParcela) {
|
||||
return "Informe o valor da parcela";
|
||||
}
|
||||
if (!data.numeroParcelas || data.numeroParcelas < 1) {
|
||||
return "Informe o número de parcelas";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Mostrar campos de entrada quando for percentual com entrada
|
||||
const showEntrada =
|
||||
formData.formaPagamento === "ENTRADA_MAIS_PARCELAS" ||
|
||||
formData.formaPagamento === "PERCENTUAL_CAUSA_COM_ENTRADA";
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ p: 3 }}>
|
||||
<h2 className="text-lg font-semibold mb-4">
|
||||
{editingHonorario ? "Editar Honorário" : "Novo Honorário"}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Forma de Pagamento</InputLabel>
|
||||
<Select
|
||||
value={formData.formaPagamento}
|
||||
label="Forma de Pagamento"
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, formaPagamento: e.target.value })
|
||||
}
|
||||
>
|
||||
{Object.values(FormaPagamento).map((forma) => (
|
||||
<MenuItem key={forma} value={forma}>
|
||||
{forma.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{formData.formaPagamento === "VISTA" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor"
|
||||
type="number"
|
||||
value={formData.valorFixo}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorFixo: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(formData.formaPagamento === "PERCENTUAL_CAUSA" ||
|
||||
formData.formaPagamento === "PERCENTUAL_CAUSA_COM_ENTRADA") && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual da Causa"
|
||||
type="number"
|
||||
value={formData.percentualCausa}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentualCausa: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formData.formaPagamento === "PERCENTUAL_EXITO" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Percentual do Êxito"
|
||||
type="number"
|
||||
value={formData.percentualExito}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, percentualExito: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01", min: "0", max: "100" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(formData.formaPagamento === "PARCELADO" ||
|
||||
formData.formaPagamento === "ENTRADA_MAIS_PARCELAS") && (
|
||||
<>
|
||||
{showEntrada && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor da Entrada"
|
||||
type="number"
|
||||
value={formData.valorEntrada}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorEntrada: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
)}
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Número de Parcelas"
|
||||
type="number"
|
||||
value={formData.numeroParcelas}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
numeroParcelas: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
required
|
||||
inputProps={{ min: "1" }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Valor da Parcela"
|
||||
type="number"
|
||||
value={formData.valorParcela}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, valorParcela: e.target.value })
|
||||
}
|
||||
required
|
||||
inputProps={{ step: "0.01" }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Dia do Vencimento"
|
||||
type="number"
|
||||
value={formData.diaVencimento}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
diaVencimento: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
inputProps={{ min: 1, max: 31 }}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Status"
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, status: e.target.value })
|
||||
}
|
||||
required
|
||||
>
|
||||
{Object.values(StatusPagamento).map((status) => (
|
||||
<MenuItem key={status} value={status}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ptBR}
|
||||
>
|
||||
<DatePicker
|
||||
label="Data de Vencimento"
|
||||
value={formData.dataVencimento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, dataVencimento: date || new Date() })
|
||||
}
|
||||
sx={{ width: "100%" }}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Observações"
|
||||
multiline
|
||||
rows={3}
|
||||
value={formData.observacoes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, observacoes: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formError && (
|
||||
<Box sx={{ mt: 2, p: 2, bgcolor: "error.light", borderRadius: 1 }}>
|
||||
<Typography color="error">{formError}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "flex-end", gap: 2 }}
|
||||
>
|
||||
<Button onClick={onClose}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Salvar
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogHonorario;
|
||||
@@ -0,0 +1,213 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import ptBR from "date-fns/locale/pt-BR";
|
||||
import { formatCurrency } from "../../../utils/format";
|
||||
import { StatusPagamento } from "@prisma/client";
|
||||
|
||||
interface ParcelaHonorario {
|
||||
id: string;
|
||||
honorarioId: string;
|
||||
numeroParcela: number;
|
||||
valor: number;
|
||||
valorPago?: number | null;
|
||||
vencimento: Date;
|
||||
dataPagamento?: Date | null;
|
||||
status: StatusPagamento;
|
||||
comprovante?: string | null;
|
||||
observacoes?: string | null;
|
||||
}
|
||||
|
||||
interface DialogParcelaProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (data: any) => Promise<void>;
|
||||
parcela: ParcelaHonorario;
|
||||
tipo: "honorario" | "custa";
|
||||
}
|
||||
|
||||
export function DialogParcela({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
parcela,
|
||||
tipo,
|
||||
}: DialogParcelaProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
status: parcela?.status || "PENDENTE",
|
||||
dataPagamento: parcela?.dataPagamento
|
||||
? new Date(parcela?.dataPagamento)
|
||||
: null,
|
||||
valorPago: parcela?.valorPago || "",
|
||||
comprovante: parcela?.comprovante || "",
|
||||
observacoes: parcela?.observacoes || "",
|
||||
});
|
||||
|
||||
const handleValorPagoChange = (valor: string) => {
|
||||
const valorPago = Number(valor);
|
||||
const valorParcela = Number(parcela.valor);
|
||||
|
||||
let novoStatus = "PENDENTE";
|
||||
if (valorPago >= valorParcela) {
|
||||
novoStatus = "PAGO";
|
||||
} else if (valorPago > 0) {
|
||||
novoStatus = "PARCIALMENTE_PAGO";
|
||||
}
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
valorPago: valor,
|
||||
status: novoStatus,
|
||||
dataPagamento: novoStatus === "PAGO" ? new Date() : prev.dataPagamento,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await onSave(formData);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm" />
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="div"
|
||||
className="flex items-center justify-between mb-4"
|
||||
>
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900">
|
||||
Atualizar Parcela {parcela?.numeroParcela}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-gray-500"
|
||||
onClick={onClose}
|
||||
>
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</Dialog.Title>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Valor da Parcela
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formatCurrency(parcela.valor)}
|
||||
disabled
|
||||
className="mt-1 block w-full rounded-md border-gray-300 bg-gray-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Valor Pago
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.valorPago}
|
||||
onChange={(e) => handleValorPagoChange(e.target.value)}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, status: e.target.value })
|
||||
}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
>
|
||||
{Object.values(StatusPagamento).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{formData.status === "PAGO" && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Data do Pagamento
|
||||
</label>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ptBR}
|
||||
>
|
||||
<DatePicker
|
||||
value={formData.dataPagamento}
|
||||
onChange={(date) =>
|
||||
setFormData({ ...formData, dataPagamento: date })
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Comprovante (URL)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.comprovante}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, comprovante: e.target.value })
|
||||
}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Observações
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.observacoes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, observacoes: e.target.value })
|
||||
}
|
||||
rows={3}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
Salvar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as DialogCustaProcessual } from "./DialogCustaProcessual";
|
||||
export { default as DialogHonorario } from "./DialogHonorario";
|
||||
export { default as DialogParcela } from "./DialogParcela";
|
||||
@@ -0,0 +1,53 @@
|
||||
import { memo } from "react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
import { Edit as EditIcon } from "@mui/icons-material";
|
||||
import { formatCurrency, formatDate } from "../../../utils/format";
|
||||
|
||||
const CustasTable = memo(({ custas, onEdit, getStatusColor }) => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Tipo</TableCell>
|
||||
<TableCell>Descrição</TableCell>
|
||||
<TableCell align="right">Valor</TableCell>
|
||||
<TableCell>Vencimento</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Ações</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{custas?.map((custa) => (
|
||||
<TableRow key={custa.id}>
|
||||
<TableCell>{custa.tipo}</TableCell>
|
||||
<TableCell>{custa.descricao}</TableCell>
|
||||
<TableCell align="right">{formatCurrency(custa.valor)}</TableCell>
|
||||
<TableCell>{formatDate(custa.vencimento)}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={custa.status}
|
||||
color={getStatusColor(custa.status)}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => onEdit(custa)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
});
|
||||
|
||||
export default CustasTable;
|
||||
Reference in new issue
Block a user