From 33be47506f0aa62b969d82521580a29e95080268 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 13 Aug 2025 11:05:09 +0000 Subject: (대표님) 입찰, 법무검토, EDP 변경사항 대응, dolce 개선, form-data 개선, 정규업체 등록관리 추가 (최겸) pq 미사용 컴포넌트 및 페이지 제거, 파일 라우트에 pq 적용 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../additional-info-dialog.tsx | 502 +++++++++++++++++++++ .../document-status-dialog.tsx | 265 +++++++++++ .../skip-reason-dialog.tsx | 98 ++++ 3 files changed, 865 insertions(+) create mode 100644 components/vendor-regular-registrations/additional-info-dialog.tsx create mode 100644 components/vendor-regular-registrations/document-status-dialog.tsx create mode 100644 components/vendor-regular-registrations/skip-reason-dialog.tsx (limited to 'components/vendor-regular-registrations') diff --git a/components/vendor-regular-registrations/additional-info-dialog.tsx b/components/vendor-regular-registrations/additional-info-dialog.tsx new file mode 100644 index 00000000..fbd60515 --- /dev/null +++ b/components/vendor-regular-registrations/additional-info-dialog.tsx @@ -0,0 +1,502 @@ +"use client"; + +import * as React from "react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Plus, Trash2 } from "lucide-react"; +import { toast } from "sonner"; +import { + saveVendorBusinessContacts, + saveVendorAdditionalInfo, + fetchVendorRegistrationStatus +} from "@/lib/vendor-regular-registrations/service"; + +// 업무담당자 정보 스키마 +const businessContactSchema = z.object({ + contactType: z.enum(["sales", "design", "delivery", "quality", "tax_invoice"]), + contactName: z.string().min(1, "담당자명은 필수입니다"), + position: z.string().min(1, "직급은 필수입니다"), + department: z.string().min(1, "부서는 필수입니다"), + responsibility: z.string().min(1, "담당업무는 필수입니다"), + email: z.string().email("올바른 이메일 형식이 아닙니다"), +}); + +// 추가정보 스키마 +const additionalInfoSchema = z.object({ + businessType: z.string().optional(), + industryType: z.string().optional(), + companySize: z.string().optional(), + revenue: z.string().optional(), + factoryEstablishedDate: z.string().optional(), + preferredContractTerms: z.string().optional(), +}); + +// 전체 폼 스키마 +const formSchema = z.object({ + businessContacts: z.array(businessContactSchema).min(5, "모든 업무담당자 정보를 입력해주세요"), + additionalInfo: additionalInfoSchema, +}); + +type FormData = z.infer; + +interface AdditionalInfoDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + vendorId: number; + onSave?: () => void; +} + +const contactTypes = [ + { value: "sales", label: "영업", required: true }, + { value: "design", label: "설계", required: true }, + { value: "delivery", label: "납기", required: true }, + { value: "quality", label: "품질", required: true }, + { value: "tax_invoice", label: "세금계산서", required: true }, +]; + +const businessTypes = [ + { value: "manufacturing", label: "제조업" }, + { value: "trading", label: "무역업" }, + { value: "service", label: "서비스업" }, + { value: "construction", label: "건설업" }, + { value: "other", label: "기타" }, +]; + +const industryTypes = [ + { value: "shipbuilding", label: "조선업" }, + { value: "marine", label: "해양플랜트" }, + { value: "energy", label: "에너지" }, + { value: "automotive", label: "자동차" }, + { value: "other", label: "기타" }, +]; + +const companySizes = [ + { value: "large", label: "대기업" }, + { value: "medium", label: "중견기업" }, + { value: "small", label: "중소기업" }, + { value: "startup", label: "스타트업" }, +]; + +export function AdditionalInfoDialog({ + open, + onOpenChange, + vendorId, + onSave, +}: AdditionalInfoDialogProps) { + const [saving, setSaving] = useState(false); + const [loading, setLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + businessContacts: contactTypes.map(type => ({ + contactType: type.value as any, + contactName: "", + position: "", + department: "", + responsibility: "", + email: "", + })), + additionalInfo: { + businessType: "", + industryType: "", + companySize: "", + revenue: "", + factoryEstablishedDate: "", + preferredContractTerms: "", + }, + }, + }); + + // 기존 데이터 로드 + const loadExistingData = async () => { + if (!vendorId || !open) return; + + setLoading(true); + try { + const result = await fetchVendorRegistrationStatus(vendorId); + if (result.success && result.data) { + const { businessContacts, additionalInfo } = result.data; + + // 업무담당자 데이터 설정 + const contactsData = contactTypes.map(type => { + const existingContact = businessContacts.find(c => c.contactType === type.value); + return existingContact ? { + contactType: existingContact.contactType, + contactName: existingContact.contactName, + position: existingContact.position, + department: existingContact.department, + responsibility: existingContact.responsibility, + email: existingContact.email, + } : { + contactType: type.value as any, + contactName: "", + position: "", + department: "", + responsibility: "", + email: "", + }; + }); + + // 추가정보 데이터 설정 + const additionalData = { + businessType: additionalInfo?.businessType || "", + industryType: additionalInfo?.industryType || "", + companySize: additionalInfo?.companySize || "", + revenue: additionalInfo?.revenue || "", + factoryEstablishedDate: additionalInfo?.factoryEstablishedDate + ? new Date(additionalInfo.factoryEstablishedDate).toISOString().split('T')[0] + : "", + preferredContractTerms: additionalInfo?.preferredContractTerms || "", + }; + + // 폼 데이터 업데이트 + form.reset({ + businessContacts: contactsData, + additionalInfo: additionalData, + }); + } + } catch (error) { + console.error("Error loading existing data:", error); + toast.error("기존 데이터를 불러오는 중 오류가 발생했습니다."); + } finally { + setLoading(false); + } + }; + + // 다이얼로그가 열릴 때 데이터 로드 + React.useEffect(() => { + loadExistingData(); + }, [vendorId, open]); + + const handleSave = async (data: FormData) => { + setSaving(true); + try { + // 업무담당자 정보 저장 + const contactsResult = await saveVendorBusinessContacts(vendorId, data.businessContacts); + if (!contactsResult.success) { + throw new Error(contactsResult.error); + } + + // 추가정보 저장 + const additionalResult = await saveVendorAdditionalInfo(vendorId, data.additionalInfo); + if (!additionalResult.success) { + throw new Error(additionalResult.error); + } + + toast.success("추가정보가 저장되었습니다."); + onSave?.(); + onOpenChange(false); + } catch (error) { + console.error("Error saving additional info:", error); + toast.error(error instanceof Error ? error.message : "추가정보 저장 중 오류가 발생했습니다."); + } finally { + setSaving(false); + } + }; + + return ( + + + + 추가정보 입력 +

+ 정규업체 등록을 위한 추가정보를 입력해주세요. * 표시는 필수 입력 항목입니다. +

+
+ + {loading ? ( +
+
데이터를 불러오는 중...
+
+ ) : ( + +
+ + + + 업무담당자 정보 + 추가정보 + + + +
+ {contactTypes.map((contactType, index) => ( + + + + {contactType.label} 담당자 + 필수 + + + +
+ ( + + 담당자명 * + + + + + + )} + /> + ( + + 직급 * + + + + + + )} + /> +
+
+ ( + + 부서 * + + + + + + )} + /> + ( + + Email * + + + + + + )} + /> +
+ ( + + 담당업무 * + +