mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 07:51:02 +00:00
158 lines
4.1 KiB
TypeScript
158 lines
4.1 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
Button,
|
|
FileInput,
|
|
Group,
|
|
Modal,
|
|
Stack,
|
|
Text,
|
|
TextInput,
|
|
Textarea,
|
|
} from "@mantine/core";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { Camera, PenLine } from "lucide-react";
|
|
|
|
import { ContractSignaturePad } from "@/components/bookings/ContractSignaturePad";
|
|
import { lastMileService } from "@/services/last-mile.service";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
interface ProofOfDeliveryModalProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
lastMileId: string | null;
|
|
reference?: string | null;
|
|
/** Called after a successful capture so the caller can refetch. */
|
|
onDone: () => void;
|
|
}
|
|
|
|
/**
|
|
* Proof of delivery capture for an EDR last-mile leg: recipient name, a drawn
|
|
* signature, and proof photos. On confirm it uploads everything and completes
|
|
* the delivery (marks the leg DELIVERED).
|
|
*/
|
|
export function ProofOfDeliveryModal({
|
|
opened,
|
|
onClose,
|
|
lastMileId,
|
|
reference,
|
|
onDone,
|
|
}: ProofOfDeliveryModalProps) {
|
|
const { toast } = useToast();
|
|
const [recipient, setRecipient] = useState("");
|
|
const [notes, setNotes] = useState("");
|
|
const [signatureUrl, setSignatureUrl] = useState<string | null>(null);
|
|
const [photos, setPhotos] = useState<File[]>([]);
|
|
|
|
const reset = () => {
|
|
setRecipient("");
|
|
setNotes("");
|
|
setSignatureUrl(null);
|
|
setPhotos([]);
|
|
};
|
|
|
|
const close = () => {
|
|
reset();
|
|
onClose();
|
|
};
|
|
|
|
const submit = useMutation({
|
|
mutationFn: async () => {
|
|
if (!lastMileId) throw new Error("No delivery selected");
|
|
const signature = signatureUrl
|
|
? await (await fetch(signatureUrl)).blob()
|
|
: null;
|
|
return lastMileService.recordProofOfDelivery(lastMileId, {
|
|
recipientName: recipient.trim(),
|
|
notes: notes.trim() || undefined,
|
|
signature,
|
|
photos,
|
|
});
|
|
},
|
|
onSuccess: () => {
|
|
toast({
|
|
title: "Proof of delivery recorded",
|
|
description: "The delivery has been completed.",
|
|
});
|
|
reset();
|
|
onDone();
|
|
onClose();
|
|
},
|
|
onError: (e) =>
|
|
toast({
|
|
variant: "destructive",
|
|
title: "Could not record delivery",
|
|
description: e instanceof Error ? e.message : undefined,
|
|
}),
|
|
});
|
|
|
|
// Require a recipient plus at least one form of proof (signature or a photo).
|
|
const canSubmit =
|
|
recipient.trim().length > 0 && (Boolean(signatureUrl) || photos.length > 0);
|
|
|
|
return (
|
|
<Modal
|
|
opened={opened}
|
|
onClose={close}
|
|
title={`Record delivery${reference ? ` — ${reference}` : ""}`}
|
|
size="lg"
|
|
centered
|
|
>
|
|
<Stack gap="md">
|
|
<TextInput
|
|
label="Received by"
|
|
placeholder="Recipient's name"
|
|
required
|
|
value={recipient}
|
|
onChange={(e) => setRecipient(e.currentTarget.value)}
|
|
/>
|
|
|
|
<div>
|
|
<Text size="sm" fw={500} mb={4}>
|
|
Recipient signature
|
|
</Text>
|
|
<ContractSignaturePad onChange={setSignatureUrl} />
|
|
</div>
|
|
|
|
<FileInput
|
|
label="Proof photos"
|
|
placeholder="Attach delivery photo(s)"
|
|
leftSection={<Camera size={16} />}
|
|
accept="image/*"
|
|
multiple
|
|
clearable
|
|
value={photos}
|
|
onChange={setPhotos}
|
|
/>
|
|
|
|
<Textarea
|
|
label="Notes"
|
|
placeholder="Optional delivery notes"
|
|
autosize
|
|
minRows={2}
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.currentTarget.value)}
|
|
/>
|
|
|
|
<Text size="xs" c="dimmed">
|
|
Provide a signature or at least one photo. Confirming completes the delivery.
|
|
</Text>
|
|
|
|
<Group justify="flex-end">
|
|
<Button variant="default" onClick={close} disabled={submit.isPending}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
color="green"
|
|
leftSection={<PenLine size={16} />}
|
|
loading={submit.isPending}
|
|
disabled={!canSubmit}
|
|
onClick={() => submit.mutate()}
|
|
>
|
|
Confirm delivery
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
);
|
|
}
|