my stock update page ui

This commit is contained in:
2026-10-05 19:20:08 +05:30
parent bcb5d1de28
commit b7f9a6aaeb
2 changed files with 37 additions and 51 deletions

View File

@@ -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) }
: {})} : {})}
/> />

View File

@@ -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'}