350 lines
16 KiB
TypeScript
350 lines
16 KiB
TypeScript
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import { PageTitle, Panel, controlClass } from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
import SupplierEvaluationFields, {
|
|
emptyEvaluation,
|
|
evaluationStates,
|
|
} from '@/components/supplier-evaluation-fields';
|
|
import type { EvaluationOrder } from '@/components/supplier-evaluation-fields';
|
|
import proposal from './evaluation-proposal';
|
|
|
|
type Row = {
|
|
id: number;
|
|
revision: number;
|
|
version: number;
|
|
status: string;
|
|
supplier: string;
|
|
title: string;
|
|
period_start: string | null;
|
|
period_end: string | null;
|
|
};
|
|
export default function SupplierEvaluations({
|
|
ready,
|
|
records,
|
|
filters,
|
|
suppliers,
|
|
orders,
|
|
notice,
|
|
}: {
|
|
ready: boolean;
|
|
records: {
|
|
data: Row[];
|
|
total: number;
|
|
current_page: number;
|
|
last_page: number;
|
|
prev_page_url: string | null;
|
|
next_page_url: string | null;
|
|
} | null;
|
|
filters: { supplier_id?: number; status?: string };
|
|
suppliers: { id: number; name: string; nuit: string }[];
|
|
orders: EvaluationOrder[];
|
|
notice?: string;
|
|
}) {
|
|
const form = useForm({
|
|
supplier_id: '',
|
|
operation_key: crypto.randomUUID(),
|
|
payload: emptyEvaluation(),
|
|
});
|
|
const filter = useForm({
|
|
supplier_id: String(filters.supplier_id ?? ''),
|
|
status: filters.status ?? '',
|
|
});
|
|
const supplierOrders = orders.filter(
|
|
(order) => order.supplier_id === Number(form.data.supplier_id),
|
|
);
|
|
return (
|
|
<>
|
|
<Head title="Avaliação de fornecedores" />
|
|
<div className="mx-auto max-w-7xl space-y-6 px-5 py-8 lg:px-8">
|
|
<PageTitle
|
|
eyebrow="Fornecedores"
|
|
title="Avaliação de fornecedores"
|
|
description="Prepare a avaliação qualitativa, reúna evidências, submeta para revisão independente e consulte o histórico e o relatório de cada revisão."
|
|
action={
|
|
<Button asChild variant="outline">
|
|
<Link href="/relatorios/medidas-fornecedores">
|
|
Acompanhar medidas de melhoria
|
|
</Link>
|
|
</Button>
|
|
}
|
|
/>
|
|
{notice && (
|
|
<p
|
|
role="status"
|
|
className="rounded border border-teal-300 p-3 text-sm"
|
|
>
|
|
{notice}
|
|
</p>
|
|
)}
|
|
{!ready && (
|
|
<p
|
|
role="status"
|
|
className="rounded border border-amber-300 p-3"
|
|
>
|
|
O módulo está a ser instalado. O cadastro de
|
|
fornecedores e os processos existentes permanecem
|
|
disponíveis.
|
|
</p>
|
|
)}
|
|
<Panel title="Avaliações e histórico por fornecedor">
|
|
<form
|
|
className="mb-5 flex flex-wrap items-end gap-3"
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
filter.get('/fornecedores/avaliacoes', {
|
|
preserveState: true,
|
|
});
|
|
}}
|
|
>
|
|
<label className="grid gap-1 text-sm">
|
|
Fornecedor
|
|
<select
|
|
className={controlClass}
|
|
value={filter.data.supplier_id}
|
|
onChange={(event) =>
|
|
filter.setData(
|
|
'supplier_id',
|
|
event.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Todos os autorizados</option>
|
|
{suppliers.map((supplier) => (
|
|
<option
|
|
key={supplier.id}
|
|
value={supplier.id}
|
|
>
|
|
{supplier.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<label className="grid gap-1 text-sm">
|
|
Estado
|
|
<select
|
|
className={controlClass}
|
|
value={filter.data.status}
|
|
onChange={(event) =>
|
|
filter.setData('status', event.target.value)
|
|
}
|
|
>
|
|
<option value="">Todos</option>
|
|
{Object.entries(evaluationStates).map(
|
|
([value, label]) => (
|
|
<option key={value} value={value}>
|
|
{label}
|
|
</option>
|
|
),
|
|
)}
|
|
</select>
|
|
</label>
|
|
<Button disabled={filter.processing}>
|
|
Aplicar filtros
|
|
</Button>
|
|
<Link
|
|
className="py-2 text-sm underline"
|
|
href="/fornecedores/avaliacoes"
|
|
>
|
|
Limpar
|
|
</Link>
|
|
</form>
|
|
{records && (
|
|
<>
|
|
<p className="mb-3 text-sm">
|
|
{records.total} avaliações/revisões no âmbito
|
|
autorizado.
|
|
</p>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full min-w-180 text-left text-sm">
|
|
<thead>
|
|
<tr>
|
|
<th className="p-2">Avaliação</th>
|
|
<th className="p-2">
|
|
Fornecedor / período
|
|
</th>
|
|
<th className="p-2">Estado</th>
|
|
<th className="p-2">Consultar</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{records.data.map((row) => (
|
|
<tr
|
|
key={row.id}
|
|
className="border-t"
|
|
>
|
|
<td className="p-2">
|
|
AVF-{row.id} · Revisão{' '}
|
|
{row.revision}
|
|
<span className="block font-medium">
|
|
{row.title}
|
|
</span>
|
|
</td>
|
|
<td className="p-2">
|
|
{row.supplier}
|
|
<span className="block text-muted-foreground">
|
|
{row.period_start ??
|
|
'Início por indicar'}{' '}
|
|
—{' '}
|
|
{row.period_end ??
|
|
'Fim por indicar'}
|
|
</span>
|
|
</td>
|
|
<td className="p-2">
|
|
{evaluationStates[
|
|
row.status
|
|
] ?? row.status}
|
|
</td>
|
|
<td className="space-y-2 p-2">
|
|
<Link
|
|
className="block underline"
|
|
href={`/fornecedores/avaliacoes/${row.id}`}
|
|
>
|
|
Abrir avaliação
|
|
</Link>
|
|
<Link
|
|
className="block underline"
|
|
href={`/fornecedores/avaliacoes/${row.id}/relatorio`}
|
|
>
|
|
Relatório
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{records.data.length === 0 && (
|
|
<p className="py-4 text-sm">
|
|
Sem avaliações nos filtros e no âmbito
|
|
autorizado.
|
|
</p>
|
|
)}
|
|
<nav
|
|
aria-label="Paginação das avaliações"
|
|
className="mt-4 flex flex-wrap justify-between gap-3 text-sm"
|
|
>
|
|
<span>
|
|
Página {records.current_page} de{' '}
|
|
{records.last_page}
|
|
</span>
|
|
<div className="flex gap-4">
|
|
{records.prev_page_url && (
|
|
<Link
|
|
className="underline"
|
|
href={records.prev_page_url}
|
|
>
|
|
Anterior
|
|
</Link>
|
|
)}
|
|
{records.next_page_url && (
|
|
<Link
|
|
className="underline"
|
|
href={records.next_page_url}
|
|
>
|
|
Seguinte
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
</>
|
|
)}
|
|
</Panel>
|
|
<Panel
|
|
title="Preparar nova avaliação"
|
|
description="Bens e serviços são avaliados separadamente. O relatório regista o âmbito escolhido e não calcula médias entre componentes."
|
|
>
|
|
<form
|
|
className="space-y-5"
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
form.post('/fornecedores/avaliacoes', {
|
|
preserveState: 'errors',
|
|
preserveScroll: true,
|
|
});
|
|
}}
|
|
>
|
|
<label className="grid gap-1 text-sm">
|
|
Fornecedor
|
|
<select
|
|
className={controlClass}
|
|
disabled={!ready || form.processing}
|
|
value={form.data.supplier_id}
|
|
onChange={(event) =>
|
|
form.setData({
|
|
...form.data,
|
|
supplier_id: event.target.value,
|
|
payload: {
|
|
...form.data.payload,
|
|
order_ids: [],
|
|
},
|
|
})
|
|
}
|
|
>
|
|
<option value="">Escolher fornecedor</option>
|
|
{suppliers.map((supplier) => (
|
|
<option
|
|
key={supplier.id}
|
|
value={supplier.id}
|
|
>
|
|
{supplier.name} · {supplier.nuit}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<SupplierEvaluationFields
|
|
payload={form.data.payload}
|
|
orders={supplierOrders}
|
|
disabled={!ready || form.processing}
|
|
onChange={(payload) =>
|
|
form.setData('payload', payload)
|
|
}
|
|
/>
|
|
{Object.entries(form.errors).map(([key, error]) => (
|
|
<p
|
|
key={key}
|
|
role="alert"
|
|
className="text-sm text-red-700"
|
|
>
|
|
{error}
|
|
</p>
|
|
))}
|
|
<Button
|
|
disabled={
|
|
!ready ||
|
|
form.processing ||
|
|
!form.data.supplier_id
|
|
}
|
|
>
|
|
Guardar rascunho
|
|
</Button>
|
|
</form>
|
|
</Panel>
|
|
<details className="rounded-lg border p-4 text-sm">
|
|
<summary className="cursor-pointer font-medium">
|
|
Proposta de grelhas quantitativas e evoluções previstas
|
|
</summary>
|
|
<p className="my-3">
|
|
O ciclo disponível é qualitativo. Pesos, limiares,
|
|
pontuações, planos de melhoria acompanhados e
|
|
contestação mantêm-se distintos desta entrega; os
|
|
valores ilustrativos abaixo não foram activados como
|
|
política.
|
|
</p>
|
|
<div className="whitespace-pre-wrap text-muted-foreground">
|
|
{proposal}
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
SupplierEvaluations.layout = {
|
|
breadcrumbs: [
|
|
{ title: 'Fornecedores', href: '/fornecedores' },
|
|
{
|
|
title: 'Avaliação de fornecedores',
|
|
href: '/fornecedores/avaliacoes',
|
|
},
|
|
],
|
|
};
|