377 lines
16 KiB
TypeScript
377 lines
16 KiB
TypeScript
import { Head, Link, router, useForm } from '@inertiajs/react';
|
|
import { FormErrors, controlClass } from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
type Data = {
|
|
name: string;
|
|
phone: string | null;
|
|
summary: string;
|
|
availability: { province: string; district: string; locality: string };
|
|
additional_availability?: Data['availability'][];
|
|
};
|
|
export default function Profile({
|
|
companyId,
|
|
email,
|
|
profile,
|
|
emailVerified = false,
|
|
emailDeliveryAvailable = false,
|
|
accessStatus,
|
|
opportunities = [],
|
|
opportunityPagination,
|
|
selectedOpportunity,
|
|
submissions = [],
|
|
submissionPagination,
|
|
}: {
|
|
companyId: number;
|
|
email: string;
|
|
profile: { version: number; data: Data };
|
|
emailVerified?: boolean;
|
|
emailDeliveryAvailable?: boolean;
|
|
accessStatus?: string | null;
|
|
opportunities?: { id: number; title: string; closes_on: string }[];
|
|
opportunityPagination?: { prev: string | null; next: string | null };
|
|
selectedOpportunity?: { id: number; title: string } | null;
|
|
submissions?: {
|
|
id: number;
|
|
reference: string;
|
|
created_at: string;
|
|
title?: string | null;
|
|
}[];
|
|
submissionPagination?: {
|
|
prev: string | null;
|
|
next: string | null;
|
|
total: number;
|
|
};
|
|
}) {
|
|
const form = useForm({
|
|
version: profile.version,
|
|
...profile.data,
|
|
phone: profile.data.phone ?? '',
|
|
additional_availability: profile.data.additional_availability ?? [],
|
|
reason: '',
|
|
});
|
|
return (
|
|
<>
|
|
<Head title="O meu perfil" />
|
|
<section className="mx-auto max-w-3xl space-y-3 p-6">
|
|
{selectedOpportunity && (
|
|
<aside className="space-y-2 rounded-lg border p-4">
|
|
<h2 className="font-semibold">
|
|
Oportunidade escolhida: {selectedOpportunity.title}
|
|
</h2>
|
|
<p>
|
|
Prepare o perfil e confirme o email. A candidatura
|
|
só será enviada quando confirmar a submissão no
|
|
formulário.
|
|
</p>
|
|
<Link
|
|
className="underline"
|
|
href={`/candidaturas/${companyId}/oportunidades/${selectedOpportunity.id}`}
|
|
>
|
|
Continuar a preparar esta candidatura
|
|
</Link>
|
|
</aside>
|
|
)}
|
|
<h2 className="text-xl font-semibold">
|
|
Oportunidades publicadas
|
|
</h2>
|
|
{opportunities.map((item) => (
|
|
<p key={item.id}>
|
|
<Link
|
|
className="underline"
|
|
href={`/candidaturas/${companyId}/oportunidades/${item.id}`}
|
|
>
|
|
{item.title} · {item.closes_on}
|
|
</Link>
|
|
</p>
|
|
))}
|
|
<nav
|
|
aria-label="Páginas de oportunidades"
|
|
className="flex gap-4"
|
|
>
|
|
{opportunityPagination?.prev && (
|
|
<Link href={opportunityPagination.prev} preserveScroll>
|
|
Anterior
|
|
</Link>
|
|
)}
|
|
{opportunityPagination?.next && (
|
|
<Link href={opportunityPagination.next} preserveScroll>
|
|
Seguinte
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
<h2 className="text-xl font-semibold">
|
|
As minhas candidaturas
|
|
</h2>
|
|
{submissions.map((item) => (
|
|
<p key={item.id}>
|
|
<Link
|
|
className="underline"
|
|
href={`/candidaturas/${companyId}/submissoes/${item.id}`}
|
|
>
|
|
{item.title ? `${item.title} · ` : ''}
|
|
{item.reference} · {item.created_at}
|
|
</Link>
|
|
</p>
|
|
))}
|
|
{submissionPagination && (
|
|
<p>
|
|
{submissionPagination.total} candidatura(s) entregue(s).
|
|
</p>
|
|
)}
|
|
<nav
|
|
aria-label="Páginas dos meus comprovativos"
|
|
className="flex gap-4"
|
|
>
|
|
{submissionPagination?.prev && (
|
|
<Link href={submissionPagination.prev} preserveScroll>
|
|
Comprovativos anteriores
|
|
</Link>
|
|
)}
|
|
{submissionPagination?.next && (
|
|
<Link href={submissionPagination.next} preserveScroll>
|
|
Mais comprovativos
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
</section>
|
|
<main className="mx-auto max-w-3xl space-y-6 p-6">
|
|
<p>SGC · Área do candidato</p>
|
|
<h1 className="text-3xl font-semibold">O meu perfil</h1>
|
|
<p>
|
|
{email} · versão {profile.version}
|
|
</p>
|
|
<p>
|
|
{emailVerified
|
|
? 'Email confirmado. A identidade ainda depende de verificação própria.'
|
|
: 'Email ainda não confirmado. A entrada comprova apenas o acesso à conta.'}{' '}
|
|
Pode candidatar-se às oportunidades publicadas. O envio de
|
|
documentos segue os requisitos de cada oportunidade.
|
|
</p>
|
|
{!emailVerified && (
|
|
<Button
|
|
variant="outline"
|
|
disabled={!emailDeliveryAvailable}
|
|
onClick={() =>
|
|
router.post(
|
|
`/candidaturas/${companyId}/pedir-confirmacao`,
|
|
)
|
|
}
|
|
>
|
|
Pedir confirmação do email
|
|
</Button>
|
|
)}
|
|
{!emailVerified && !emailDeliveryAvailable && (
|
|
<p>
|
|
O envio de email ainda não está configurado; pode
|
|
continuar a preparar o perfil.
|
|
</p>
|
|
)}
|
|
{accessStatus === 'requested' && (
|
|
<p>
|
|
Se o envio estiver disponível, receberá uma ligação
|
|
temporária de confirmação.
|
|
</p>
|
|
)}
|
|
{accessStatus === 'opportunity_unavailable' && (
|
|
<p role="status">
|
|
Entrou na sua conta, mas a oportunidade escolhida já não
|
|
está disponível. Consulte as oportunidades publicadas
|
|
acima.
|
|
</p>
|
|
)}
|
|
<form
|
|
className="space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.post(`/candidaturas/${companyId}/perfil`, {
|
|
preserveScroll: true,
|
|
preserveState: false,
|
|
});
|
|
}}
|
|
>
|
|
<FormErrors errors={form.errors} />
|
|
<label className="grid gap-2">
|
|
Nome completo
|
|
<input
|
|
className={controlClass}
|
|
required
|
|
minLength={3}
|
|
maxLength={200}
|
|
value={form.data.name}
|
|
onChange={(e) =>
|
|
form.setData('name', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="grid gap-2">
|
|
Telefone
|
|
<input
|
|
className={controlClass}
|
|
maxLength={50}
|
|
value={form.data.phone}
|
|
onChange={(e) =>
|
|
form.setData('phone', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="grid gap-2">
|
|
Resumo profissional
|
|
<textarea
|
|
className={controlClass}
|
|
required
|
|
minLength={10}
|
|
maxLength={5000}
|
|
value={form.data.summary}
|
|
onChange={(e) =>
|
|
form.setData('summary', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<fieldset className="grid gap-3 sm:grid-cols-3">
|
|
<legend>
|
|
Local de disponibilidade para trabalhar ou prestar
|
|
serviço
|
|
</legend>
|
|
{(['province', 'district', 'locality'] as const).map(
|
|
(key) => (
|
|
<label className="grid gap-2" key={key}>
|
|
{
|
|
{
|
|
province: 'Província',
|
|
district: 'Distrito',
|
|
locality: 'Localidade',
|
|
}[key]
|
|
}
|
|
<input
|
|
className={controlClass}
|
|
required
|
|
maxLength={150}
|
|
value={form.data.availability[key]}
|
|
onChange={(e) =>
|
|
form.setData('availability', {
|
|
...form.data.availability,
|
|
[key]: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
),
|
|
)}
|
|
</fieldset>
|
|
{form.data.additional_availability.map(
|
|
(location, index) => (
|
|
<fieldset
|
|
key={index}
|
|
className="grid gap-3 sm:grid-cols-3"
|
|
>
|
|
<legend>
|
|
Outro local de disponibilidade {index + 1}
|
|
</legend>
|
|
{(
|
|
[
|
|
'province',
|
|
'district',
|
|
'locality',
|
|
] as const
|
|
).map((key) => (
|
|
<label className="grid gap-2" key={key}>
|
|
{
|
|
{
|
|
province: 'Província',
|
|
district: 'Distrito',
|
|
locality: 'Localidade',
|
|
}[key]
|
|
}
|
|
<input
|
|
className={controlClass}
|
|
required
|
|
maxLength={150}
|
|
value={location[key]}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'additional_availability',
|
|
form.data.additional_availability.map(
|
|
(item, position) =>
|
|
position === index
|
|
? {
|
|
...item,
|
|
[key]: e
|
|
.target
|
|
.value,
|
|
}
|
|
: item,
|
|
),
|
|
)
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() =>
|
|
form.setData(
|
|
'additional_availability',
|
|
form.data.additional_availability.filter(
|
|
(_, position) =>
|
|
position !== index,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
Retirar este local
|
|
</Button>
|
|
</fieldset>
|
|
),
|
|
)}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
disabled={
|
|
form.data.additional_availability.length >= 20
|
|
}
|
|
onClick={() =>
|
|
form.setData('additional_availability', [
|
|
...form.data.additional_availability,
|
|
{ province: '', district: '', locality: '' },
|
|
])
|
|
}
|
|
>
|
|
Adicionar local de disponibilidade
|
|
</Button>
|
|
<p className="text-sm">
|
|
Disponibilidade é distinta da morada. Actualizar o
|
|
perfil cria uma versão para usos futuros, preservando as
|
|
anteriores.
|
|
</p>
|
|
<label className="grid gap-2">
|
|
Motivo da actualização
|
|
<textarea
|
|
className={controlClass}
|
|
required
|
|
minLength={10}
|
|
maxLength={4000}
|
|
value={form.data.reason}
|
|
onChange={(e) =>
|
|
form.setData('reason', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<Button disabled={form.processing}>
|
|
Guardar nova versão do perfil
|
|
</Button>
|
|
</form>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() =>
|
|
router.post(`/candidaturas/${companyId}/sair`)
|
|
}
|
|
>
|
|
Sair da área do candidato
|
|
</Button>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|