1 parent
6e23f1533b
commit
419325db12
52 files changed
+1329
-110
No files matched your search
@@ -49,7 +49,7 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
const client = useQueryClient();
|
||||
const [jobId, setJobId] = useState<string | null>(null);
|
||||
const [step, setStep] = useState(0);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<{ text: string; action?: string; reference?: string } | null>(null);
|
||||
const [criteria, setCriteria] = useState(false);
|
||||
const [sheetDialog, setSheetDialog] = useState<{ sourceId: string; sheets: string[] } | null>(null);
|
||||
const [mappings, setMappings] = useState<{ sourceColumn: string; modelColumn: string }[]>([]);
|
||||
@@ -94,6 +94,10 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
});
|
||||
}
|
||||
|
||||
function fail(error?: { message?: string; action?: string; code?: string }, fallback = "Não foi possível continuar.") {
|
||||
setMessage({ text: error?.message ?? fallback, action: error?.action, reference: error?.code });
|
||||
}
|
||||
|
||||
async function start(modelId: string) {
|
||||
setMessage(null);
|
||||
const response = await fetch("/api/spreadsheet-adapter/jobs", {
|
||||
@@ -101,9 +105,9 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ modelId, idempotencyKey: crypto.randomUUID() }),
|
||||
});
|
||||
const data = await readJson<{ job?: JobDto; error?: { message: string } }>(response);
|
||||
const data = await readJson<{ job?: JobDto; error?: { message: string; action?: string; code?: string } }>(response);
|
||||
if (!response.ok || !data.job) {
|
||||
setMessage(data.error?.message ?? "Não foi possível abrir a operação");
|
||||
fail(data.error, "Não foi possível abrir a operação");
|
||||
return;
|
||||
}
|
||||
setJobId(data.job.id);
|
||||
@@ -117,12 +121,12 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
form.set("file", file);
|
||||
form.set("jobId", jobId);
|
||||
const response = await fetch("/api/spreadsheet-adapter/sources/validate", { method: "POST", body: form });
|
||||
const data = await readJson<{ job: JobDto; code?: string; message?: string; error?: { message: string } }>(response);
|
||||
const data = await readJson<{ job: JobDto; code?: string; message?: string; error?: { message: string; action?: string; code?: string } }>(response);
|
||||
if (data.error) {
|
||||
setMessage(data.error.message);
|
||||
fail(data.error);
|
||||
return;
|
||||
}
|
||||
setMessage(data.message ?? null);
|
||||
setMessage(data.message ? { text: data.message } : null);
|
||||
const created = data.job.sources.at(-1);
|
||||
if (created && data.code === "SHEET_REQUIRED") setSheetDialog({ sourceId: created.id, sheets: created.sheets });
|
||||
await job.refetch();
|
||||
@@ -147,7 +151,7 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
});
|
||||
const data = await readJson<PreviewContract & { error?: { message: string } }>(response);
|
||||
if (!response.ok) {
|
||||
setMessage(data.error?.message ?? "Prévia indisponível");
|
||||
fail(data.error, "Prévia indisponível");
|
||||
return;
|
||||
}
|
||||
setPreview(data);
|
||||
@@ -163,8 +167,8 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ sourceId: source.id, idempotencyKey: crypto.randomUUID() }),
|
||||
});
|
||||
const data = await readJson<{ error?: { message: string } }>(response);
|
||||
if (!response.ok) setMessage(data.error?.message ?? "Processamento não iniciado");
|
||||
const data = await readJson<{ error?: { message: string; action?: string; code?: string } }>(response);
|
||||
if (!response.ok) fail(data.error, "Processamento não iniciado");
|
||||
await job.refetch();
|
||||
setStep(5);
|
||||
}
|
||||
@@ -176,7 +180,7 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
<p className="mt-2 text-sm text-ink/75">Uma transferência por vez. A prévia é obrigatória antes de gerar o arquivo.</p>
|
||||
</header>
|
||||
<JourneyTrail steps={["Modelo", "Fontes", "Mapeamento", "Revisão", "Processamento", "Resultado"]} current={step} />
|
||||
{message ? <ErrorState message={message} /> : null}
|
||||
{message ? <ErrorState message={message.text} action={message.action} reference={message.reference} /> : null}
|
||||
{step === 0 ? (
|
||||
<Card>
|
||||
{models.isLoading ? <LoadingBlock /> : null}
|
||||
@@ -284,7 +288,9 @@ export function Operation({ ctx }: { ctx: RequestContext }) {
|
||||
<Card>
|
||||
<h2 className="text-lg font-semibold">{job.data.progress?.label ?? "Processando"}</h2>
|
||||
<p className="mt-2 text-sm">{job.data.progress?.percent == null ? "Progresso ainda não é estimável." : `${job.data.progress.percent}%`}</p>
|
||||
{job.data.status === "failed" ? <ErrorState message={job.data.errorCode ?? "Falha no processamento"} onRetry={() => void processCurrent()} /> : null}
|
||||
{job.data.status === "failed" ? (
|
||||
<ErrorState message="O processamento não foi concluído." action="Tente novamente." reference={job.data.errorCode ?? undefined} onRetry={() => void processCurrent()} />
|
||||
) : null}
|
||||
{source?.hasOutput ? (
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{(["xlsx", "csv", "pdf"] as const).map((format) => (
|
||||
|
||||
@@ -19,11 +19,23 @@ export function EmptyState({ title, description, action }: { title: string; desc
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorState({ message, onRetry }: { message: string; onRetry?: () => void }) {
|
||||
export function ErrorState({
|
||||
message,
|
||||
action,
|
||||
reference,
|
||||
onRetry,
|
||||
}: {
|
||||
message: string;
|
||||
action?: string;
|
||||
reference?: string;
|
||||
onRetry?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div role="alert" className="rounded-[14px] border border-danger bg-white p-4">
|
||||
<p className="font-semibold text-danger">Não foi possível continuar</p>
|
||||
<p className="mt-1 text-sm">{message}</p>
|
||||
{action ? <p className="mt-1 text-sm">{action}</p> : null}
|
||||
{reference ? <p className="mt-2 text-xs">Referência: {reference}</p> : null}
|
||||
{onRetry ? (
|
||||
<button type="button" className="mt-3 text-sm font-semibold underline" onClick={onRetry}>
|
||||
Tentar novamente
|
||||
|
||||
Reference in new issue
Block a user