308 lines
15 KiB
TypeScript
308 lines
15 KiB
TypeScript
import { Head, Link, useForm, usePage } from '@inertiajs/react';
|
|
import {
|
|
FormErrors,
|
|
PageTitle,
|
|
Panel,
|
|
controlClass,
|
|
} from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { conflictScope } from '@/types/conflict';
|
|
import type { ConflictDeclaration } from '@/types/conflict';
|
|
import { date } from '@/types/procurement';
|
|
|
|
type Props = {
|
|
declarations: {
|
|
data: ConflictDeclaration[];
|
|
prev_page_url: string | null;
|
|
next_page_url: string | null;
|
|
};
|
|
suppliers: { id: number; name: string }[];
|
|
requisitions: { id: number; number: string }[];
|
|
};
|
|
|
|
export default function Conflicts({
|
|
declarations,
|
|
suppliers,
|
|
requisitions,
|
|
}: Props) {
|
|
const { auth } = usePage().props;
|
|
const form = useForm({
|
|
scope: 'supplier',
|
|
supplier_id: '',
|
|
requisition_id: '',
|
|
has_conflict: '1',
|
|
known_since: '',
|
|
reason: '',
|
|
});
|
|
return (
|
|
<>
|
|
<Head title="Conflitos de interesses" />
|
|
<div className="mx-auto flex w-full max-w-6xl flex-col gap-7 p-5 lg:p-8">
|
|
<PageTitle
|
|
eyebrow="Integridade nas compras · RF-AF-01"
|
|
title="Conflitos de interesses"
|
|
description="Registe a sua situação pessoal perante um fornecedor ou processo. Administração e Finanças podem consultar o registo da empresa; os restantes perfis consultam apenas as suas declarações."
|
|
/>
|
|
<div className="rounded-xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100">
|
|
Um conflito registado mantém o impedimento, mesmo que exista
|
|
uma declaração posterior de ausência. Não há caducidade
|
|
automática nem retirada de conflitos nesta etapa: essas
|
|
condições dependem da política da empresa. A data de
|
|
conhecimento descreve o facto e não adia o bloqueio.
|
|
</div>
|
|
<Panel
|
|
title="Nova declaração pessoal"
|
|
description={`Autor: ${auth.user.name}. A data e a hora do registo são guardadas pelo servidor.`}
|
|
>
|
|
<form
|
|
className="space-y-5"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.transform((data) => ({
|
|
...data,
|
|
has_conflict: data.has_conflict === '1',
|
|
supplier_id:
|
|
data.scope === 'process'
|
|
? null
|
|
: data.supplier_id || null,
|
|
requisition_id:
|
|
data.scope === 'supplier'
|
|
? null
|
|
: data.requisition_id || null,
|
|
}));
|
|
form.post('/conflitos', {
|
|
preserveScroll: true,
|
|
onSuccess: () => form.reset(),
|
|
});
|
|
}}
|
|
>
|
|
<FormErrors errors={form.errors} />
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-scope">Âmbito</Label>
|
|
<select
|
|
id="conflict-scope"
|
|
className={controlClass}
|
|
value={form.data.scope}
|
|
onChange={(e) =>
|
|
form.setData('scope', e.target.value)
|
|
}
|
|
>
|
|
<option value="supplier">
|
|
Fornecedor, em qualquer processo
|
|
</option>
|
|
<option value="process">
|
|
Processo, independentemente do
|
|
fornecedor
|
|
</option>
|
|
<option value="process_supplier">
|
|
Fornecedor neste processo
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-result">
|
|
Situação declarada
|
|
</Label>
|
|
<select
|
|
id="conflict-result"
|
|
className={controlClass}
|
|
value={form.data.has_conflict}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'has_conflict',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="1">
|
|
Existe conflito de interesses
|
|
</option>
|
|
<option value="0">
|
|
Não identifico conflito de interesses
|
|
</option>
|
|
</select>
|
|
</div>
|
|
{form.data.scope !== 'process' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-supplier">
|
|
Fornecedor
|
|
</Label>
|
|
<select
|
|
id="conflict-supplier"
|
|
className={controlClass}
|
|
required
|
|
value={form.data.supplier_id}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'supplier_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Seleccione</option>
|
|
{suppliers.map((s) => (
|
|
<option key={s.id} value={s.id}>
|
|
{s.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
{form.data.scope !== 'supplier' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-process">
|
|
Processo / requisição
|
|
</Label>
|
|
<select
|
|
id="conflict-process"
|
|
className={controlClass}
|
|
required
|
|
value={form.data.requisition_id}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'requisition_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Seleccione</option>
|
|
{requisitions.map((r) => (
|
|
<option key={r.id} value={r.id}>
|
|
{r.number}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-known">
|
|
Data de conhecimento da situação
|
|
</Label>
|
|
<Input
|
|
id="conflict-known"
|
|
type="date"
|
|
required
|
|
value={form.data.known_since}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'known_since',
|
|
e.target.value,
|
|
)
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conflict-reason">
|
|
Fundamento da declaração
|
|
</Label>
|
|
<textarea
|
|
id="conflict-reason"
|
|
className={`${controlClass} h-28 py-3`}
|
|
required
|
|
minLength={10}
|
|
maxLength={5000}
|
|
value={form.data.reason}
|
|
onChange={(e) =>
|
|
form.setData('reason', e.target.value)
|
|
}
|
|
/>
|
|
</div>
|
|
<Button disabled={form.processing}>
|
|
Registar declaração pessoal
|
|
</Button>
|
|
</form>
|
|
</Panel>
|
|
<Panel
|
|
title="Declarações e correcções"
|
|
description="O histórico é preservado. Só o autor pode corrigir o fundamento e a data de conhecimento, sem mudar o âmbito ou retirar o conflito."
|
|
>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="border-b text-muted-foreground">
|
|
<tr>
|
|
<th className="pr-4 pb-3">
|
|
Autor e registo
|
|
</th>
|
|
<th className="pr-4">Âmbito</th>
|
|
<th className="pr-4">Situação</th>
|
|
<th>
|
|
<span className="sr-only">
|
|
Histórico
|
|
</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{declarations.data.map((d) => (
|
|
<tr
|
|
key={d.id}
|
|
className="border-b last:border-0"
|
|
>
|
|
<td className="py-4 pr-4">
|
|
<p className="font-medium">
|
|
{d.author.name}
|
|
</p>
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
{date(d.declared_at)} · #{d.id}
|
|
{d.previous_id
|
|
? ` · Correcção de #${d.previous_id}`
|
|
: ''}
|
|
</p>
|
|
</td>
|
|
<td className="pr-4">
|
|
{conflictScope(d)}
|
|
</td>
|
|
<td className="pr-4">
|
|
{d.has_conflict
|
|
? 'Conflito registado'
|
|
: 'Ausência declarada'}
|
|
{d.successor && (
|
|
<p className="text-xs text-muted-foreground">
|
|
Com correcção posterior
|
|
</p>
|
|
)}
|
|
</td>
|
|
<td>
|
|
<Link
|
|
className="text-teal-700 dark:text-teal-400"
|
|
href={`/conflitos/${d.id}`}
|
|
>
|
|
Ver histórico
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{declarations.data.length === 0 && (
|
|
<p className="py-6 text-sm text-muted-foreground">
|
|
Ainda não existem declarações disponíveis para o seu
|
|
perfil.
|
|
</p>
|
|
)}
|
|
<div className="mt-4 flex gap-4 text-sm">
|
|
{declarations.prev_page_url && (
|
|
<Link href={declarations.prev_page_url}>
|
|
Anterior
|
|
</Link>
|
|
)}
|
|
{declarations.next_page_url && (
|
|
<Link href={declarations.next_page_url}>
|
|
Seguinte
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
Conflicts.layout = {
|
|
breadcrumbs: [{ title: 'Conflitos de interesses', href: '/conflitos' }],
|
|
};
|