Add delivery task deliver dialog

This commit is contained in:
2026-09-03 13:28:55 +05:30
parent 5f82090272
commit 38b893b9f6
@@ -0,0 +1,131 @@
'use client'
import { useEffect, useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog'
import { Field, FieldGroup, FieldLabel } from '@/components/ui/field'
import { toast } from '@/components/ui/toast'
import { deliveryTasks as deliveryTasksApi, stock as stockApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { DeliveryTaskResponse } from '@/types/api'
interface DeliveryTaskDeliverDialogProps {
open: boolean
onClose: () => void
onSuccess: () => void
task: DeliveryTaskResponse | null
vehicleId: string | null
}
export function DeliveryTaskDeliverDialog({ open, onClose, onSuccess, task, vehicleId }: DeliveryTaskDeliverDialogProps) {
const [quantities, setQuantities] = useState<Record<string, number>>({})
const [available, setAvailable] = useState<Record<string, number>>({})
const [submitting, setSubmitting] = useState(false)
useEffect(() => {
if (!open || !task) return
setQuantities(Object.fromEntries(task.items.map((i) => [i.itemId, i.quantityFulfilled])))
setAvailable({})
if (!vehicleId) return
stockApi.balances({ locationId: vehicleId, locationType: 'Vehicle', pageSize: 200 })
.then((res) => {
const map = Object.fromEntries(getPaginatedItems(res).map((b) => [b.itemId, b.quantity]))
setAvailable(map)
})
.catch((err) => toast.add({ title: 'Failed to load vehicle stock', description: getApiError(err), type: 'error' }))
}, [open, task, vehicleId])
if (!task) return null
const hasShortfall = task.items.some((item) => (quantities[item.itemId] ?? 0) > (available[item.itemId] ?? 0))
const nothingToDeliver = task.items.every((item) => (quantities[item.itemId] ?? 0) === 0)
async function onSubmit() {
setSubmitting(true)
try {
const items = Object.entries(quantities).map(([itemId, quantityFulfilled]) => ({ itemId, quantityFulfilled }))
await deliveryTasksApi.setQuantities(task!.id, items)
await deliveryTasksApi.confirm(task!.id)
toast.add({ title: 'Delivery confirmed', type: 'success' })
onSuccess()
onClose()
} catch (err) {
toast.add({ title: 'Failed to confirm delivery', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Confirm Delivery</DialogTitle>
<DialogDescription>
Enter the quantity actually delivered for each item — you can't hand over more than what's
currently loaded on the vehicle.
</DialogDescription>
</DialogHeader>
<FieldGroup>
{task.items.map((item) => {
const onVehicle = available[item.itemId] ?? 0
const requested = quantities[item.itemId] ?? 0
const short = requested > onVehicle
const cap = Math.min(item.quantity, onVehicle)
return (
<Field key={item.itemId}>
<FieldLabel htmlFor={`qty-${item.itemId}`}>
{item.itemName}{' '}
<span className="text-muted-foreground">
(ordered {item.quantity}, on vehicle {onVehicle})
</span>
</FieldLabel>
<Input
id={`qty-${item.itemId}`}
type="number"
min={0}
max={cap}
aria-invalid={short}
value={requested}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setQuantities((prev) => ({ ...prev, [item.itemId]: Number(e.target.value) }))
}
/>
{short && (
<p className="text-xs text-destructive">
Only {onVehicle} of this item is loaded on the vehicle.
</p>
)}
</Field>
)
})}
</FieldGroup>
{nothingToDeliver && (
<p className="text-sm text-destructive">
Nothing is available to deliver — no stock for this order is currently loaded on the
vehicle.
</p>
)}
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="button" onClick={onSubmit} disabled={submitting || hasShortfall || nothingToDeliver}>
{submitting ? 'Confirming…' : 'Confirm Delivery'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}