183 lines
6.6 KiB
TypeScript
183 lines
6.6 KiB
TypeScript
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import {
|
|
FormErrors,
|
|
PageTitle,
|
|
Panel,
|
|
controlClass,
|
|
} from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
type Assignment = {
|
|
user_id: number;
|
|
scopes: string[];
|
|
expires_at: string | null;
|
|
};
|
|
function Person({
|
|
person,
|
|
scopes,
|
|
assigned,
|
|
version,
|
|
processId,
|
|
}: {
|
|
person: { id: number; name: string };
|
|
scopes: Record<string, string>;
|
|
assigned?: Assignment;
|
|
version: number;
|
|
processId: number;
|
|
}) {
|
|
const expiry = assigned?.expires_at
|
|
? new Date(assigned.expires_at.replace(' ', 'T') + 'Z')
|
|
: null;
|
|
const localExpiry = expiry
|
|
? new Date(expiry.getTime() - expiry.getTimezoneOffset() * 60000)
|
|
.toISOString()
|
|
.slice(0, 16)
|
|
: '';
|
|
const form = useForm({
|
|
version,
|
|
user_id: person.id,
|
|
scopes: assigned?.scopes ?? [],
|
|
expires_at: localExpiry,
|
|
reason: '',
|
|
});
|
|
return (
|
|
<details className="rounded-lg border p-4">
|
|
<summary className="cursor-pointer font-medium">
|
|
{person.name} · {assigned?.scopes.length ?? 0} atribuição(ões)
|
|
</summary>
|
|
<form
|
|
className="mt-4 space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.transform((data) => ({
|
|
...data,
|
|
expires_at: data.expires_at
|
|
? new Date(data.expires_at).toISOString()
|
|
: null,
|
|
}));
|
|
form.post(`/pessoas/${processId}/acessos`, {
|
|
preserveState: 'errors',
|
|
preserveScroll: true,
|
|
});
|
|
}}
|
|
>
|
|
<FormErrors errors={form.errors} />
|
|
<div className="grid gap-3 md:grid-cols-2">
|
|
{Object.entries(scopes).map(([key, label]) => (
|
|
<label className="flex gap-3" key={key}>
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.scopes.includes(key)}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'scopes',
|
|
e.target.checked
|
|
? [...form.data.scopes, key]
|
|
: form.data.scopes.filter(
|
|
(s) => s !== key,
|
|
),
|
|
)
|
|
}
|
|
/>
|
|
{label}
|
|
</label>
|
|
))}
|
|
</div>
|
|
<label className="block">
|
|
Validade até (opcional)
|
|
<input
|
|
className={controlClass}
|
|
type="datetime-local"
|
|
value={form.data.expires_at}
|
|
onChange={(e) =>
|
|
form.setData('expires_at', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="block">
|
|
Motivo
|
|
<textarea
|
|
className={controlClass}
|
|
required
|
|
minLength={10}
|
|
maxLength={4000}
|
|
value={form.data.reason}
|
|
onChange={(e) => form.setData('reason', e.target.value)}
|
|
/>
|
|
</label>
|
|
<p className="text-sm">
|
|
Retirar todas as opções revoga o acesso. Seleccionar uma
|
|
intervenção inclui a consulta do processo; dados
|
|
financeiros, entrevistas e minuta têm permissões próprias. A
|
|
atribuição não elimina as regras de independência.
|
|
</p>
|
|
<Button disabled={form.processing}>Guardar atribuição</Button>
|
|
</form>
|
|
</details>
|
|
);
|
|
}
|
|
export default function Access({
|
|
process,
|
|
version,
|
|
scopes,
|
|
users,
|
|
assignments,
|
|
canView,
|
|
}: {
|
|
process: { id: number; kind: string; status: string };
|
|
version: number;
|
|
scopes: Record<string, string>;
|
|
users: { id: number; name: string }[];
|
|
assignments: Assignment[];
|
|
canView: boolean;
|
|
}) {
|
|
return (
|
|
<>
|
|
<Head title="Equipa do processo" />
|
|
<div className="mx-auto max-w-5xl space-y-6 p-5 lg:p-8">
|
|
<PageTitle
|
|
eyebrow={
|
|
process.kind === 'staff'
|
|
? 'Recrutamento'
|
|
: 'Serviços individuais'
|
|
}
|
|
title={`Equipa do processo #${process.id}`}
|
|
description="Configurar a equipa não concede acesso automático a candidaturas, entrevistas ou condições financeiras."
|
|
/>
|
|
<Link href="/pessoas" className="underline">
|
|
Voltar a Pessoas e contratações
|
|
</Link>
|
|
{canView && (
|
|
<Link
|
|
href={`/pessoas/${process.id}`}
|
|
className="ml-4 underline"
|
|
>
|
|
Consultar processo
|
|
</Link>
|
|
)}
|
|
<Panel title="Atribuições nominativas">
|
|
<p className="mb-4 text-sm">
|
|
Escolha pessoas com perfil compatível na parametrização
|
|
da empresa. Separe preparação, avaliação e decisão.
|
|
Versão {version}.
|
|
</p>
|
|
<div className="space-y-3">
|
|
{users.map((person) => (
|
|
<Person
|
|
key={`${person.id}-${version}`}
|
|
person={person}
|
|
scopes={scopes}
|
|
assigned={assignments.find(
|
|
(a) => a.user_id === person.id,
|
|
)}
|
|
version={version}
|
|
processId={process.id}
|
|
/>
|
|
))}
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|