Files
Investidor-V2/resources/js/lib/dossier-export-state.ts
T
2026-10-21 08:43:12 +02:00

90 lines
2.9 KiB
TypeScript

import {
validateDossierAttachments,
type DossierAttachment,
} from './project-dossier-report.ts';
export type DossierExportOperation =
| 'word'
| 'package'
| 'pdf'
| 'csv'
| 'attachments';
const messages: Record<DossierExportOperation, string> = {
word: 'Não foi possível gerar o relatório Word.',
package: 'Não foi possível preparar o pacote de anexos.',
pdf: 'Não foi possível preparar a versão para PDF.',
csv: 'Não foi possível preparar o ficheiro CSV.',
attachments: 'Não foi possível ler os anexos seleccionados.',
};
let failureSequence = 0;
export function dossierExportFailure(
operation: DossierExportOperation,
cause: unknown,
log: (message: string, detail: unknown) => void = console.error,
): string {
const reference = `EXP-${Date.now().toString(36)}-${++failureSequence}`;
// Local diagnostics only: never send dossier values or file contents.
log('[InvesteMoz exportação]', { reference, operation, cause });
return `${messages[operation]} Os dados, as secções e os anexos foram mantidos. Tente novamente pelo mesmo botão. Referência: ${reference}.`;
}
/** Synchronous acquisition prevents a second click before React renders busy. */
export class DossierExportGate {
private active = false;
async run(task: () => Promise<void>): Promise<boolean> {
if (this.active) return false;
this.active = true;
try {
await task();
return true;
} finally {
this.active = false;
}
}
}
export async function prepareDossierAttachments(
current: DossierAttachment[],
incoming: File[],
): Promise<{
attachments: DossierAttachment[];
duplicateCount: number;
problem?: string;
}> {
for (const file of incoming) {
const problem = validateDossierAttachments([file]);
if (problem)
return { attachments: current, duplicateCount: 0, problem };
}
const identity = async (file: File) => {
const hash = await crypto.subtle.digest(
'SHA-256',
await file.arrayBuffer(),
);
return `${file.name}:${Array.from(new Uint8Array(hash), (b) => b.toString(16).padStart(2, '0')).join('')}`;
};
const known = new Set(
await Promise.all(current.map((item) => identity(item.file))),
);
const added: DossierAttachment[] = [];
let duplicateCount = 0;
for (const file of incoming) {
const key = await identity(file);
if (known.has(key)) {
duplicateCount++;
continue;
}
known.add(key);
added.push({ file, include: true, caption: '' });
}
const problem = validateDossierAttachments(
[...current, ...added].map((item) => item.file),
);
return {
attachments: problem ? current : [...current, ...added],
duplicateCount,
problem: problem ?? undefined,
};
}