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/attachment/request-revision-dialog.tsx | |
| parent | fa6a6093014c5d60188edfc9c4552e81c4b97bd1 (diff) | |
(대표님) 20250617 18시 작업사항
Diffstat (limited to 'lib/b-rfq/attachment/request-revision-dialog.tsx')
| -rw-r--r-- | lib/b-rfq/attachment/request-revision-dialog.tsx | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/b-rfq/attachment/request-revision-dialog.tsx b/lib/b-rfq/attachment/request-revision-dialog.tsx new file mode 100644 index 00000000..90d5b543 --- /dev/null +++ b/lib/b-rfq/attachment/request-revision-dialog.tsx @@ -0,0 +1,205 @@ +// components/rfq/request-revision-dialog.tsx +"use client"; + +import { useState, useTransition } 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 { AlertTriangle, Loader2 } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { requestRevision } from "../service"; + +const revisionFormSchema = z.object({ + revisionReason: z + .string() + .min(10, "수정 요청 사유를 최소 10자 이상 입력해주세요") + .max(500, "수정 요청 사유는 500자를 초과할 수 없습니다"), +}); + +type RevisionFormData = z.infer<typeof revisionFormSchema>; + +interface RequestRevisionDialogProps { + responseId: number; + attachmentType: string; + serialNo: string; + vendorName?: string; + currentRevision: string; + trigger?: React.ReactNode; + onSuccess?: () => void; +} + +export function RequestRevisionDialog({ + responseId, + attachmentType, + serialNo, + vendorName, + currentRevision, + trigger, + onSuccess, +}: RequestRevisionDialogProps) { + const [open, setOpen] = useState(false); + const [isPending, startTransition] = useTransition(); + const { toast } = useToast(); + + const form = useForm<RevisionFormData>({ + resolver: zodResolver(revisionFormSchema), + defaultValues: { + revisionReason: "", + }, + }); + + const handleOpenChange = (newOpen: boolean) => { + setOpen(newOpen); + // 다이얼로그가 닫힐 때 form 리셋 + if (!newOpen) { + form.reset(); + } + }; + + const handleCancel = () => { + form.reset(); + setOpen(false); + }; + + const onSubmit = async (data: RevisionFormData) => { + startTransition(async () => { + try { + const result = await requestRevision(responseId, data.revisionReason); + + if (!result.success) { + throw new Error(result.message); + } + + toast({ + title: "수정 요청 완료", + description: result.message, + }); + + setOpen(false); + form.reset(); + onSuccess?.(); + + } catch (error) { + console.error("Request revision error:", error); + toast({ + title: "수정 요청 실패", + description: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + variant: "destructive", + }); + } + }); + }; + + return ( + <Dialog open={open} onOpenChange={handleOpenChange}> + <DialogTrigger asChild> + {trigger || ( + <Button size="sm" variant="outline"> + <AlertTriangle className="h-3 w-3 mr-1" /> + 수정요청 + </Button> + )} + </DialogTrigger> + <DialogContent className="max-w-lg"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <AlertTriangle className="h-5 w-5 text-orange-600" /> + 수정 요청 + </DialogTitle> + <div className="flex items-center gap-2 text-sm text-muted-foreground"> + <Badge variant="outline">{serialNo}</Badge> + <span>{attachmentType}</span> + <Badge variant="secondary">{currentRevision}</Badge> + {vendorName && ( + <> + <span>•</span> + <span>{vendorName}</span> + </> + )} + </div> + </DialogHeader> + + <div className="space-y-4"> + <div className="bg-orange-50 border border-orange-200 rounded-lg p-4"> + <div className="flex items-start gap-2"> + <AlertTriangle className="h-4 w-4 text-orange-600 mt-0.5 flex-shrink-0" /> + <div className="text-sm text-orange-800"> + <p className="font-medium mb-1">수정 요청 안내</p> + <p> + 벤더에게 현재 제출된 응답에 대한 수정을 요청합니다. + 수정 요청 후 벤더는 새로운 파일을 다시 제출할 수 있습니다. + </p> + </div> + </div> + </div> + + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> + <FormField + control={form.control} + name="revisionReason" + render={({ field }) => ( + <FormItem> + <FormLabel className="text-base font-medium"> + 수정 요청 사유 <span className="text-red-500">*</span> + </FormLabel> + <FormControl> + <Textarea + placeholder="수정이 필요한 구체적인 사유를 입력해주세요... 예: 제출된 도면에서 치수 정보가 누락되었습니다." + className="resize-none" + rows={4} + disabled={isPending} + {...field} + /> + </FormControl> + <div className="flex justify-between text-xs text-muted-foreground"> + <FormMessage /> + <span>{field.value?.length || 0}/500</span> + </div> + </FormItem> + )} + /> + + <div className="flex justify-end gap-2 pt-2"> + <Button + type="button" + variant="outline" + onClick={handleCancel} + disabled={isPending} + > + 취소 + </Button> + <Button + type="submit" + disabled={isPending} + // className="bg-orange-600 hover:bg-orange-700" + > + {isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />} + {isPending ? "요청 중..." : "수정 요청"} + </Button> + </div> + </form> + </Form> + </div> + </DialogContent> + </Dialog> + ); +}
\ No newline at end of file |
