diff options
Diffstat (limited to 'lib/tbe/table/tbe-result-dialog.tsx')
| -rw-r--r-- | lib/tbe/table/tbe-result-dialog.tsx | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/lib/tbe/table/tbe-result-dialog.tsx b/lib/tbe/table/tbe-result-dialog.tsx new file mode 100644 index 00000000..59e2f49b --- /dev/null +++ b/lib/tbe/table/tbe-result-dialog.tsx @@ -0,0 +1,208 @@ +"use client" + +import * as React from "react" +import { toast } from "sonner" + +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { Label } from "@/components/ui/label" +import { VendorWithTbeFields } from "@/config/vendorTbeColumnsConfig" +import { getErrorMessage } from "@/lib/handle-error" +import { saveTbeResult } from "@/lib/rfqs/service" + +// Define the props for the TbeResultDialog component +interface TbeResultDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + tbe: VendorWithTbeFields | null + onRefresh?: () => void +} + +// Define TBE result options +const TBE_RESULT_OPTIONS = [ + { value: "pass", label: "Pass", badgeVariant: "default" }, + { value: "non-pass", label: "Non-Pass", badgeVariant: "destructive" }, + { value: "conditional pass", label: "Conditional Pass", badgeVariant: "secondary" }, +] as const + +type TbeResultOption = typeof TBE_RESULT_OPTIONS[number]["value"] + +export function TbeResultDialog({ + open, + onOpenChange, + tbe, + onRefresh, +}: TbeResultDialogProps) { + // Initialize state for form inputs + const [result, setResult] = React.useState<TbeResultOption | "">("") + const [note, setNote] = React.useState("") + const [isSubmitting, setIsSubmitting] = React.useState(false) + + // Update form values when the tbe prop changes + React.useEffect(() => { + if (tbe) { + setResult((tbe.tbeResult as TbeResultOption) || "") + setNote(tbe.tbeNote || "") + } + }, [tbe]) + + // Reset form when dialog closes + React.useEffect(() => { + if (!open) { + // Small delay to avoid visual glitches when dialog is closing + const timer = setTimeout(() => { + if (!tbe) { + setResult("") + setNote("") + } + }, 300) + return () => clearTimeout(timer) + } + }, [open, tbe]) + + // Handle form submission with server action + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!tbe || !result) return + + setIsSubmitting(true) + + try { + // Call the server action to save the TBE result + const response = await saveTbeResult({ + id: tbe.tbeId ?? 0, // This is the id in the rfq_evaluations table + vendorId: tbe.vendorId, // This is the vendorId in the rfq_evaluations table + result: result, // The selected evaluation result + notes: note, // The evaluation notes + }) + + if (!response.success) { + throw new Error(response.message || "Failed to save TBE result") + } + + // Show success toast + toast.success("TBE result saved successfully") + + // Close the dialog + onOpenChange(false) + + // Refresh the data if refresh callback is provided + if (onRefresh) { + onRefresh() + } + } catch (error) { + // Show error toast + toast.error(`Failed to save: ${getErrorMessage(error)}`) + } finally { + setIsSubmitting(false) + } + } + + // Find the selected result option + const selectedOption = TBE_RESULT_OPTIONS.find(option => option.value === result) + + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="sm:max-w-[500px]"> + <DialogHeader> + <DialogTitle className="text-xl font-semibold"> + {tbe?.tbeResult ? "Edit TBE Result" : "Enter TBE Result"} + </DialogTitle> + {tbe && ( + <DialogDescription className="text-sm text-muted-foreground mt-1"> + <div className="flex flex-col gap-1"> + <span> + <strong>Vendor:</strong> {tbe.vendorName} + </span> + <span> + <strong>RFQ Code:</strong> {tbe.rfqCode} + </span> + {tbe.email && ( + <span> + <strong>Email:</strong> {tbe.email} + </span> + )} + </div> + </DialogDescription> + )} + </DialogHeader> + + <form onSubmit={handleSubmit} className="space-y-6 py-2"> + <div className="space-y-2"> + <Label htmlFor="tbe-result" className="text-sm font-medium"> + Evaluation Result + </Label> + <Select + value={result} + onValueChange={(value) => setResult(value as TbeResultOption)} + required + > + <SelectTrigger id="tbe-result" className="w-full"> + <SelectValue placeholder="Select a result" /> + </SelectTrigger> + <SelectContent> + {TBE_RESULT_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + <div className="flex items-center"> + <Badge variant={option.badgeVariant as any} className="mr-2"> + {option.label} + </Badge> + </div> + </SelectItem> + ))} + </SelectContent> + </Select> + </div> + + <div className="space-y-2"> + <Label htmlFor="tbe-note" className="text-sm font-medium"> + Evaluation Note + </Label> + <Textarea + id="tbe-note" + placeholder="Enter evaluation notes..." + value={note} + onChange={(e) => setNote(e.target.value)} + className="min-h-[120px] resize-y" + /> + </div> + + <DialogFooter className="gap-2 sm:gap-0"> + <Button + type="button" + variant="outline" + onClick={() => onOpenChange(false)} + disabled={isSubmitting} + > + Cancel + </Button> + <Button + type="submit" + disabled={!result || isSubmitting} + className="min-w-[100px]" + > + {isSubmitting ? "Saving..." : "Save"} + </Button> + </DialogFooter> + </form> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
