ask again
This commit is contained in:
@@ -216,8 +216,12 @@ function RequestsPanel({
|
||||
};
|
||||
|
||||
const decide = useMutation({
|
||||
mutationFn: ({ requestid, approve }: { requestid: number; approve: boolean }) =>
|
||||
approve ? stockApi.approve(requestid) : stockApi.reject(requestid),
|
||||
mutationFn: ({ requestid, action }: { requestid: number; action: 'approve' | 'reject' | 'arrive' }) =>
|
||||
action === 'approve'
|
||||
? stockApi.approve(requestid)
|
||||
: action === 'arrive'
|
||||
? stockApi.confirmArrival(requestid)
|
||||
: stockApi.reject(requestid),
|
||||
onSettled: async () => {
|
||||
setActing(null);
|
||||
await refreshAfterDecision();
|
||||
@@ -225,7 +229,17 @@ function RequestsPanel({
|
||||
});
|
||||
|
||||
const pending = requests.filter((entry) => entry.status === 'Pending');
|
||||
/**
|
||||
* Agreed to, not yet arrived.
|
||||
*
|
||||
* The stage that did not exist: approving wrote the stock onto the shelf
|
||||
* straight away, so the count claimed the goods were there from the moment
|
||||
* the admin agreed to send them — days before the van turns up, and the
|
||||
* branch sells against a shelf that is empty.
|
||||
*/
|
||||
const inTransit = requests.filter((entry) => entry.status === 'Approved');
|
||||
const selection = useSelection(pending.map((entry) => entry.requestid));
|
||||
const arriving = useSelection(inTransit.map((entry) => entry.requestid));
|
||||
|
||||
/**
|
||||
* The batch decision.
|
||||
@@ -235,18 +249,24 @@ function RequestsPanel({
|
||||
* really been received and the merchant must not approve them again.
|
||||
*/
|
||||
const decideBatch = useMutation({
|
||||
mutationFn: ({ ids, approve }: { ids: number[]; approve: boolean }) =>
|
||||
approve ? stockApi.approveMany(ids) : stockApi.rejectMany(ids),
|
||||
onSuccess: (result, { ids, approve }) => {
|
||||
mutationFn: ({ ids, action }: { ids: number[]; action: 'approve' | 'reject' | 'arrive' }) =>
|
||||
action === 'approve'
|
||||
? stockApi.approveMany(ids)
|
||||
: action === 'arrive'
|
||||
? stockApi.confirmArrivalMany(ids)
|
||||
: stockApi.rejectMany(ids),
|
||||
onSuccess: (result, { ids, action }) => {
|
||||
const done = result?.updated?.length ?? ids.length;
|
||||
const failed = result?.failed?.length ?? 0;
|
||||
const verb = approve ? 'approved' : 'rejected';
|
||||
const verb =
|
||||
action === 'approve' ? 'approved' : action === 'arrive' ? 'added to the shelf' : 'rejected';
|
||||
setOutcome(
|
||||
failed === 0
|
||||
? `${count(done)} ${verb}.`
|
||||
: `${count(done)} ${verb}. ${count(failed)} could not be — they are still waiting.`,
|
||||
);
|
||||
selection.clear();
|
||||
arriving.clear();
|
||||
},
|
||||
onError: () => setOutcome('Nothing was changed. Try again, or decide them one at a time.'),
|
||||
onSettled: refreshAfterDecision,
|
||||
@@ -342,7 +362,7 @@ function RequestsPanel({
|
||||
size="sm"
|
||||
icon={<X size={14} />}
|
||||
isDisabled={decideBatch.isPending}
|
||||
onClick={() => decideBatch.mutate({ ids: selection.chosen, approve: false })}
|
||||
onClick={() => decideBatch.mutate({ ids: selection.chosen, action: 'reject' })}
|
||||
/>
|
||||
<Button
|
||||
label={
|
||||
@@ -355,7 +375,7 @@ function RequestsPanel({
|
||||
icon={<Check size={14} />}
|
||||
isLoading={decideBatch.isPending}
|
||||
isDisabled={decideBatch.isPending}
|
||||
onClick={() => decideBatch.mutate({ ids: selection.chosen, approve: true })}
|
||||
onClick={() => decideBatch.mutate({ ids: selection.chosen, action: 'approve' })}
|
||||
/>
|
||||
</HStack>
|
||||
) : null}
|
||||
@@ -424,7 +444,7 @@ function RequestsPanel({
|
||||
isDisabled={decide.isPending && acting === request.requestid}
|
||||
onClick={() => {
|
||||
setActing(request.requestid);
|
||||
decide.mutate({ requestid: request.requestid, approve: false });
|
||||
decide.mutate({ requestid: request.requestid, action: 'reject' });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -435,14 +455,100 @@ function RequestsPanel({
|
||||
isDisabled={decide.isPending && acting === request.requestid}
|
||||
onClick={() => {
|
||||
setActing(request.requestid);
|
||||
decide.mutate({ requestid: request.requestid, approve: true });
|
||||
decide.mutate({ requestid: request.requestid, action: 'approve' });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Card>
|
||||
))}
|
||||
</VStack>
|
||||
|
||||
{/* ── Agreed, not yet arrived ─────────────────────────────────────────
|
||||
The stage between deciding and stocking. Nothing here has touched the
|
||||
shelf: the ledger is written when the goods turn up, so the count
|
||||
never claims stock that is still on a van. */}
|
||||
{inTransit.length > 0 ? (
|
||||
<VStack gap={1}>
|
||||
<SectionHeader
|
||||
title="On their way"
|
||||
note="nothing reaches a shelf until arrival is confirmed"
|
||||
/>
|
||||
|
||||
<div className="bulkbar" data-active={arriving.count > 0 ? 'yes' : 'no'}>
|
||||
<HStack justify="between" align="center" gap={2} wrap="wrap">
|
||||
<label className="bulkbar-all">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={arriving.allChosen}
|
||||
ref={(el) => {
|
||||
if (el) el.indeterminate = arriving.someChosen;
|
||||
}}
|
||||
onChange={arriving.toggleAll}
|
||||
aria-label={arriving.allChosen ? 'Clear selection' : 'Select everything on its way'}
|
||||
/>
|
||||
<Text type="body" size="sm">
|
||||
{arriving.count > 0
|
||||
? `${count(arriving.count)} selected`
|
||||
: `Select all ${count(inTransit.length)}`}
|
||||
</Text>
|
||||
</label>
|
||||
|
||||
{arriving.count > 0 ? (
|
||||
<HStack gap={1} align="center" wrap="wrap">
|
||||
<Button label="Clear" variant="ghost" size="sm" isDisabled={decideBatch.isPending} onClick={arriving.clear} />
|
||||
<Button
|
||||
label={decideBatch.isPending ? 'Working…' : `Confirm arrival of ${count(arriving.count)}`}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
icon={<CheckCheck size={14} />}
|
||||
isLoading={decideBatch.isPending}
|
||||
isDisabled={decideBatch.isPending}
|
||||
onClick={() => decideBatch.mutate({ ids: arriving.chosen, action: 'arrive' })}
|
||||
/>
|
||||
</HStack>
|
||||
) : null}
|
||||
</HStack>
|
||||
</div>
|
||||
|
||||
{inTransit.map((request) => (
|
||||
<Card key={request.requestid} padding={0} elevation="low">
|
||||
<HStack justify="between" align="center" gap={2} style={{ padding: 16 }}>
|
||||
<HStack gap={1.5} align="center" style={{ minWidth: 0 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="bulkbar-tick"
|
||||
checked={arriving.has(request.requestid)}
|
||||
onChange={() => arriving.toggle(request.requestid)}
|
||||
aria-label={`Select ${request.productname ?? `product ${request.productid}`}`}
|
||||
/>
|
||||
<VStack gap={0} style={{ minWidth: 0 }}>
|
||||
<Text type="label" size="sm" weight="semibold" maxLines={1}>
|
||||
{request.productname ?? `Product ${request.productid}`}
|
||||
</Text>
|
||||
<Text type="body" size="xsm" color="secondary">
|
||||
{branchLabel(request.locationname) || `Branch ${request.locationid}`} · <strong>{count(request.qty)}</strong> units approved
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
|
||||
<Button
|
||||
label={`Confirm arrival of ${count(request.qty)}`}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
icon={<CheckCheck size={14} />}
|
||||
isDisabled={decide.isPending && acting === request.requestid}
|
||||
onClick={() => {
|
||||
setActing(request.requestid);
|
||||
decide.mutate({ requestid: request.requestid, action: 'arrive' });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</Card>
|
||||
))}
|
||||
</VStack>
|
||||
) : null}
|
||||
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* Stated once, under the queue, rather than on every row. */}
|
||||
|
||||
Reference in New Issue
Block a user