Files
advx/src/components/processos/ProcessoDetalhes.tsx
T
2025-04-10 23:07:21 -03:00

647 lines
26 KiB
TypeScript

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>
);
};