summaryrefslogtreecommitdiff
path: root/lib/vendors
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-09 03:19:41 +0000
committerjoonhoekim <26rote@gmail.com>2025-09-09 03:19:41 +0000
commite06913008f124ce8e7389fbdc1e57206ce9bbb2b (patch)
tree010292c2cd6116065d70893055deba230899d4ca /lib/vendors
parent61fd1ff7162390f9ec2480da74840e58bb0b6ebd (diff)
(김준회) 협력업체관리 업체분류, 정규업체등록현황 부 수정, 파일다운로드 오류 수정, 업데이트 시트 수정
Diffstat (limited to 'lib/vendors')
-rw-r--r--lib/vendors/service.ts37
-rw-r--r--lib/vendors/table/attachmentButton.tsx7
-rw-r--r--lib/vendors/table/update-vendor-sheet.tsx6
-rw-r--r--lib/vendors/table/vendors-table-columns.tsx101
-rw-r--r--lib/vendors/validations.ts2
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(),