This commit is contained in:
2026-09-08 15:40:48 +05:30
parent 024fc4adfd
commit 59828811f9
12 changed files with 437 additions and 71 deletions

View File

@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Badge } from '@astryxdesign/core/Badge';
import { Button } from '@astryxdesign/core/Button';
import { Card } from '@astryxdesign/core/Card';
@@ -118,6 +118,41 @@ export function SheetImportPanel({ tenantid, locationid }: SheetImportPanelProps
*/
const [dropId, setDropId] = useState<string | null>(null);
const [isWorking, setIsWorking] = useState(false);
/**
* The live watch on the current batch.
*
* Held so it can be called off — when a new file replaces this one, when the
* panel resets, and when the drawer closes. Without that, closing the drawer
* mid-run left a poll reading a batch nothing was rendering.
*/
const watcher = useRef<AbortController | null>(null);
useEffect(() => () => watcher.current?.abort(), []);
/**
* Follows a batch until it finishes, updating the steps as it goes.
*
* NOT awaited by the caller, and that is the point. The upload is over once
* the service has the file; what follows is a wait — often on their admin to
* release the drop — and holding the submit handler open for it would keep
* the button spinning for hours. Every reading lands through `setBatch`, so
* the panel re-renders on each one.
*/
function watch(batchId: string) {
watcher.current?.abort();
const controller = new AbortController();
watcher.current = controller;
void pollBatch(batchId, setBatch, controller.signal)
.then((settled) => {
if (!controller.signal.aborted) setBatch(settled);
})
.catch(() => {
/* Aborted, or the service stopped answering. The last reading stays on
screen with its batch id, which is what the operator comes back
with — inventing an error over a poll that was cancelled would
report a failure that did not happen. */
});
}
/** Set when the upload succeeded but its receipt could not be filed. */
const [receiptError, setReceiptError] = useState<string | null>(null);
@@ -143,6 +178,8 @@ export function SheetImportPanel({ tenantid, locationid }: SheetImportPanelProps
setFile(chosen);
setParsed(null);
setParseError(null);
// A new file is a new upload, so stop watching the old one first.
watcher.current?.abort();
setBatch(null);
setDropId(null);
setReceiptError(null);
@@ -234,7 +271,8 @@ export function SheetImportPanel({ tenantid, locationid }: SheetImportPanelProps
}
}
setBatch(await pollBatch(submitted.batch_id, setBatch));
// The steps from here on arrive by themselves — see `watch`.
watch(submitted.batch_id);
} catch (cause) {
setParseError(errorMessage(cause));
} finally {
@@ -695,6 +733,7 @@ export function SheetImportPanel({ tenantid, locationid }: SheetImportPanelProps
label="Send another file"
variant="secondary"
onClick={() => {
watcher.current?.abort();
setBatch(null);
setParsed(null);
setFile(null);