215 lines
8.2 KiB
TypeScript
215 lines
8.2 KiB
TypeScript
import { createOperationKey } from '@/lib/operation-key';
|
|
import { Head, Link, router } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import ProfileNavigation from '@/components/profile-navigation';
|
|
import { profileJson } from '@/lib/profile-json';
|
|
import { button, control } from '@/pages/management/shared';
|
|
import { modalityLabels } from '@/components/opportunity-search-filters';
|
|
import ProfileProofPicker from '@/components/profile-proof-picker';
|
|
|
|
const representations: Record<string, string> = {
|
|
individual: 'Em nome próprio',
|
|
company: 'Empresa',
|
|
public_entity: 'Entidade pública',
|
|
association: 'Associação',
|
|
partner: 'Parceiro ou representante',
|
|
};
|
|
const requestStates: Record<string, string> = {
|
|
pending: 'Em análise',
|
|
approved: 'Aprovado',
|
|
rejected: 'Devolvido ou recusado',
|
|
cancelled: 'Cancelado',
|
|
};
|
|
|
|
type RequestEntry = {
|
|
id: string;
|
|
status: string;
|
|
reason: string;
|
|
decision_reason: string | null;
|
|
created_at: string;
|
|
};
|
|
export default function RequestProfile({
|
|
requests,
|
|
types,
|
|
organizations,
|
|
documents,
|
|
}: {
|
|
requests: RequestEntry[];
|
|
types: string[];
|
|
organizations: { id: string; name: string }[];
|
|
documents: { id: string; file_name: string }[];
|
|
}) {
|
|
const [representation, setRepresentation] = useState('individual');
|
|
const [organization, setOrganization] = useState('');
|
|
const [selected, setSelected] = useState<string[]>([]);
|
|
const [proofs, setProofs] = useState<string[]>([]);
|
|
const [reason, setReason] = useState('');
|
|
const [key, setKey] = useState(() => createOperationKey());
|
|
const [message, setMessage] = useState('');
|
|
const [busy, setBusy] = useState(false);
|
|
async function send() {
|
|
setBusy(true);
|
|
setMessage('');
|
|
try {
|
|
const result = await profileJson('/perfil/promotor', {
|
|
creation_key: key,
|
|
representation_type: representation,
|
|
organization_id:
|
|
representation === 'individual' ? null : organization,
|
|
opportunity_types: selected,
|
|
document_ids: proofs,
|
|
reason,
|
|
});
|
|
setMessage(result.message);
|
|
setKey(createOperationKey());
|
|
router.reload();
|
|
} catch (error) {
|
|
setMessage(
|
|
error instanceof Error
|
|
? error.message
|
|
: 'Resposta indisponível. Confira o acompanhamento antes de repetir.',
|
|
);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
const toggle = (values: string[], id: string) =>
|
|
values.includes(id) ? values.filter((v) => v !== id) : [...values, id];
|
|
return (
|
|
<main className="space-y-6 p-6">
|
|
<Head title="Perfil de Promotor" />
|
|
<ProfileNavigation />
|
|
<h1 className="text-2xl font-semibold">
|
|
Solicitar perfil de Promotor
|
|
</h1>
|
|
<p>
|
|
Indique em nome de quem vai actuar, as modalidades que pretende
|
|
preparar e os comprovativos do pedido. Pode acompanhar a decisão
|
|
nesta página. A submissão do pedido não activa o perfil.
|
|
</p>
|
|
<form
|
|
className="max-w-2xl space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
void send();
|
|
}}
|
|
>
|
|
<label className="block">
|
|
Em nome de quem vai actuar?
|
|
<select
|
|
className={control}
|
|
value={representation}
|
|
onChange={(e) => setRepresentation(e.target.value)}
|
|
>
|
|
{[
|
|
'individual',
|
|
'company',
|
|
'public_entity',
|
|
'association',
|
|
'partner',
|
|
].map((v) => (
|
|
<option key={v} value={v}>
|
|
{representations[v]}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
{representation !== 'individual' && (
|
|
<label className="block">
|
|
Organização representada
|
|
<select
|
|
required
|
|
className={control}
|
|
value={organization}
|
|
onChange={(e) => setOrganization(e.target.value)}
|
|
>
|
|
<option value="">Selecionar</option>
|
|
{organizations.map((o) => (
|
|
<option key={o.id} value={o.id}>
|
|
{o.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
)}
|
|
<fieldset>
|
|
<legend>Que oportunidades pretende preparar?</legend>
|
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
{types.map((type) => (
|
|
<label
|
|
key={type}
|
|
className="flex gap-2 rounded border p-3"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={selected.includes(type)}
|
|
onChange={() =>
|
|
setSelected(toggle(selected, type))
|
|
}
|
|
/>{' '}
|
|
{modalityLabels[type] ?? 'Outra modalidade'}
|
|
</label>
|
|
))}
|
|
</div>
|
|
</fieldset>
|
|
<ProfileProofPicker
|
|
documents={documents}
|
|
value={proofs}
|
|
onChange={setProofs}
|
|
disabled={busy}
|
|
/>
|
|
{!documents.length && (
|
|
<p>
|
|
Sem comprovativos próprios disponíveis. Pode anexar ao
|
|
seu dossier no{' '}
|
|
<Link
|
|
className="underline"
|
|
href="/balcao-virtual/painel"
|
|
>
|
|
Balcão
|
|
</Link>
|
|
; o ficheiro precisa de análise antimalware concluída.
|
|
</p>
|
|
)}
|
|
<label className="block">
|
|
Para que actividades precisa do perfil de Promotor?
|
|
<textarea
|
|
required
|
|
minLength={20}
|
|
maxLength={1000}
|
|
className={control}
|
|
value={reason}
|
|
onChange={(e) => setReason(e.target.value)}
|
|
/>
|
|
</label>
|
|
<button disabled={busy} className={button}>
|
|
Submeter pedido de perfil
|
|
</button>
|
|
</form>
|
|
<p role="status" aria-live="polite">
|
|
{message}
|
|
</p>
|
|
<section>
|
|
<h2 className="text-xl font-semibold">
|
|
Acompanhamento dos meus pedidos
|
|
</h2>
|
|
{requests.map((r) => (
|
|
<article key={r.id} className="border-b py-3">
|
|
<p>
|
|
Pedido de{' '}
|
|
{new Date(r.created_at).toLocaleDateString('pt-MZ')}{' '}
|
|
—{' '}
|
|
{requestStates[r.status] ?? 'Estado por consultar'}
|
|
</p>
|
|
<p>
|
|
{r.decision_reason ??
|
|
'Aguarda revisão independente.'}
|
|
</p>
|
|
</article>
|
|
))}
|
|
{!requests.length && <p>Nenhum pedido recebido.</p>}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|