summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor/rfq-vendor-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/vendor/rfq-vendor-table.tsx')
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx341
1 files changed, 268 insertions, 73 deletions
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx
index 72539113..0ebcecbd 100644
--- a/lib/rfq-last/vendor/rfq-vendor-table.tsx
+++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx
@@ -29,7 +29,8 @@ import {
Router,
Shield,
CheckSquare,
- GitCompare
+ GitCompare,
+ Link
} from "lucide-react";
import { format } from "date-fns";
import { ko } from "date-fns/locale";
@@ -69,6 +70,7 @@ import { VendorResponseDetailDialog } from "./vendor-detail-dialog";
import { DeleteVendorDialog } from "./delete-vendor-dialog";
import { useRouter } from "next/navigation"
import { EditContractDialog } from "./edit-contract-dialog";
+import { createFilterFn } from "@/components/client-data-table/table-filters";
// 타입 정의
interface RfqDetail {
@@ -292,13 +294,14 @@ export function RfqVendorTable({
);
console.log(mergedData, "mergedData")
+ console.log(rfqId, "rfqId")
// Short List 확정 핸들러
const handleShortListConfirm = React.useCallback(async () => {
try {
setIsUpdatingShortList(true);
-
+
const vendorIds = selectedRows
.map(vendor => vendor.vendorId)
.filter(id => id != null);
@@ -320,7 +323,7 @@ export function RfqVendorTable({
// 견적 비교 핸들러
const handleQuotationCompare = React.useCallback(() => {
- const vendorsWithQuotation = selectedRows.filter(row =>
+ const vendorsWithQuotation = selectedRows.filter(row =>
row.response?.submission?.submittedAt
);
@@ -334,7 +337,7 @@ export function RfqVendorTable({
.map(v => v.vendorId)
.filter(id => id != null)
.join(',');
-
+
router.push(`/evcp/rfq-last/${rfqId}/compare?vendors=${vendorIds}`);
}, [selectedRows, rfqId, router]);
@@ -349,8 +352,8 @@ export function RfqVendorTable({
setIsLoadingSendData(true);
// 선택된 벤더 ID들 추출
- const selectedVendorIds = rfqCode?.startsWith("I")? selectedRows
- .filter(v=>v.shortList)
+ const selectedVendorIds = rfqCode?.startsWith("I") ? selectedRows
+ .filter(v => v.shortList)
.map(row => row.vendorId)
.filter(id => id != null) :
selectedRows
@@ -468,7 +471,7 @@ export function RfqVendorTable({
} else {
toast.success(`${data.vendors.length}개 업체에 RFQ를 발송했습니다.`);
}
-
+
// 페이지 새로고침
router.refresh();
} catch (error) {
@@ -593,6 +596,8 @@ export function RfqVendorTable({
{
accessorKey: "rfqCode",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="ITB/RFQ/견적 No." />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
return (
<span className="font-mono text-xs">{row.original.rfqCode || "-"}</span>
@@ -603,6 +608,8 @@ export function RfqVendorTable({
{
accessorKey: "vendorName",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="협력업체정보" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const vendor = row.original;
return (
@@ -620,12 +627,16 @@ export function RfqVendorTable({
{
accessorKey: "vendorCategory",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="업체분류" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => row.original.vendorCategory || "-",
size: 100,
},
{
accessorKey: "vendorCountry",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="내외자 (위치)" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const country = row.original.vendorCountry;
const isLocal = country === "KR" || country === "한국";
@@ -640,6 +651,8 @@ export function RfqVendorTable({
{
accessorKey: "vendorGrade",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="AVL 정보 (등급)" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const grade = row.original.vendorGrade;
if (!grade) return <span className="text-muted-foreground">-</span>;
@@ -661,9 +674,11 @@ export function RfqVendorTable({
header: ({ column }) => (
<ClientDataTableColumnHeaderSimple column={column} title="TBE 상태" />
),
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const status = row.original.tbeStatus;
-
+
if (!status || status === "준비중") {
return (
<Badge variant="outline" className="text-gray-500">
@@ -672,7 +687,7 @@ export function RfqVendorTable({
</Badge>
);
}
-
+
const statusConfig = {
"진행중": { variant: "default", icon: <Clock className="h-3 w-3 mr-1" />, color: "text-blue-600" },
"검토중": { variant: "secondary", icon: <Eye className="h-3 w-3 mr-1" />, color: "text-orange-600" },
@@ -680,7 +695,7 @@ export function RfqVendorTable({
"완료": { variant: "success", icon: <CheckCircle className="h-3 w-3 mr-1" />, color: "text-green-600" },
"취소": { variant: "destructive", icon: <XCircle className="h-3 w-3 mr-1" />, color: "text-red-600" },
}[status] || { variant: "outline", icon: null, color: "text-gray-600" };
-
+
return (
<Badge variant={statusConfig.variant as any} className={statusConfig.color}>
{statusConfig.icon}
@@ -690,42 +705,44 @@ export function RfqVendorTable({
},
size: 100,
},
-
+
{
accessorKey: "tbeEvaluationResult",
header: ({ column }) => (
<ClientDataTableColumnHeaderSimple column={column} title="TBE 평가" />
),
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const result = row.original.tbeEvaluationResult;
const status = row.original.tbeStatus;
-
+
// TBE가 완료되지 않았으면 표시하지 않음
if (status !== "완료" || !result) {
return <span className="text-xs text-muted-foreground">-</span>;
}
-
+
const resultConfig = {
- "Acceptable": {
- variant: "success",
- icon: <CheckCircle className="h-3 w-3" />,
+ "Acceptable": {
+ variant: "success",
+ icon: <CheckCircle className="h-3 w-3" />,
text: "적합",
color: "bg-green-50 text-green-700 border-green-200"
},
- "Acceptable with Comment": {
- variant: "warning",
- icon: <AlertCircle className="h-3 w-3" />,
+ "Acceptable with Comment": {
+ variant: "warning",
+ icon: <AlertCircle className="h-3 w-3" />,
text: "조건부 적합",
color: "bg-yellow-50 text-yellow-700 border-yellow-200"
},
- "Not Acceptable": {
- variant: "destructive",
- icon: <XCircle className="h-3 w-3" />,
+ "Not Acceptable": {
+ variant: "destructive",
+ icon: <XCircle className="h-3 w-3" />,
text: "부적합",
color: "bg-red-50 text-red-700 border-red-200"
},
}[result];
-
+
return (
<TooltipProvider>
<Tooltip>
@@ -755,6 +772,8 @@ export function RfqVendorTable({
{
accessorKey: "contractRequirements",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="기본계약 요청" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const vendor = row.original;
const isKorean = vendor.vendorCountry === "KR" || vendor.vendorCountry === "한국";
@@ -833,6 +852,8 @@ export function RfqVendorTable({
{
accessorKey: "sendVersion",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="발송 회차" />,
+ filterFn: createFilterFn("number"),
+
cell: ({ row }) => {
const version = row.original.sendVersion;
@@ -844,6 +865,8 @@ export function RfqVendorTable({
{
accessorKey: "emailStatus",
header: "이메일 상태",
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const response = row.original;
const emailSentAt = response?.emailSentAt;
@@ -936,6 +959,8 @@ export function RfqVendorTable({
{
accessorKey: "currency",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="요청 통화" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const currency = row.original.currency;
return currency ? (
@@ -949,6 +974,8 @@ export function RfqVendorTable({
{
accessorKey: "paymentTermsCode",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="지급조건" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const code = row.original.paymentTermsCode;
const desc = row.original.paymentTermsDescription;
@@ -972,12 +999,16 @@ export function RfqVendorTable({
{
accessorKey: "taxCode",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="Tax" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => row.original.taxCode || "-",
size: 60,
},
{
accessorKey: "deliveryDate",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="계약납기일/기간" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const deliveryDate = row.original.deliveryDate;
const contractDuration = row.original.contractDuration;
@@ -1003,6 +1034,8 @@ export function RfqVendorTable({
{
accessorKey: "incotermsCode",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="Incoterms" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const code = row.original.incotermsCode;
const detail = row.original.incotermsDetail;
@@ -1030,6 +1063,8 @@ export function RfqVendorTable({
{
accessorKey: "placeOfShipping",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="선적지" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const place = row.original.placeOfShipping;
return place ? (
@@ -1046,6 +1081,7 @@ export function RfqVendorTable({
{
accessorKey: "placeOfDestination",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="도착지" />,
+ filterFn: createFilterFn("text"),
cell: ({ row }) => {
const place = row.original.placeOfDestination;
return place ? (
@@ -1062,6 +1098,8 @@ export function RfqVendorTable({
{
id: "additionalConditions",
header: "추가조건",
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const conditions = formatAdditionalConditions(row.original);
if (conditions === "-") {
@@ -1084,6 +1122,8 @@ export function RfqVendorTable({
{
accessorKey: "response.submission.submittedAt",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="참여여부 (회신일)" />,
+ filterFn: createFilterFn("text"),
+
cell: ({ row }) => {
const participationRepliedAt = row.original.response?.attend?.participationRepliedAt;
@@ -1131,6 +1171,7 @@ export function RfqVendorTable({
},
...(rfqCode?.startsWith("I") ? [{
accessorKey: "shortList",
+ filterFn: createFilterFn("boolean"), // boolean으로 변경
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="Short List" />,
cell: ({ row }) => (
row.original.shortList ? (
@@ -1143,6 +1184,7 @@ export function RfqVendorTable({
}] : []),
{
accessorKey: "updatedByUserName",
+ filterFn: createFilterFn("text"), // 추가
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="최신수정자" />,
cell: ({ row }) => {
const name = row.original.updatedByUserName;
@@ -1238,24 +1280,160 @@ export function RfqVendorTable({
}
], [handleAction, rfqCode, isLoadingSendData]);
+ // advancedFilterFields 정의 - columns와 매칭되도록 정리
const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [
- { id: "vendorName", label: "벤더명", type: "text" },
- { id: "vendorCode", label: "벤더코드", type: "text" },
- { id: "vendorCountry", label: "국가", type: "text" },
{
- id: "response.status",
- label: "응답 상태",
+ id: "rfqCode",
+ label: "ITB/RFQ/견적 No.",
+ type: "text"
+ },
+ {
+ id: "vendorName",
+ label: "협력업체명",
+ type: "text"
+ },
+ {
+ id: "vendorCode",
+ label: "협력업체코드",
+ type: "text"
+ },
+ {
+ id: "vendorCategory",
+ label: "업체분류",
+ type: "select",
+ options: [
+ { label: "제조업체", value: "제조업체" },
+ { label: "무역업체", value: "무역업체" },
+ { label: "대리점", value: "대리점" },
+ // 실제 카테고리에 맞게 추가
+ ]
+ },
+ {
+ id: "vendorCountry",
+ label: "내외자(위치)",
+ type: "select",
+ options: [
+ { label: "한국(KR)", value: "KR" },
+ { label: "한국", value: "한국" },
+ { label: "중국(CN)", value: "CN" },
+ { label: "일본(JP)", value: "JP" },
+ { label: "미국(US)", value: "US" },
+ { label: "독일(DE)", value: "DE" },
+ // 필요한 국가 추가
+ ]
+ },
+ {
+ id: "vendorGrade",
+ label: "AVL 등급",
type: "select",
options: [
- { label: "초대됨", value: "초대됨" },
- { label: "작성중", value: "작성중" },
- { label: "제출완료", value: "제출완료" },
- { label: "수정요청", value: "수정요청" },
- { label: "최종확정", value: "최종확정" },
+ { label: "A", value: "A" },
+ { label: "B", value: "B" },
+ { label: "C", value: "C" },
+ { label: "D", value: "D" },
+ ]
+ },
+ {
+ id: "tbeStatus",
+ label: "TBE 상태",
+ type: "select",
+ options: [
+ { label: "대기", value: "준비중" },
+ { label: "진행중", value: "진행중" },
+ { label: "검토중", value: "검토중" },
+ { label: "보류", value: "보류" },
+ { label: "완료", value: "완료" },
{ label: "취소", value: "취소" },
]
},
{
+ id: "tbeEvaluationResult",
+ label: "TBE 평가결과",
+ type: "select",
+ options: [
+ { label: "적합", value: "Acceptable" },
+ { label: "조건부 적합", value: "Acceptable with Comment" },
+ { label: "부적합", value: "Not Acceptable" },
+ ]
+ },
+ {
+ id: "sendVersion",
+ label: "발송 회차",
+ type: "number"
+ },
+ {
+ id: "emailStatus",
+ label: "이메일 상태",
+ type: "select",
+ options: [
+ { label: "미발송", value: "미발송" },
+ { label: "발송됨", value: "sent" },
+ { label: "발송 실패", value: "failed" },
+ ]
+ },
+ {
+ id: "currency",
+ label: "요청 통화",
+ type: "select",
+ options: [
+ { label: "KRW", value: "KRW" },
+ { label: "USD", value: "USD" },
+ { label: "EUR", value: "EUR" },
+ { label: "JPY", value: "JPY" },
+ { label: "CNY", value: "CNY" },
+ ]
+ },
+ {
+ id: "paymentTermsCode",
+ label: "지급조건",
+ type: "text"
+ },
+ {
+ id: "taxCode",
+ label: "Tax",
+ type: "text",
+ },
+ {
+ id: "deliveryDate",
+ label: "계약납기일",
+ type: "date"
+ },
+ {
+ id: "contractDuration",
+ label: "계약기간",
+ type: "text"
+ },
+ {
+ id: "incotermsCode",
+ label: "Incoterms",
+ type: "text",
+ },
+ {
+ id: "placeOfShipping",
+ label: "선적지",
+ type: "text"
+ },
+ {
+ id: "placeOfDestination",
+ label: "도착지",
+ type: "text"
+ },
+ {
+ id: "firstYn",
+ label: "초도품",
+ type: "boolean"
+ },
+ {
+ id: "materialPriceRelatedYn",
+ label: "연동제",
+ type: "boolean"
+ },
+ {
+ id: "sparepartYn",
+ label: "스페어파트",
+ type: "boolean"
+ },
+ ...(rfqCode?.startsWith("I") ? [{
id: "shortList",
label: "Short List",
type: "select",
@@ -1263,7 +1441,12 @@ export function RfqVendorTable({
{ label: "선정", value: "true" },
{ label: "대기", value: "false" },
]
- },
+ }] : []),
+ {
+ id: "updatedByUserName",
+ label: "최신수정자",
+ type: "text"
+ }
];
// 선택된 벤더 정보 (BatchUpdate용)
@@ -1280,15 +1463,27 @@ export function RfqVendorTable({
// 참여 의사가 있는 선택된 벤더 수 계산
const participatingCount = selectedRows.length;
- const shortListCount = selectedRows.filter(v=>v.shortList).length;
+ const shortListCount = selectedRows.filter(v => v.shortList).length;
// 견적서가 있는 선택된 벤더 수 계산
- const quotationCount = selectedRows.filter(row =>
+ const quotationCount = selectedRows.filter(row =>
row.response?.submission?.submittedAt
).length;
return (
<div className="flex items-center gap-2">
+ {(rfqCode?.startsWith("I") || rfqCode?.startsWith("R")) &&
+
+ <Button
+ variant="outline"
+ size="sm"
+ >
+ <Link className="h-4 w-4 mr-2" />
+ AVL 연동
+ </Button>
+ }
+
+
<Button
variant="outline"
size="sm"
@@ -1298,32 +1493,32 @@ export function RfqVendorTable({
<Plus className="h-4 w-4 mr-2" />
벤더 추가
</Button>
-
+
{selectedRows.length > 0 && (
<>
{/* Short List 확정 버튼 */}
- {rfqCode?.startsWith("I")&&
- <Button
- variant="outline"
- size="sm"
- onClick={handleShortListConfirm}
- disabled={isUpdatingShortList }
+ {rfqCode?.startsWith("I") &&
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleShortListConfirm}
+ disabled={isUpdatingShortList}
// className={ "border-green-500 text-green-600 hover:bg-green-50" }
- >
- {isUpdatingShortList ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- 처리중...
- </>
- ) : (
- <>
- <CheckSquare className="h-4 w-4 mr-2" />
- Short List 확정
- {participatingCount > 0 && ` (${participatingCount})`}
- </>
- )}
- </Button>
- }
+ >
+ {isUpdatingShortList ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ 처리중...
+ </>
+ ) : (
+ <>
+ <CheckSquare className="h-4 w-4 mr-2" />
+ Short List 확정
+ {participatingCount > 0 && ` (${participatingCount})`}
+ </>
+ )}
+ </Button>
+ }
{/* 견적 비교 버튼 */}
<Button
@@ -1334,7 +1529,7 @@ export function RfqVendorTable({
className={quotationCount >= 2 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""}
>
<GitCompare className="h-4 w-4 mr-2" />
- 견적 비교
+ 견적 비교
{quotationCount > 0 && ` (${quotationCount})`}
</Button>
@@ -1370,7 +1565,7 @@ export function RfqVendorTable({
</Button>
</>
)}
-
+
<Button
variant="outline"
size="sm"
@@ -1464,19 +1659,19 @@ export function RfqVendorTable({
/>
)}
- {/* 기본계약 수정 다이얼로그 - 새로 추가 */}
- {editContractVendor && (
- <EditContractDialog
- open={!!editContractVendor}
- onOpenChange={(open) => !open && setEditContractVendor(null)}
- rfqId={rfqId}
- vendor={editContractVendor}
- onSuccess={() => {
- setEditContractVendor(null);
- router.refresh();
- }}
- />
- )}
+ {/* 기본계약 수정 다이얼로그 - 새로 추가 */}
+ {editContractVendor && (
+ <EditContractDialog
+ open={!!editContractVendor}
+ onOpenChange={(open) => !open && setEditContractVendor(null)}
+ rfqId={rfqId}
+ vendor={editContractVendor}
+ onSuccess={() => {
+ setEditContractVendor(null);
+ router.refresh();
+ }}
+ />
+ )}
</>
);
} \ No newline at end of file