summaryrefslogtreecommitdiff
path: root/lib/vendors/table/update-vendor-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors/table/update-vendor-sheet.tsx')
-rw-r--r--lib/vendors/table/update-vendor-sheet.tsx710
1 files changed, 552 insertions, 158 deletions
diff --git a/lib/vendors/table/update-vendor-sheet.tsx b/lib/vendors/table/update-vendor-sheet.tsx
index e65c4b1c..08994b6a 100644
--- a/lib/vendors/table/update-vendor-sheet.tsx
+++ b/lib/vendors/table/update-vendor-sheet.tsx
@@ -3,7 +3,25 @@
import * as React from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
-import { Loader } from "lucide-react"
+import {
+ Loader,
+ Activity,
+ AlertCircle,
+ AlertTriangle,
+ ClipboardList,
+ FilePenLine,
+ XCircle,
+ ClipboardCheck,
+ FileCheck2,
+ FileX2,
+ BadgeCheck,
+ CheckCircle2,
+ Circle as CircleIcon,
+ User,
+ Building,
+ AlignLeft,
+ Calendar
+} from "lucide-react"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
@@ -14,6 +32,7 @@ import {
FormItem,
FormLabel,
FormMessage,
+ FormDescription
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import {
@@ -33,27 +52,143 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
+import { Separator } from "@/components/ui/separator"
+import { useSession } from "next-auth/react" // Import useSession
-import { Vendor } from "@/db/schema/vendors"
+import { VendorWithType, vendors } from "@/db/schema/vendors"
import { updateVendorSchema, type UpdateVendorSchema } from "../validations"
import { modifyVendor } from "../service"
-// 예: import { modifyVendor } from "@/lib/vendors/service"
interface UpdateVendorSheetProps
extends React.ComponentPropsWithRef<typeof Sheet> {
- vendor: Vendor | null
+ vendor: VendorWithType | null
+}
+type StatusType = (typeof vendors.status.enumValues)[number];
+
+type StatusConfig = {
+ Icon: React.ElementType;
+ className: string;
+ label: string;
+};
+
+// 상태 표시 유틸리티 함수
+const getStatusConfig = (status: StatusType): StatusConfig => {
+ switch(status) {
+ case "PENDING_REVIEW":
+ return {
+ Icon: ClipboardList,
+ className: "text-yellow-600",
+ label: "가입 신청 중"
+ };
+ case "IN_REVIEW":
+ return {
+ Icon: FilePenLine,
+ className: "text-blue-600",
+ label: "심사 중"
+ };
+ case "REJECTED":
+ return {
+ Icon: XCircle,
+ className: "text-red-600",
+ label: "심사 거부됨"
+ };
+ case "IN_PQ":
+ return {
+ Icon: ClipboardCheck,
+ className: "text-purple-600",
+ label: "PQ 진행 중"
+ };
+ case "PQ_SUBMITTED":
+ return {
+ Icon: FileCheck2,
+ className: "text-indigo-600",
+ label: "PQ 제출"
+ };
+ case "PQ_FAILED":
+ return {
+ Icon: FileX2,
+ className: "text-red-600",
+ label: "PQ 실패"
+ };
+ case "PQ_APPROVED":
+ return {
+ Icon: BadgeCheck,
+ className: "text-green-600",
+ label: "PQ 통과"
+ };
+ case "APPROVED":
+ return {
+ Icon: CheckCircle2,
+ className: "text-green-600",
+ label: "승인됨"
+ };
+ case "READY_TO_SEND":
+ return {
+ Icon: CheckCircle2,
+ className: "text-emerald-600",
+ label: "MDG 송부대기"
+ };
+ case "ACTIVE":
+ return {
+ Icon: Activity,
+ className: "text-emerald-600",
+ label: "활성 상태"
+ };
+ case "INACTIVE":
+ return {
+ Icon: AlertCircle,
+ className: "text-gray-600",
+ label: "비활성 상태"
+ };
+ case "BLACKLISTED":
+ return {
+ Icon: AlertTriangle,
+ className: "text-slate-800",
+ label: "거래 금지"
+ };
+ default:
+ return {
+ Icon: CircleIcon,
+ className: "text-gray-600",
+ label: status
+ };
+ }
+};
+
+// 신용평가기관 목록
+const creditAgencies = [
+ { value: "NICE", label: "NICE평가정보" },
+ { value: "KIS", label: "KIS (한국신용평가)" },
+ { value: "KED", label: "KED (한국기업데이터)" },
+ { value: "SCI", label: "SCI평가정보" },
+]
+
+// 신용등급 스케일
+const creditRatingScaleMap: Record<string, string[]> = {
+ NICE: ["AAA", "AA", "A", "BBB", "BB", "B", "C", "D"],
+ KIS: ["AAA", "AA+", "AA", "A+", "A", "BBB+", "BBB", "BB", "B", "C"],
+ KED: ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "CC", "C", "D"],
+ SCI: ["AAA", "AA+", "AA", "AA-", "A+", "A", "A-", "BBB+", "BBB-", "B"],
+}
+
+// 현금흐름등급 스케일
+const cashFlowRatingScaleMap: Record<string, string[]> = {
+ NICE: ["우수", "양호", "보통", "미흡", "불량"],
+ KIS: ["A+", "A", "B+", "B", "C", "D"],
+ KED: ["1등급", "2등급", "3등급", "4등급", "5등급"],
+ SCI: ["Level 1", "Level 2", "Level 3", "Level 4"],
}
// 폼 컴포넌트
export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps) {
const [isPending, startTransition] = React.useTransition()
+ const [selectedAgency, setSelectedAgency] = React.useState<string>(vendor?.creditAgency || "NICE")
- console.log(vendor)
-
- // RHF + Zod
+ // 폼 정의 - UpdateVendorSchema 타입을 직접 사용
const form = useForm<UpdateVendorSchema>({
resolver: zodResolver(updateVendorSchema),
defaultValues: {
+ // 업체 기본 정보
vendorName: vendor?.vendorName ?? "",
vendorCode: vendor?.vendorCode ?? "",
address: vendor?.address ?? "",
@@ -61,7 +196,18 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
phone: vendor?.phone ?? "",
email: vendor?.email ?? "",
website: vendor?.website ?? "",
+ creditRating: vendor?.creditRating ?? "",
+ cashFlowRating: vendor?.cashFlowRating ?? "",
status: vendor?.status ?? "ACTIVE",
+ vendorTypeId: vendor?.vendorTypeId ?? undefined,
+
+ // 구매담당자 정보 (기본값은 비어있음)
+ buyerName: "",
+ buyerDepartment: "",
+ contractStartDate: undefined,
+ contractEndDate: undefined,
+ internalNotes: "",
+ // evaluationScore: "",
},
})
@@ -75,191 +221,439 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
phone: vendor?.phone ?? "",
email: vendor?.email ?? "",
website: vendor?.website ?? "",
+ creditRating: vendor?.creditRating ?? "",
+ cashFlowRating: vendor?.cashFlowRating ?? "",
status: vendor?.status ?? "ACTIVE",
+ vendorTypeId: vendor?.vendorTypeId ?? undefined,
+
+ // 구매담당자 필드는 유지
+ buyerName: form.getValues("buyerName"),
+ buyerDepartment: form.getValues("buyerDepartment"),
+ contractStartDate: form.getValues("contractStartDate"),
+ contractEndDate: form.getValues("contractEndDate"),
+ internalNotes: form.getValues("internalNotes"),
+ // evaluationScore: form.getValues("evaluationScore"),
});
}
}, [vendor, form]);
- console.log(form.getValues())
+ // 신용평가기관 변경 시 등급 필드를 초기화하는 효과
+ React.useEffect(() => {
+ // 선택된 평가기관에 따라 현재 선택된 등급이 유효한지 확인
+ const currentCreditRating = form.getValues("creditRating");
+ const currentCashFlowRating = form.getValues("cashFlowRating");
+
+ // 선택된 기관에 따른 유효한 등급 목록
+ const validCreditRatings = creditRatingScaleMap[selectedAgency] || [];
+ const validCashFlowRatings = cashFlowRatingScaleMap[selectedAgency] || [];
+
+ // 현재 등급이 유효하지 않으면 초기화
+ if (currentCreditRating && !validCreditRatings.includes(currentCreditRating)) {
+ form.setValue("creditRating", "");
+ }
+
+ if (currentCashFlowRating && !validCashFlowRatings.includes(currentCashFlowRating)) {
+ form.setValue("cashFlowRating", "");
+ }
+
+ // 신용평가기관 필드 업데이트
+ if(selectedAgency){
+ form.setValue("creditAgency", selectedAgency as "NICE" | "KIS" | "KED" | "SCI");
+ }
+
+ }, [selectedAgency, form]);
+
// 제출 핸들러
async function onSubmit(data: UpdateVendorSchema) {
if (!vendor) return
+ const { data: session } = useSession()
- startTransition(async () => {
- // 서버 액션 or API
- // const { error } = await modifyVendor({ id: vendor.id, ...data })
- // 여기선 간단 예시
- try {
- // 예시:
- const { error } = await modifyVendor({ id: String(vendor.id), ...data })
- if (error) throw new Error(error)
-
- toast.success("Vendor updated!")
- form.reset()
- props.onOpenChange?.(false)
- } catch (err: any) {
- toast.error(String(err))
- }
- })
- }
+ if (!session?.user?.id) {
+ toast.error("사용자 인증 정보를 찾을 수 없습니다.")
+ return
+ }
+ startTransition(async () => {
+ try {
+ // Add status change comment if status has changed
+ const oldStatus = vendor.status ?? "ACTIVE" // Default to ACTIVE if undefined
+ const newStatus = data.status ?? "ACTIVE" // Default to ACTIVE if undefined
+
+ const statusComment =
+ oldStatus !== newStatus
+ ? `상태 변경: ${getStatusConfig(oldStatus).label} → ${getStatusConfig(newStatus).label}`
+ : "" // Empty string instead of undefined
+
+ // 업체 정보 업데이트 - userId와 상태 변경 코멘트 추가
+ const { error } = await modifyVendor({
+ id: String(vendor.id),
+ userId: Number(session.user.id), // Add user ID from session
+ comment: statusComment, // Add comment for status changes
+ ...data // 모든 데이터 전달 - 서비스 함수에서 필요한 필드만 처리
+ })
+
+ if (error) throw new Error(error)
+
+ toast.success("업체 정보가 업데이트되었습니다!")
+ form.reset()
+ props.onOpenChange?.(false)
+ } catch (err: any) {
+ toast.error(String(err))
+ }
+ })
+}
return (
<Sheet {...props}>
- <SheetContent className="flex flex-col gap-6 sm:max-w-md">
+ <SheetContent className="flex flex-col gap-6 sm:max-w-lg overflow-y-auto">
<SheetHeader className="text-left">
- <SheetTitle>Update Vendor</SheetTitle>
+ <SheetTitle>업체 정보 수정</SheetTitle>
<SheetDescription>
- Update the vendor details and save the changes
+ 업체 세부 정보를 수정하고 변경 사항을 저장하세요
</SheetDescription>
</SheetHeader>
<Form {...form}>
- <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
- {/* vendorName */}
- <FormField
- control={form.control}
- name="vendorName"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Vendor Name</FormLabel>
- <FormControl>
- <Input placeholder="Vendor Name" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6">
+ {/* 업체 기본 정보 섹션 */}
+ <div className="space-y-4">
+ <div className="flex items-center">
+ <Building className="mr-2 h-5 w-5 text-muted-foreground" />
+ <h3 className="text-sm font-medium">업체 기본 정보</h3>
+ </div>
+ <FormDescription>
+ 업체가 제공한 기본 정보입니다. 필요시 수정하세요.
+ </FormDescription>
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
+ {/* vendorName */}
+ <FormField
+ control={form.control}
+ name="vendorName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>업체명</FormLabel>
+ <FormControl>
+ <Input placeholder="업체명 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* vendorCode */}
+ <FormField
+ control={form.control}
+ name="vendorCode"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>업체 코드</FormLabel>
+ <FormControl>
+ <Input placeholder="예: ABC123" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* address */}
+ <FormField
+ control={form.control}
+ name="address"
+ render={({ field }) => (
+ <FormItem className="md:col-span-2">
+ <FormLabel>주소</FormLabel>
+ <FormControl>
+ <Input placeholder="주소 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* country */}
+ <FormField
+ control={form.control}
+ name="country"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>국가</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 대한민국" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* phone */}
+ <FormField
+ control={form.control}
+ name="phone"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>전화번호</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 010-1234-5678" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* email */}
+ <FormField
+ control={form.control}
+ name="email"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>이메일</FormLabel>
+ <FormControl>
+ <Input placeholder="예: info@company.com" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* website */}
+ <FormField
+ control={form.control}
+ name="website"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>웹사이트</FormLabel>
+ <FormControl>
+ <Input placeholder="예: https://www.company.com" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
- {/* vendorCode */}
- <FormField
- control={form.control}
- name="vendorCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Vendor Code</FormLabel>
- <FormControl>
- <Input placeholder="Code123" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ {/* status with icons */}
+ <FormField
+ control={form.control}
+ name="status"
+ render={({ field }) => {
+ // 현재 선택된 상태의 구성 정보 가져오기
+ const selectedConfig = getStatusConfig(field.value ?? "ACTIVE");
+ const SelectedIcon = selectedConfig?.Icon || CircleIcon;
- {/* address */}
- <FormField
- control={form.control}
- name="address"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Address</FormLabel>
- <FormControl>
- <Input placeholder="123 Main St" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ return (
+ <FormItem>
+ <FormLabel>업체승인상태</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value}
+ onValueChange={field.onChange}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue>
+ {field.value && (
+ <div className="flex items-center">
+ <SelectedIcon className={`mr-2 h-4 w-4 ${selectedConfig.className}`} />
+ <span>{selectedConfig.label}</span>
+ </div>
+ )}
+ </SelectValue>
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ {vendors.status.enumValues.map((status) => {
+ const config = getStatusConfig(status);
+ const StatusIcon = config.Icon;
+ return (
+ <SelectItem key={status} value={status}>
+ <div className="flex items-center">
+ <StatusIcon className={`mr-2 h-4 w-4 ${config.className}`} />
+ <span>{config.label}</span>
+ </div>
+ </SelectItem>
+ );
+ })}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ );
+ }}
+ />
- {/* country */}
- <FormField
- control={form.control}
- name="country"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Country</FormLabel>
- <FormControl>
- <Input placeholder="USA" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+
+ {/* 신용평가기관 선택 */}
+ <FormField
+ control={form.control}
+ name="creditAgency"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>신용평가기관</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value || ""}
+ onValueChange={(value) => {
+ field.onChange(value);
+ setSelectedAgency(value);
+ }}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="평가기관 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ {creditAgencies.map((agency) => (
+ <SelectItem key={agency.value} value={agency.value}>
+ {agency.label}
+ </SelectItem>
+ ))}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 평가년도 - 나중에 추가 가능 */}
+
+ {/* 신용등급 - 선택된 기관에 따라 옵션 변경 */}
+ <FormField
+ control={form.control}
+ name="creditRating"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>신용등급</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value || ""}
+ onValueChange={field.onChange}
+ disabled={!selectedAgency}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="신용등급 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ {(creditRatingScaleMap[selectedAgency] || []).map((rating) => (
+ <SelectItem key={rating} value={rating}>
+ {rating}
+ </SelectItem>
+ ))}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 현금흐름등급 - 선택된 기관에 따라 옵션 변경 */}
+ <FormField
+ control={form.control}
+ name="cashFlowRating"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>현금흐름등급</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value || ""}
+ onValueChange={field.onChange}
+ disabled={!selectedAgency}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="현금흐름등급 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ {(cashFlowRatingScaleMap[selectedAgency] || []).map((rating) => (
+ <SelectItem key={rating} value={rating}>
+ {rating}
+ </SelectItem>
+ ))}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
- {/* phone */}
- <FormField
- control={form.control}
- name="phone"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Phone</FormLabel>
- <FormControl>
- <Input placeholder="+1 555-1234" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ </div>
+ </div>
- {/* email */}
- <FormField
- control={form.control}
- name="email"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Email</FormLabel>
- <FormControl>
- <Input placeholder="vendor@example.com" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ {/* 구분선 */}
+ <Separator className="my-2" />
- {/* website */}
- <FormField
- control={form.control}
- name="website"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Website</FormLabel>
- <FormControl>
- <Input placeholder="https://www.vendor.com" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ {/* 구매담당자 입력 섹션 */}
+ <div className="space-y-4 bg-slate-50 p-4 rounded-md border border-slate-200">
+ <div className="flex items-center">
+ <User className="mr-2 h-5 w-5 text-blue-600" />
+ <h3 className="text-sm font-medium text-blue-800">구매담당자 정보</h3>
+ </div>
+ <FormDescription>
+ 구매담당자가 관리하는 추가 정보입니다. 이 정보는 내부용으로만 사용됩니다.
+ </FormDescription>
+
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
+ {/* 여기에 구매담당자 필드 추가 */}
+ <FormField
+ control={form.control}
+ name="buyerName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>담당자 이름</FormLabel>
+ <FormControl>
+ <Input placeholder="담당자 이름" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="buyerDepartment"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>담당 부서</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 구매부" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
- {/* status */}
- <FormField
- control={form.control}
- name="status"
- render={({ field }) => (
- <FormItem>
- <FormLabel>Status</FormLabel>
- <FormControl>
- <Select
- value={field.value}
- onValueChange={field.onChange}
- >
- <SelectTrigger className="capitalize">
- <SelectValue placeholder="Select a status" />
- </SelectTrigger>
- <SelectContent>
- <SelectGroup>
- {/* enum ["ACTIVE","INACTIVE","BLACKLISTED"] */}
- <SelectItem value="ACTIVE">ACTIVE</SelectItem>
- <SelectItem value="INACTIVE">INACTIVE</SelectItem>
- <SelectItem value="BLACKLISTED">BLACKLISTED</SelectItem>
- </SelectGroup>
- </SelectContent>
- </Select>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+
+ <FormField
+ control={form.control}
+ name="internalNotes"
+ render={({ field }) => (
+ <FormItem className="md:col-span-2">
+ <FormLabel>내부 메모</FormLabel>
+ <FormControl>
+ <Input placeholder="내부 참고사항을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ </div>
<SheetFooter className="gap-2 pt-2 sm:space-x-0">
<SheetClose asChild>
<Button type="button" variant="outline">
- Cancel
+ 취소
</Button>
</SheetClose>
<Button disabled={isPending}>
{isPending && (
<Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
)}
- Save
+ 저장
</Button>
</SheetFooter>
</form>