309 lines
12 KiB
TypeScript
309 lines
12 KiB
TypeScript
import { useState } from "react";
|
|
import { useParams, useNavigate } from "react-router-dom";
|
|
import { useQuery, useQueryClient } from "react-query";
|
|
import { Tab } from "@headlessui/react";
|
|
import { classNames } from "../../utils/classNames";
|
|
import {
|
|
ArrowLeftIcon,
|
|
DocumentTextIcon,
|
|
ClockIcon,
|
|
PencilIcon,
|
|
TrashIcon,
|
|
CurrencyDollarIcon,
|
|
} from "@heroicons/react/24/outline";
|
|
import { formatCurrency } from "../../utils/format";
|
|
import { useProcessosAdministrativos } from "../../hooks/useProcessosAdministrativos";
|
|
import { ProcessoAdminModal } from "./ProcessoAdminModal";
|
|
import { MovimentacaoAdministrativaDialog } from "./MovimentacaoAdministrativaDialog";
|
|
import { MovimentacaoAdminActions } from "./MovimentacaoAdminActions"; // Adicionar esta importação
|
|
import api from "../../services/api";
|
|
import { CustaAdministrativaPanel } from "./CustaAdministrativaPanel";
|
|
|
|
const tabs = [
|
|
{ id: "geral", name: "Informações Gerais", icon: DocumentTextIcon },
|
|
{ id: "movimentacoes", name: "Movimentações", icon: ClockIcon },
|
|
{ id: "financeiro", name: "Financeiro", icon: CurrencyDollarIcon }, // Adicionar nova tab
|
|
];
|
|
|
|
export function ProcessoAdminDetalhes() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const [selectedTab, setSelectedTab] = useState(0);
|
|
const [isMovimentacaoModalOpen, setIsMovimentacaoModalOpen] = useState(false);
|
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
const [movimentacaoParaEditar, setMovimentacaoParaEditar] = useState<MovimentacaoAdministrativa | null>(null);
|
|
|
|
// Se o id for "novo", renderizar diretamente o modal de criação
|
|
if (id === "novo") {
|
|
return (
|
|
<ProcessoAdminModal
|
|
isOpen={true}
|
|
onClose={() => navigate("/processos-administrativos")}
|
|
onSuccess={() => navigate("/processos-administrativos")}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const { data: processo, isLoading, error } = useQuery(
|
|
["processo-administrativo", id],
|
|
async () => {
|
|
const response = await api.get(`/processos-administrativos/${id}`);
|
|
return response.data;
|
|
},
|
|
{
|
|
enabled: !!id && id !== "novo", // Só executa se tiver um id válido
|
|
retry: 2, // Tenta no máximo 2 vezes em caso de erro
|
|
onError: (error) => {
|
|
console.error("Erro ao carregar processo:", error);
|
|
}
|
|
}
|
|
);
|
|
|
|
const { deleteProcesso } = useProcessosAdministrativos();
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
await deleteProcesso.mutateAsync(id!);
|
|
navigate("/processos-administrativos");
|
|
} catch (error) {
|
|
console.error("Erro ao excluir processo:", error);
|
|
}
|
|
};
|
|
|
|
const handleSaveMovimentacao = async (data: any) => {
|
|
try {
|
|
await api.post(`/processos-administrativos/${id}/movimentacoes`, data);
|
|
// Recarregar dados
|
|
queryClient.invalidateQueries(["processo-administrativo", id]);
|
|
} catch (error) {
|
|
console.error("Erro ao salvar movimentação:", error);
|
|
}
|
|
};
|
|
|
|
const handleEditMovimentacao = async (data: any) => {
|
|
try {
|
|
// Atualizar URL para usar a nova rota
|
|
await api.put(`/processos-administrativos/movimentacoes/${data.id}`, data);
|
|
queryClient.invalidateQueries(["processo-administrativo", id]);
|
|
setMovimentacaoParaEditar(null);
|
|
} catch (error) {
|
|
console.error("Erro ao editar movimentação:", error);
|
|
}
|
|
};
|
|
|
|
const handleDeleteMovimentacao = async (movimentacaoId: string) => {
|
|
if (!window.confirm("Deseja realmente excluir esta movimentação?")) return;
|
|
|
|
try {
|
|
// Atualizar URL para usar a nova rota
|
|
await api.delete(`/processos-administrativos/movimentacoes/${movimentacaoId}`);
|
|
queryClient.invalidateQueries(["processo-administrativo", id]);
|
|
} catch (error) {
|
|
console.error("Erro ao excluir movimentação:", error);
|
|
}
|
|
};
|
|
|
|
if (!processo) return <div>Processo não encontrado</div>;
|
|
if (isLoading) return <div>Carregando...</div>;
|
|
if (error) return <div>Erro ao carregar processo</div>;
|
|
|
|
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-administrativos")}
|
|
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">
|
|
{processo.numero}
|
|
</h1>
|
|
<p className="text-sm text-gray-500">{processo.orgao}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-2">
|
|
<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
|
|
</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>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<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-4">
|
|
{/* 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/Protocolo
|
|
</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">{processo.numero}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm font-medium text-gray-500">Órgão</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">{processo.orgao}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm font-medium text-gray-500">Status</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">{processo.status}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm font-medium text-gray-500">
|
|
Valor da Pretensão
|
|
</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">
|
|
{processo.valorPretensao
|
|
? formatCurrency(processo.valorPretensao)
|
|
: "-"}
|
|
</dd>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<dt className="text-sm font-medium text-gray-500">Descrição</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">
|
|
{processo.descricao}
|
|
</dd>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<dt className="text-sm font-medium text-gray-500">Observações</dt>
|
|
<dd className="mt-1 text-sm text-gray-900">
|
|
{processo.observacoes || "-"}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</Tab.Panel>
|
|
|
|
{/* Movimentações */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-lg font-medium text-gray-900">Movimentações</h3>
|
|
<button
|
|
onClick={() => setIsMovimentacaoModalOpen(true)}
|
|
className="flex items-center px-4 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-lg"
|
|
>
|
|
Nova Movimentação
|
|
</button>
|
|
</div>
|
|
|
|
{processo.movimentacoes?.length > 0 ? (
|
|
<div className="flow-root">
|
|
<ul role="list" className="-mb-8">
|
|
{processo.movimentacoes.map((mov, idx) => (
|
|
<li key={mov.id}>
|
|
<div className="relative pb-8">
|
|
{idx !== processo.movimentacoes.length - 1 ? (
|
|
<span
|
|
className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
|
|
aria-hidden="true"
|
|
/>
|
|
) : null}
|
|
<div className="relative flex space-x-3">
|
|
<div>
|
|
<span className="h-8 w-8 rounded-full bg-gray-100 flex items-center justify-center">
|
|
<ClockIcon className="h-5 w-5 text-gray-500" />
|
|
</span>
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex justify-between items-start">
|
|
<div className="text-sm text-gray-500">
|
|
<time>{new Date(mov.data).toLocaleDateString()}</time>
|
|
</div>
|
|
<MovimentacaoAdminActions
|
|
movimentacao={mov}
|
|
onEdit={setMovimentacaoParaEditar}
|
|
onDelete={handleDeleteMovimentacao}
|
|
/>
|
|
</div>
|
|
<div className="mt-1">
|
|
<p className="text-sm text-gray-900">
|
|
{mov.descricao}
|
|
</p>
|
|
{mov.observacoes && (
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
{mov.observacoes}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-gray-500 text-center py-4">
|
|
Nenhuma movimentação registrada
|
|
</p>
|
|
)}
|
|
</Tab.Panel>
|
|
|
|
{/* Painel Financeiro */}
|
|
<Tab.Panel className="bg-white rounded-xl p-6">
|
|
<CustaAdministrativaPanel processoId={id} />
|
|
</Tab.Panel>
|
|
</Tab.Panels>
|
|
</Tab.Group>
|
|
|
|
{/* Modais */}
|
|
<ProcessoAdminModal
|
|
isOpen={isEditModalOpen}
|
|
onClose={() => setIsEditModalOpen(false)}
|
|
processo={processo}
|
|
onSuccess={() => {
|
|
queryClient.invalidateQueries(["processo-administrativo", id]);
|
|
setIsEditModalOpen(false);
|
|
}}
|
|
/>
|
|
<MovimentacaoAdministrativaDialog
|
|
isOpen={isMovimentacaoModalOpen}
|
|
onClose={() => setIsMovimentacaoModalOpen(false)}
|
|
onSave={handleSaveMovimentacao}
|
|
/>
|
|
<MovimentacaoAdministrativaDialog
|
|
isOpen={!!movimentacaoParaEditar}
|
|
onClose={() => setMovimentacaoParaEditar(null)}
|
|
onSave={handleEditMovimentacao}
|
|
movimentacao={movimentacaoParaEditar}
|
|
/>
|
|
</div>
|
|
);
|
|
} |