fix loading sheet UI
This commit is contained in:
@@ -25,6 +25,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { FieldGroup } from '@/components/ui/field'
|
import { FieldGroup } from '@/components/ui/field'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { toast } from '@/components/ui/toast'
|
import { toast } from '@/components/ui/toast'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import {
|
import {
|
||||||
shopPos as shopPosApi,
|
shopPos as shopPosApi,
|
||||||
regions as regionsApi,
|
regions as regionsApi,
|
||||||
@@ -53,6 +54,39 @@ import type { LoadingSheetItemInput } from '@/lib/api/operations'
|
|||||||
|
|
||||||
const ALL = '__all__'
|
const ALL = '__all__'
|
||||||
|
|
||||||
|
const STEPS = [
|
||||||
|
{
|
||||||
|
n: 1,
|
||||||
|
label: 'Route & Warehouse',
|
||||||
|
title: 'Where is this load coming from?',
|
||||||
|
help: "Pick the route this ride will deliver (optional if you're only adding items manually below) and the warehouse every item will be dispatched from.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 2,
|
||||||
|
label: 'Ride & Assignment',
|
||||||
|
title: 'Which vehicle, and who is populating this sheet?',
|
||||||
|
help: 'Choose the open ride whose vehicle gets loaded, and who is creating this sheet.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 3,
|
||||||
|
label: 'Items',
|
||||||
|
title: 'What should be loaded from Shop POs?',
|
||||||
|
help: "Expand a shop to tick items and set quantities. If the main warehouse doesn't have enough stock, use \"Take remaining from another warehouse\" to top up the rest.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 4,
|
||||||
|
label: 'Additional Items',
|
||||||
|
title: "Anything to load that's not on a PO?",
|
||||||
|
help: "Optional — add extra stock for the vehicle that isn't tied to any shop order.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
n: 5,
|
||||||
|
label: 'Review & Confirm',
|
||||||
|
title: 'Double-check before saving',
|
||||||
|
help: 'This is everything that will be saved to the sheet. Fix anything short before confirming later on the loading sheet list.',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
interface ExtraAllocation {
|
interface ExtraAllocation {
|
||||||
warehouseId: string
|
warehouseId: string
|
||||||
quantity: number
|
quantity: number
|
||||||
@@ -115,6 +149,8 @@ function LoadingSheetForm() {
|
|||||||
const [rideId, setRideId] = useState('')
|
const [rideId, setRideId] = useState('')
|
||||||
const [populatedByUserId, setPopulatedByUserId] = useState('')
|
const [populatedByUserId, setPopulatedByUserId] = useState('')
|
||||||
|
|
||||||
|
const [step, setStep] = useState<1 | 2 | 3 | 4 | 5>(1)
|
||||||
|
|
||||||
const [viewStoreId, setViewStoreId] = useState('')
|
const [viewStoreId, setViewStoreId] = useState('')
|
||||||
const [itemsList, setItemsList] = useState<ItemSummaryResponse[]>([])
|
const [itemsList, setItemsList] = useState<ItemSummaryResponse[]>([])
|
||||||
const [manualItems, setManualItems] = useState<ManualItem[]>([])
|
const [manualItems, setManualItems] = useState<ManualItem[]>([])
|
||||||
@@ -441,6 +477,34 @@ function LoadingSheetForm() {
|
|||||||
updateExtras(line, line.extras.filter((_, i) => i !== index))
|
updateExtras(line, line.extras.filter((_, i) => i !== index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goNext() {
|
||||||
|
if (step === 1) {
|
||||||
|
if (!warehouseId) {
|
||||||
|
toast.add({ title: 'Select a warehouse first', type: 'error' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStep(2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (step === 2) {
|
||||||
|
if (!rideId) {
|
||||||
|
toast.add({ title: 'Select a ride to load the vehicle', type: 'error' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!populatedByUserId) {
|
||||||
|
toast.add({ title: 'Select who is populating this sheet', type: 'error' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStep(3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (step < 5) setStep((s) => (s + 1) as typeof step)
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
setStep((s) => Math.max(1, s - 1) as typeof step)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!routeId && manualItems.length === 0) {
|
if (!routeId && manualItems.length === 0) {
|
||||||
toast.add({ title: 'Select a route first', type: 'error' })
|
toast.add({ title: 'Select a route first', type: 'error' })
|
||||||
@@ -541,13 +605,43 @@ function LoadingSheetForm() {
|
|||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold text-foreground">{isEdit ? 'Edit Loading Sheet' : 'New Loading Sheet'}</h1>
|
<h1 className="text-2xl font-semibold text-foreground">{isEdit ? 'Edit Loading Sheet' : 'New Loading Sheet'}</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">Work through the steps below to build the loading sheet.</p>
|
||||||
Select a route first. Confirmed Shop POs for that route's shops will then be
|
|
||||||
available to pick items from. Pick the ride to load last.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Step indicator */}
|
||||||
|
<div className="flex flex-wrap items-center gap-2 text-xs">
|
||||||
|
{STEPS.map((s, idx) => (
|
||||||
|
<div key={s.n} className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={s.n > step}
|
||||||
|
onClick={() => s.n < step && setStep(s.n)}
|
||||||
|
className={cn(
|
||||||
|
'flex h-6 w-6 items-center justify-center rounded-full border font-semibold transition-colors',
|
||||||
|
step === s.n
|
||||||
|
? 'border-primary bg-primary text-primary-foreground'
|
||||||
|
: step > s.n
|
||||||
|
? 'border-primary/40 bg-primary/10 text-primary hover:bg-primary/20'
|
||||||
|
: 'border-muted-foreground/30 text-muted-foreground',
|
||||||
|
s.n < step && 'cursor-pointer'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{step > s.n ? '✓' : s.n}
|
||||||
|
</button>
|
||||||
|
<span className={cn(step === s.n ? 'font-medium text-foreground' : 'text-muted-foreground')}>{s.label}</span>
|
||||||
|
{idx < STEPS.length - 1 && <div className="mx-1 h-px w-8 bg-border" />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step title & guidance */}
|
||||||
|
<div className="rounded-lg bg-muted/40 px-3 py-2">
|
||||||
|
<p className="text-sm font-medium">{STEPS[step - 1].title}</p>
|
||||||
|
<p className="mt-0.5 text-xs text-muted-foreground">{STEPS[step - 1].help}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{step === 1 && (
|
||||||
<div className="rounded-2xl border p-6">
|
<div className="rounded-2xl border p-6">
|
||||||
<h3 className="mb-4 text-sm font-medium text-foreground">Route & Warehouse</h3>
|
<h3 className="mb-4 text-sm font-medium text-foreground">Route & Warehouse</h3>
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
@@ -616,7 +710,9 @@ function LoadingSheetForm() {
|
|||||||
</div>
|
</div>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 3 && (
|
||||||
<div className="rounded-2xl border p-6">
|
<div className="rounded-2xl border p-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-sm font-medium text-foreground">Items</h3>
|
<h3 className="text-sm font-medium text-foreground">Items</h3>
|
||||||
@@ -824,7 +920,9 @@ function LoadingSheetForm() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 4 && (
|
||||||
<div className="rounded-2xl border p-6">
|
<div className="rounded-2xl border p-6">
|
||||||
<div className="mb-4 flex items-center justify-between gap-4">
|
<div className="mb-4 flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -897,7 +995,9 @@ function LoadingSheetForm() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 2 && (
|
||||||
<div className="rounded-2xl border p-6">
|
<div className="rounded-2xl border p-6">
|
||||||
<h3 className="mb-4 text-sm font-medium text-foreground">Ride & Assignment</h3>
|
<h3 className="mb-4 text-sm font-medium text-foreground">Ride & Assignment</h3>
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
@@ -935,7 +1035,9 @@ function LoadingSheetForm() {
|
|||||||
</div>
|
</div>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 5 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="w-full rounded-2xl border p-6">
|
<div className="w-full rounded-2xl border p-6">
|
||||||
<h3 className="text-sm font-medium text-foreground">Loading Sheet Summary</h3>
|
<h3 className="text-sm font-medium text-foreground">Loading Sheet Summary</h3>
|
||||||
@@ -969,15 +1071,31 @@ function LoadingSheetForm() {
|
|||||||
<p className="text-sm font-medium text-foreground">Total Quantity</p>
|
<p className="text-sm font-medium text-foreground">Total Quantity</p>
|
||||||
<p className="text-xl font-semibold text-foreground">{totalQuantity}</p>
|
<p className="text-xl font-semibold text-foreground">{totalQuantity}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end gap-2">
|
<div className="flex items-center justify-between rounded-2xl border bg-muted/20 p-4">
|
||||||
<Button type="button" variant="outline" onClick={() => router.push('/operations/loading')}>
|
<div>
|
||||||
|
{step > 1 && (
|
||||||
|
<Button type="button" variant="outline" onClick={goBack}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button type="button" variant="ghost" onClick={() => router.push('/operations/loading')}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
{step < 5 ? (
|
||||||
|
<Button type="button" onClick={goNext}>
|
||||||
|
{step === 1 ? 'Next: Ride & Assignment' : step === 4 ? `Next: Review${(selectedRows.length + manualItems.length) > 0 ? ` (${totalQuantity})` : ''}` : 'Next'}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
<Button type="button" disabled={submitting} onClick={handleSubmit}>
|
<Button type="button" disabled={submitting} onClick={handleSubmit}>
|
||||||
{submitting ? 'Saving…' : isEdit ? 'Save Changes' : 'Create Loading Sheet'}
|
{submitting ? 'Saving…' : isEdit ? 'Save Changes' : 'Create Loading Sheet'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user