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

@@ -33,7 +33,7 @@ import { Card } from '@astryxdesign/core/Card';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
import { VStack } from '@astryxdesign/core/VStack';
import { FileSpreadsheet, RefreshCw, UploadCloud } from 'lucide-react';
import { FileSpreadsheet, UploadCloud } from 'lucide-react';
import { errorMessage } from '@/api/client';
import {
fetchBatch,
@@ -119,7 +119,6 @@ export function UploadsPanel({
const [search, setSearch] = useState('');
const from = range?.fromdate ?? '';
const to = range?.todate ?? '';
const [isRefreshing, setIsRefreshing] = useState(false);
const load = useCallback(async () => {
setError(null);
@@ -151,7 +150,6 @@ export function UploadsPanel({
*/
const refreshLive = useCallback(
async (rows: UploadReceipt[]) => {
setIsRefreshing(true);
const readings: Record<string, LiveReading> = {};
for (const receipt of rows) {
// Nothing further will arrive for these; the receipt is the answer.
@@ -191,7 +189,6 @@ export function UploadsPanel({
}
}
setLive((prev) => ({ ...prev, ...readings }));
setIsRefreshing(false);
},
[],
);
@@ -200,6 +197,55 @@ export function UploadsPanel({
if (receipts && receipts.length > 0) void refreshLive(receipts);
}, [receipts, refreshLive]);
/** Receipts that could still change. A finished, shelved one never will. */
const moving = useMemo(
() => (receipts ?? []).filter((receipt) => !(receipt.laststatus === 'done' && receipt.shelvedat)),
[receipts],
);
/**
* Keeps the moving rows current without anyone reloading the page.
*
* The list used to be read once, when the page loaded. Everything after that
* — their admin releasing a drop, the run finishing, products reaching the
* shelf — arrived only if the operator reloaded the page, which is exactly
* what a status page exists to avoid. The "Check again" button that used to
* paper over it is gone with the same change.
*
* Fifteen seconds, and only while something is unfinished: a page of forty
* settled receipts makes no requests at all. It also pauses entirely while
* the tab is in the background and takes a reading the moment it comes
* forward, so a tab left open all afternoon costs nothing and is up to date
* by the time it is looked at.
*/
useEffect(() => {
if (moving.length === 0) return;
let stopped = false;
let timer: ReturnType<typeof setTimeout>;
const read = () => {
if (stopped || document.visibilityState !== 'visible') return;
void refreshLive(moving);
};
const loop = () => {
read();
timer = setTimeout(loop, 15000);
};
timer = setTimeout(loop, 15000);
// Coming back to the tab is worth a reading straight away — that is the
// moment somebody wants the answer.
const onVisible = () => read();
document.addEventListener('visibilitychange', onVisible);
return () => {
stopped = true;
clearTimeout(timer);
document.removeEventListener('visibilitychange', onVisible);
};
}, [moving, refreshLive]);
const rows = useMemo(() => receipts ?? [], [receipts]);
const showTenant = !tenantid;
@@ -312,15 +358,12 @@ export function UploadsPanel({
aria-label="Search by file name"
onChange={(event) => setSearch(event.target.value)}
/>
<Button
label={isRefreshing ? 'Checking…' : 'Check again'}
variant="secondary"
size="sm"
isLoading={isRefreshing}
isDisabled={isRefreshing}
icon={<RefreshCw size={13} />}
onClick={() => void refreshLive(rows)}
/>
{/* "Check again" stood here. It is gone because the page reads the
moving rows by itself now — every fifteen seconds while something is
unfinished, and again the moment the tab comes forward. A refresh
button beside a list that refreshes itself is a button that never
changes anything, and pressing it to find out whether it did is
worse than not having it. */}
{onUpload ? (
<Button
label="Upload spreadsheet"