my stock update page ui
This commit is contained in:
@@ -260,10 +260,17 @@ export function ProductDrawer({
|
|||||||
{/* ── Health score ──────────────────────────────────────────────────
|
{/* ── Health score ──────────────────────────────────────────────────
|
||||||
Under Sizes and above the shop detail: it is what a SHOPPER reads,
|
Under Sizes and above the shop detail: it is what a SHOPPER reads,
|
||||||
and it belongs with the other shopper-facing facts. */}
|
and it belongs with the other shopper-facing facts. */}
|
||||||
|
{/* The toggle is behind `canManage` for the same reason hiding a product
|
||||||
|
is: it writes `products.showhealthscore`, which is per TENANT and not
|
||||||
|
per outlet, so a branch turning a rating off would turn it off at
|
||||||
|
every shop the business runs. Absent rather than disabled — a greyed
|
||||||
|
switch invites "why", and the answer is that it is not a decision one
|
||||||
|
outlet makes. The panel itself still renders: a branch user sees what
|
||||||
|
a customer sees, they just cannot change it. */}
|
||||||
<HealthScorePanel
|
<HealthScorePanel
|
||||||
product={product}
|
product={product}
|
||||||
category={product.categoryname || product.Subcategoryname}
|
category={product.categoryname || product.Subcategoryname}
|
||||||
{...(product.tenantid && product.productid
|
{...(canManage && product.tenantid && product.productid
|
||||||
? { onToggleShown: (show: boolean) => showScore.mutate(show) }
|
? { onToggleShown: (show: boolean) => showScore.mutate(show) }
|
||||||
: {})}
|
: {})}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -227,14 +227,14 @@ function RequestsPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const decide = useMutation({
|
const decide = useMutation({
|
||||||
mutationFn: ({ requestid, action }: { requestid: number; action: 'approve' | 'reject' | 'arrive' }) =>
|
/* No 'arrive' here. Confirming arrival writes stock onto a branch's
|
||||||
action === 'approve'
|
shelf and belongs to the branch — see the row actions below. Narrowing
|
||||||
? stockApi.approve(requestid)
|
the type rather than leaving the branch unreachable, so a future caller
|
||||||
: action === 'arrive'
|
cannot quietly reintroduce it from this screen. */
|
||||||
? stockApi.confirmArrival(requestid)
|
mutationFn: ({ requestid, action }: { requestid: number; action: 'approve' | 'reject' }) =>
|
||||||
: stockApi.reject(requestid),
|
action === 'approve' ? stockApi.approve(requestid) : stockApi.reject(requestid),
|
||||||
onSuccess: (_data, { action }) => {
|
onSuccess: (_data, { action }) => {
|
||||||
const verb = action === 'approve' ? 'approved' : action === 'arrive' ? 'confirmed as arrived' : 'rejected';
|
const verb = action === 'approve' ? 'approved' : 'rejected';
|
||||||
setOutcome(`Request ${verb}.`);
|
setOutcome(`Request ${verb}.`);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@@ -292,26 +292,14 @@ function RequestsPanel({
|
|||||||
[requests, selection],
|
[requests, selection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedInTransit = useMemo(
|
|
||||||
() =>
|
|
||||||
requests
|
|
||||||
.filter((r) => selection.has(r.requestid) && r.status === 'Approved')
|
|
||||||
.map((r) => r.requestid),
|
|
||||||
[requests, selection],
|
|
||||||
);
|
|
||||||
|
|
||||||
const decideBatch = useMutation({
|
const decideBatch = useMutation({
|
||||||
mutationFn: ({ ids, action }: { ids: number[]; action: 'approve' | 'reject' | 'arrive' }) =>
|
mutationFn: ({ ids, action }: { ids: number[]; action: 'approve' | 'reject' }) =>
|
||||||
action === 'approve'
|
action === 'approve' ? stockApi.approveMany(ids) : stockApi.rejectMany(ids),
|
||||||
? stockApi.approveMany(ids)
|
|
||||||
: action === 'arrive'
|
|
||||||
? stockApi.confirmArrivalMany(ids)
|
|
||||||
: stockApi.rejectMany(ids),
|
|
||||||
onSuccess: (result, { ids, action }) => {
|
onSuccess: (result, { ids, action }) => {
|
||||||
const done = result?.updated?.length ?? ids.length;
|
const done = result?.updated?.length ?? ids.length;
|
||||||
const failed = result?.failed?.length ?? 0;
|
const failed = result?.failed?.length ?? 0;
|
||||||
const verb =
|
const verb = action === 'approve' ? 'approved' : 'rejected';
|
||||||
action === 'approve' ? 'approved' : action === 'arrive' ? 'added to shelf' : 'rejected';
|
|
||||||
setOutcome(
|
setOutcome(
|
||||||
failed === 0
|
failed === 0
|
||||||
? `${count(done)} request${done === 1 ? '' : 's'} ${verb}.`
|
? `${count(done)} request${done === 1 ? '' : 's'} ${verb}.`
|
||||||
@@ -490,21 +478,11 @@ function RequestsPanel({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{selectedInTransit.length > 0 ? (
|
{/* No bulk "confirm arrival" either, for the same reason as
|
||||||
<Button
|
the row action: the stock is written when the BRANCH says
|
||||||
label={
|
the goods arrived, and an admin confirming forty deliveries
|
||||||
decideBatch.isPending
|
they never saw is the version of this that puts stock on a
|
||||||
? 'Working…'
|
shelf that is empty. */}
|
||||||
: `Confirm arrival of ${count(selectedInTransit.length)}`
|
|
||||||
}
|
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
icon={<CheckCheck size={14} />}
|
|
||||||
isLoading={decideBatch.isPending}
|
|
||||||
isDisabled={decideBatch.isPending}
|
|
||||||
onClick={() => decideBatch.mutate({ ids: selectedInTransit, action: 'arrive' })}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
</div>
|
||||||
@@ -710,19 +688,20 @@ function RequestsPanel({
|
|||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
) : request.status === 'Approved' ? (
|
) : request.status === 'Approved' ? (
|
||||||
<Button
|
/* No action here, deliberately.
|
||||||
label="Confirm arrival"
|
|
||||||
variant="primary"
|
Confirming arrival is what WRITES THE STOCK onto
|
||||||
size="sm"
|
a branch's shelf, and only the branch can know
|
||||||
style={{ height: 28, padding: '0 10px', fontSize: 12 }}
|
the goods actually turned up. For the admin this
|
||||||
icon={<CheckCheck size={13} />}
|
row is an acknowledgement — they agreed to send
|
||||||
isDisabled={decide.isPending && isActing}
|
it — so the status is shown and nothing is
|
||||||
isLoading={decide.isPending && isActing}
|
offered. The control lives on the Store user's
|
||||||
onClick={() => {
|
own products screen.
|
||||||
setActing(request.requestid);
|
|
||||||
decide.mutate({ requestid: request.requestid, action: 'arrive' });
|
Absent rather than disabled: a greyed button
|
||||||
}}
|
invites "why", and the answer is that it is not
|
||||||
/>
|
this person's call. */
|
||||||
|
null
|
||||||
) : (
|
) : (
|
||||||
<span style={{ fontSize: 12, color: 'var(--color-ink-4)' }}>
|
<span style={{ fontSize: 12, color: 'var(--color-ink-4)' }}>
|
||||||
{request.status === 'Received' ? 'Completed' : 'Declined'}
|
{request.status === 'Received' ? 'Completed' : 'Declined'}
|
||||||
|
|||||||
Reference in New Issue
Block a user