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 | 667 |
1 files changed, 337 insertions, 330 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 index d5588be4..6cbb885f 100644 --- a/lib/pq/pq-review-table-new/request-investigation-dialog.tsx +++ b/lib/pq/pq-review-table-new/request-investigation-dialog.tsx @@ -1,331 +1,338 @@ -"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> - ) +"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([
+ "PURCHASE_SELF_EVAL", // 구매자체평가
+ "DOCUMENT_EVAL", // 서류평가
+ // "PRODUCT_INSPECTION", // 제품검사평가
+ // "SITE_VISIT_EVAL" // 방문실사평가
+ ], {
+ 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: "PURCHASE_SELF_EVAL" | "DOCUMENT_EVAL" | "PRODUCT_INSPECTION" | "SITE_VISIT_EVAL",
+ qmManagerId: number,
+ forecastedAt: Date,
+ investigationAddress: string,
+ investigationMethod?: string,
+ investigationNotes?: string
+ }) => Promise<void>
+ selectedCount: number
+ // 선택된 행에서 가져온 초기값
+ initialData?: {
+ evaluationType?: "PURCHASE_SELF_EVAL" | "DOCUMENT_EVAL" | "PRODUCT_INSPECTION" | "SITE_VISIT_EVAL",
+ 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 || "PURCHASE_SELF_EVAL",
+ 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 || "PURCHASE_SELF_EVAL",
+ 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="PURCHASE_SELF_EVAL">구매자체평가</SelectItem>
+ <SelectItem value="DOCUMENT_EVAL">서류평가</SelectItem>
+ {/* <SelectItem value="PRODUCT_INSPECTION">제품검사평가</SelectItem> */}
+ {/* <SelectItem value="SITE_VISIT_EVAL">방문실사평가</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 |
