diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-25 07:51:15 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-25 07:51:15 +0000 |
| commit | 2650b7c0bb0ea12b68a58c0439f72d61df04b2f1 (patch) | |
| tree | 17156183fd74b69d78178065388ac61a18ac07b4 /lib/contact-possible-items | |
| parent | d32acea05915bd6c1ed4b95e56c41ef9204347bc (diff) | |
(대표님) 정기평가 대상, 미들웨어 수정, nextauth 토큰 처리 개선, GTC 등
(최겸) 기술영업
Diffstat (limited to 'lib/contact-possible-items')
| -rw-r--r-- | lib/contact-possible-items/service.ts | 99 | ||||
| -rw-r--r-- | lib/contact-possible-items/table/contact-possible-items-table-columns.tsx | 301 |
2 files changed, 189 insertions, 211 deletions
diff --git a/lib/contact-possible-items/service.ts b/lib/contact-possible-items/service.ts index f4b89368..960df17e 100644 --- a/lib/contact-possible-items/service.ts +++ b/lib/contact-possible-items/service.ts @@ -3,6 +3,7 @@ import db from "@/db/db"
import { techSalesContactPossibleItems } from "@/db/schema/techSales"
import { techVendors, techVendorContacts, techVendorPossibleItems } from "@/db/schema/techVendors"
+import { itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items"
import { eq, desc, ilike, count, or } from "drizzle-orm"
import { revalidatePath } from "next/cache"
import { unstable_noStore } from "next/cache"
@@ -29,15 +30,16 @@ export interface ContactPossibleItemDetail { // 연락처 정보
contactName: string | null
contactPosition: string | null
+ contactTitle: string | null
contactEmail: string | null
contactPhone: string | null
contactCountry: string | null
isPrimary: boolean | null
// 아이템 정보
- itemCode: string | null
workType: string | null
shipTypes: string | null
+ itemCode: string | null
itemList: string | null
subItemList: string | null
}
@@ -55,13 +57,11 @@ export async function getContactPossibleItems(input: GetContactPossibleItemsSche console.log("Input:", input)
console.log("Offset:", offset)
- // 검색 조건
+ // 검색 조건 (벤더명, 연락처명으로만 검색)
let whereCondition
if (input.search) {
const searchTerm = `%${input.search}%`
whereCondition = or(
- ilike(techVendorPossibleItems.itemCode, searchTerm),
- ilike(techVendorPossibleItems.itemList, searchTerm),
ilike(techVendors.vendorName, searchTerm),
ilike(techVendorContacts.contactName, searchTerm)
)
@@ -70,9 +70,11 @@ export async function getContactPossibleItems(input: GetContactPossibleItemsSche console.log("No search condition")
}
- // 원본 테이블들을 직접 조인해서 데이터 조회
+ // 새로운 스키마에 맞게 수정 - 아이템 정보를 별도로 조회해야 함
console.log("Executing data query...")
- const items = await db
+
+ // 1단계: 기본 매핑 정보 조회
+ const basicItems = await db
.select({
// 기본 매핑 정보
id: techSalesContactPossibleItems.id,
@@ -94,17 +96,16 @@ export async function getContactPossibleItems(input: GetContactPossibleItemsSche // 연락처 정보
contactName: techVendorContacts.contactName,
contactPosition: techVendorContacts.contactPosition,
+ contactTitle: techVendorContacts.contactTitle,
contactEmail: techVendorContacts.contactEmail,
contactPhone: techVendorContacts.contactPhone,
contactCountry: techVendorContacts.contactCountry,
isPrimary: techVendorContacts.isPrimary,
- // 벤더 가능 아이템 정보
- itemCode: techVendorPossibleItems.itemCode,
- workType: techVendorPossibleItems.workType,
- shipTypes: techVendorPossibleItems.shipTypes,
- itemList: techVendorPossibleItems.itemList,
- subItemList: techVendorPossibleItems.subItemList,
+ // 벤더 가능 아이템 ID 정보
+ shipbuildingItemId: techVendorPossibleItems.shipbuildingItemId,
+ offshoreTopItemId: techVendorPossibleItems.offshoreTopItemId,
+ offshoreHullItemId: techVendorPossibleItems.offshoreHullItemId,
})
.from(techSalesContactPossibleItems)
.leftJoin(techVendorContacts, eq(techSalesContactPossibleItems.contactId, techVendorContacts.id))
@@ -115,6 +116,80 @@ export async function getContactPossibleItems(input: GetContactPossibleItemsSche .offset(offset)
.limit(input.per_page)
+ // 2단계: 각 아이템의 상세 정보를 별도로 조회하여 합치기
+ const items = await Promise.all(basicItems.map(async (item) => {
+ let itemCode = null;
+ let workType = null;
+ let shipTypes = null;
+ let itemList = null;
+ let subItemList = null;
+
+ if (item.shipbuildingItemId) {
+ const shipItem = await db
+ .select({
+ itemCode: itemShipbuilding.itemCode,
+ workType: itemShipbuilding.workType,
+ shipTypes: itemShipbuilding.shipTypes,
+ itemList: itemShipbuilding.itemList,
+ })
+ .from(itemShipbuilding)
+ .where(eq(itemShipbuilding.id, item.shipbuildingItemId))
+ .limit(1);
+
+ if (shipItem.length > 0) {
+ itemCode = shipItem[0].itemCode;
+ workType = shipItem[0].workType;
+ shipTypes = shipItem[0].shipTypes;
+ itemList = shipItem[0].itemList;
+ }
+ } else if (item.offshoreTopItemId) {
+ const topItem = await db
+ .select({
+ itemCode: itemOffshoreTop.itemCode,
+ workType: itemOffshoreTop.workType,
+ itemList: itemOffshoreTop.itemList,
+ subItemList: itemOffshoreTop.subItemList,
+ })
+ .from(itemOffshoreTop)
+ .where(eq(itemOffshoreTop.id, item.offshoreTopItemId))
+ .limit(1);
+
+ if (topItem.length > 0) {
+ itemCode = topItem[0].itemCode;
+ workType = topItem[0].workType;
+ itemList = topItem[0].itemList;
+ subItemList = topItem[0].subItemList;
+ }
+ } else if (item.offshoreHullItemId) {
+ const hullItem = await db
+ .select({
+ itemCode: itemOffshoreHull.itemCode,
+ workType: itemOffshoreHull.workType,
+ itemList: itemOffshoreHull.itemList,
+ subItemList: itemOffshoreHull.subItemList,
+ })
+ .from(itemOffshoreHull)
+ .where(eq(itemOffshoreHull.id, item.offshoreHullItemId))
+ .limit(1);
+
+ if (hullItem.length > 0) {
+ itemCode = hullItem[0].itemCode;
+ workType = hullItem[0].workType;
+ itemList = hullItem[0].itemList;
+ subItemList = hullItem[0].subItemList;
+ }
+ }
+
+ return {
+ ...item,
+ itemCode,
+ workType,
+ shipTypes,
+ itemList,
+ subItemList,
+ };
+ }))
+
console.log("Items found:", items.length)
console.log("First 3 items:", items.slice(0, 3))
diff --git a/lib/contact-possible-items/table/contact-possible-items-table-columns.tsx b/lib/contact-possible-items/table/contact-possible-items-table-columns.tsx index a3b198ae..552497e3 100644 --- a/lib/contact-possible-items/table/contact-possible-items-table-columns.tsx +++ b/lib/contact-possible-items/table/contact-possible-items-table-columns.tsx @@ -2,12 +2,13 @@ import * as React from "react"
import { type DataTableRowAction } from "@/types/table"
-import { type ColumnDef } from "@tanstack/react-table"
+import { type ColumnDef, type Row, type Column } from "@tanstack/react-table"
import { Ellipsis } from "lucide-react"
import { formatDate } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
+import { Badge } from "@/components/ui/badge"
import {
DropdownMenu,
DropdownMenuContent,
@@ -18,6 +19,7 @@ import { import { ContactPossibleItemDetail } from "../service"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { contactPossibleItemsColumnsConfig } from "@/config/contactPossibleItemsColumnsConfig"
interface GetColumnsProps {
@@ -90,205 +92,106 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Contact }
// ----------------------------------------------------------------
- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성
+ // 3) config를 기반으로 컬럼 그룹들을 동적으로 생성
// ----------------------------------------------------------------
- const baseColumns: ColumnDef<ContactPossibleItemDetail>[] = [
- // 벤더 정보
- {
- id: "vendorInfo",
- header: "벤더 정보",
- columns: [
- {
- accessorKey: "vendorCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더 코드" />
- ),
- cell: ({ row }) => row.original.vendorCode ?? "",
- },
- {
- accessorKey: "vendorName",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더명" />
- ),
- cell: ({ row }) => row.original.vendorName ?? "",
- },
- {
- accessorKey: "vendorCountry",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더 국가" />
- ),
- cell: ({ row }) => {
- const country = row.original.vendorCountry
- return country || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "techVendorType",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더 타입" />
- ),
- cell: ({ row }) => {
- const type = row.original.techVendorType
- return type || <span className="text-muted-foreground">-</span>
- },
- },
- ]
- },
- // 담당자 정보
- {
- id: "contactInfo",
- header: "담당자 정보",
- columns: [
- {
- accessorKey: "contactName",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당자명" />
- ),
- cell: ({ row }) => {
- const contactName = row.original.contactName
- return contactName || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "contactPosition",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="직책" />
- ),
- cell: ({ row }) => {
- const position = row.original.contactPosition
- return position || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "contactEmail",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당자 이메일" />
- ),
- cell: ({ row }) => {
- const contactEmail = row.original.contactEmail
- return contactEmail || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "contactPhone",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당자 전화번호" />
- ),
- cell: ({ row }) => {
- const contactPhone = row.original.contactPhone
- return contactPhone || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "contactCountry",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당자 국가" />
- ),
- cell: ({ row }) => {
- const contactCountry = row.original.contactCountry
- return contactCountry || <span className="text-muted-foreground">-</span>
- },
- },
- {
- accessorKey: "isPrimary",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="주담당자" />
- ),
- cell: ({ row }) => {
- const isPrimary = row.original.isPrimary
- return isPrimary ? "예" : "아니오"
- },
- },
- ]
- },
- // 아이템 정보
- {
- id: "itemInfo",
- header: "아이템 정보",
- columns: [
- {
- accessorKey: "itemCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="아이템 코드" />
- ),
- cell: ({ row }) => row.original.itemCode ?? "",
- },
- {
- accessorKey: "itemList",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="아이템 리스트" />
- ),
- cell: ({ row }) => row.original.itemList ?? "",
- },
- {
- accessorKey: "workType",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="공종" />
- ),
- cell: ({ row }) => row.original.workType ?? "",
- },
- {
- accessorKey: "shipTypes",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="선종" />
- ),
- cell: ({ row }) => row.original.shipTypes ?? "",
- },
- {
- accessorKey: "subItemList",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="서브아이템 리스트" />
- ),
- cell: ({ row }) => row.original.subItemList ?? "",
- },
- ]
- },
-
- // 시스템 정보
- {
- id: "systemInfo",
- header: "시스템 정보",
- columns: [
- {
- accessorKey: "createdAt",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="생성일" />
- ),
- cell: ({ row }) => {
- const dateVal = row.getValue("createdAt") as Date
- return formatDate(dateVal)
- },
- },
- {
- accessorKey: "updatedAt",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="수정일" />
- ),
- cell: ({ row }) => {
- const dateVal = row.getValue("updatedAt") as Date
- return formatDate(dateVal)
- },
- },
- ]
- },
- ]
+
+ // 특수한 셀 렌더링이 필요한 컬럼들을 위한 헬퍼 함수
+ const getCellRenderer = (accessorKey: keyof ContactPossibleItemDetail) => {
+ switch (accessorKey) {
+ case 'createdAt':
+ case 'updatedAt':
+ return function DateCell({ row }: { row: Row<ContactPossibleItemDetail> }) {
+ const dateVal = row.getValue(accessorKey) as Date
+ return formatDate(dateVal, "ko-KR")
+ }
+ case 'isPrimary':
+ return function PrimaryCell({ row }: { row: Row<ContactPossibleItemDetail> }) {
+ const isPrimary = row.original.isPrimary
+ return isPrimary ? "Y" : "N"
+ }
+ case 'techVendorType':
+ return function VendorTypeCell({ row }: { row: Row<ContactPossibleItemDetail> }) {
+ const techVendorType = row.original.techVendorType
+
+ // 벤더 타입 파싱 개선 - null/undefined 안전 처리
+ let types: string[] = [];
+ if (!techVendorType) {
+ types = [];
+ } else if (techVendorType.startsWith('[') && techVendorType.endsWith(']')) {
+ // JSON 배열 형태
+ try {
+ const parsed = JSON.parse(techVendorType);
+ types = Array.isArray(parsed) ? parsed.filter(Boolean) : [techVendorType];
+ } catch {
+ types = [techVendorType];
+ }
+ } else if (techVendorType.includes(',')) {
+ // 콤마로 구분된 문자열
+ types = techVendorType.split(',').map(t => t.trim()).filter(Boolean);
+ } else {
+ // 단일 문자열
+ types = [techVendorType.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;
+ }
+ return a.localeCompare(b);
+ });
+
+ return (
+ <div className="flex flex-wrap gap-1">
+ {types.length > 0 ? types.map((type, index) => (
+ <Badge key={`${type}-${index}`} variant="secondary" className="text-xs">
+ {type}
+ </Badge>
+ )) : (
+ <span className="text-muted-foreground">-</span>
+ )}
+ </div>
+ )
+ }
+ case 'vendorCountry':
+ case 'contactName':
+ case 'contactPosition':
+ case 'contactTitle':
+ case 'contactEmail':
+ case 'contactPhone':
+ case 'contactCountry':
+ return function OptionalCell({ row }: { row: Row<ContactPossibleItemDetail> }) {
+ const value = row.original[accessorKey]
+ return value || <span className="text-muted-foreground">-</span>
+ }
+ default:
+ return function DefaultCell({ row }: { row: Row<ContactPossibleItemDetail> }) {
+ return row.original[accessorKey] ?? ""
+ }
+ }
+ }
+
+ const baseColumns: ColumnDef<ContactPossibleItemDetail>[] = contactPossibleItemsColumnsConfig.map(group => ({
+ id: group.id,
+ header: group.header,
+ columns: group.columns.map(colConfig => ({
+ accessorKey: colConfig.accessorKey,
+ enableResizing: colConfig.enableResizing,
+ enableSorting: colConfig.enableSorting,
+ size: colConfig.size,
+ minSize: colConfig.minSize,
+ maxSize: colConfig.maxSize,
+ header: function HeaderCell({ column }: { column: Column<ContactPossibleItemDetail> }) {
+ return <DataTableColumnHeaderSimple column={column} title={colConfig.title} />
+ },
+ cell: getCellRenderer(colConfig.accessorKey),
+ })),
+ }))
// ----------------------------------------------------------------
// 4) 최종 컬럼 배열: select, baseColumns, actions
|
