diff --git a/distribution_fe/app/(dashboard)/operations/deliveries/_components/delivery-task-deliver-dialog.tsx b/distribution_fe/app/(dashboard)/operations/deliveries/_components/delivery-task-deliver-dialog.tsx new file mode 100644 index 0000000..2a0f9f5 --- /dev/null +++ b/distribution_fe/app/(dashboard)/operations/deliveries/_components/delivery-task-deliver-dialog.tsx @@ -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>({}) + const [available, setAvailable] = useState>({}) + 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 ( + !v && onClose()}> + + + Confirm Delivery + + Enter the quantity actually delivered for each item — you can't hand over more than what's + currently loaded on the vehicle. + + + + + {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 ( + + + {item.itemName}{' '} + + (ordered {item.quantity}, on vehicle {onVehicle}) + + + ) => + setQuantities((prev) => ({ ...prev, [item.itemId]: Number(e.target.value) })) + } + /> + {short && ( +

+ Only {onVehicle} of this item is loaded on the vehicle. +

+ )} +
+ ) + })} +
+ + {nothingToDeliver && ( +

+ Nothing is available to deliver — no stock for this order is currently loaded on the + vehicle. +

+ )} + + + + + +
+
+ ) +}