"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { apiPostFormData, apiPostJson, ApiError } from "@/lib/api/client"; type SolicitacaoDetalhe = { id: string; tipo: string; tipo_display: string; status: string; status_display: string; criado_em: string | null; enviada_em: string | null; finalizada_em: string | null; solicitante: { matricula: string | null; nome: string | null; } | null; funcionario: { id: string; id_rm: string | null; matricula: string | null; nome: string | null; cpf: string | null; data_admissao: string | null; situacao: string | null; cargo: string | null; setor: string | null; centro_custo: string | null; cod_funcao: string | null; salario: string | null; cod_sindicato: string | null; saldo_banco_horas_minutos: number | null; inicio_periodo_banco_horas: string | null; fim_periodo_banco_horas: string | null; sincronizado_em: string | null; matricula_winthor: string | null; } | null; dados_winthor: { basicos: { matricula: string | null; nome: string | null; cpf: string | null }; admissao: { admissao: string | null; situacao: string | null; dt_exclusao: string | null }; endereco: { endereco: string | null; bairro: string | null; cidade: string | null; estado: string | null }; } | null; detalhes_tipo: { tipo: string; [key: string]: unknown; } | null; acoes: { pode_aprovar: boolean; pode_dar_parecer: boolean; pode_enviar: boolean; is_solicitante: boolean; }; pareceres: Array<{ id: string; etapa: string; etapa_display: string; texto: string; criado_em: string | null; usuario_nome: string | null; anexo_url: string | null; }>; aprovacoes: Array<{ id: string; etapa: string; etapa_display: string; decisao: string; decisao_display: string; justificativa: string; decidido_em: string | null; usuario_nome: string | null; }>; }; const DJANGO_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8888"; function toAbsoluteDjangoUrl(pathOrUrl: string) { if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) { return pathOrUrl; } return `${DJANGO_BASE_URL}${pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`}`; } function formatarData(iso: string | null) { if (!iso) return "—"; try { return new Date(iso).toLocaleString("pt-BR"); } catch { return iso; } } function formatarDinheiro(valor: string | null) { if (!valor) return "—"; const numero = Number(valor); if (Number.isNaN(numero)) return valor; return numero.toLocaleString("pt-BR", { style: "currency", currency: "BRL" }); } // #region agent log function debugLog(hypothesisId: string, message: string, data: Record) { fetch("http://localhost:7687/ingest/ee56ea93-ad22-4673-ab77-595d83a9b3c5", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "6b638a", }, body: JSON.stringify({ sessionId: "6b638a", runId: "detail-permission-debug", hypothesisId, location: "frontend/app/solicitacoes/[id]/page.tsx", message, data, timestamp: Date.now(), }), }).catch(() => {}); } // #endregion export default function SolicitacaoDetalhePage() { const router = useRouter(); const params = useParams<{ id: string }>(); const solicitacaoId = useMemo(() => String(params?.id || ""), [params]); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [actionLoading, setActionLoading] = useState(false); const [actionFeedback, setActionFeedback] = useState(""); const [parecerTexto, setParecerTexto] = useState(""); const [parecerAnexo, setParecerAnexo] = useState(null); const [decisao, setDecisao] = useState<"APROVADO" | "REPROVADO">("APROVADO"); const [justificativaDecisao, setJustificativaDecisao] = useState(""); const [reloadTick, setReloadTick] = useState(0); useEffect(() => { async function carregarDetalhe() { if (!solicitacaoId) { setError("Solicitação inválida."); setLoading(false); return; } setLoading(true); setError(""); try { // #region agent log debugLog("H1_H2", "detail_fetch_start", { solicitacaoId, endpoint: `/api/solicitacoes/${solicitacaoId}/`, }); // #endregion const res = await fetch(`/api/solicitacoes/${solicitacaoId}/`, { credentials: "include", }); // #region agent log let responsePreview = ""; try { responsePreview = (await res.clone().text()).slice(0, 300); } catch { responsePreview = "unreadable_response"; } debugLog("H1_H3_H4", "detail_fetch_response", { solicitacaoId, status: res.status, ok: res.ok, responsePreview, }); // #endregion if (res.status === 401) { // #region agent log debugLog("H5", "detail_fetch_unauthorized_redirect", { solicitacaoId, }); // #endregion router.push(`/tela_login?next=${encodeURIComponent(`/solicitacoes/${solicitacaoId}`)}`); return; } if (!res.ok) { if (res.status === 404) { setError("Solicitação não encontrada ou sem permissão."); } else { setError("Erro ao carregar detalhes da solicitação."); } return; } const json = (await res.json()) as SolicitacaoDetalhe; setData(json); } catch { setError("Erro de conexão ao carregar detalhes."); } finally { setLoading(false); } } carregarDetalhe(); }, [router, solicitacaoId, reloadTick]); async function handleEnviar() { if (!data) return; setActionLoading(true); setActionFeedback(""); try { await apiPostJson(`/api/solicitacoes/${data.id}/enviar/`, {}); setActionFeedback("Solicitação enviada para aprovação."); setReloadTick((v) => v + 1); } catch (err) { setActionFeedback(err instanceof ApiError ? err.message : "Erro ao enviar solicitação."); } finally { setActionLoading(false); } } async function handleParecer() { if (!data) return; if (!parecerTexto.trim()) { setActionFeedback("Digite o texto do parecer."); return; } setActionLoading(true); setActionFeedback(""); try { const fd = new FormData(); fd.append("texto", parecerTexto.trim()); if (parecerAnexo) fd.append("anexo", parecerAnexo); await apiPostFormData(`/api/solicitacoes/${data.id}/parecer/`, fd); setParecerTexto(""); setParecerAnexo(null); setActionFeedback("Parecer registrado com sucesso."); setReloadTick((v) => v + 1); } catch (err) { setActionFeedback(err instanceof ApiError ? err.message : "Erro ao registrar parecer."); } finally { setActionLoading(false); } } async function handleDecisao() { if (!data) return; if (decisao === "REPROVADO" && !justificativaDecisao.trim()) { setActionFeedback("Justificativa é obrigatória para reprovação."); return; } setActionLoading(true); setActionFeedback(""); try { await apiPostJson(`/api/solicitacoes/${data.id}/decidir/`, { decisao, justificativa: justificativaDecisao, }); setActionFeedback("Decisão registrada com sucesso."); setJustificativaDecisao(""); setReloadTick((v) => v + 1); } catch (err) { setActionFeedback(err instanceof ApiError ? err.message : "Erro ao registrar decisão."); } finally { setActionLoading(false); } } if (loading) { return (

Carregando detalhes…

); } if (error || !data) { return (

{error || "Falha ao carregar detalhes."}

Voltar para dashboard
); } return (

{data.tipo_display}

{data.status_display}

Solicitação: {data.id}

Criada em: {formatarData(data.criado_em)} Enviada em: {formatarData(data.enviada_em)} Finalizada em: {formatarData(data.finalizada_em)}
Voltar para dashboard Download PDF

Dados gerais

Tipo de processo
{data.tipo_display}
Solicitante
{data.solicitante?.nome || "—"} ({data.solicitante?.matricula || "—"})
Status
{data.status_display}
{data.funcionario && (

Dados do colaborador (TOTVS RM)

Snapshot no momento da criação da solicitação

Matrícula
{data.funcionario.matricula || "—"}
Nome completo
{data.funcionario.nome || "—"}
CPF
{data.funcionario.cpf || "—"}
Data admissão
{formatarData(data.funcionario.data_admissao)}
Cargo/Função
{data.funcionario.cargo || "—"}
Cód. função
{data.funcionario.cod_funcao || "—"}
Setor/Seção
{data.funcionario.setor || "—"}
Centro de custo
{data.funcionario.centro_custo || "—"}
Salário atual
{formatarDinheiro(data.funcionario.salario)}
{data.funcionario.saldo_banco_horas_minutos !== null && (
Saldo banco de horas
{data.funcionario.saldo_banco_horas_minutos >= 0 ? "+" : ""} {data.funcionario.saldo_banco_horas_minutos} min
Período referência
{formatarData(data.funcionario.inicio_periodo_banco_horas)} a {formatarData(data.funcionario.fim_periodo_banco_horas)}
)} {data.funcionario.sincronizado_em && (

Sincronizado com RM em {formatarData(data.funcionario.sincronizado_em)} | ID: {data.funcionario.id_rm || "—"}

)}
)} {data.dados_winthor && (

Dados do colaborador (Winthor)

Matrícula
{data.dados_winthor.basicos.matricula || "—"}
Nome
{data.dados_winthor.basicos.nome || "—"}
CPF
{data.dados_winthor.basicos.cpf || "—"}
Data admissão
{formatarData(data.dados_winthor.admissao.admissao)}
Situação
{data.dados_winthor.admissao.situacao || "—"}
Data exclusão
{formatarData(data.dados_winthor.admissao.dt_exclusao)}
Endereço
{data.dados_winthor.endereco.endereco || "—"}
Bairro
{data.dados_winthor.endereco.bairro || "—"}
Cidade
{data.dados_winthor.endereco.cidade || "—"}
Estado
{data.dados_winthor.endereco.estado || "—"}
)} {data.detalhes_tipo && (

Detalhes da movimentação

{data.detalhes_tipo.tipo === "DESLIGAMENTO" && (
Tipo desligamento
{(data.detalhes_tipo.tipo_desligamento_display as string) || "—"}
Aviso prévio
{(data.detalhes_tipo.aviso_previo_display as string) || "—"}
Data prevista saída
{formatarData((data.detalhes_tipo.data_prevista_desligamento as string) || null)}
Motivo
{(data.detalhes_tipo.motivo as string) || "—"}
{(data.detalhes_tipo.observacoes as string) &&
Observações
{data.detalhes_tipo.observacoes as string}
} {(data.detalhes_tipo.arquivo_pedido_url as string) && ( )}
)} {data.detalhes_tipo.tipo === "MOVIMENTACAO" && (
Nova função
{(data.detalhes_tipo.novo_cod_funcao as string) || "—"}
Nova seção
{(data.detalhes_tipo.novo_cod_secao as string) || "—"}
Novo salário
{formatarDinheiro((data.detalhes_tipo.novo_salario as string) || null)}
Data efetivação
{formatarData((data.detalhes_tipo.data_efetivacao as string) || null)}
Justificativa
{(data.detalhes_tipo.justificativa as string) || "—"}
)} {data.detalhes_tipo.tipo === "ADM_SUBSTITUICAO" && (
Data prevista
{formatarData((data.detalhes_tipo.data_previsao_contratacao as string) || null)}
Coligada/Filial
{String(data.detalhes_tipo.cod_coligada_destino || "—")} / {String(data.detalhes_tipo.cod_filial_destino || "—")}
Seção destino
{(data.detalhes_tipo.cod_secao_destino as string) || "—"}
Função destino
{(data.detalhes_tipo.cod_funcao_destino as string) || "—"}
Justificativa
{(data.detalhes_tipo.justificativa as string) || "—"}
)} {data.detalhes_tipo.tipo === "ADM_AUMENTO" && (
Data prevista
{formatarData((data.detalhes_tipo.data_previsao_contratacao as string) || null)}
Local destino
Col: {String(data.detalhes_tipo.cod_coligada_destino || "—")} / Fil: {String(data.detalhes_tipo.cod_filial_destino || "—")}
Suplementação orçamentária
{data.detalhes_tipo.requer_suplementacao_orcamentaria ? "Sim" : "Não"}
Justificativa estratégica
{(data.detalhes_tipo.justificativa_estrategica as string) || "—"}
)}
)}

Pareceres

{data.pareceres.length === 0 ? (

Nenhum parecer registrado.

) : (
{data.pareceres.map((parecer) => (

{parecer.etapa_display} · {parecer.usuario_nome || "Usuário"}

{formatarData(parecer.criado_em)}

{parecer.texto}

{parecer.anexo_url && ( 📎 Ver anexo )}
))}
)}

Aprovações

{data.aprovacoes.length === 0 ? (

Nenhuma aprovação registrada.

) : (
{data.aprovacoes.map((aprovacao) => (

{aprovacao.etapa_display} · {aprovacao.decisao_display}

{aprovacao.usuario_nome || "Usuário"} · {formatarData(aprovacao.decidido_em)}

{aprovacao.justificativa || "Sem justificativa."}

))}
)}
{data.acoes.pode_enviar && data.acoes.is_solicitante && (

Enviar para aprovação

Sua solicitação está em rascunho. Revise os dados e envie para iniciar o fluxo.

)} {data.acoes.pode_dar_parecer && (

Registrar parecer