317 lines
13 KiB
TypeScript
317 lines
13 KiB
TypeScript
import { createOperationKey } from '@/lib/operation-key';
|
|
import ContentManagementEntry from '@/components/content-management-entry';
|
|
import { Link, useForm, usePage } from '@inertiajs/react';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { Frame, input, button, card } from '@/pages/investment/shared';
|
|
type Option = {
|
|
id: string;
|
|
status?: string;
|
|
name?: string;
|
|
version_no?: number;
|
|
};
|
|
export default function Panel({
|
|
cases,
|
|
queue,
|
|
legacyRequests,
|
|
companies,
|
|
submissions,
|
|
organizations,
|
|
simulationDrafts,
|
|
journeys,
|
|
states,
|
|
manager,
|
|
guidanceTasks,
|
|
}: {
|
|
cases: { id: string; title: string }[];
|
|
queue: {
|
|
id: string;
|
|
title: string;
|
|
protocol: string;
|
|
status: string;
|
|
due_at: string | null;
|
|
}[];
|
|
legacyRequests: { id: number; reference: string; status: string }[];
|
|
companies: Option[];
|
|
submissions: Option[];
|
|
organizations: Option[];
|
|
simulationDrafts: Option[];
|
|
journeys: Option[];
|
|
states: Record<string, string>;
|
|
manager: boolean;
|
|
guidanceTasks: {
|
|
id: string;
|
|
title: string;
|
|
status: string;
|
|
due_at: string;
|
|
case_id: string;
|
|
case_title: string;
|
|
}[];
|
|
}) {
|
|
const params = new URLSearchParams(usePage().url.split('?')[1] ?? '');
|
|
const pending = useRef(false);
|
|
const [failure, setFailure] = useState<string | null>(null);
|
|
const form = useForm({
|
|
title: '',
|
|
client_key: createOperationKey(),
|
|
reuse_consent: false,
|
|
company_id: '',
|
|
submission_id: params.get('submission_id') ?? '',
|
|
simulation_id: params.get('simulation_id') ?? '',
|
|
organization_id: '',
|
|
journey_id: params.get('journey_id') ?? '',
|
|
interest_request_id: '',
|
|
});
|
|
const groups = {
|
|
company_id: ['Empresa existente', companies],
|
|
submission_id: ['Manifestação ou rascunho existente', submissions],
|
|
simulation_id: ['Dossier do simulador', simulationDrafts],
|
|
organization_id: ['Organização representada', organizations],
|
|
journey_id: ['Percurso territorial existente', journeys],
|
|
} as const;
|
|
const derivedTitle = useRef('');
|
|
const contextTitle =
|
|
journeys.find((item) => item.id === form.data.journey_id)?.name ??
|
|
submissions.find((item) => item.id === form.data.submission_id)?.name ??
|
|
companies.find((item) => item.id === form.data.company_id)?.name ??
|
|
'';
|
|
useEffect(() => {
|
|
if (
|
|
(!form.data.title || form.data.title === derivedTitle.current) &&
|
|
contextTitle
|
|
) {
|
|
form.setData('title', contextTitle.slice(0, 200));
|
|
derivedTitle.current = contextTitle.slice(0, 200);
|
|
}
|
|
}, [contextTitle, form.data.title]);
|
|
return (
|
|
<Frame title="Balcão Virtual — acompanhamento">
|
|
<ContentManagementEntry />
|
|
<nav className="flex flex-wrap gap-4">
|
|
<Link href="/balcao-virtual/servicos" className="underline">
|
|
Serviços
|
|
</Link>
|
|
<Link href="/investidor/manifestacoes" className="underline">
|
|
Área do investidor
|
|
</Link>
|
|
<Link href="/parcerias/painel" className="underline">
|
|
Parcerias
|
|
</Link>
|
|
{manager && (
|
|
<Link
|
|
href="/management/balcao-install"
|
|
className="underline"
|
|
>
|
|
Instalação e serviços
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
<section className="space-y-3">
|
|
<h2 className="text-xl font-bold">Dossiers actuais</h2>
|
|
{cases.map((c) => (
|
|
<Link
|
|
key={c.id}
|
|
className={card + ' block'}
|
|
href={`/balcao-virtual/dossiers/${c.id}`}
|
|
>
|
|
{c.title}
|
|
</Link>
|
|
))}
|
|
{!cases.length && <p>Ainda sem dossier neste fluxo.</p>}
|
|
</section>
|
|
<details className={card}>
|
|
<summary>Preparar acompanhamento de um projecto</summary>
|
|
<p>
|
|
Selecione um percurso ou investimento da sua conta. O título
|
|
é recuperado do registo existente quando disponível; pode
|
|
corrigi-lo antes de guardar. A associação requer a sua
|
|
autorização abaixo.
|
|
</p>
|
|
<form
|
|
className="mt-4 space-y-3"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (pending.current) return;
|
|
pending.current = true;
|
|
setFailure(null);
|
|
form.post('/balcao-virtual/dossiers', {
|
|
preserveState: true,
|
|
onError: () =>
|
|
setFailure(
|
|
'O dossier não foi guardado. Corrija os campos indicados abaixo; os dados preenchidos foram mantidos.',
|
|
),
|
|
onHttpException: (response) => {
|
|
const reasons: Record<number, string> = {
|
|
403: 'Não tem autorização para a associação selecionada.',
|
|
404: 'O registo que pretende associar não está disponível.',
|
|
409: 'Esta operação já foi utilizada com outro conteúdo ou o contexto mudou. Consulte os dossiers atuais antes de repetir.',
|
|
419: 'A sessão expirou. Inicie sessão novamente antes de repetir.',
|
|
422: 'Os dados não permitem guardar este dossier. Reveja as associações e o consentimento.',
|
|
503: 'O servidor está temporariamente indisponível.',
|
|
};
|
|
setFailure(
|
|
`${reasons[response.status] ?? 'O servidor não confirmou a operação.'} Os dados preenchidos foram mantidos.`,
|
|
);
|
|
return false;
|
|
},
|
|
onNetworkError: () => {
|
|
setFailure(
|
|
'A ligação falhou e não foi possível confirmar o resultado. Os dados foram mantidos. Consulte os dossiers atuais antes de repetir; não submeta um pedido para testar.',
|
|
);
|
|
return false;
|
|
},
|
|
onFinish: () => {
|
|
pending.current = false;
|
|
},
|
|
});
|
|
}}
|
|
>
|
|
<label className="block">
|
|
Título do projecto
|
|
<input
|
|
className={input}
|
|
value={form.data.title}
|
|
maxLength={200}
|
|
required
|
|
onChange={(e) =>
|
|
form.setData('title', e.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
{Object.entries(groups).map(([key, [label, options]]) => (
|
|
<label className="block" key={key}>
|
|
{label}
|
|
<select
|
|
className={input}
|
|
value={form.data[key as keyof typeof groups]}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
key as keyof typeof groups,
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Sem ligação nesta fase</option>
|
|
{options.map((o) => (
|
|
<option key={o.id} value={o.id}>
|
|
{o.name ??
|
|
`${o.id} — ${o.status ?? `versão ${o.version_no ?? ''}`}`}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
))}
|
|
<label>
|
|
Pedido de apoio anterior
|
|
<select
|
|
className={input}
|
|
value={form.data.interest_request_id}
|
|
onChange={(e) =>
|
|
form.setData(
|
|
'interest_request_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Sem associação nesta fase</option>
|
|
{legacyRequests.map((r) => (
|
|
<option key={r.id} value={r.id}>
|
|
{r.reference} — {r.status}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<label className="flex gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.reuse_consent}
|
|
onChange={(e) =>
|
|
form.setData('reuse_consent', e.target.checked)
|
|
}
|
|
/>
|
|
Autorizo reutilizar os dados seleccionados para preparar
|
|
e acompanhar o projecto internamente.
|
|
</label>
|
|
<p>
|
|
O dossier não submete pedidos, não publica documentos e
|
|
não transmite dados para serviços externos.
|
|
</p>
|
|
{Object.entries(form.errors).map(([key, v]) => (
|
|
<p role="alert" key={key}>
|
|
{v}
|
|
</p>
|
|
))}
|
|
{failure && (
|
|
<p
|
|
role="alert"
|
|
className="rounded border border-red-700 bg-red-50 p-3"
|
|
>
|
|
{failure}
|
|
</p>
|
|
)}
|
|
{form.processing && (
|
|
<p role="status">
|
|
A guardar o dossier no servidor. Aguarde a
|
|
confirmação.
|
|
</p>
|
|
)}
|
|
<button className={button} disabled={form.processing}>
|
|
{form.processing ? 'A guardar…' : 'Preparar dossier'}
|
|
</button>
|
|
</form>
|
|
</details>
|
|
<section className="space-y-3">
|
|
<h2 className="text-xl font-bold">
|
|
Pedidos em acompanhamento{' '}
|
|
{manager ? '— equipa / CRM interno' : ''}
|
|
</h2>
|
|
{queue.map((r) => (
|
|
<article className={card} key={r.id}>
|
|
<Link
|
|
className="underline"
|
|
href={`/balcao-virtual/pedidos/${r.id}`}
|
|
>
|
|
{r.protocol} — {r.title}
|
|
</Link>
|
|
<p>
|
|
{states[r.status]} · Prazo interno:{' '}
|
|
{r.due_at ?? 'Não atribuído'}
|
|
</p>
|
|
</article>
|
|
))}
|
|
{!queue.length && <p>Sem pedidos atribuídos neste fluxo.</p>}
|
|
</section>
|
|
<section className="space-y-3">
|
|
<h2 className="text-xl font-bold">
|
|
Tarefas assumidas no CRM interno
|
|
</h2>
|
|
{guidanceTasks.map((t) => (
|
|
<article className={card} key={t.id}>
|
|
<Link
|
|
className="underline"
|
|
href={`/balcao-virtual/dossiers/${t.case_id}`}
|
|
>
|
|
{t.case_title} ? {t.title}
|
|
</Link>
|
|
<p>
|
|
{t.status} ? Prazo interno: {t.due_at}
|
|
</p>
|
|
</article>
|
|
))}
|
|
</section>
|
|
<section>
|
|
<h2 className="text-xl font-bold">
|
|
Pedidos anteriores preservados
|
|
</h2>
|
|
{legacyRequests.map((r) => (
|
|
<p key={r.reference}>
|
|
{r.reference} — {r.status}
|
|
</p>
|
|
))}
|
|
{!legacyRequests.length && (
|
|
<p>Sem pedidos anteriores associados à conta.</p>
|
|
)}
|
|
</section>
|
|
</Frame>
|
|
);
|
|
}
|