summaryrefslogtreecommitdiff
path: root/lib/vendors/table
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-08 11:14:11 +0000
committerjoonhoekim <26rote@gmail.com>2025-09-08 11:14:11 +0000
commit3828852af2708d477975600cec60ff8c1802e279 (patch)
treea0dd8cf44ac35c2787886907b6eca09741f77c13 /lib/vendors/table
parent10aa3d34bc599232af07d8a643c9938be14cb5bf (diff)
(김준회) 협력업체관리 - 수정 기능 컨텍스트 메뉴에 추가, 업데이트 시트 항목 추가
Diffstat (limited to 'lib/vendors/table')
-rw-r--r--lib/vendors/table/update-vendor-sheet.tsx262
-rw-r--r--lib/vendors/table/vendors-table-columns.tsx77
2 files changed, 251 insertions, 88 deletions
diff --git a/lib/vendors/table/update-vendor-sheet.tsx b/lib/vendors/table/update-vendor-sheet.tsx
index 08994b6a..5138d299 100644
--- a/lib/vendors/table/update-vendor-sheet.tsx
+++ b/lib/vendors/table/update-vendor-sheet.tsx
@@ -18,9 +18,7 @@ import {
CheckCircle2,
Circle as CircleIcon,
User,
- Building,
- AlignLeft,
- Calendar
+ Building
} from "lucide-react"
import { toast } from "sonner"
@@ -183,6 +181,7 @@ const cashFlowRatingScaleMap: Record<string, string[]> = {
export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps) {
const [isPending, startTransition] = React.useTransition()
const [selectedAgency, setSelectedAgency] = React.useState<string>(vendor?.creditAgency || "NICE")
+ const { data: session } = useSession()
// 폼 정의 - UpdateVendorSchema 타입을 직접 사용
const form = useForm<UpdateVendorSchema>({
@@ -192,14 +191,28 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
vendorName: vendor?.vendorName ?? "",
vendorCode: vendor?.vendorCode ?? "",
address: vendor?.address ?? "",
+ addressDetail: vendor?.addressDetail ?? "",
+ postalCode: vendor?.postalCode ?? "",
country: vendor?.country ?? "",
phone: vendor?.phone ?? "",
email: vendor?.email ?? "",
website: vendor?.website ?? "",
creditRating: vendor?.creditRating ?? "",
cashFlowRating: vendor?.cashFlowRating ?? "",
- status: vendor?.status ?? "ACTIVE",
+ status: (vendor?.status as any) ?? "ACTIVE",
vendorTypeId: vendor?.vendorTypeId ?? undefined,
+ isAssociationMember: (vendor as any)?.isAssociationMember || "NONE",
+
+ // 대표자 정보
+ representativeName: (vendor as any)?.representativeName ?? "",
+ representativeBirth: (vendor as any)?.representativeBirth ?? "",
+ representativeEmail: (vendor as any)?.representativeEmail ?? "",
+ representativePhone: (vendor as any)?.representativePhone ?? "",
+ representativeWorkExpirence: (vendor as any)?.representativeWorkExpirence ?? false,
+ corporateRegistrationNumber: vendor?.corporateRegistrationNumber ?? "",
+
+ // 사업 정보
+ businessSize: vendor?.businessSize ?? "",
// 구매담당자 정보 (기본값은 비어있음)
buyerName: "",
@@ -217,14 +230,28 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
vendorName: vendor?.vendorName ?? "",
vendorCode: vendor?.vendorCode ?? "",
address: vendor?.address ?? "",
+ addressDetail: vendor?.addressDetail ?? "",
+ postalCode: vendor?.postalCode ?? "",
country: vendor?.country ?? "",
phone: vendor?.phone ?? "",
email: vendor?.email ?? "",
website: vendor?.website ?? "",
creditRating: vendor?.creditRating ?? "",
cashFlowRating: vendor?.cashFlowRating ?? "",
- status: vendor?.status ?? "ACTIVE",
+ status: (vendor?.status as any) ?? "ACTIVE",
vendorTypeId: vendor?.vendorTypeId ?? undefined,
+ isAssociationMember: (vendor as any)?.isAssociationMember || "NONE",
+
+ // 대표자 정보
+ representativeName: (vendor as any)?.representativeName ?? "",
+ representativeBirth: (vendor as any)?.representativeBirth ?? "",
+ representativeEmail: (vendor as any)?.representativeEmail ?? "",
+ representativePhone: (vendor as any)?.representativePhone ?? "",
+ representativeWorkExpirence: (vendor as any)?.representativeWorkExpirence ?? false,
+ corporateRegistrationNumber: vendor?.corporateRegistrationNumber ?? "",
+
+ // 사업 정보
+ businessSize: vendor?.businessSize ?? "",
// 구매담당자 필드는 유지
buyerName: form.getValues("buyerName"),
@@ -264,19 +291,19 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
}, [selectedAgency, form]);
// 제출 핸들러
- async function onSubmit(data: UpdateVendorSchema) {
+ function onSubmit(data: UpdateVendorSchema) {
if (!vendor) return
- const { data: session } = useSession()
-
+
if (!session?.user?.id) {
toast.error("사용자 인증 정보를 찾을 수 없습니다.")
return
}
- startTransition(async () => {
+
+ 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 oldStatus = (vendor.status as any) ?? "ACTIVE" // Default to ACTIVE if undefined
+ const newStatus = (data.status as any) ?? "ACTIVE" // Default to ACTIVE if undefined
const statusComment =
oldStatus !== newStatus
@@ -289,7 +316,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
userId: Number(session.user.id), // Add user ID from session
comment: statusComment, // Add comment for status changes
...data // 모든 데이터 전달 - 서비스 함수에서 필요한 필드만 처리
- })
+ } as any)
if (error) throw new Error(error)
@@ -316,12 +343,8 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
{/* 업체 기본 정보 섹션 */}
<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>
+ <h3 className="text-sm font-medium">협력업체 정보 수정</h3>
</div>
- <FormDescription>
- 업체가 제공한 기본 정보입니다. 필요시 수정하세요.
- </FormDescription>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* vendorName */}
<FormField
@@ -338,7 +361,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
)}
/>
- {/* vendorCode */}
+ {/* vendorCode - 읽기전용 */}
<FormField
control={form.control}
name="vendorCode"
@@ -346,7 +369,11 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
<FormItem>
<FormLabel>업체 코드</FormLabel>
<FormControl>
- <Input placeholder="예: ABC123" {...field} />
+ <Input
+ {...field}
+ readOnly
+ className="bg-gray-50 text-gray-600 cursor-not-allowed"
+ />
</FormControl>
<FormMessage />
</FormItem>
@@ -368,6 +395,36 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
)}
/>
+ {/* addressDetail */}
+ <FormField
+ control={form.control}
+ name="addressDetail"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>상세주소</FormLabel>
+ <FormControl>
+ <Input placeholder="상세주소 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* postalCode */}
+ <FormField
+ control={form.control}
+ name="postalCode"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>우편번호</FormLabel>
+ <FormControl>
+ <Input placeholder="우편번호 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
{/* country */}
<FormField
control={form.control}
@@ -479,6 +536,37 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
}}
/>
+ {/* 성조회 가입여부 */}
+ <FormField
+ control={form.control}
+ name="isAssociationMember"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>성조회 가입여부</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value || "NONE"}
+ onValueChange={(value) => {
+ field.onChange(value === "NONE" ? "" : value);
+ }}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="가입여부 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="NONE">정보없음</SelectItem>
+ <SelectItem value="Y">가입</SelectItem>
+ <SelectItem value="N">미가입</SelectItem>
+ <SelectItem value="E">해당없음</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
{/* 신용평가기관 선택 */}
<FormField
@@ -586,6 +674,142 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
{/* 구분선 */}
<Separator className="my-2" />
+ {/* 대표자 정보 섹션 */}
+ <div className="space-y-4">
+ <div className="flex items-center">
+ <User 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">
+ {/* 대표자명 */}
+ <FormField
+ control={form.control}
+ name="representativeName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자명</FormLabel>
+ <FormControl>
+ <Input placeholder="대표자명 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 대표자 생년월일 */}
+ <FormField
+ control={form.control}
+ name="representativeBirth"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 생년월일</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 1970-01-01" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 대표자 이메일 */}
+ <FormField
+ control={form.control}
+ name="representativeEmail"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 이메일</FormLabel>
+ <FormControl>
+ <Input placeholder="예: ceo@company.com" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 대표자 전화번호 */}
+ <FormField
+ control={form.control}
+ name="representativePhone"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 전화번호</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 010-1234-5678" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 법인등록번호 */}
+ <FormField
+ control={form.control}
+ name="corporateRegistrationNumber"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>법인등록번호</FormLabel>
+ <FormControl>
+ <Input placeholder="법인등록번호 입력" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 사업 규모 */}
+ <FormField
+ control={form.control}
+ name="businessSize"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>사업 규모</FormLabel>
+ <FormControl>
+ <Input placeholder="예: 중소기업, 대기업" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 대표자 경력 */}
+ <FormField
+ control={form.control}
+ name="representativeWorkExpirence"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 삼성중공업 근무경험 여부</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value ? "true" : "false"}
+ onValueChange={(value) => field.onChange(value === "true")}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="경력 보유 여부 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="false">없음</SelectItem>
+ <SelectItem value="true">있음</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <div className="space-y-1 leading-none">
+
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+ </div>
+
+ {/* 구분선 */}
+ <Separator className="my-2" />
+
{/* 구매담당자 입력 섹션 */}
<div className="space-y-4 bg-slate-50 p-4 rounded-md border border-slate-200">
<div className="flex items-center">
diff --git a/lib/vendors/table/vendors-table-columns.tsx b/lib/vendors/table/vendors-table-columns.tsx
index f2de179c..738b8b5f 100644
--- a/lib/vendors/table/vendors-table-columns.tsx
+++ b/lib/vendors/table/vendors-table-columns.tsx
@@ -61,16 +61,6 @@ interface GetColumnsProps {
userId: number;
}
-// 권한 체크 헬퍼 함수들 (향후 실제 권한 시스템과 연동)
-const checkEditAssociationPermission = (userId: number): boolean => {
- // TODO: 실제 권한 체크 로직 구현
- // 예: 특정 역할(ADMIN, VENDOR_MANAGER 등)을 가진 사용자만 수정 가능
- // const userRoles = await getUserRoles(userId);
- // return userRoles.includes('VENDOR_MANAGER') || userRoles.includes('ADMIN');
-
- // 개발 중에는 모든 사용자가 수정 가능
- return true;
-};
@@ -118,8 +108,6 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C
enableHiding: false,
cell: function Cell({ row }) {
const [isUpdatePending, startUpdateTransition] = React.useTransition()
- const isApproved = row.original.status === "PQ_APPROVED";
- const afterApproved = row.original.status === "ACTIVE";
return (
<DropdownMenu>
@@ -133,13 +121,11 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
- {(isApproved || afterApproved) && (
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "update" })}
- >
- 레코드 편집
- </DropdownMenuItem>
- )}
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "update" })}
+ >
+ 레코드 편집
+ </DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
@@ -420,9 +406,8 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C
);
}
- // 성조회가입여부 처리 - 권한에 따라 드롭다운 또는 읽기전용 배지
+ // 성조회가입여부 처리 - 읽기전용 배지만 표시 (편집은 update-vendor-sheet에서 처리)
if (cfg.id === "isAssociationMember") {
- const [isUpdating, setIsUpdating] = React.useState(false);
const memberVal = row.original.isAssociationMember as string | null;
const getDisplayText = (value: string | null) => {
@@ -430,7 +415,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C
case "Y": return "가입";
case "N": return "미가입";
case "E": return "해당없음";
- default: return "-";
+ default: return "정보없음";
}
};
@@ -447,53 +432,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C
}
};
- // 권한 체크: 성조회가입여부 수정 권한이 있는지 확인
- const hasEditPermission = checkEditAssociationPermission(userId);
-
- const handleValueChange = async (newValue: string) => {
- // "NONE" 값을 null로 변환
- const actualValue = newValue === "NONE" ? null : newValue;
-
- setIsUpdating(true);
- try {
- await modifyVendor({
- id: String(row.original.id),
- isAssociationMember: actualValue,
- userId,
- vendorName: row.original.vendorName,
- comment: `성조회가입여부 변경: ${getDisplayText(memberVal)} → ${getDisplayText(actualValue)}`
- } as any);
-
- toast.success("성조회가입여부가 업데이트되었습니다.");
- } catch (error) {
- toast.error("업데이트에 실패했습니다: " + getErrorMessage(error));
- } finally {
- setIsUpdating(false);
- }
- };
-
- // 권한이 있는 경우 드롭다운 표시
- if (hasEditPermission) {
- return (
- <Select
- value={memberVal || "NONE"}
- onValueChange={handleValueChange}
- disabled={isUpdating}
- >
- <SelectTrigger className="w-[120px] h-8">
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="NONE">-</SelectItem>
- <SelectItem value="Y">가입</SelectItem>
- <SelectItem value="N">미가입</SelectItem>
- <SelectItem value="E">해당없음</SelectItem>
- </SelectContent>
- </Select>
- );
- }
-
- // 권한이 없는 경우 읽기전용 배지 표시
+ // 읽기전용 배지만 표시
return (
<Badge variant="outline" className={getBadgeStyle(memberVal)}>
{getDisplayText(memberVal)}