fix(parcel-sync): add 2min timeout to no-geom scan, non-blocking UI
- Server: Promise.race with 120s timeout on no-geom-scan API route - Client: AbortController with 120s timeout on scan fetch - UI: show 'max 2 min' during scanning + hint that buttons work without scan - UI: timeout state shows retry button + explains no-geom won't be available - Prevents indefinitely stuck 'Se scanează...' on slow eTerra responses
This commit is contained in:
@@ -48,9 +48,23 @@ export async function POST(req: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = await EterraClient.create(username, password);
|
const client = await EterraClient.create(username, password);
|
||||||
const result = await scanNoGeometryParcels(client, siruta, {
|
|
||||||
|
// Global timeout: 2 minutes max for the entire scan
|
||||||
|
const scanPromise = scanNoGeometryParcels(client, siruta, {
|
||||||
workspacePk: body.workspacePk ?? null,
|
workspacePk: body.workspacePk ?? null,
|
||||||
});
|
});
|
||||||
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
"Scanare timeout — serverul eTerra răspunde lent. Reîncearcă mai târziu.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
120_000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const result = await Promise.race([scanPromise, timeoutPromise]);
|
||||||
|
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -742,6 +742,9 @@ export function ParcelSyncModule() {
|
|||||||
scannedAt: "",
|
scannedAt: "",
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
// 2min timeout — scan is informational, should not block the page
|
||||||
|
const scanAbort = new AbortController();
|
||||||
|
const scanTimer = setTimeout(() => scanAbort.abort(), 120_000);
|
||||||
const res = await fetch("/api/eterra/no-geom-scan", {
|
const res = await fetch("/api/eterra/no-geom-scan", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -749,7 +752,9 @@ export function ParcelSyncModule() {
|
|||||||
siruta: s,
|
siruta: s,
|
||||||
workspacePk: workspacePk ?? undefined,
|
workspacePk: workspacePk ?? undefined,
|
||||||
}),
|
}),
|
||||||
|
signal: scanAbort.signal,
|
||||||
});
|
});
|
||||||
|
clearTimeout(scanTimer);
|
||||||
const data = (await res.json()) as Record<string, unknown>;
|
const data = (await res.json()) as Record<string, unknown>;
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.warn("[no-geom-scan]", data.error);
|
console.warn("[no-geom-scan]", data.error);
|
||||||
@@ -783,8 +788,19 @@ export function ParcelSyncModule() {
|
|||||||
scannedAt: String(data.scannedAt ?? new Date().toISOString()),
|
scannedAt: String(data.scannedAt ?? new Date().toISOString()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
setNoGeomScan(emptyResult);
|
// Distinguish timeout from other errors for the user
|
||||||
|
const isTimeout =
|
||||||
|
err instanceof DOMException && err.name === "AbortError";
|
||||||
|
if (isTimeout) {
|
||||||
|
console.warn(
|
||||||
|
"[no-geom-scan] Timeout after 2 min — server eTerra lent",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setNoGeomScan({
|
||||||
|
...emptyResult,
|
||||||
|
scannedAt: isTimeout ? "timeout" : "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setNoGeomScanning(false);
|
setNoGeomScanning(false);
|
||||||
},
|
},
|
||||||
@@ -2642,8 +2658,37 @@ export function ParcelSyncModule() {
|
|||||||
<CardContent className="py-3 px-4">
|
<CardContent className="py-3 px-4">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-amber-500" />
|
<Loader2 className="h-4 w-4 animate-spin text-amber-500" />
|
||||||
Se scanează lista de imobile din eTerra…
|
Se scanează lista de imobile din eTerra… (max 2 min)
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground mt-1 ml-6">
|
||||||
|
Poți folosi butoanele de mai jos fără să aștepți scanarea.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scan timed out
|
||||||
|
if (scanDone && noGeomScan.scannedAt === "timeout")
|
||||||
|
return (
|
||||||
|
<Card className="border-amber-200/50 dark:border-amber-800/50">
|
||||||
|
<CardContent className="py-3 px-4">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400">
|
||||||
|
<Clock className="h-4 w-4 shrink-0" />
|
||||||
|
Scanarea a depășit 2 minute — serverul eTerra e lent.
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground mt-1 ml-6">
|
||||||
|
Poți lansa sincronizarea fundal fără rezultate de scanare.
|
||||||
|
Include no-geom nu va fi disponibil.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-6 text-xs mt-1 ml-6"
|
||||||
|
onClick={() => void handleNoGeomScan()}
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-3 w-3 mr-1" />
|
||||||
|
Reîncearcă scanarea
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user