diff options
Diffstat (limited to 'lib/rfq-last')
| -rw-r--r-- | lib/rfq-last/service.ts | 82 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table.tsx | 29 |
2 files changed, 95 insertions, 16 deletions
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index 2c1aa2ca..461a635a 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -3,7 +3,7 @@ import { revalidatePath, unstable_cache, unstable_noStore } from "next/cache"; import db from "@/db/db"; -import { avlVendorInfo, paymentTerms, incoterms, rfqLastVendorQuotationItems, rfqLastVendorAttachments, rfqLastVendorResponses, RfqsLastView, rfqLastAttachmentRevisions, rfqLastAttachments, rfqsLast, rfqsLastView, users, rfqPrItems, prItemsLastView, vendors, rfqLastDetails, rfqLastVendorResponseHistory, rfqLastDetailsView, vendorContacts, projects, basicContract, basicContractTemplates, rfqLastTbeSessions, rfqLastTbeDocumentReviews, templateDetailView, RfqStatus } from "@/db/schema"; +import { avlVendorInfo, paymentTerms, incoterms, rfqLastVendorQuotationItems, rfqLastVendorAttachments, rfqLastVendorResponses, RfqsLastView, rfqLastAttachmentRevisions, rfqLastAttachments, rfqsLast, rfqsLastView, users, rfqPrItems, prItemsLastView, vendors, rfqLastDetails, rfqLastVendorResponseHistory, rfqLastDetailsView, vendorContacts, projects, basicContract, basicContractTemplates, rfqLastTbeSessions, rfqLastTbeDocumentReviews, templateDetailView, RfqStatus, purchaseRequests } from "@/db/schema"; import { sql, and, desc, asc, like, ilike, or, eq, SQL, count, gte, lte, isNotNull, ne, inArray } from "drizzle-orm"; import { filterColumns } from "@/lib/filter-columns"; import { GetRfqLastAttachmentsSchema, GetRfqsSchema } from "./validations"; @@ -135,9 +135,25 @@ export async function getRfqs(input: GetRfqsSchema) { console.log("총 데이터 수:", total); - // 6. 정렬 및 페이징 처리 - const orderByColumns = input.sort.map((sort) => { + // 6. 정렬 및 페이징 처리 (NULL 값 처리 포함) + // classNo는 별도로 처리하므로 제외 + const validSorts = input.sort.filter((sort) => sort.id !== 'classNo'); + + const orderByColumns = validSorts.map((sort) => { const column = sort.id as keyof typeof rfqsLastView.$inferSelect; + + // NULL 값이 있을 수 있는 컬럼들 (NULLS LAST 처리) + const nullableColumns = ['rfqTitle', 'majorItemMaterialDescription', 'itemCode', 'projectName', 'packageName']; + + if (nullableColumns.includes(sort.id)) { + // NULL 값은 마지막에 정렬 (NULLS LAST) + // drizzle에서 컬럼을 직접 참조하여 SQL 템플릿 사용 + const colRef = rfqsLastView[column]; + return sort.desc + ? sql`${colRef} DESC NULLS LAST` + : sql`${colRef} ASC NULLS LAST`; + } + return sort.desc ? desc(rfqsLastView[column]) : asc(rfqsLastView[column]); @@ -147,13 +163,59 @@ export async function getRfqs(input: GetRfqsSchema) { orderByColumns.push(desc(rfqsLastView.createdAt)); } - const rfqData = await db - .select() - .from(rfqsLastView) - .where(finalWhere) - .orderBy(...orderByColumns) - .limit(input.perPage) - .offset(offset); + // classNo 정렬이 있는 경우 서브쿼리로 purchaseRequests에서 classNo 가져오기 + const hasClassNoSort = input.sort.some(s => s.id === 'classNo'); + + let rfqData; + try { + if (hasClassNoSort) { + // classNo 정렬이 있는 경우 서브쿼리로 첫 번째 purchaseRequests의 classNo 가져오기 + const classNoOrderBy = input.sort + .filter(s => s.id === 'classNo') + .map(s => { + // 서브쿼리로 purchaseRequests에서 첫 번째 classNo 가져오기 (NULLS LAST 처리) + // 외부 쿼리의 rfqs_last_view.id를 참조하기 위해 correlation 사용 + return s.desc + ? sql<string | null>`(SELECT class_no FROM purchase_requests WHERE rfq_id = rfqs_last_view.id ORDER BY id LIMIT 1) DESC NULLS LAST` + : sql<string | null>`(SELECT class_no FROM purchase_requests WHERE rfq_id = rfqs_last_view.id ORDER BY id LIMIT 1) ASC NULLS LAST`; + }); + + // classNo 정렬을 먼저 적용하고 나머지 정렬을 추가 + const finalOrderBy = [...classNoOrderBy, ...orderByColumns]; + + console.log('=== classNo 정렬 실행 (서브쿼리) ===', { + hasClassNoSort, + classNoOrderByCount: classNoOrderBy.length, + finalOrderByCount: finalOrderBy.length + }); + + rfqData = await db + .select() + .from(rfqsLastView) + .where(finalWhere) + .orderBy(...finalOrderBy) + .limit(input.perPage) + .offset(offset); + } else { + rfqData = await db + .select() + .from(rfqsLastView) + .where(finalWhere) + .orderBy(...orderByColumns) + .limit(input.perPage) + .offset(offset); + } + } catch (orderError) { + console.error('정렬 오류:', orderError); + // 정렬 오류 발생 시 기본 정렬로 대체 + rfqData = await db + .select() + .from(rfqsLastView) + .where(finalWhere) + .orderBy(desc(rfqsLastView.createdAt)) + .limit(input.perPage) + .offset(offset); + } const pageCount = Math.ceil(total / input.perPage); diff --git a/lib/rfq-last/table/rfq-table.tsx b/lib/rfq-last/table/rfq-table.tsx index 9f78f578..162dd343 100644 --- a/lib/rfq-last/table/rfq-table.tsx +++ b/lib/rfq-last/table/rfq-table.tsx @@ -215,6 +215,9 @@ export function RfqTable({ expandedRows: [] }), [getSearchParam, parseSearchParam]); + // 탭별로 독립적인 tableId 사용 (정렬 상태 분리) + const tableId = React.useMemo(() => `rfq-table-${rfqCategory}`, [rfqCategory]); + const { presets, activePresetId, @@ -227,7 +230,7 @@ export function RfqTable({ setDefaultPreset, renamePreset, getCurrentSettings, - } = useTablePresets<RfqsLastView>('rfq-table', initialSettings); + } = useTablePresets<RfqsLastView>(tableId, initialSettings); // 컬럼 정의 const columns = React.useMemo(() => { @@ -305,11 +308,25 @@ export function RfqTable({ const currentSettings = React.useMemo(() => getCurrentSettings(), [getCurrentSettings]); - const initialState = React.useMemo(() => ({ - sorting: initialSettings.sort.filter((s: any) => columns.some((c: any) => ("accessorKey" in c ? c.accessorKey : c.id) === s.id)), - columnVisibility: currentSettings.columnVisibility, - columnPinning: currentSettings.pinnedColumns, - }), [columns, currentSettings, initialSettings.sort]); + // 탭별로 독립적인 정렬 상태 관리 + // rfqCategory가 변경되면 정렬 상태를 재계산하여 탭 간 정렬 충돌 방지 + const initialState = React.useMemo(() => { + // 현재 탭의 컬럼에 존재하는 정렬만 유효한 것으로 필터링 + const validSorting = initialSettings.sort.filter((s: any) => + columns.some((c: any) => ("accessorKey" in c ? c.accessorKey : c.id) === s.id) + ); + + // 유효한 정렬이 없으면 기본 정렬 사용 + const sorting = validSorting.length > 0 + ? validSorting + : [{ id: "createdAt", desc: true }]; + + return { + sorting, + columnVisibility: currentSettings.columnVisibility, + columnPinning: currentSettings.pinnedColumns, + }; + }, [columns, currentSettings, initialSettings.sort, rfqCategory]); const { table } = useDataTable({ data: tableData.data, |
