diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/vendors/service.ts | 37 | ||||
| -rw-r--r-- | lib/vendors/table/attachmentButton.tsx | 7 | ||||
| -rw-r--r-- | lib/vendors/table/update-vendor-sheet.tsx | 6 | ||||
| -rw-r--r-- | lib/vendors/table/vendors-table-columns.tsx | 101 | ||||
| -rw-r--r-- | lib/vendors/validations.ts | 2 |
5 files changed, 115 insertions, 38 deletions
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 0c8254f2..a22a1551 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -1376,43 +1376,18 @@ interface CreateCompanyInput { export async function downloadVendorAttachments(vendorId: number, fileId?: number) { try { // API 경로 생성 (단일 파일 또는 모든 파일) - const url = fileId + const path = fileId ? `/api/vendors/attachments/download?id=${fileId}&vendorId=${vendorId}` : `/api/vendors/attachments/download-all?vendorId=${vendorId}`; - // fetch 요청 (기본적으로 Blob으로 응답 받기) - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) { - throw new Error(`Server responded with ${response.status}: ${response.statusText}`); - } - - // 파일명 가져오기 (Content-Disposition 헤더에서) - const contentDisposition = response.headers.get('content-disposition'); - let fileName = fileId ? `file-${fileId}.zip` : `vendor-${vendorId}-files.zip`; - - if (contentDisposition) { - const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); - if (matches && matches[1]) { - fileName = matches[1].replace(/['"]/g, ''); - } - } - - // Blob으로 응답 변환 - const blob = await response.blob(); - - // Blob URL 생성 - const blobUrl = window.URL.createObjectURL(blob); + // 서버에서는 URL만 반환하고, 클라이언트에서 실제 다운로드 처리 + // 파일명도 기본값으로 설정 + const fileName = fileId ? `file-${fileId}.zip` : `vendor-${vendorId}-attachments.zip`; return { - url: blobUrl, + url: path, // 상대 경로 반환 fileName, - blob + isServerAction: true // 서버 액션임을 표시 }; } catch (error) { console.error('Download API error:', error); diff --git a/lib/vendors/table/attachmentButton.tsx b/lib/vendors/table/attachmentButton.tsx index 3ffa9c5f..86cf992b 100644 --- a/lib/vendors/table/attachmentButton.tsx +++ b/lib/vendors/table/attachmentButton.tsx @@ -35,9 +35,14 @@ export function AttachmentsButton({ vendorId, hasAttachments, attachmentsList = // 파일 다운로드 트리거 toast.success('첨부파일 다운로드가 시작되었습니다.'); + // 서버 액션에서 상대 URL을 반환하므로 절대 URL로 변환 + const downloadUrl = result.isServerAction + ? `${window.location.origin}${result.url}` + : result.url; + // 다운로드 링크 열기 const a = document.createElement('a'); - a.href = result.url; + a.href = downloadUrl; a.download = result.fileName || '첨부파일.zip'; a.style.display = 'none'; document.body.appendChild(a); diff --git a/lib/vendors/table/update-vendor-sheet.tsx b/lib/vendors/table/update-vendor-sheet.tsx index 5138d299..d8d4ad67 100644 --- a/lib/vendors/table/update-vendor-sheet.tsx +++ b/lib/vendors/table/update-vendor-sheet.tsx @@ -201,7 +201,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps) cashFlowRating: vendor?.cashFlowRating ?? "", status: (vendor?.status as any) ?? "ACTIVE", vendorTypeId: vendor?.vendorTypeId ?? undefined, - isAssociationMember: (vendor as any)?.isAssociationMember || "NONE", + isAssociationMember: (vendor as any)?.isAssociationMember ?? "NONE", // 대표자 정보 representativeName: (vendor as any)?.representativeName ?? "", @@ -240,7 +240,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps) cashFlowRating: vendor?.cashFlowRating ?? "", status: (vendor?.status as any) ?? "ACTIVE", vendorTypeId: vendor?.vendorTypeId ?? undefined, - isAssociationMember: (vendor as any)?.isAssociationMember || "NONE", + isAssociationMember: (vendor as any)?.isAssociationMember ?? "NONE", // 대표자 정보 representativeName: (vendor as any)?.representativeName ?? "", @@ -547,7 +547,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps) <Select value={field.value || "NONE"} onValueChange={(value) => { - field.onChange(value === "NONE" ? "" : value); + field.onChange(value === "NONE" ? null : value); }} > <SelectTrigger className="w-full"> diff --git a/lib/vendors/table/vendors-table-columns.tsx b/lib/vendors/table/vendors-table-columns.tsx index 738b8b5f..36809715 100644 --- a/lib/vendors/table/vendors-table-columns.tsx +++ b/lib/vendors/table/vendors-table-columns.tsx @@ -217,7 +217,8 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C // child column 정의 const childCol: ColumnDef<VendorWithTypeAndMaterials> = { - accessorKey: cfg.id, + accessorKey: cfg.id === "vendorClassification" ? undefined : cfg.id, + id: cfg.id as string, enableResizing: true, header: ({ column }) => ( <DataTableColumnHeaderSimple column={column} title={cfg.label} /> @@ -364,6 +365,57 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C ); } + // 업체분류 컬럼 처리 - status 값에 따라 업체분류 결정 + if (cfg.id === "vendorClassification") { + const statusVal = row.original.status as StatusType; + + // status 값에 따른 업체분류 매핑 + const getVendorClassification = (status: StatusType): string => { + const classificationMap: Record<StatusType, string> = { + "PENDING_REVIEW": "발굴업체", // 업체발굴 + "REJECTED": "발굴업체", // 가입거절 + "APPROVED": "잠재업체", // 가입승인 + "IN_PQ": "잠재업체", // PQ요청 + "PQ_SUBMITTED": "잠재업체", // PQ제출 + "PQ_FAILED": "잠재업체", // 실사실패 + "PQ_APPROVED": "잠재업체", // 실사통과 + "IN_REVIEW": "잠재업체", // 정규등록검토 + "READY_TO_SEND": "잠재업체", // 정규등록검토 + "ACTIVE": "정규업체", // 정규등록 + "INACTIVE": "중지업체", // 비활성화 + "BLACKLISTED": "중지업체", // 거래금지 + }; + + return classificationMap[status] || "미분류"; + }; + + const classification = getVendorClassification(statusVal); + + // 업체분류별 배지 스타일 + const getBadgeStyle = (classification: string) => { + switch (classification) { + case "발굴업체": + return "bg-orange-50 text-orange-700 border-orange-200"; + case "잠재업체": + return "bg-blue-50 text-blue-700 border-blue-200"; + case "정규업체": + return "bg-green-50 text-green-700 border-green-200"; + case "중지업체": + return "bg-red-50 text-red-700 border-red-200"; + case "폐기업체": + return "bg-gray-50 text-gray-700 border-gray-200"; + default: + return "bg-gray-50 text-gray-700 border-gray-200"; + } + }; + + return ( + <Badge variant="outline" className={getBadgeStyle(classification)}> + {classification} + </Badge> + ); + } + // 업체 분류 컬럼 처리 (별도로 표시하고 싶은 경우) if (cfg.id === "vendorCategory") { const categoryVal = row.original.vendorCategory as string | null; @@ -474,9 +526,54 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C ); } + // 정규업체등록현황 컬럼 - status 필드 표시 + if (cfg.id === "regularVendorRegistration") { + const statusVal = row.original.status as StatusType; + if (!statusVal) return <span className="text-gray-400">-</span>; + + // status 값을 한글로 변환하여 표시 + const getStatusDisplay = (status: StatusType): string => { + const statusMap: StatusDisplayMap = { + // "PENDING_REVIEW": "가입 신청 중", + // "IN_REVIEW": "심사 중", + // "REJECTED": "심사 거부됨", + // "IN_PQ": "PQ 진행 중", + // "PQ_SUBMITTED": "PQ 제출", + // "PQ_FAILED": "PQ 실패", + // "PQ_APPROVED": "PQ 통과", + // "APPROVED": "승인됨", + // "READY_TO_SEND": "MDG 송부대기", + // "ACTIVE": "활성 상태", + // "INACTIVE": "비활성 상태", + // "BLACKLISTED": "거래 금지" + + // 구매가 제공한 화면정의서 상태로 텍스트 변경, 세분화 필요 + "PENDING_REVIEW": "업체발굴", + "APPROVED": "가입승인", + "REJECTED": "가입거절", + "IN_PQ": "PQ요청", + "PQ_SUBMITTED": "PQ제출", + "PQ_FAILED": "PQ실패", + "PQ_APPROVED": "PQ 통과", + "IN_REVIEW": "정규등록검토", + "READY_TO_SEND": "정규등록검토", + "ACTIVE": "정규등록", + "INACTIVE": "비활성화", + "BLACKLISTED": "거래금지" + }; + return statusMap[status] || status; + }; + + return ( + <span className="text-sm font-medium"> + {getStatusDisplay(statusVal)} + </span> + ); + } + // TODO 컬럼들 (UI만) - 모두 "-" 표시 if (cfg.id === "regularEvaluationGrade" || cfg.id === "faContract" || - cfg.id === "avlRegistration" || cfg.id === "regularVendorRegistration" || + cfg.id === "avlRegistration" || cfg.id === "recentDeliveryNumber" || cfg.id === "recentDeliveryBy") { return <span className="text-gray-400">-</span>; } diff --git a/lib/vendors/validations.ts b/lib/vendors/validations.ts index 917242d3..54b5a29b 100644 --- a/lib/vendors/validations.ts +++ b/lib/vendors/validations.ts @@ -130,7 +130,7 @@ export const updateVendorSchema = z.object({ website: z.string().optional(), status: z.enum(vendors.status.enumValues).optional(), vendorTypeId: z.number().optional(), - isAssociationMember: z.string().optional(), // 성조회가입여부 추가 + isAssociationMember: z.string().nullable().optional(), // 성조회가입여부 추가 // Representative information representativeName: z.string().optional(), |
