diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-17 09:02:32 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-17 09:02:32 +0000 |
| commit | 7a1524ba54f43d0f2a19e4bca2c6a2e0b01c5ef1 (patch) | |
| tree | daa214d404c7fc78b32419a028724e5671a6c7a4 /lib/b-rfq/vendor-response/waive-response-dialog.tsx | |
| parent | fa6a6093014c5d60188edfc9c4552e81c4b97bd1 (diff) | |
(대표님) 20250617 18시 작업사항
Diffstat (limited to 'lib/b-rfq/vendor-response/waive-response-dialog.tsx')
| -rw-r--r-- | lib/b-rfq/vendor-response/waive-response-dialog.tsx | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/b-rfq/vendor-response/waive-response-dialog.tsx b/lib/b-rfq/vendor-response/waive-response-dialog.tsx new file mode 100644 index 00000000..5ded4da3 --- /dev/null +++ b/lib/b-rfq/vendor-response/waive-response-dialog.tsx @@ -0,0 +1,210 @@ +// components/rfq/waive-response-dialog.tsx +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Textarea } from "@/components/ui/textarea"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import * as z from "zod"; +import { FileX, Loader2, AlertTriangle } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { useRouter } from "next/navigation"; + +const waiveFormSchema = z.object({ + responseComment: z.string().min(1, "포기 사유를 입력해주세요"), + vendorComment: z.string().optional(), +}); + +type WaiveFormData = z.infer<typeof waiveFormSchema>; + +interface WaiveResponseDialogProps { + responseId: number; + attachmentType: string; + serialNo: string; + trigger?: React.ReactNode; + onSuccess?: () => void; +} + +export function WaiveResponseDialog({ + responseId, + attachmentType, + serialNo, + trigger, + onSuccess, +}: WaiveResponseDialogProps) { + const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const { toast } = useToast(); + const router = useRouter(); + + const form = useForm<WaiveFormData>({ + resolver: zodResolver(waiveFormSchema), + defaultValues: { + responseComment: "", + vendorComment: "", + }, + }); + + const onSubmit = async (data: WaiveFormData) => { + setIsSubmitting(true); + + try { + const response = await fetch("/api/vendor-responses/waive", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + responseId, + responseComment: data.responseComment, + vendorComment: data.vendorComment, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || "응답 포기 처리 실패"); + } + + toast({ + title: "응답 포기 완료", + description: "해당 항목에 대한 응답이 포기 처리되었습니다.", + }); + + setOpen(false); + form.reset(); + + router.refresh(); + onSuccess?.(); + + } catch (error) { + console.error("Waive error:", error); + toast({ + title: "처리 실패", + description: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } + }; + + return ( + <Dialog open={open} onOpenChange={setOpen}> + <DialogTrigger asChild> + {trigger || ( + <Button size="sm" variant="outline"> + <FileX className="h-3 w-3 mr-1" /> + 포기 + </Button> + )} + </DialogTrigger> + <DialogContent className="max-w-lg"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2 text-orange-600"> + <FileX className="h-5 w-5" /> + 응답 포기 + </DialogTitle> + <div className="flex items-center gap-2 text-sm text-muted-foreground"> + <Badge variant="outline">{serialNo}</Badge> + <span>{attachmentType}</span> + </div> + </DialogHeader> + + <div className="bg-orange-50 border border-orange-200 rounded-lg p-4 mb-4"> + <div className="flex items-center gap-2 text-orange-800 text-sm font-medium mb-2"> + <AlertTriangle className="h-4 w-4" /> + 주의사항 + </div> + <p className="text-orange-700 text-sm"> + 응답을 포기하면 해당 항목에 대한 입찰 참여가 불가능합니다. + 포기 사유를 명확히 기입해 주세요. + </p> + </div> + + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> + {/* 포기 사유 (필수) */} + <FormField + control={form.control} + name="responseComment" + render={({ field }) => ( + <FormItem> + <FormLabel className="text-red-600"> + 포기 사유 <span className="text-red-500">*</span> + </FormLabel> + <FormControl> + <Textarea + placeholder="응답을 포기하는 사유를 구체적으로 입력하세요..." + className="resize-none" + rows={4} + {...field} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 내부 코멘트 (선택) */} + <FormField + control={form.control} + name="vendorComment" + render={({ field }) => ( + <FormItem> + <FormLabel>내부 코멘트 (선택)</FormLabel> + <FormControl> + <Textarea + placeholder="내부 참고용 코멘트를 입력하세요..." + className="resize-none" + rows={2} + {...field} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 버튼 */} + <div className="flex justify-end gap-2"> + <Button + type="button" + variant="outline" + onClick={() => setOpen(false)} + disabled={isSubmitting} + > + 취소 + </Button> + <Button + type="submit" + variant="destructive" + disabled={isSubmitting} + > + {isSubmitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />} + {isSubmitting ? "처리 중..." : "포기하기"} + </Button> + </div> + </form> + </Form> + </DialogContent> + </Dialog> + ); +}
\ No newline at end of file |
