Group investor reports and preserved financial copies in their existing dossier
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
createInvestorDossierReport,
|
||||
investorDossierReportIndex,
|
||||
type InvestorDossierReportInput,
|
||||
} from '@/lib/investor-dossier-report';
|
||||
import {
|
||||
createProjectDossierPrintHtml,
|
||||
generateProjectDossierDocx,
|
||||
} from '@/lib/project-dossier-report';
|
||||
|
||||
export function InvestorDossierReports({
|
||||
dossier,
|
||||
}: {
|
||||
dossier: InvestorDossierReportInput;
|
||||
}) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const requiresChoice =
|
||||
dossier.opportunity_options.length > 1 && !dossier.context_opportunity;
|
||||
const entries = requiresChoice ? [] : investorDossierReportIndex(dossier);
|
||||
async function exportReport(format: 'word' | 'html') {
|
||||
setBusy(true);
|
||||
setError('');
|
||||
try {
|
||||
const model = createInvestorDossierReport(dossier);
|
||||
const blob =
|
||||
format === 'word'
|
||||
? await generateProjectDossierDocx(model, [], true)
|
||||
: new Blob(
|
||||
[
|
||||
await createProjectDossierPrintHtml(
|
||||
model,
|
||||
[],
|
||||
true,
|
||||
),
|
||||
],
|
||||
{ type: 'text/html;charset=utf-8' },
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `dossier-${dossier.case.id}-v${dossier.case.version}-${dossier.context_opportunity ? encodeURIComponent(dossier.context_opportunity) : 'comum'}.${format === 'word' ? 'docx' : 'html'}`;
|
||||
link.click();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
} catch (cause) {
|
||||
setError(
|
||||
cause instanceof Error
|
||||
? cause.message
|
||||
: 'Não foi possível preparar o relatório. Os registos foram preservados.',
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<section id="relatorios" className="space-y-4 rounded-xl border p-4">
|
||||
<h2 className="text-lg font-semibold">Relatórios deste dossier</h2>
|
||||
<p>
|
||||
Dossier {dossier.case.title} · versão {dossier.case.version} ·
|
||||
oportunidade {dossier.context_opportunity ?? 'contexto comum'}.
|
||||
</p>
|
||||
<p>
|
||||
Reúna as etapas, a viabilidade, a análise SWOT e a preparação
|
||||
APIEX. O relatório mantém os estados e as pendências; os
|
||||
originais continuam nas respectivas versões.
|
||||
</p>
|
||||
{requiresChoice ? (
|
||||
<p role="status">
|
||||
Seleccione a oportunidade em tratamento para reunir os seus
|
||||
relatórios sem misturar contextos.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
className="rounded border px-3 py-2"
|
||||
disabled={busy}
|
||||
onClick={() => exportReport('word')}
|
||||
>
|
||||
Descarregar relatório consolidado (Word)
|
||||
</button>
|
||||
<button
|
||||
className="rounded border px-3 py-2"
|
||||
disabled={busy}
|
||||
onClick={() => exportReport('html')}
|
||||
>
|
||||
Descarregar versão para impressão/PDF
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm">
|
||||
A versão HTML pode ser aberta e impressa ou guardada
|
||||
como PDF. As descargas são privadas; não enviam
|
||||
processos ou mensagens a entidades externas.
|
||||
</p>
|
||||
{!!entries.length && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<caption className="sr-only">
|
||||
Índice por etapa, versão e data
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2">Etapa</th>
|
||||
<th className="p-2">
|
||||
Relatório ou documento
|
||||
</th>
|
||||
<th className="p-2">Versão e data</th>
|
||||
<th className="p-2">Estado</th>
|
||||
<th className="p-2">Consultar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry) => (
|
||||
<tr key={entry.id} className="border-t">
|
||||
<td className="p-2">
|
||||
{entry.stage}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{entry.title}
|
||||
</td>
|
||||
<td className="max-w-56 p-2 break-words">
|
||||
{entry.version} ·{' '}
|
||||
{entry.date ||
|
||||
'Data não registada'}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{entry.state}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<a
|
||||
className="underline"
|
||||
href={entry.href}
|
||||
>
|
||||
{entry.action}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{!entries.length && (
|
||||
<p>
|
||||
Nenhum relatório ou documento produzido e autorizado
|
||||
neste âmbito. O relatório consolidado identifica as
|
||||
etapas e as pendências existentes.
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm">
|
||||
Os relatórios financeiros são apresentados apenas ao
|
||||
titular autorizado do rascunho. Consulte os documentos
|
||||
de cada acto antes de executar o investimento.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{error && (
|
||||
<p role="alert" className="text-red-700">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user