122 lines
5.1 KiB
TypeScript
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>
|
|
);
|
|
}
|