Files
compras/resources/js/components/people-operational-insights.tsx
T
nala c6373365bf
tests / ci (push) Canceled after 0s
commit9
2026-10-30 20:02:30 +02:00

122 lines
5.1 KiB
TypeScript

import { Link } from '@inertiajs/react';
import { Panel } from '@/components/procurement';
export type OperationalInsights = {
method: 'operational_facts_v1';
generated_at: string;
coverage: { processes: number; tasks: number; approvals: number };
overdue: {
items: number;
processes: number;
maximum_days: number | null;
oldest: {
id: string;
reference: string;
label: string;
due_on: string;
days_overdue: number;
href: string;
}[];
};
task_actions: Record<string, number>;
tasks_without_deadline: number;
};
const actions: Record<string, string> = {
triage: 'Triagem de candidaturas electrónicas',
apply: 'Recepção de candidaturas',
close: 'Encerramento da recepção',
evaluate: 'Avaliação de candidaturas',
select: 'Preparação da selecção',
contract: 'Preparação da minuta',
clarification_assign: 'Atribuição de esclarecimentos',
clarification_respond: 'Resposta a esclarecimentos',
clarification_review: 'Conferência de esclarecimentos',
};
export function PeopleOperationalInsights({
data,
}: {
data: OperationalInsights;
}) {
return (
<Panel title="Leitura operacional">
<div className="space-y-4 text-sm">
<p>
Base: {data.coverage.processes} processos autorizados nos
filtros actuais. Indicadores calculados a partir das tarefas
e dos prazos registados.
</p>
{data.coverage.processes === 0 ? (
<p>Sem processos neste âmbito para analisar.</p>
) : (
<>
<p>
{data.overdue.items} prazos vencidos em{' '}
{data.overdue.processes} processos.{' '}
{data.coverage.approvals} aprovações aguardam a sua
intervenção independente.
</p>
{data.overdue.maximum_days !== null && (
<p>
Prazo vencido há mais tempo:{' '}
{data.overdue.maximum_days} dias de calendário.
</p>
)}
{data.overdue.oldest.length > 0 && (
<div>
<p className="font-medium">
Prazos vencidos há mais tempo — até cinco
ocorrências
</p>
<ul className="list-disc space-y-1 pl-5">
{data.overdue.oldest.map((item) => (
<li key={item.id}>
<Link
className="underline"
href={item.href}
>
{item.reference} · {item.label}
</Link>{' '}
· {item.days_overdue} dias · prazo{' '}
{item.due_on}
</li>
))}
</ul>
</div>
)}
{Object.keys(data.task_actions).length > 0 && (
<div>
<p className="font-medium">
Distribuição das suas tarefas
</p>
<ul className="list-disc pl-5">
{Object.entries(data.task_actions).map(
([action, count]) => (
<li key={action}>
{actions[action] ??
'Outra intervenção'}
: {count}
</li>
),
)}
</ul>
</div>
)}
<p>
{data.tasks_without_deadline} tarefas sem prazo
próprio registado; esta ausência não indica
incumprimento.
</p>
</>
)}
<p className="text-muted-foreground">
Leitura factual dos registos, sem previsão por machine
learning. Um atraso pede análise do contexto e não determina
o resultado de uma candidatura.
</p>
</div>
</Panel>
);
}