162 lines
4.8 KiB
TypeScript
162 lines
4.8 KiB
TypeScript
'use client';
|
|
|
|
import { useState, type FormEvent } from 'react';
|
|
import { Loader2, Trash2 } from 'lucide-react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogBody,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Label } from '@/components/ui/label';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import type { DetectionDiscardReason, DiscardDetectionPayload } from '@/types';
|
|
|
|
const DISCARD_REASON_OPTIONS: Array<{
|
|
value: DetectionDiscardReason;
|
|
label: string;
|
|
}> = [
|
|
{ value: 'not_a_defect', label: 'Not a defect' },
|
|
{ value: 'wrong_class', label: 'Wrong classification' },
|
|
{ value: 'duplicate', label: 'Duplicate detection' },
|
|
{ value: 'poor_image_quality', label: 'Poor image quality' },
|
|
{ value: 'incorrect_location', label: 'Incorrect location' },
|
|
{ value: 'already_repaired', label: 'Already repaired' },
|
|
{ value: 'other', label: 'Other' },
|
|
];
|
|
|
|
interface DetectionDiscardDialogProps {
|
|
open: boolean;
|
|
detectionLabel: string;
|
|
isPending: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onSubmit: (payload: DiscardDetectionPayload) => Promise<void>;
|
|
}
|
|
|
|
export function DetectionDiscardDialog({
|
|
open,
|
|
detectionLabel,
|
|
isPending,
|
|
onOpenChange,
|
|
onSubmit,
|
|
}: DetectionDiscardDialogProps) {
|
|
const [reason, setReason] = useState<DetectionDiscardReason | ''>('');
|
|
const [comment, setComment] = useState('');
|
|
|
|
const resetForm = () => {
|
|
setReason('');
|
|
setComment('');
|
|
};
|
|
|
|
const handleOpenChange = (nextOpen: boolean) => {
|
|
if (isPending) return;
|
|
|
|
if (!nextOpen) resetForm();
|
|
onOpenChange(nextOpen);
|
|
};
|
|
|
|
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
if (!reason || isPending) return;
|
|
|
|
try {
|
|
await onSubmit({
|
|
reason,
|
|
comment: comment.trim() || undefined,
|
|
});
|
|
resetForm();
|
|
} catch {
|
|
// The mutation displays the request error and keeps the dialog open.
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
<DialogContent showCloseButton={!isPending}>
|
|
<form className="flex min-h-0 flex-1 flex-col" onSubmit={handleSubmit}>
|
|
<DialogHeader>
|
|
<DialogTitle>Discard detection</DialogTitle>
|
|
<DialogDescription>
|
|
Remove {detectionLabel} from this ticket and record why the AI
|
|
result is incorrect.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<DialogBody className="space-y-5">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="discard-reason">Reason</Label>
|
|
<Select
|
|
value={reason}
|
|
onValueChange={(value) =>
|
|
setReason(value as DetectionDiscardReason)
|
|
}
|
|
disabled={isPending}
|
|
>
|
|
<SelectTrigger id="discard-reason" className="w-full">
|
|
<SelectValue placeholder="Select a reason" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{DISCARD_REASON_OPTIONS.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<Label htmlFor="discard-comment">Comment</Label>
|
|
<span className="text-xs text-muted-foreground">
|
|
Optional | {comment.length}/500
|
|
</span>
|
|
</div>
|
|
<Textarea
|
|
id="discard-comment"
|
|
value={comment}
|
|
onChange={(event) => setComment(event.target.value)}
|
|
placeholder="Add details that can help improve future detections"
|
|
rows={4}
|
|
maxLength={500}
|
|
disabled={isPending}
|
|
/>
|
|
</div>
|
|
</DialogBody>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => handleOpenChange(false)}
|
|
disabled={isPending}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
variant="destructive"
|
|
disabled={!reason || isPending}
|
|
>
|
|
{isPending ? <Loader2 className="animate-spin" /> : <Trash2 />}
|
|
{isPending ? 'Discarding' : 'Discard detection'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|