diff options
Diffstat (limited to 'lib/tech-vendor-possible-items')
6 files changed, 856 insertions, 1383 deletions
diff --git a/lib/tech-vendor-possible-items/repository.ts b/lib/tech-vendor-possible-items/repository.ts index 5c1487b5..4d876643 100644 --- a/lib/tech-vendor-possible-items/repository.ts +++ b/lib/tech-vendor-possible-items/repository.ts @@ -1,12 +1,14 @@ -import { eq, desc, count, SQL, sql, and, or, ilike } from "drizzle-orm";
+import { eq, desc, count, SQL, sql } from "drizzle-orm";
import {
techVendors,
techVendorPossibleItems
} from "@/db/schema/techVendors";
+import { itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
import type { PgTransaction } from "drizzle-orm/pg-core";
/**
- * 기술영업 벤더 가능 아이템 목록 조회 (조인 포함)
+ * 새로운 스키마에 맞는 기술영업 벤더 가능 아이템 목록 조회 (조인 포함)
+ * techVendorPossibleItems는 shipbuildingItemId, offshoreTopItemId, offshoreHullItemId 중 하나만 가짐
*/
export async function selectTechVendorPossibleItemsWithJoin(
tx: PgTransaction<any, any, any>,
@@ -19,22 +21,38 @@ export async function selectTechVendorPossibleItemsWithJoin( .select({
id: techVendorPossibleItems.id,
vendorId: techVendorPossibleItems.vendorId,
- vendorCode: techVendorPossibleItems.vendorCode, // 테이블에서 직접 조회
- vendorName: techVendors.vendorName,
- vendorEmail: techVendorPossibleItems.vendorEmail, // 테이블에서 직접 조회
- techVendorType: techVendors.techVendorType,
- vendorStatus: techVendors.status,
- itemCode: techVendorPossibleItems.itemCode,
- // 새로운 스키마: 테이블에서 직접 조회
- workType: techVendorPossibleItems.workType,
- shipTypes: techVendorPossibleItems.shipTypes,
- itemList: techVendorPossibleItems.itemList,
- subItemList: techVendorPossibleItems.subItemList,
+ shipbuildingItemId: techVendorPossibleItems.shipbuildingItemId,
+ offshoreTopItemId: techVendorPossibleItems.offshoreTopItemId,
+ offshoreHullItemId: techVendorPossibleItems.offshoreHullItemId,
createdAt: techVendorPossibleItems.createdAt,
updatedAt: techVendorPossibleItems.updatedAt,
+ // 벤더 정보
+ vendorCode: techVendors.vendorCode,
+ vendorName: techVendors.vendorName,
+ vendorEmail: techVendors.email,
+ vendorStatus: techVendors.status,
+ techVendorType: techVendors.techVendorType,
+ // 조선 아이템 정보 (shipbuildingItemId가 있을 때만)
+ shipItemCode: itemShipbuilding.itemCode,
+ shipWorkType: itemShipbuilding.workType,
+ shipItemList: itemShipbuilding.itemList,
+ shipTypes: itemShipbuilding.shipTypes,
+ // 해양 TOP 아이템 정보 (offshoreTopItemId가 있을 때만)
+ topItemCode: itemOffshoreTop.itemCode,
+ topWorkType: itemOffshoreTop.workType,
+ topItemList: itemOffshoreTop.itemList,
+ topSubItemList: itemOffshoreTop.subItemList,
+ // 해양 HULL 아이템 정보 (offshoreHullItemId가 있을 때만)
+ hullItemCode: itemOffshoreHull.itemCode,
+ hullWorkType: itemOffshoreHull.workType,
+ hullItemList: itemOffshoreHull.itemList,
+ hullSubItemList: itemOffshoreHull.subItemList,
})
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .leftJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .leftJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .leftJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id))
.where(where)
.orderBy(...(orderBy || [desc(techVendorPossibleItems.createdAt)]))
.limit(limit)
@@ -52,58 +70,70 @@ export async function countTechVendorPossibleItemsWithJoin( .select({ count: count() })
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .leftJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .leftJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .leftJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id))
.where(where);
return result.count;
}
/**
- * 새로운 필드들을 위한 그룹별 통계 조회
+ * 공종별 통계 조회 (새로운 스키마 적용)
*/
-export async function getTechVendorPossibleItemsGroupStats(
+export async function getWorkTypeStats(
tx: PgTransaction<any, any, any>,
- groupBy: 'workType' | 'shipTypes' | 'vendorCode' | 'vendorEmail',
where?: SQL | undefined
) {
- const groupField = techVendorPossibleItems[groupBy];
-
- return await tx
+ // 각 아이템 타입별로 별도 쿼리 실행 후 통합
+ const shipStats = await tx
.select({
- groupValue: groupField,
+ workType: itemShipbuilding.workType,
count: count(),
vendorCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.vendorId})`.as('vendorCount'),
- itemCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.itemCode})`.as('itemCount'),
+ itemCount: sql<number>`COUNT(DISTINCT ${itemShipbuilding.itemCode})`.as('itemCount'),
})
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
.where(where)
- .groupBy(groupField)
+ .groupBy(itemShipbuilding.workType)
.orderBy(desc(count()));
-}
-/**
- * 공종별 통계 조회
- */
-export async function getWorkTypeStats(
- tx: PgTransaction<any, any, any>,
- where?: SQL | undefined
-) {
- return await tx
+ const topStats = await tx
+ .select({
+ workType: itemOffshoreTop.workType,
+ count: count(),
+ vendorCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.vendorId})`.as('vendorCount'),
+ itemCount: sql<number>`COUNT(DISTINCT ${itemOffshoreTop.itemCode})`.as('itemCount'),
+ })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .where(where)
+ .groupBy(itemOffshoreTop.workType)
+ .orderBy(desc(count()));
+
+ const hullStats = await tx
.select({
- workType: techVendorPossibleItems.workType,
+ workType: itemOffshoreHull.workType,
count: count(),
vendorCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.vendorId})`.as('vendorCount'),
- itemCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.itemCode})`.as('itemCount'),
+ itemCount: sql<number>`COUNT(DISTINCT ${itemOffshoreHull.itemCode})`.as('itemCount'),
})
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id))
.where(where)
- .groupBy(techVendorPossibleItems.workType)
+ .groupBy(itemOffshoreHull.workType)
.orderBy(desc(count()));
+
+ // 결과 통합
+ return [...shipStats, ...topStats, ...hullStats];
}
/**
- * 선종별 통계 조회
+ * 선종별 통계 조회 (조선 아이템만 해당)
*/
export async function getShipTypeStats(
tx: PgTransaction<any, any, any>,
@@ -111,15 +141,16 @@ export async function getShipTypeStats( ) {
return await tx
.select({
- shipTypes: techVendorPossibleItems.shipTypes,
+ shipTypes: itemShipbuilding.shipTypes,
count: count(),
vendorCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.vendorId})`.as('vendorCount'),
- itemCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.itemCode})`.as('itemCount'),
+ itemCount: sql<number>`COUNT(DISTINCT ${itemShipbuilding.itemCode})`.as('itemCount'),
})
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
.where(where)
- .groupBy(techVendorPossibleItems.shipTypes)
+ .groupBy(itemShipbuilding.shipTypes)
.orderBy(desc(count()));
}
@@ -133,24 +164,61 @@ export async function getVendorStats( return await tx
.select({
vendorId: techVendorPossibleItems.vendorId,
- vendorCode: techVendorPossibleItems.vendorCode,
+ vendorCode: techVendors.vendorCode,
vendorName: techVendors.vendorName,
- vendorEmail: techVendorPossibleItems.vendorEmail,
+ vendorEmail: techVendors.email,
+ vendorStatus: techVendors.status,
itemCount: count(),
- distinctItemCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.itemCode})`.as('distinctItemCount'),
- workTypeCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.workType})`.as('workTypeCount'),
- shipTypeCount: sql<number>`COUNT(DISTINCT ${techVendorPossibleItems.shipTypes})`.as('shipTypeCount'),
latestUpdate: sql<Date>`MAX(${techVendorPossibleItems.updatedAt})`.as('latestUpdate'),
})
.from(techVendorPossibleItems)
.innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .leftJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .leftJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .leftJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id))
.where(where)
.groupBy(
techVendorPossibleItems.vendorId,
- techVendorPossibleItems.vendorCode,
+ techVendors.vendorCode,
techVendors.vendorName,
- techVendorPossibleItems.vendorEmail
+ techVendors.email,
+ techVendors.status
)
.orderBy(desc(count()));
}
+/**
+ * 아이템 타입별 통계 조회 (조선, 해양TOP, 해양HULL)
+ */
+export async function getItemTypeStats(
+ tx: PgTransaction<any, any, any>,
+ where?: SQL | undefined
+) {
+ const [shipCount] = await tx
+ .select({ count: count() })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .where(where);
+
+ const [topCount] = await tx
+ .select({ count: count() })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .where(where);
+
+ const [hullCount] = await tx
+ .select({ count: count() })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .innerJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id))
+ .where(where);
+
+ return [
+ { itemType: "조선", count: shipCount.count },
+ { itemType: "해양TOP", count: topCount.count },
+ { itemType: "해양HULL", count: hullCount.count },
+ ];
+}
+
diff --git a/lib/tech-vendor-possible-items/service.ts b/lib/tech-vendor-possible-items/service.ts index c630e33a..48f9b869 100644 --- a/lib/tech-vendor-possible-items/service.ts +++ b/lib/tech-vendor-possible-items/service.ts @@ -1,5 +1,6 @@ -"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택)
-import { eq, and, inArray, desc, asc, or, ilike, isNull } from "drizzle-orm";
+"use server";
+
+import { eq } from "drizzle-orm";
import db from "@/db/db";
import {
techVendors,
@@ -7,69 +8,48 @@ import { } from "@/db/schema/techVendors";
import { itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
import { unstable_cache } from "@/lib/unstable-cache";
-import { filterColumns } from "@/lib/filter-columns";
-import type { GetTechVendorPossibleItemsSchema } from "./validations";
-import {
- selectTechVendorPossibleItemsWithJoin,
- countTechVendorPossibleItemsWithJoin,
-} from "./repository";
+// 새로운 스키마에 맞는 데이터 인터페이스
export interface TechVendorPossibleItemsData {
id: number;
vendorId: number;
+ shipbuildingItemId: number | null;
+ offshoreTopItemId: number | null;
+ offshoreHullItemId: number | null;
+ createdAt: Date;
+ updatedAt: Date;
+ // 조인된 벤더 정보
vendorCode: string | null;
vendorName: string;
vendorEmail: string | null;
+ vendorStatus: string;
techVendorType: string;
+ // 조인된 아이템 정보
itemCode: string;
workType: string | null;
shipTypes: string | null;
itemList: string | null;
subItemList: string | null;
- createdAt: Date;
- updatedAt: Date;
}
-export interface CreateTechVendorPossibleItemData {
- vendorId: number; // 필수: 벤더 ID (Add Dialog에서 벤더 선택 시 사용)
- itemCode: string; // 필수: 아이템 코드
- workType?: string | null; // 공종 (아이템에서 가져온 정보)
- shipTypes?: string | null; // 선종 (아이템에서 가져온 정보)
- itemList?: string | null; // 아이템리스트 (아이템에서 가져온 정보)
- subItemList?: string | null; // 서브아이템리스트 (아이템에서 가져온 정보)
-}
-
-export interface ImportTechVendorPossibleItemData {
+export interface GetTechVendorPossibleItemsSchema {
+ page: number;
+ perPage: number;
+ search?: string;
+ filters?: Array<{ id: string; value: string | number | boolean; operator?: string }>;
+ joinOperator?: "and" | "or";
+ sort?: Array<{ id: string; desc: boolean }>;
vendorCode?: string;
- vendorEmail: string; // 필수: 벤더 이메일
- itemCode: string; // 필수: 아이템 코드
- workType?: string;
- shipTypes?: string;
- itemList?: string;
- subItemList?: string;
-}
-
-export interface ImportResult {
- success: boolean;
- totalRows: number;
- successCount: number;
- failedRows: {
- row: number;
- error: string;
- vendorCode?: string;
- vendorEmail?: string;
- itemCode?: string;
- workType?: string;
- shipTypes?: string;
- itemList?: string;
- subItemList?: string;
- }[];
+ vendorName?: string;
+ vendorEmail?: string;
+ vendorStatus?: string;
+ itemCode?: string;
+ vendorType?: string;
}
-
-
/**
- * 견적프로젝트 패턴에 맞는 메인 조회 함수
+ * 새로운 스키마에 맞는 tech vendor possible items 조회 함수
+ * 벤더 정보와 각 아이템 테이블별 정보를 조인해서 가져옴
*/
export async function getTechVendorPossibleItems(input: GetTechVendorPossibleItemsSchema) {
return unstable_cache(
@@ -77,39 +57,112 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte try {
const offset = (input.page - 1) * input.perPage;
- // 고급 필터링 (DataTableAdvancedToolbar용)
- const advancedWhere = filterColumns({
- table: techVendorPossibleItems,
- filters: input.filters,
- joinOperator: input.joinOperator,
- });
-
- // 전역 검색 (search box용)
- let globalWhere;
+ // 한 쿼리로 모든 아이템 조회 (각 레코드는 3개 중 하나의 ID만 가짐)
+ const rawItems = await db
+ .select({
+ id: techVendorPossibleItems.id,
+ vendorId: techVendorPossibleItems.vendorId,
+ shipbuildingItemId: techVendorPossibleItems.shipbuildingItemId,
+ offshoreTopItemId: techVendorPossibleItems.offshoreTopItemId,
+ offshoreHullItemId: techVendorPossibleItems.offshoreHullItemId,
+ createdAt: techVendorPossibleItems.createdAt,
+ updatedAt: techVendorPossibleItems.updatedAt,
+ // 벤더 정보
+ vendorCode: techVendors.vendorCode,
+ vendorName: techVendors.vendorName,
+ vendorEmail: techVendors.email,
+ vendorStatus: techVendors.status,
+ techVendorType: techVendors.techVendorType,
+ // 조선 아이템 정보 (shipbuildingItemId가 있을 때만)
+ shipItemCode: itemShipbuilding.itemCode,
+ shipWorkType: itemShipbuilding.workType,
+ shipItemList: itemShipbuilding.itemList,
+ shipTypes: itemShipbuilding.shipTypes,
+ // 해양 TOP 아이템 정보 (offshoreTopItemId가 있을 때만)
+ topItemCode: itemOffshoreTop.itemCode,
+ topWorkType: itemOffshoreTop.workType,
+ topItemList: itemOffshoreTop.itemList,
+ topSubItemList: itemOffshoreTop.subItemList,
+ // 해양 HULL 아이템 정보 (offshoreHullItemId가 있을 때만)
+ hullItemCode: itemOffshoreHull.itemCode,
+ hullWorkType: itemOffshoreHull.workType,
+ hullItemList: itemOffshoreHull.itemList,
+ hullSubItemList: itemOffshoreHull.subItemList,
+ })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .leftJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .leftJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .leftJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id));
+
+ // 결과를 통합된 형태로 변환
+ const allItems: TechVendorPossibleItemsData[] = rawItems.map((item: Record<string, any>) => ({
+ id: item.id,
+ vendorId: item.vendorId,
+ shipbuildingItemId: item.shipbuildingItemId,
+ offshoreTopItemId: item.offshoreTopItemId,
+ offshoreHullItemId: item.offshoreHullItemId,
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ vendorCode: item.vendorCode,
+ vendorName: item.vendorName,
+ vendorEmail: item.vendorEmail,
+ vendorStatus: item.vendorStatus,
+ techVendorType: item.techVendorType,
+ // 어떤 타입의 아이템인지에 따라 적절한 값 선택
+ itemCode: item.shipItemCode || item.topItemCode || item.hullItemCode || "",
+ workType: item.shipWorkType || item.topWorkType || item.hullWorkType,
+ itemList: item.shipItemList || item.topItemList || item.hullItemList,
+ shipTypes: item.shipTypes, // 조선에만 있음
+ subItemList: item.topSubItemList || item.hullSubItemList, // 해양에만 있음
+ }));
+
+ let filteredItems = allItems;
+
+ // 필터링 적용
if (input.search) {
- const s = `%${input.search}%`;
- globalWhere = or(
- ilike(techVendors.vendorCode, s),
- ilike(techVendors.vendorName, s),
- ilike(techVendorPossibleItems.vendorEmail, s),
- ilike(techVendorPossibleItems.itemCode, s),
- ilike(techVendorPossibleItems.workType, s),
- ilike(techVendorPossibleItems.shipTypes, s),
- ilike(techVendorPossibleItems.itemList, s),
- ilike(techVendorPossibleItems.subItemList, s),
+ const s = input.search.toLowerCase();
+ filteredItems = filteredItems.filter(item =>
+ item.vendorCode?.toLowerCase().includes(s) ||
+ item.vendorName?.toLowerCase().includes(s) ||
+ item.vendorEmail?.toLowerCase().includes(s) ||
+ item.itemCode?.toLowerCase().includes(s) ||
+ item.workType?.toLowerCase().includes(s) ||
+ item.itemList?.toLowerCase().includes(s) ||
+ item.shipTypes?.toLowerCase().includes(s) ||
+ item.subItemList?.toLowerCase().includes(s)
);
}
- // 기존 호환성을 위한 개별 필터들
- const legacyFilters = [];
+ // 개별 필터들
if (input.vendorCode) {
- legacyFilters.push(ilike(techVendorPossibleItems.vendorCode, `%${input.vendorCode}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.vendorCode?.toLowerCase().includes(input.vendorCode!.toLowerCase())
+ );
}
+
if (input.vendorName) {
- legacyFilters.push(ilike(techVendors.vendorName, `%${input.vendorName}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.vendorName?.toLowerCase().includes(input.vendorName!.toLowerCase())
+ );
+ }
+
+ if (input.vendorEmail) {
+ filteredItems = filteredItems.filter(item =>
+ item.vendorEmail?.toLowerCase().includes(input.vendorEmail!.toLowerCase())
+ );
}
+
+ if (input.vendorStatus) {
+ filteredItems = filteredItems.filter(item =>
+ item.vendorStatus === input.vendorStatus
+ );
+ }
+
if (input.itemCode) {
- legacyFilters.push(ilike(techVendorPossibleItems.itemCode, `%${input.itemCode}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.itemCode?.toLowerCase().includes(input.itemCode!.toLowerCase())
+ );
}
// 벤더 타입 필터링
@@ -123,31 +176,36 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte const actualVendorType = vendorTypeMap[input.vendorType as keyof typeof vendorTypeMap] || input.vendorType;
if (actualVendorType) {
- legacyFilters.push(ilike(techVendors.techVendorType, `%${actualVendorType}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.techVendorType?.includes(actualVendorType)
+ );
}
}
- // 모든 조건 결합
- const finalWhere = and(
- advancedWhere,
- globalWhere,
- ...(legacyFilters.length > 0 ? [and(...legacyFilters)] : [])
- );
-
- // 정렬 조건
- const orderBy = [desc(techVendorPossibleItems.createdAt)];
-
- // 트랜잭션 내에서 Repository 호출
- const { data, total } = await db.transaction(async (tx) => {
- const data = await selectTechVendorPossibleItemsWithJoin(tx, finalWhere, orderBy, offset, input.perPage);
+ // 정렬
+ if (input.sort && input.sort.length > 0) {
+ filteredItems.sort((a, b) => {
+ for (const sortItem of input.sort!) {
+ const aVal = (a as Record<string, any>)[sortItem.id] || "";
+ const bVal = (b as Record<string, any>)[sortItem.id] || "";
+
+ if (aVal < bVal) return sortItem.desc ? 1 : -1;
+ if (aVal > bVal) return sortItem.desc ? -1 : 1;
+ }
+ return 0;
+ });
+ } else {
+ // 기본 정렬: createdAt 내림차순
+ filteredItems.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
+ }
- const total = await countTechVendorPossibleItemsWithJoin(tx, finalWhere);
- return { data, total };
- });
+ const totalCount = filteredItems.length;
+ const pageCount = Math.ceil(totalCount / input.perPage);
- const pageCount = Math.ceil(total / input.perPage);
+ // 페이지네이션 적용
+ const data = filteredItems.slice(offset, offset + input.perPage);
- return { data, pageCount, totalCount: total };
+ return { data, pageCount, totalCount };
} catch (error) {
console.error("Error fetching tech vendor possible items:", error);
return { data: [], pageCount: 0, totalCount: 0 };
@@ -159,659 +217,4 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte tags: ["tech-vendor-possible-items"],
}
)();
-}
-
-
-
-/**
- * 페이지네이션을 포함한 tech vendor possible items 조회
- */
-// export async function getTechVendorPossibleItemsWithPagination(
-// page: number = 1,
-// pageSize: number = 50,
-// searchTerm?: string,
-// vendorType?: string
-// ): Promise<{
-// data: TechVendorPossibleItemsData[];
-// totalCount: number;
-// totalPages: number;
-// }> {
-// const whereConditions = [];
-
-// if (searchTerm) {
-// whereConditions.push(
-// sql`(
-// ${techVendors.vendorName} ILIKE ${`%${searchTerm}%`} OR
-// ${techVendors.vendorCode} ILIKE ${`%${searchTerm}%`} OR
-// ${techVendorPossibleItems.itemCode} ILIKE ${`%${searchTerm}%`}
-// )`
-// );
-// }
-
-// // 벤더 타입 필터링 로직 추가
-// if (vendorType && vendorType !== "all") {
-// // URL의 vendorType 파라미터를 실제 벤더 타입으로 매핑
-// const vendorTypeMap = {
-// "ship": "조선",
-// "top": "해양TOP",
-// "hull": "해양HULL"
-// };
-
-// const actualVendorType = vendorType in vendorTypeMap
-// ? vendorTypeMap[vendorType as keyof typeof vendorTypeMap]
-// : vendorType; // 매핑되지 않는 경우 원본 값 사용
-
-// if (actualVendorType) {
-// // techVendorType 필드는 콤마로 구분된 문자열이므로 LIKE 사용
-// whereConditions.push(sql`${techVendors.techVendorType} ILIKE ${`%${actualVendorType}%`}`);
-// }
-// }
-
-// const whereClause = whereConditions.length > 0 ? and(...whereConditions) : undefined;
-
-// // 총 개수 조회
-// const [totalCountResult] = await db
-// .select({ count: count() })
-// .from(techVendorPossibleItems)
-// .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
-// .where(whereClause);
-
-// const totalCount = totalCountResult.count;
-// const totalPages = Math.ceil(totalCount / pageSize);
-// const offset = (page - 1) * pageSize;
-
-// // 데이터 조회
-// const data = await db
-// .select({
-// id: techVendorPossibleItems.id,
-// vendorId: techVendorPossibleItems.vendorId,
-// vendorCode: techVendors.vendorCode,
-// vendorName: techVendors.vendorName,
-// techVendorType: techVendors.techVendorType,
-// itemCode: techVendorPossibleItems.itemCode,
-// createdAt: techVendorPossibleItems.createdAt,
-// updatedAt: techVendorPossibleItems.updatedAt,
-// })
-// .from(techVendorPossibleItems)
-// .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
-// .where(whereClause)
-// .orderBy(desc(techVendorPossibleItems.createdAt))
-// .limit(pageSize)
-// .offset(offset);
-
-// return {
-// data,
-// totalCount,
-// totalPages,
-// };
-// }
-
-/**
- * tech vendor possible item 생성 (Add Dialog용 - vendorId 기반)
- */
-export async function createTechVendorPossibleItem(
- data: CreateTechVendorPossibleItemData
-): Promise<{ success: boolean; error?: string }> {
- try {
- // 벤더 ID로 벤더 조회
- const vendor = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.id, data.vendorId))
- .limit(1);
-
- if (!vendor[0]) {
- return { success: false, error: "벤더를 찾을 수 없습니다." };
- }
-
- // 중복 체크 (벤더 + 아이템코드 + 공종 + 선종 조합)
- const existing = await db
- .select()
- .from(techVendorPossibleItems)
- .where(
- and(
- eq(techVendorPossibleItems.vendorId, data.vendorId),
- eq(techVendorPossibleItems.itemCode, data.itemCode),
- data.workType
- ? eq(techVendorPossibleItems.workType, data.workType)
- : isNull(techVendorPossibleItems.workType),
- data.shipTypes
- ? eq(techVendorPossibleItems.shipTypes, data.shipTypes)
- : isNull(techVendorPossibleItems.shipTypes)
- )
- )
- .limit(1);
-
- if (existing.length > 0) {
- return { success: false, error: "이미 존재하는 벤더-아이템 조합입니다." };
- }
-
- // 새로운 아이템 생성 (선택한 아이템의 정보를 그대로 저장)
- await db.insert(techVendorPossibleItems).values({
- vendorId: vendor[0].id,
- vendorCode: vendor[0].vendorCode,
- vendorEmail: vendor[0].email,
- itemCode: data.itemCode,
- workType: data.workType,
- shipTypes: data.shipTypes,
- itemList: data.itemList,
- subItemList: data.subItemList,
- });
-
- return { success: true };
- } catch (error) {
- console.error("Failed to create tech vendor possible item:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "생성 중 오류가 발생했습니다."
- };
- }
-}
-
-/**
- * tech vendor possible items 삭제
- */
-export async function deleteTechVendorPossibleItems(
- ids: number[]
-): Promise<{ success: boolean; error?: string }> {
- try {
- await db
- .delete(techVendorPossibleItems)
- .where(inArray(techVendorPossibleItems.id, ids));
-
- return { success: true };
- } catch (error) {
- console.error("Failed to delete tech vendor possible items:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "삭제 중 오류가 발생했습니다."
- };
- }
-}
-
-/**
- * 벤더 코드로 벤더 정보 조회
- */
-export async function getTechVendorByCode(vendorCode: string) {
- const result = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.vendorCode, vendorCode))
- .limit(1);
-
- return result[0] || null;
-}
-
-/**
- * 벤더 이메일로 벤더 정보 조회
- */
-export async function getTechVendorByEmail(vendorEmail: string) {
- const result = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.email, vendorEmail))
- .limit(1);
-
- return result[0] || null;
-}
-
-/**
- * 벤더 타입에 따라 적절한 아이템 테이블에서 아이템 조회
- */
-export async function getItemByCodeAndVendorType(itemCode: string, vendorType: string) {
- try {
- switch (vendorType) {
- case "조선":
- const shipItem = await db
- .select()
- .from(itemShipbuilding)
- .where(eq(itemShipbuilding.itemCode, itemCode))
- .limit(1);
- return shipItem[0] ? {
- itemCode: shipItem[0].itemCode,
- workType: shipItem[0].workType
- } : null;
-
- case "해양TOP":
- const topItem = await db
- .select()
- .from(itemOffshoreTop)
- .where(eq(itemOffshoreTop.itemCode, itemCode))
- .limit(1);
- return topItem[0] ? {
- itemCode: topItem[0].itemCode,
- workType: topItem[0].workType
- } : null;
-
- case "해양HULL":
- const hullItem = await db
- .select()
- .from(itemOffshoreHull)
- .where(eq(itemOffshoreHull.itemCode, itemCode))
- .limit(1);
- return hullItem[0] ? {
- itemCode: hullItem[0].itemCode,
- workType: hullItem[0].workType
- } : null;
-
- default:
- return null;
- }
- } catch (error) {
- console.error("Error fetching item by code and vendor type:", error);
- return null;
- }
-}
-
-/**
- * 아이템 코드로 아이템 정보 조회 (기존 함수 - 호환성 유지)
- */
-export async function getItemByCode(itemCode: string) {
- // 기존 items 테이블 대신 조선 테이블에서 먼저 조회 시도
- try {
- const shipItem = await db
- .select()
- .from(itemShipbuilding)
- .where(eq(itemShipbuilding.itemCode, itemCode))
- .limit(1);
-
- if (shipItem[0]) {
- return {
- itemCode: shipItem[0].itemCode,
- };
- }
-
- const topItem = await db
- .select()
- .from(itemOffshoreTop)
- .where(eq(itemOffshoreTop.itemCode, itemCode))
- .limit(1);
-
- if (topItem[0]) {
- return {
- itemCode: topItem[0].itemCode,
- };
- }
-
- const hullItem = await db
- .select()
- .from(itemOffshoreHull)
- .where(eq(itemOffshoreHull.itemCode, itemCode))
- .limit(1);
-
- if (hullItem[0]) {
- return {
- itemCode: hullItem[0].itemCode,
- };
- }
-
- return null;
- } catch (error) {
- console.error("Error fetching item by code:", error);
- return null;
- }
-}
-
-/**
- * Import 기능: 벤더이메일과 아이템정보를 통한 batch insert (새로운 스키마 버전)
- */
-export async function importTechVendorPossibleItems(
- data: ImportTechVendorPossibleItemData[]
-): Promise<ImportResult> {
- const result: ImportResult = {
- success: true,
- totalRows: data.length,
- successCount: 0,
- failedRows: [],
- };
-
- for (let i = 0; i < data.length; i++) {
- const row = data[i];
- const rowNumber = i + 1;
-
- try {
- // 벤더 이메일로 벤더 찾기 (필수)
- let vendor = null;
-
- if (row.vendorEmail && row.vendorEmail.trim()) {
- vendor = await getTechVendorByEmail(row.vendorEmail);
- } else {
- result.failedRows.push({
- row: rowNumber,
- error: "벤더 이메일은 필수입니다.",
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- if (!vendor) {
- result.failedRows.push({
- row: rowNumber,
- error: `벤더 이메일 '${row.vendorEmail}'을(를) 찾을 수 없습니다.`,
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- // 중복 체크 (벤더 + 아이템코드 + 공종 + 선종 조합)
- const existing = await db
- .select()
- .from(techVendorPossibleItems)
- .where(
- and(
- eq(techVendorPossibleItems.vendorId, vendor.id),
- eq(techVendorPossibleItems.itemCode, row.itemCode),
- row.workType
- ? eq(techVendorPossibleItems.workType, row.workType)
- : isNull(techVendorPossibleItems.workType),
- row.shipTypes
- ? eq(techVendorPossibleItems.shipTypes, row.shipTypes)
- : isNull(techVendorPossibleItems.shipTypes)
- )
- )
- .limit(1);
-
- if (existing.length > 0) {
- result.failedRows.push({
- row: rowNumber,
- error: `이미 존재하는 벤더-아이템 조합입니다.`,
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- // 새로운 아이템 생성
- await db.insert(techVendorPossibleItems).values({
- vendorId: vendor.id,
- vendorCode: vendor.vendorCode,
- vendorEmail: vendor.email,
- itemCode: row.itemCode,
- workType: row.workType || null,
- shipTypes: row.shipTypes || null,
- itemList: row.itemList || null,
- subItemList: row.subItemList || null,
- });
-
- result.successCount++;
- } catch (error) {
- result.failedRows.push({
- row: rowNumber,
- error: error instanceof Error ? error.message : "알 수 없는 오류",
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- }
- }
-
- if (result.failedRows.length > 0) {
- result.success = false;
- }
-
- return result;
-}
-
-/**
- * 모든 기술영업 벤더 조회 (드롭다운용)
- */
-export async function getAllTechVendors() {
- return await db
- .select({
- id: techVendors.id,
- vendorCode: techVendors.vendorCode,
- vendorName: techVendors.vendorName,
- techVendorType: techVendors.techVendorType,
- })
- .from(techVendors)
- .where(eq(techVendors.status, "ACTIVE"))
- .orderBy(asc(techVendors.vendorName));
-}
-
-/**
- * 고유한 벤더 타입 목록 조회 (필터용)
- */
-export async function getUniqueTechVendorTypes(): Promise<string[]> {
- try {
- const result = await db
- .select({
- techVendorType: techVendors.techVendorType,
- })
- .from(techVendors)
- .where(eq(techVendors.status, "ACTIVE"));
-
- // techVendorType이 JSON 배열 형태로 저장된 경우를 고려
- const allTypes = new Set<string>();
-
- result.forEach(row => {
- try {
- // techVendorType이 JSON 문자열인지 확인
- if (row.techVendorType && row.techVendorType.startsWith('[')) {
- const types = JSON.parse(row.techVendorType);
- if (Array.isArray(types)) {
- types.forEach(type => {
- if (type && typeof type === 'string') {
- allTypes.add(type.trim());
- }
- });
- }
- } else if (row.techVendorType) {
- // 단순 문자열인 경우
- row.techVendorType.split(',').forEach(type => {
- const trimmedType = type.trim();
- if (trimmedType) {
- allTypes.add(trimmedType);
- }
- });
- }
- } catch {
- // JSON 파싱 실패시 문자열로 처리
- if (row.techVendorType) {
- row.techVendorType.split(',').forEach(type => {
- const trimmedType = type.trim();
- if (trimmedType) {
- allTypes.add(trimmedType);
- }
- });
- }
- }
- });
-
- return Array.from(allTypes).sort();
- } catch (error) {
- console.error("Error fetching unique tech vendor types:", error);
- // 오류 발생시 기본 벤더 타입 반환
- return ["조선", "해양TOP", "해양HULL"];
- }
-}
-
-/**
- * 벤더 타입에 따른 아이템 목록 조회
- */
-export async function getItemsByVendorType(vendorTypes: string): Promise<{
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes?: string | null;
- subItemList?: string | null;
-}[]> {
- try {
- // 벤더 타입 파싱 개선
- let types: string[] = [];
- if (!vendorTypes) {
- return [];
- }
-
- if (vendorTypes.startsWith('[') && vendorTypes.endsWith(']')) {
- // JSON 배열 형태
- try {
- const parsed = JSON.parse(vendorTypes);
- types = Array.isArray(parsed) ? parsed.filter(Boolean) : [vendorTypes];
- } catch {
- types = [vendorTypes];
- }
- } else if (vendorTypes.includes(',')) {
- // 콤마로 구분된 문자열
- types = vendorTypes.split(',').map(t => t.trim()).filter(Boolean);
- } else {
- // 단일 문자열
- types = [vendorTypes.trim()].filter(Boolean);
- }
- // 벤더 타입 정렬 - 조선 > 해양TOP > 해양HULL 순
- const typeOrder = ["조선", "해양TOP", "해양HULL"];
- types.sort((a, b) => {
- const indexA = typeOrder.indexOf(a);
- const indexB = typeOrder.indexOf(b);
-
- // 정의된 순서에 있는 경우 우선순위 적용
- if (indexA !== -1 && indexB !== -1) {
- return indexA - indexB;
- }
- // 정의된 순서에 없는 경우 마지막에 배치하고 알파벳 순으로 정렬
- if (indexA !== -1) return -1;
- if (indexB !== -1) return 1;
- return a.localeCompare(b);
- });
-
- const allItems: any[] = [];
-
- // 각 벤더 타입에 따라 해당 아이템 테이블에서 조회
- for (const type of types) {
- switch (type) {
- case "조선":
- const shipItems = await db
- .select({
- itemCode: itemShipbuilding.itemCode,
- itemList: itemShipbuilding.itemList,
- workType: itemShipbuilding.workType,
- shipTypes: itemShipbuilding.shipTypes,
- })
- .from(itemShipbuilding);
- allItems.push(...shipItems);
- break;
-
- case "해양TOP":
- const topItems = await db
- .select({
- itemCode: itemOffshoreTop.itemCode,
- itemList: itemOffshoreTop.itemList,
- workType: itemOffshoreTop.workType,
- subItemList: itemOffshoreTop.subItemList,
- })
- .from(itemOffshoreTop);
- allItems.push(...topItems);
- break;
-
- case "해양HULL":
- const hullItems = await db
- .select({
- itemCode: itemOffshoreHull.itemCode,
- itemList: itemOffshoreHull.itemList,
- workType: itemOffshoreHull.workType,
- subItemList: itemOffshoreHull.subItemList,
- })
- .from(itemOffshoreHull);
- allItems.push(...hullItems);
- break;
- }
- }
- // // 중복 제거 (itemCode 기준)
- // const uniqueItems = allItems.filter((item, index, self) =>
- // index === self.findIndex(i => i.itemCode === item.itemCode)
- // );
-
- // const finalItems = uniqueItems.filter(item => item.itemCode); // itemCode가 있는 것만 반환
- // console.log("Final items after deduplication and filtering:", finalItems.length);
-
- return allItems;
- } catch (error) {
- console.error("Error fetching items by vendor type:", error);
- return [];
- }
-}
-
-/**
- * Excel Export 기능: 기술영업 벤더 가능 아이템 목록 내보내기
- */
-export async function exportTechVendorPossibleItemsToExcel(): Promise<{
- success: boolean;
- data?: Array<{
- 벤더코드: string | null;
- 벤더명: string;
- 벤더이메일: string | null;
- 벤더타입: string;
- 아이템코드: string;
- 공종: string | null;
- 선종: string | null;
- 아이템리스트: string | null;
- 서브아이템리스트: string | null;
- 생성일: string;
- }>;
- error?: string;
-}> {
- try {
- // 모든 데이터 조회 (페이지네이션 없이)
- const allData = await db
- .select({
- vendorCode: techVendorPossibleItems.vendorCode,
- vendorName: techVendors.vendorName,
- vendorEmail: techVendorPossibleItems.vendorEmail,
- techVendorType: techVendors.techVendorType,
- itemCode: techVendorPossibleItems.itemCode,
- workType: techVendorPossibleItems.workType,
- shipTypes: techVendorPossibleItems.shipTypes,
- itemList: techVendorPossibleItems.itemList,
- subItemList: techVendorPossibleItems.subItemList,
- createdAt: techVendorPossibleItems.createdAt,
- })
- .from(techVendorPossibleItems)
- .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
- .orderBy(desc(techVendorPossibleItems.createdAt));
-
- // Excel 형태로 변환
- const excelData = allData.map(item => ({
- 벤더코드: item.vendorCode,
- 벤더명: item.vendorName,
- 벤더이메일: item.vendorEmail,
- 벤더타입: item.techVendorType,
- 아이템코드: item.itemCode,
- 공종: item.workType,
- 선종: item.shipTypes,
- 아이템리스트: item.itemList,
- 서브아이템리스트: item.subItemList,
- 생성일: item.createdAt.toISOString().split('T')[0], // YYYY-MM-DD 형식
- }));
-
- return {
- success: true,
- data: excelData,
- };
- } catch (error) {
- console.error("Error exporting tech vendor possible items:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "내보내기 중 오류가 발생했습니다.",
- };
- }
-}
+}
\ No newline at end of file diff --git a/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx b/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx index cdce60af..85a551ea 100644 --- a/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx +++ b/lib/tech-vendor-possible-items/table/add-possible-item-dialog.tsx @@ -1,450 +1,450 @@ -"use client";
+// "use client";
-import * as React from "react";
-import { Search, Plus, X } from "lucide-react";
+// import * as React from "react";
+// import { Search, Plus, X } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Badge } from "@/components/ui/badge";
-import { useToast } from "@/hooks/use-toast";
-import {
- getAllTechVendors,
- createTechVendorPossibleItem,
- getItemsByVendorType
-} from "@/lib/tech-vendor-possible-items/service";
+// import { Button } from "@/components/ui/button";
+// import {
+// Dialog,
+// DialogContent,
+// DialogDescription,
+// DialogHeader,
+// DialogTitle,
+// DialogTrigger,
+// } from "@/components/ui/dialog";
+// import { Input } from "@/components/ui/input";
+// import { Label } from "@/components/ui/label";
+// import { Badge } from "@/components/ui/badge";
+// import { useToast } from "@/hooks/use-toast";
+// import {
+// getAllTechVendors,
+// createTechVendorPossibleItem,
+// getItemsByVendorType
+// } from "@/lib/tech-vendor-possible-items/service";
-interface TechVendor {
- id: number;
- vendorCode: string | null;
- vendorName: string;
- techVendorType: string;
-}
+// interface TechVendor {
+// id: number;
+// vendorCode: string | null;
+// vendorName: string;
+// techVendorType: string;
+// }
-interface ItemData {
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes?: string | null;
- subItemList?: string | null;
-}
+// interface ItemData {
+// itemCode: string;
+// itemList: string | null;
+// workType: string | null;
+// shipTypes?: string | null;
+// subItemList?: string | null;
+// }
-interface AddPossibleItemDialogProps {
- children?: React.ReactNode;
- onSuccess?: () => void;
-}
+// interface AddPossibleItemDialogProps {
+// children?: React.ReactNode;
+// onSuccess?: () => void;
+// }
-export function AddPossibleItemDialog({
- children,
- onSuccess
-}: AddPossibleItemDialogProps) {
- const { toast } = useToast();
- const [open, setOpen] = React.useState(false);
+// export function AddPossibleItemDialog({
+// children,
+// onSuccess
+// }: AddPossibleItemDialogProps) {
+// const { toast } = useToast();
+// const [open, setOpen] = React.useState(false);
- // 벤더 관련 상태
- const [vendors, setVendors] = React.useState<TechVendor[]>([]);
- const [filteredVendors, setFilteredVendors] = React.useState<TechVendor[]>([]);
- const [vendorSearch, setVendorSearch] = React.useState("");
- const [selectedVendor, setSelectedVendor] = React.useState<TechVendor | null>(null);
+// // 벤더 관련 상태
+// const [vendors, setVendors] = React.useState<TechVendor[]>([]);
+// const [filteredVendors, setFilteredVendors] = React.useState<TechVendor[]>([]);
+// const [vendorSearch, setVendorSearch] = React.useState("");
+// const [selectedVendor, setSelectedVendor] = React.useState<TechVendor | null>(null);
- // 아이템 관련 상태
- const [items, setItems] = React.useState<ItemData[]>([]);
- const [filteredItems, setFilteredItems] = React.useState<ItemData[]>([]);
- const [itemSearch, setItemSearch] = React.useState("");
- const [selectedItems, setSelectedItems] = React.useState<ItemData[]>([]);
+// // 아이템 관련 상태
+// const [items, setItems] = React.useState<ItemData[]>([]);
+// const [filteredItems, setFilteredItems] = React.useState<ItemData[]>([]);
+// const [itemSearch, setItemSearch] = React.useState("");
+// const [selectedItems, setSelectedItems] = React.useState<ItemData[]>([]);
- const [isLoading, setIsLoading] = React.useState(false);
+// const [isLoading, setIsLoading] = React.useState(false);
- // 벤더 목록 로드
- React.useEffect(() => {
- if (open) {
- loadVendors();
- }
- }, [open]);
+// // 벤더 목록 로드
+// React.useEffect(() => {
+// if (open) {
+// loadVendors();
+// }
+// }, [open]);
- // 벤더 검색 필터링
- React.useEffect(() => {
- if (!vendorSearch) {
- setFilteredVendors(vendors);
- } else {
- const filtered = vendors.filter(vendor =>
- vendor.vendorName.toLowerCase().includes(vendorSearch.toLowerCase()) ||
- vendor.vendorCode?.toLowerCase().includes(vendorSearch.toLowerCase())
- );
- setFilteredVendors(filtered);
- }
- }, [vendors, vendorSearch]);
+// // 벤더 검색 필터링
+// React.useEffect(() => {
+// if (!vendorSearch) {
+// setFilteredVendors(vendors);
+// } else {
+// const filtered = vendors.filter(vendor =>
+// vendor.vendorName.toLowerCase().includes(vendorSearch.toLowerCase()) ||
+// vendor.vendorCode?.toLowerCase().includes(vendorSearch.toLowerCase())
+// );
+// setFilteredVendors(filtered);
+// }
+// }, [vendors, vendorSearch]);
- // 아이템 검색 필터링
- React.useEffect(() => {
- if (!itemSearch) {
- setFilteredItems(items);
- } else {
- const filtered = items.filter(item =>
- item.itemCode.toLowerCase().includes(itemSearch.toLowerCase()) ||
- item.itemList?.toLowerCase().includes(itemSearch.toLowerCase()) ||
- item.workType?.toLowerCase().includes(itemSearch.toLowerCase())
- );
- setFilteredItems(filtered);
- }
- }, [items, itemSearch]);
+// // 아이템 검색 필터링
+// React.useEffect(() => {
+// if (!itemSearch) {
+// setFilteredItems(items);
+// } else {
+// const filtered = items.filter(item =>
+// item.itemCode.toLowerCase().includes(itemSearch.toLowerCase()) ||
+// item.itemList?.toLowerCase().includes(itemSearch.toLowerCase()) ||
+// item.workType?.toLowerCase().includes(itemSearch.toLowerCase())
+// );
+// setFilteredItems(filtered);
+// }
+// }, [items, itemSearch]);
- const loadVendors = async () => {
- try {
- setIsLoading(true);
- const vendorData = await getAllTechVendors();
- setVendors(vendorData);
- } catch (error) {
- console.error("Failed to load vendors:", error);
- toast({
- title: "오류",
- description: "벤더 목록을 불러오는데 실패했습니다.",
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
+// const loadVendors = async () => {
+// try {
+// setIsLoading(true);
+// const vendorData = await getAllTechVendors();
+// setVendors(vendorData);
+// } catch (error) {
+// console.error("Failed to load vendors:", error);
+// toast({
+// title: "오류",
+// description: "벤더 목록을 불러오는데 실패했습니다.",
+// variant: "destructive",
+// });
+// } finally {
+// setIsLoading(false);
+// }
+// };
- const loadItemsByVendorType = async (vendorTypes: string) => {
- try {
- setIsLoading(true);
- console.log("Loading items for vendor types:", vendorTypes);
- const itemData = await getItemsByVendorType(vendorTypes);
- console.log("Loaded items:", itemData.length, itemData);
- setItems(itemData);
- } catch (error) {
- console.error("Failed to load items:", error);
- toast({
- title: "오류",
- description: "아이템 목록을 불러오는데 실패했습니다.",
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
+// const loadItemsByVendorType = async (vendorTypes: string) => {
+// try {
+// setIsLoading(true);
+// console.log("Loading items for vendor types:", vendorTypes);
+// const itemData = await getItemsByVendorType(vendorTypes);
+// console.log("Loaded items:", itemData.length, itemData);
+// setItems(itemData);
+// } catch (error) {
+// console.error("Failed to load items:", error);
+// toast({
+// title: "오류",
+// description: "아이템 목록을 불러오는데 실패했습니다.",
+// variant: "destructive",
+// });
+// } finally {
+// setIsLoading(false);
+// }
+// };
- const handleVendorSelect = (vendor: TechVendor) => {
- setSelectedVendor(vendor);
- setSelectedItems([]); // 벤더 변경시 선택된 아이템 초기화
- loadItemsByVendorType(vendor.techVendorType);
- };
+// const handleVendorSelect = (vendor: TechVendor) => {
+// setSelectedVendor(vendor);
+// setSelectedItems([]); // 벤더 변경시 선택된 아이템 초기화
+// loadItemsByVendorType(vendor.techVendorType);
+// };
- const handleItemToggle = (item: ItemData) => {
- setSelectedItems(prev => {
- const isSelected = prev.some(i => i.itemCode === item.itemCode);
- if (isSelected) {
- return prev.filter(i => i.itemCode !== item.itemCode);
- } else {
- return [...prev, item];
- }
- });
- };
+// const handleItemToggle = (item: ItemData) => {
+// setSelectedItems(prev => {
+// const isSelected = prev.some(i => i.itemCode === item.itemCode);
+// if (isSelected) {
+// return prev.filter(i => i.itemCode !== item.itemCode);
+// } else {
+// return [...prev, item];
+// }
+// });
+// };
- const handleSubmit = async () => {
- if (!selectedVendor || selectedItems.length === 0) return;
+// const handleSubmit = async () => {
+// if (!selectedVendor || selectedItems.length === 0) return;
- try {
- setIsLoading(true);
- let successCount = 0;
- let errorCount = 0;
+// try {
+// setIsLoading(true);
+// let successCount = 0;
+// let errorCount = 0;
- for (const item of selectedItems) {
- const result = await createTechVendorPossibleItem({
- vendorId: selectedVendor.id,
- itemCode: item.itemCode,
- workType: item.workType,
- shipTypes: item.shipTypes,
- itemList: item.itemList,
- subItemList: item.subItemList,
- });
+// for (const item of selectedItems) {
+// const result = await createTechVendorPossibleItem({
+// vendorId: selectedVendor.id,
+// itemCode: item.itemCode,
+// workType: item.workType,
+// shipTypes: item.shipTypes,
+// itemList: item.itemList,
+// subItemList: item.subItemList,
+// });
- if (result.success) {
- successCount++;
- } else {
- errorCount++;
- }
- }
+// if (result.success) {
+// successCount++;
+// } else {
+// errorCount++;
+// }
+// }
- if (successCount > 0) {
- toast({
- title: "성공",
- description: `${successCount}개의 아이템이 추가되었습니다.${errorCount > 0 ? ` (${errorCount}개 실패)` : ""}`,
- });
+// if (successCount > 0) {
+// toast({
+// title: "성공",
+// description: `${successCount}개의 아이템이 추가되었습니다.${errorCount > 0 ? ` (${errorCount}개 실패)` : ""}`,
+// });
- handleClose();
- onSuccess?.();
- } else {
- toast({
- title: "오류",
- description: "아이템 추가에 실패했습니다.",
- variant: "destructive",
- });
- }
- } catch (error) {
- console.error("Failed to add items:", error);
- toast({
- title: "오류",
- description: "아이템 추가 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
+// handleClose();
+// onSuccess?.();
+// } else {
+// toast({
+// title: "오류",
+// description: "아이템 추가에 실패했습니다.",
+// variant: "destructive",
+// });
+// }
+// } catch (error) {
+// console.error("Failed to add items:", error);
+// toast({
+// title: "오류",
+// description: "아이템 추가 중 오류가 발생했습니다.",
+// variant: "destructive",
+// });
+// } finally {
+// setIsLoading(false);
+// }
+// };
- const handleClose = () => {
- setOpen(false);
- setTimeout(() => {
- setSelectedVendor(null);
- setSelectedItems([]);
- setVendorSearch("");
- setItemSearch("");
- setVendors([]);
- setItems([]);
- setFilteredVendors([]);
- setFilteredItems([]);
- }, 200);
- };
+// const handleClose = () => {
+// setOpen(false);
+// setTimeout(() => {
+// setSelectedVendor(null);
+// setSelectedItems([]);
+// setVendorSearch("");
+// setItemSearch("");
+// setVendors([]);
+// setItems([]);
+// setFilteredVendors([]);
+// setFilteredItems([]);
+// }, 200);
+// };
- const parseVendorTypes = (vendorType: string): string[] => {
- if (!vendorType) return [];
+// const parseVendorTypes = (vendorType: string): string[] => {
+// if (!vendorType) return [];
- // JSON 배열 형태인지 확인
- if (vendorType.startsWith('[') && vendorType.endsWith(']')) {
- try {
- const parsed = JSON.parse(vendorType);
- return Array.isArray(parsed) ? parsed.filter(Boolean) : [vendorType];
- } catch {
- return [vendorType];
- }
- }
+// // JSON 배열 형태인지 확인
+// if (vendorType.startsWith('[') && vendorType.endsWith(']')) {
+// try {
+// const parsed = JSON.parse(vendorType);
+// return Array.isArray(parsed) ? parsed.filter(Boolean) : [vendorType];
+// } catch {
+// return [vendorType];
+// }
+// }
- // 콤마로 구분된 문자열인지 확인
- if (vendorType.includes(',')) {
- return vendorType.split(',').map(t => t.trim()).filter(Boolean);
- }
+// // 콤마로 구분된 문자열인지 확인
+// if (vendorType.includes(',')) {
+// return vendorType.split(',').map(t => t.trim()).filter(Boolean);
+// }
- // 단일 문자열
- return [vendorType.trim()].filter(Boolean);
- };
+// // 단일 문자열
+// return [vendorType.trim()].filter(Boolean);
+// };
- return (
- <Dialog open={open} onOpenChange={setOpen}>
- <DialogTrigger asChild>
- {children || (
- <Button size="sm">
- <Plus className="mr-2 h-4 w-4" />
- 추가
- </Button>
- )}
- </DialogTrigger>
- <DialogContent className="max-w-6xl max-h-[90vh] flex flex-col">
- <DialogHeader>
- <DialogTitle>
- 벤더별 아이템 추가
- </DialogTitle>
- <DialogDescription>
- 왼쪽에서 벤더를 선택하고, 오른쪽에서 아이템을 선택하세요.
- </DialogDescription>
- </DialogHeader>
+// return (
+// <Dialog open={open} onOpenChange={setOpen}>
+// <DialogTrigger asChild>
+// {children || (
+// <Button size="sm">
+// <Plus className="mr-2 h-4 w-4" />
+// 추가
+// </Button>
+// )}
+// </DialogTrigger>
+// <DialogContent className="max-w-6xl max-h-[90vh] flex flex-col">
+// <DialogHeader>
+// <DialogTitle>
+// 벤더별 아이템 추가
+// </DialogTitle>
+// <DialogDescription>
+// 왼쪽에서 벤더를 선택하고, 오른쪽에서 아이템을 선택하세요.
+// </DialogDescription>
+// </DialogHeader>
- <div className="flex-1 min-h-0">
- <div className="grid grid-cols-2 gap-4 h-[500px]">
- {/* 왼쪽: 벤더 선택/표시 */}
- <div className="space-y-4 h-full flex flex-col">
- {!selectedVendor ? (
- <>
- <div className="space-y-2">
- <Label htmlFor="vendor-search">벤더 검색</Label>
- <div className="relative">
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Input
- id="vendor-search"
- placeholder="벤더명 또는 벤더코드로 검색..."
- value={vendorSearch}
- onChange={(e) => setVendorSearch(e.target.value)}
- className="pl-10"
- />
- </div>
- </div>
+// <div className="flex-1 min-h-0">
+// <div className="grid grid-cols-2 gap-4 h-[500px]">
+// {/* 왼쪽: 벤더 선택/표시 */}
+// <div className="space-y-4 h-full flex flex-col">
+// {!selectedVendor ? (
+// <>
+// <div className="space-y-2">
+// <Label htmlFor="vendor-search">벤더 검색</Label>
+// <div className="relative">
+// <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Input
+// id="vendor-search"
+// placeholder="벤더명 또는 벤더코드로 검색..."
+// value={vendorSearch}
+// onChange={(e) => setVendorSearch(e.target.value)}
+// className="pl-10"
+// />
+// </div>
+// </div>
- <div className="max-h-96 overflow-y-auto border rounded-lg bg-gray-50 p-2">
- <div className="space-y-2">
- {isLoading ? (
- <div className="text-center py-4">로딩 중...</div>
- ) : filteredVendors.length === 0 ? (
- <div className="text-center py-4 text-muted-foreground">
- 검색 결과가 없습니다.
- </div>
- ) : (
- filteredVendors.map((vendor) => (
- <div
- key={vendor.id}
- className="p-3 bg-white border rounded-lg cursor-pointer transition-colors hover:bg-gray-50"
- onClick={() => handleVendorSelect(vendor)}
- >
- <div className="font-medium">{vendor.vendorName}</div>
- <div className="text-sm text-muted-foreground">
- {vendor.vendorCode}
- </div>
- <div className="flex flex-wrap gap-1 mt-2">
- {parseVendorTypes(vendor.techVendorType).map((type, index) => (
- <Badge key={`${vendor.id}-${type}-${index}`} variant="secondary" className="text-xs">
- {type}
- </Badge>
- ))}
- </div>
- </div>
- ))
- )}
- </div>
- </div>
- </>
- ) : (
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <Label>선택된 벤더</Label>
- <Button
- variant="outline"
- size="sm"
- onClick={() => {
- setSelectedVendor(null);
- setSelectedItems([]);
- setItems([]);
- setFilteredItems([]);
- }}
- >
- 변경
- </Button>
- </div>
- <div className="p-4 border rounded-md bg-muted/20">
- <div className="font-medium">{selectedVendor?.vendorName}</div>
- <div className="text-sm text-muted-foreground">
- {selectedVendor?.vendorCode}
- </div>
- <div className="flex flex-wrap gap-1 mt-2">
- {selectedVendor && parseVendorTypes(selectedVendor.techVendorType).map((type, index) => (
- <Badge key={`selected-${type}-${index}`} variant="outline" className="text-xs">
- {type}
- </Badge>
- ))}
- </div>
- </div>
- </div>
- )}
- </div>
+// <div className="max-h-96 overflow-y-auto border rounded-lg bg-gray-50 p-2">
+// <div className="space-y-2">
+// {isLoading ? (
+// <div className="text-center py-4">로딩 중...</div>
+// ) : filteredVendors.length === 0 ? (
+// <div className="text-center py-4 text-muted-foreground">
+// 검색 결과가 없습니다.
+// </div>
+// ) : (
+// filteredVendors.map((vendor) => (
+// <div
+// key={vendor.id}
+// className="p-3 bg-white border rounded-lg cursor-pointer transition-colors hover:bg-gray-50"
+// onClick={() => handleVendorSelect(vendor)}
+// >
+// <div className="font-medium">{vendor.vendorName}</div>
+// <div className="text-sm text-muted-foreground">
+// {vendor.vendorCode}
+// </div>
+// <div className="flex flex-wrap gap-1 mt-2">
+// {parseVendorTypes(vendor.techVendorType).map((type, index) => (
+// <Badge key={`${vendor.id}-${type}-${index}`} variant="secondary" className="text-xs">
+// {type}
+// </Badge>
+// ))}
+// </div>
+// </div>
+// ))
+// )}
+// </div>
+// </div>
+// </>
+// ) : (
+// <div className="space-y-4">
+// <div className="flex items-center justify-between">
+// <Label>선택된 벤더</Label>
+// <Button
+// variant="outline"
+// size="sm"
+// onClick={() => {
+// setSelectedVendor(null);
+// setSelectedItems([]);
+// setItems([]);
+// setFilteredItems([]);
+// }}
+// >
+// 변경
+// </Button>
+// </div>
+// <div className="p-4 border rounded-md bg-muted/20">
+// <div className="font-medium">{selectedVendor?.vendorName}</div>
+// <div className="text-sm text-muted-foreground">
+// {selectedVendor?.vendorCode}
+// </div>
+// <div className="flex flex-wrap gap-1 mt-2">
+// {selectedVendor && parseVendorTypes(selectedVendor.techVendorType).map((type, index) => (
+// <Badge key={`selected-${type}-${index}`} variant="outline" className="text-xs">
+// {type}
+// </Badge>
+// ))}
+// </div>
+// </div>
+// </div>
+// )}
+// </div>
- {/* 오른쪽: 아이템 선택 */}
- <div className="space-y-4 h-full flex flex-col">
- {selectedVendor ? (
- <>
+// {/* 오른쪽: 아이템 선택 */}
+// <div className="space-y-4 h-full flex flex-col">
+// {selectedVendor ? (
+// <>
- <Label htmlFor="item-search">아이템 검색</Label>
- <div className="relative">
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Input
- id="item-search"
- placeholder="아이템코드, 아이템리스트, 공종으로 검색..."
- value={itemSearch}
- onChange={(e) => setItemSearch(e.target.value)}
- className="pl-10"
- />
- </div>
+// <Label htmlFor="item-search">아이템 검색</Label>
+// <div className="relative">
+// <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Input
+// id="item-search"
+// placeholder="아이템코드, 아이템리스트, 공종으로 검색..."
+// value={itemSearch}
+// onChange={(e) => setItemSearch(e.target.value)}
+// className="pl-10"
+// />
+// </div>
- {selectedItems.length > 0 && (
- <div className="space-y-2">
- <Label>선택된 아이템 ({selectedItems.length}개)</Label>
- <div className="flex flex-wrap gap-1 p-2 border rounded-md bg-muted/50 max-h-20 overflow-y-auto">
- {selectedItems.map((item) => (
- <Badge key={`selected-${item.itemCode}`} variant="default" className="text-xs">
- {item.itemCode}
- <X
- className="ml-1 h-3 w-3 cursor-pointer"
- onClick={(e) => {
- e.stopPropagation();
- handleItemToggle(item);
- }}
- />
- </Badge>
- ))}
- </div>
- </div>
- )}
+// {selectedItems.length > 0 && (
+// <div className="space-y-2">
+// <Label>선택된 아이템 ({selectedItems.length}개)</Label>
+// <div className="flex flex-wrap gap-1 p-2 border rounded-md bg-muted/50 max-h-20 overflow-y-auto">
+// {selectedItems.map((item) => (
+// <Badge key={`selected-${item.itemCode}`} variant="default" className="text-xs">
+// {item.itemCode}
+// <X
+// className="ml-1 h-3 w-3 cursor-pointer"
+// onClick={(e) => {
+// e.stopPropagation();
+// handleItemToggle(item);
+// }}
+// />
+// </Badge>
+// ))}
+// </div>
+// </div>
+// )}
- <div className="max-h-80 overflow-y-auto border rounded-lg bg-gray-50 p-2">
- <div className="space-y-2">
- {isLoading ? (
- <div className="text-center py-4">아이템 로딩 중...</div>
- ) : filteredItems.length === 0 && items.length === 0 ? (
- <div className="text-center py-4 text-muted-foreground">
- 해당 벤더 타입에 대한 아이템이 없습니다.
- </div>
- ) : filteredItems.length === 0 ? (
- <div className="text-center py-4 text-muted-foreground">
- 검색 결과가 없습니다.
- </div>
- ) : (
- filteredItems.map((item) => {
- const isSelected = selectedItems.some(i => i.itemCode === item.itemCode);
- return (
- <div
- key={`item-${item.itemCode}`}
- className={`p-3 bg-white border rounded-lg cursor-pointer transition-colors ${
- isSelected
- ? "bg-primary/10 border-primary hover:bg-primary/20"
- : "hover:bg-gray-50"
- }`}
- onClick={() => handleItemToggle(item)}
- >
- <div className="font-medium">{item.itemCode}</div>
- <div className="text-sm text-muted-foreground">
- {item.itemList || "-"}
- </div>
- <div className="flex gap-2 mt-1 text-xs">
- <span>공종: {item.workType || "-"}</span>
- {item.shipTypes && <span>선종: {item.shipTypes}</span>}
- {item.subItemList && <span>서브아이템: {item.subItemList}</span>}
- </div>
- </div>
- );
- })
- )}
- </div>
- </div>
- </>
- ) : (
- <div className="flex-1 flex items-center justify-center text-muted-foreground">
- 왼쪽에서 벤더를 선택하세요.
- </div>
- )}
- </div>
- </div>
- </div>
+// <div className="max-h-80 overflow-y-auto border rounded-lg bg-gray-50 p-2">
+// <div className="space-y-2">
+// {isLoading ? (
+// <div className="text-center py-4">아이템 로딩 중...</div>
+// ) : filteredItems.length === 0 && items.length === 0 ? (
+// <div className="text-center py-4 text-muted-foreground">
+// 해당 벤더 타입에 대한 아이템이 없습니다.
+// </div>
+// ) : filteredItems.length === 0 ? (
+// <div className="text-center py-4 text-muted-foreground">
+// 검색 결과가 없습니다.
+// </div>
+// ) : (
+// filteredItems.map((item) => {
+// const isSelected = selectedItems.some(i => i.itemCode === item.itemCode);
+// return (
+// <div
+// key={`item-${item.itemCode}`}
+// className={`p-3 bg-white border rounded-lg cursor-pointer transition-colors ${
+// isSelected
+// ? "bg-primary/10 border-primary hover:bg-primary/20"
+// : "hover:bg-gray-50"
+// }`}
+// onClick={() => handleItemToggle(item)}
+// >
+// <div className="font-medium">{item.itemCode}</div>
+// <div className="text-sm text-muted-foreground">
+// {item.itemList || "-"}
+// </div>
+// <div className="flex gap-2 mt-1 text-xs">
+// <span>공종: {item.workType || "-"}</span>
+// {item.shipTypes && <span>선종: {item.shipTypes}</span>}
+// {item.subItemList && <span>서브아이템: {item.subItemList}</span>}
+// </div>
+// </div>
+// );
+// })
+// )}
+// </div>
+// </div>
+// </>
+// ) : (
+// <div className="flex-1 flex items-center justify-center text-muted-foreground">
+// 왼쪽에서 벤더를 선택하세요.
+// </div>
+// )}
+// </div>
+// </div>
+// </div>
- <div className="flex justify-end gap-2 pt-4 border-t">
- <Button variant="outline" onClick={handleClose}>
- 취소
- </Button>
- <Button
- onClick={handleSubmit}
- disabled={!selectedVendor || selectedItems.length === 0 || isLoading}
- >
- {isLoading ? "추가 중..." : `추가 (${selectedItems.length})`}
- </Button>
- </div>
- </DialogContent>
- </Dialog>
- );
-}
\ No newline at end of file +// <div className="flex justify-end gap-2 pt-4 border-t">
+// <Button variant="outline" onClick={handleClose}>
+// 취소
+// </Button>
+// <Button
+// onClick={handleSubmit}
+// disabled={!selectedVendor || selectedItems.length === 0 || isLoading}
+// >
+// {isLoading ? "추가 중..." : `추가 (${selectedItems.length})`}
+// </Button>
+// </div>
+// </DialogContent>
+// </Dialog>
+// );
+// }
\ No newline at end of file diff --git a/lib/tech-vendor-possible-items/table/possible-items-data-table.tsx b/lib/tech-vendor-possible-items/table/possible-items-data-table.tsx index 28b9774f..42417059 100644 --- a/lib/tech-vendor-possible-items/table/possible-items-data-table.tsx +++ b/lib/tech-vendor-possible-items/table/possible-items-data-table.tsx @@ -9,21 +9,8 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv import { getColumns } from "./possible-items-table-columns";
import { PossibleItemsTableToolbarActions } from "./possible-items-table-toolbar-actions";
-// 타입만 import
-type TechVendorPossibleItemsData = {
- id: number;
- vendorId: number;
- vendorCode: string | null;
- vendorName: string;
- techVendorType: string;
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes: string | null;
- subItemList: string | null;
- createdAt: Date;
- updatedAt: Date;
-};
+// 새로운 스키마에 맞는 타입 import
+import type { TechVendorPossibleItemsData } from "../service";
import type { DataTableAdvancedFilterField } from "@/types/table";
interface PossibleItemsDataTableProps {
@@ -51,6 +38,22 @@ export function PossibleItemsDataTable({ promises }: PossibleItemsDataTableProps type: "text",
},
{
+ id: "vendorEmail",
+ label: "벤더이메일",
+ type: "text",
+ },
+ {
+ id: "vendorStatus",
+ label: "벤더상태",
+ type: "multi-select",
+ options: [
+ { label: "ACTIVE", value: "ACTIVE", count: 0 },
+ { label: "PENDING_INVITE", value: "PENDING_INVITE", count: 0 },
+ { label: "PENDING_REVIEW", value: "PENDING_REVIEW", count: 0 },
+ { label: "INACTIVE", value: "INACTIVE", count: 0 },
+ ],
+ },
+ {
id: "itemCode",
label: "아이템코드",
type: "text",
diff --git a/lib/tech-vendor-possible-items/table/possible-items-table-columns.tsx b/lib/tech-vendor-possible-items/table/possible-items-table-columns.tsx index 7fdcc900..e9707a88 100644 --- a/lib/tech-vendor-possible-items/table/possible-items-table-columns.tsx +++ b/lib/tech-vendor-possible-items/table/possible-items-table-columns.tsx @@ -4,22 +4,8 @@ import { ColumnDef } from "@tanstack/react-table"; import { Checkbox } from "@/components/ui/checkbox";
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header";
import Link from "next/link";
-// 타입만 import
-type TechVendorPossibleItemsData = {
- id: number;
- vendorId: number;
- vendorCode: string | null;
- vendorName: string;
- techVendorType: string;
- vendorStatus: string;
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes: string | null;
- subItemList: string | null;
- createdAt: Date;
- updatedAt: Date;
-};
+// 새로운 스키마에 맞는 타입 import
+import type { TechVendorPossibleItemsData } from "../service";
import { format } from "date-fns";
import { ko } from "date-fns/locale";
import { Badge } from "@/components/ui/badge";
@@ -153,6 +139,22 @@ export function getColumns(): ColumnDef<TechVendorPossibleItemsData>[] { },
},
{
+ accessorKey: "vendorEmail",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="벤더이메일" />
+ ),
+ cell: ({ row }) => {
+ const vendorEmail = row.getValue("vendorEmail") as string | null;
+ return <div className="max-w-[200px] truncate">{vendorEmail || "-"}</div>;
+ },
+ filterFn: (row, id, value) => {
+ const vendorEmail = row.getValue(id) as string | null;
+ if (!value) return true;
+ if (!vendorEmail) return false;
+ return vendorEmail.toLowerCase().includes(value.toLowerCase());
+ },
+ },
+ {
accessorKey: "techVendorType",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="벤더타입" />
@@ -216,29 +218,35 @@ export function getColumns(): ColumnDef<TechVendorPossibleItemsData>[] { },
},
- {
- accessorKey: "vendorStatus",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더상태" />
- ),
- cell: ({ row }) => {
- const vendorStatus = row.getValue("vendorStatus") as string;
- const getStatusColor = (status: string) => {
- switch (status) {
- case "ACTIVE": return "bg-green-100 text-green-800";
- case "PENDING_INVITE": return "bg-yellow-100 text-yellow-800";
- case "PENDING_REVIEW": return "bg-blue-100 text-blue-800";
- case "INACTIVE": return "bg-gray-100 text-gray-800";
- default: return "bg-gray-100 text-gray-800";
- }
- };
- return (
- <Badge className={getStatusColor(vendorStatus)}>
- {vendorStatus}
- </Badge>
- );
- },
- },
+ // {
+ // accessorKey: "vendorStatus",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="벤더상태" />
+ // ),
+ // cell: ({ row }) => {
+ // const vendorStatus = row.getValue("vendorStatus") as string;
+ // const getStatusColor = (status: string) => {
+ // switch (status) {
+ // case "ACTIVE": return "bg-green-100 text-green-800";
+ // case "PENDING_INVITE": return "bg-yellow-100 text-yellow-800";
+ // case "PENDING_REVIEW": return "bg-blue-100 text-blue-800";
+ // case "INACTIVE": return "bg-gray-100 text-gray-800";
+ // default: return "bg-gray-100 text-gray-800";
+ // }
+ // };
+ // return (
+ // <Badge className={getStatusColor(vendorStatus)}>
+ // {vendorStatus}
+ // </Badge>
+ // );
+ // },
+ // filterFn: (row, id, value) => {
+ // const vendorStatus = row.getValue(id) as string;
+ // if (!value) return true;
+ // if (!vendorStatus) return false;
+ // return vendorStatus === value;
+ // },
+ // },
{
accessorKey: "createdAt",
header: ({ column }) => (
diff --git a/lib/tech-vendor-possible-items/table/possible-items-table-toolbar-actions.tsx b/lib/tech-vendor-possible-items/table/possible-items-table-toolbar-actions.tsx index dc67221f..2914fb9c 100644 --- a/lib/tech-vendor-possible-items/table/possible-items-table-toolbar-actions.tsx +++ b/lib/tech-vendor-possible-items/table/possible-items-table-toolbar-actions.tsx @@ -2,152 +2,143 @@ import * as React from "react";
import { type Table } from "@tanstack/react-table";
-import { Download, Upload, FileSpreadsheet, Plus } from "lucide-react";
+// import { Plus } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { useToast } from "@/hooks/use-toast";
-import { AddPossibleItemDialog } from "./add-possible-item-dialog";
-import { DeletePossibleItemsDialog } from "./delete-possible-items-dialog";
-// Excel 함수들을 동적 import로만 사용하기 위해 타입만 import
-type TechVendorPossibleItemsData = {
- id: number;
- vendorId: number;
- vendorCode: string | null;
- vendorName: string;
- techVendorType: string;
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes: string | null;
- subItemList: string | null;
- createdAt: Date;
- updatedAt: Date;
-};
+// import { Button } from "@/components/ui/button";
+// import { Input } from "@/components/ui/input"; // 주석처리 (Excel 기능 사용 안함)
+// import { useToast } from "@/hooks/use-toast"; // 주석처리 (Excel 기능 사용 안함)
+// import { AddPossibleItemDialog } from "./add-possible-item-dialog";
+// import { DeletePossibleItemsDialog } from "./delete-possible-items-dialog";
+// 새로운 스키마에 맞는 타입 import
+import type { TechVendorPossibleItemsData } from "../service";
interface PossibleItemsTableToolbarActionsProps {
table: Table<TechVendorPossibleItemsData>;
}
export function PossibleItemsTableToolbarActions({
- table,
+ // table,
}: PossibleItemsTableToolbarActionsProps) {
- const { toast } = useToast();
+ // const { toast } = useToast(); // 주석처리 (Excel 기능 사용 안함)
- const selectedRows = table.getFilteredSelectedRowModel().rows;
- const hasSelection = selectedRows.length > 0;
- const selectedItems = selectedRows.map(row => row.original);
+ // const selectedRows = table.getFilteredSelectedRowModel().rows;
+ // const hasSelection = selectedRows.length > 0;
+ // const selectedItems = selectedRows.map(row => row.original);
- const handleSuccess = () => {
- table.toggleAllRowsSelected(false);
- // 페이지 새로고침이나 데이터 다시 로드 필요
- window.location.reload();
- };
+ // const handleSuccess = () => {
+ // table.toggleAllRowsSelected(false);
+ // // 페이지 새로고침이나 데이터 다시 로드 필요
+ // window.location.reload();
+ // };
- const handleExport = async () => {
- try {
- const { exportTechVendorPossibleItemsToExcel } = await import("./excel-export");
- const result = await exportTechVendorPossibleItemsToExcel(table.getFilteredRowModel().rows.map(row => row.original));
-
- if (result.success) {
- toast({
- title: "성공",
- description: "Excel 파일이 다운로드되었습니다.",
- });
- } else {
- toast({
- title: "오류",
- description: result.error || "내보내기 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- }
- } catch (error) {
- console.error("Export error:", error);
- toast({
- title: "오류",
- description: "내보내기 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- }
- };
+ // Excel Export 함수 주석처리 (새 스키마에서 사용하지 않음)
+ // const handleExport = async () => {
+ // try {
+ // const { exportTechVendorPossibleItemsToExcel } = await import("./excel-export");
+ // const result = await exportTechVendorPossibleItemsToExcel(table.getFilteredRowModel().rows.map(row => row.original));
+ //
+ // if (result.success) {
+ // toast({
+ // title: "성공",
+ // description: "Excel 파일이 다운로드되었습니다.",
+ // });
+ // } else {
+ // toast({
+ // title: "오류",
+ // description: result.error || "내보내기 중 오류가 발생했습니다.",
+ // variant: "destructive",
+ // });
+ // }
+ // } catch (error) {
+ // console.error("Export error:", error);
+ // toast({
+ // title: "오류",
+ // description: "내보내기 중 오류가 발생했습니다.",
+ // variant: "destructive",
+ // });
+ // }
+ // };
- const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
- const file = event.target.files?.[0];
- if (!file) return;
+ // Excel Import 함수 주석처리 (새 스키마에서 사용하지 않음)
+ // const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
+ // const file = event.target.files?.[0];
+ // if (!file) return;
- try {
- const { importTechVendorPossibleItemsFromExcel } = await import("./excel-import");
- const result = await importTechVendorPossibleItemsFromExcel(file);
-
- if (result.success) {
- toast({
- title: "성공",
- description: `${result.successCount}개의 아이템이 가져와졌습니다.`,
- });
- // 페이지 새로고침이나 데이터 다시 로드 필요
- window.location.reload();
- } else {
- toast({
- title: "가져오기 완료",
- description: `${result.successCount}개 성공, ${result.failedRows.length}개 실패`,
- variant: result.successCount > 0 ? "default" : "destructive",
- });
- }
- } catch (error) {
- console.error("Import error:", error);
- toast({
- title: "오류",
- description: "가져오기 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- }
+ // try {
+ // const { importTechVendorPossibleItemsFromExcel } = await import("./excel-import");
+ // const result = await importTechVendorPossibleItemsFromExcel(file);
+ //
+ // if (result.success) {
+ // toast({
+ // title: "성공",
+ // description: `${result.successCount}개의 아이템이 가져와졌습니다.`,
+ // });
+ // // 페이지 새로고침이나 데이터 다시 로드 필요
+ // window.location.reload();
+ // } else {
+ // toast({
+ // title: "가져오기 완료",
+ // description: `${result.successCount}개 성공, ${result.failedRows.length}개 실패`,
+ // variant: result.successCount > 0 ? "default" : "destructive",
+ // });
+ // }
+ // } catch (error) {
+ // console.error("Import error:", error);
+ // toast({
+ // title: "오류",
+ // description: "가져오기 중 오류가 발생했습니다.",
+ // variant: "destructive",
+ // });
+ // }
- // Reset input
- event.target.value = "";
- };
+ // // Reset input
+ // event.target.value = "";
+ // };
- const handleDownloadTemplate = async () => {
- try {
- const { exportTechVendorPossibleItemsTemplate } = await import("./excel-template");
- const result = await exportTechVendorPossibleItemsTemplate();
- if (result.success) {
- toast({
- title: "성공",
- description: "템플릿 파일이 다운로드되었습니다.",
- });
- } else {
- toast({
- title: "오류",
- description: result.error || "템플릿 다운로드 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- }
- } catch (error) {
- console.error("Template download error:", error);
- toast({
- title: "오류",
- description: "템플릿 다운로드 중 오류가 발생했습니다.",
- variant: "destructive",
- });
- }
- };
+ // Excel Template 함수 주석처리 (새 스키마에서 사용하지 않음)
+ // const handleDownloadTemplate = async () => {
+ // try {
+ // const { exportTechVendorPossibleItemsTemplate } = await import("./excel-template");
+ // const result = await exportTechVendorPossibleItemsTemplate();
+ // if (result.success) {
+ // toast({
+ // title: "성공",
+ // description: "템플릿 파일이 다운로드되었습니다.",
+ // });
+ // } else {
+ // toast({
+ // title: "오류",
+ // description: result.error || "템플릿 다운로드 중 오류가 발생했습니다.",
+ // variant: "destructive",
+ // });
+ // }
+ // } catch (error) {
+ // console.error("Template download error:", error);
+ // toast({
+ // title: "오류",
+ // description: "템플릿 다운로드 중 오류가 발생했습니다.",
+ // variant: "destructive",
+ // });
+ // }
+ // };
return (
<div className="flex items-center gap-2">
- {hasSelection && (
- <DeletePossibleItemsDialog
- selectedItems={selectedItems}
- onSuccess={handleSuccess}
- />
- )}
- <AddPossibleItemDialog onSuccess={handleSuccess}>
- <Button size="sm">
- <Plus className="mr-2 h-4 w-4" />
- 추가
- </Button>
- </AddPossibleItemDialog>
+ {/* {hasSelection && ( */}
+ {/* // <DeletePo ssibleItemsDialog
+ // selectedItems={selectedItems}
+ // onSuccess={handleSuccess}
+ // />
+ // )}
+ // <AddPossibleItemDialog onSuccess={handleSuccess}>
+ // <Button size="sm">
+ // <Plus className="mr-2 h-4 w-4" />
+ // 추가
+ // </Button>
+ // </AddPossibleItemDialog>
- <Button
+ {/* Excel 관련 버튼들 주석처리 (새 스키마에서 사용하지 않음) */}
+ {/* <Button
variant="outline"
size="sm"
onClick={() => document.getElementById("import-file")?.click()}
@@ -173,7 +164,7 @@ export function PossibleItemsTableToolbarActions({ <Button variant="outline" size="sm" onClick={handleDownloadTemplate}>
<FileSpreadsheet className="mr-2 h-4 w-4" />
Download Template
- </Button>
+ </Button> */}
</div>
);
}
\ No newline at end of file |
