deliveries
This commit is contained in:
101
src/features/store-admin/DeliveryProgress.tsx
Normal file
101
src/features/store-admin/DeliveryProgress.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { VStack } from '@astryxdesign/core/VStack';
|
||||
import { errorMessage } from '@/api/client';
|
||||
import { deliveriesApi } from '@/api/deliveries';
|
||||
import type { DeliveryRow } from '@/api/types';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
import { stampNow } from './assignDelivery';
|
||||
|
||||
/**
|
||||
* Moving a delivery along from the back office.
|
||||
*
|
||||
* The rider's app drives this ladder in the ordinary case. This is for when it
|
||||
* cannot: a rider whose phone died, a job cancelled by phone, a delivery that
|
||||
* was completed but never marked. Without it the only way to correct a stuck
|
||||
* job is a database write.
|
||||
*
|
||||
* ── Two things the backend does that shape this ─────────────────────────────
|
||||
*
|
||||
* `updatedelivery` mirrors the status onto the parent order for `pending`,
|
||||
* `delivered` and `cancelled` — but NOT for `picked`, which updates the
|
||||
* delivery and leaves the order where it was. So the order and its delivery
|
||||
* legitimately disagree in the middle of a journey, and this does not try to
|
||||
* paper over that.
|
||||
*
|
||||
* The lifecycle timestamp is sent explicitly. `Updates()` skips zero-valued
|
||||
* struct fields, so a status sent without its stamp moved the status and left
|
||||
* the column blank — which is how a delivery ends up marked delivered with no
|
||||
* delivery time against it.
|
||||
*/
|
||||
|
||||
/** What can be set from here, in the order a job actually walks. */
|
||||
const MOVES = [
|
||||
{ to: 'picked', label: 'Picked up', stamp: 'pickuptime' },
|
||||
{ to: 'delivered', label: 'Delivered', stamp: 'deliverytime' },
|
||||
{ to: 'cancelled', label: 'Cancelled', stamp: 'canceltime' },
|
||||
] as const;
|
||||
|
||||
/** The end states. Nothing follows them, so nothing is offered. */
|
||||
const SETTLED = ['delivered', 'cancelled'];
|
||||
|
||||
export function DeliveryProgress({ job }: { job: DeliveryRow }) {
|
||||
const client = useQueryClient();
|
||||
const [problem, setProblem] = useState<string | null>(null);
|
||||
|
||||
const status = (job.orderstatus ?? '').trim().toLowerCase();
|
||||
const isSettled = SETTLED.includes(status);
|
||||
|
||||
const move = useMutation({
|
||||
mutationFn: ({ to, stamp }: { to: string; stamp: string }) =>
|
||||
deliveriesApi.update({
|
||||
deliveryid: job.deliveryid,
|
||||
orderheaderid: job.orderheaderid ?? 0,
|
||||
orderstatus: to,
|
||||
[stamp]: stampNow(),
|
||||
}),
|
||||
onSuccess: () => client.invalidateQueries({ queryKey: queryKeys.insights.all }),
|
||||
onError: (error) => setProblem(errorMessage(error)),
|
||||
});
|
||||
|
||||
if (isSettled) {
|
||||
return (
|
||||
<Text type="body" size="xsm" color="secondary">
|
||||
This job is {status}. Nothing further to record.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack gap={1}>
|
||||
<div className="progress-moves">
|
||||
{MOVES.map((step) => (
|
||||
<button
|
||||
key={step.to}
|
||||
type="button"
|
||||
className="progress-move"
|
||||
data-danger={step.to === 'cancelled'}
|
||||
disabled={move.isPending || step.to === status}
|
||||
onClick={() => {
|
||||
setProblem(null);
|
||||
move.mutate({ to: step.to, stamp: step.stamp });
|
||||
}}
|
||||
>
|
||||
{step.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{problem ? (
|
||||
<Text type="body" size="xsm" role="alert" style={{ color: 'var(--color-error, #d64545)' }}>
|
||||
{problem}
|
||||
</Text>
|
||||
) : (
|
||||
<Text type="body" size="xsm" color="secondary">
|
||||
Use these only when the rider's app has not. Marking a job delivered here also closes the
|
||||
order.
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user