diff options
Diffstat (limited to 'lib/pq/pq-review-table-new/request-investigation-dialog.tsx')
| -rw-r--r-- | lib/pq/pq-review-table-new/request-investigation-dialog.tsx | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/lib/pq/pq-review-table-new/request-investigation-dialog.tsx b/lib/pq/pq-review-table-new/request-investigation-dialog.tsx new file mode 100644 index 00000000..d5588be4 --- /dev/null +++ b/lib/pq/pq-review-table-new/request-investigation-dialog.tsx @@ -0,0 +1,331 @@ +"use client" + +import * as React from "react" +import { CalendarIcon } from "lucide-react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns" +import { z } from "zod" + +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { UserCombobox } from "./user-combobox" +import { getQMManagers } from "@/lib/pq/service" + +// QM 사용자 타입 +interface QMUser { + id: number + name: string + email: string + department?: string +} + +const requestInvestigationFormSchema = z.object({ + evaluationType: z.enum(["SITE_AUDIT", "QM_SELF_AUDIT"], { + required_error: "평가 유형을 선택해주세요.", + }), + qmManagerId: z.number({ + required_error: "QM 담당자를 선택해주세요.", + }), + forecastedAt: z.date({ + required_error: "실사 예정일을 선택해주세요.", + }), + investigationAddress: z.string().min(1, "실사 장소를 입력해주세요."), + investigationMethod: z.string().optional(), + investigationNotes: z.string().optional(), +}) + +type RequestInvestigationFormValues = z.infer<typeof requestInvestigationFormSchema> + +interface RequestInvestigationDialogProps { + isOpen: boolean + onClose: () => void + onSubmit: (data: { + evaluationType: "SITE_AUDIT" | "QM_SELF_AUDIT", + qmManagerId: number, + forecastedAt: Date, + investigationAddress: string, + investigationMethod?: string, + investigationNotes?: string + }) => Promise<void> + selectedCount: number + // 선택된 행에서 가져온 초기값 + initialData?: { + evaluationType?: "SITE_AUDIT" | "QM_SELF_AUDIT", + qmManagerId?: number, + forecastedAt?: Date, + investigationAddress?: string, + investigationMethod?: string, + investigationNotes?: string + } +} + +export function RequestInvestigationDialog({ + isOpen, + onClose, + onSubmit, + selectedCount, + initialData, +}: RequestInvestigationDialogProps) { + const [isPending, setIsPending] = React.useState(false) + const [qmManagers, setQMManagers] = React.useState<QMUser[]>([]) + const [isLoadingManagers, setIsLoadingManagers] = React.useState(false) + + // form 객체 생성 시 initialData 활용 + const form = useForm<RequestInvestigationFormValues>({ + resolver: zodResolver(requestInvestigationFormSchema), + defaultValues: { + evaluationType: initialData?.evaluationType || "SITE_AUDIT", + qmManagerId: initialData?.qmManagerId || undefined, + forecastedAt: initialData?.forecastedAt || undefined, + investigationAddress: initialData?.investigationAddress || "", + investigationMethod: initialData?.investigationMethod || "", + investigationNotes: initialData?.investigationNotes || "", + }, + }) + + // Dialog가 열릴 때마다 초기값으로 폼 재설정 + React.useEffect(() => { + if (isOpen) { + form.reset({ + evaluationType: initialData?.evaluationType || "SITE_AUDIT", + qmManagerId: initialData?.qmManagerId || undefined, + forecastedAt: initialData?.forecastedAt || undefined, + investigationAddress: initialData?.investigationAddress || "", + investigationMethod: initialData?.investigationMethod || "", + investigationNotes: initialData?.investigationNotes || "", + }); + } + }, [isOpen, initialData, form]); + + // Dialog가 열릴 때 QM 담당자 목록 로드 + React.useEffect(() => { + if (isOpen && qmManagers.length === 0) { + const loadQMManagers = async () => { + setIsLoadingManagers(true) + try { + const result = await getQMManagers() + if (result.success && result.data) { + setQMManagers(result.data) + } + } catch (error) { + console.error("QM 담당자 로드 오류:", error) + } finally { + setIsLoadingManagers(false) + } + } + + loadQMManagers() + } + }, [isOpen, qmManagers.length]) + + async function handleSubmit(data: RequestInvestigationFormValues) { + setIsPending(true) + try { + await onSubmit(data) + } finally { + setIsPending(false) + form.reset() + } + } + + return ( + <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}> + <DialogContent className="sm:max-w-[500px]"> + <DialogHeader> + <DialogTitle>실사 의뢰</DialogTitle> + <DialogDescription> + {selectedCount}개 협력업체에 대한 실사를 의뢰합니다. 실사 관련 정보를 입력해주세요. + </DialogDescription> + </DialogHeader> + <Form {...form}> + <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4"> + <FormField + control={form.control} + name="evaluationType" + render={({ field }) => ( + <FormItem> + <FormLabel>평가 유형</FormLabel> + <Select + onValueChange={field.onChange} + defaultValue={field.value} + disabled={isPending} + > + <FormControl> + <SelectTrigger> + <SelectValue placeholder="평가 유형을 선택하세요" /> + </SelectTrigger> + </FormControl> + <SelectContent> + <SelectItem value="SITE_AUDIT">실사의뢰평가</SelectItem> + <SelectItem value="QM_SELF_AUDIT">QM자체평가</SelectItem> + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="qmManagerId" + render={({ field }) => ( + <FormItem> + <FormLabel>QM 담당자</FormLabel> + <FormControl> + <UserCombobox + users={qmManagers} + value={field.value} + onChange={field.onChange} + placeholder={isLoadingManagers ? "담당자 로딩 중..." : "담당자 선택..."} + disabled={isPending || isLoadingManagers} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="forecastedAt" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>실사 예정일</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant={"outline"} + className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} + disabled={isPending} + > + {field.value ? ( + format(field.value, "yyyy년 MM월 dd일") + ) : ( + <span>실사 예정일을 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + disabled={(date) => date < new Date()} + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="investigationAddress" + render={({ field }) => ( + <FormItem> + <FormLabel>실사 장소</FormLabel> + <FormControl> + <Textarea + placeholder="실사가 진행될 주소를 입력하세요" + {...field} + disabled={isPending} + className="min-h-[60px]" + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="investigationMethod" + render={({ field }) => ( + <FormItem> + <FormLabel>실사 방법 (선택사항)</FormLabel> + <FormControl> + <Input + placeholder="실사 방법을 입력하세요" + {...field} + disabled={isPending} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="investigationNotes" + render={({ field }) => ( + <FormItem> + <FormLabel>특이사항 (선택사항)</FormLabel> + <FormControl> + <Textarea + placeholder="실사 관련 특이사항을 입력하세요" + className="resize-none min-h-[60px]" + {...field} + disabled={isPending} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <DialogFooter> + <Button + type="button" + variant="outline" + onClick={onClose} + disabled={isPending} + > + 취소 + </Button> + <Button type="submit" disabled={isPending || isLoadingManagers}> + {isPending ? "처리 중..." : "실사 의뢰"} + </Button> + </DialogFooter> + </form> + </Form> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
