import type { UiHeadProps, UiTableSort } from "@common/business_components/ui"; const TABLE_SORT_COLLATOR = new Intl.Collator("ko-KR", { numeric: true, sensitivity: "base", }); const isEmptySortValue = (value: unknown) => value === null || value === undefined || String(value).trim() === "" || String(value).trim() === "-"; const toComparableNumber = (value: unknown) => { const normalized = String(value ?? "") .trim() .replace(/,/g, "") .replace(/%$/, ""); if (!/^-?\d+(?:\.\d+)?$/.test(normalized)) return undefined; const number = Number(normalized); return Number.isFinite(number) ? number : undefined; }; /** 업로드된 엑셀의 실제 데이터 컬럼만 공통 UiTable 정렬 대상으로 만든다. */ export const makeSortableHeaders = (headers: T[]) => headers.map((header) => ({ ...header, sortable: true })); /** 서버 페이징이 없는 엑셀 상세 그리드의 전체 행을 안정 정렬한다. */ export const sortLocalTableRows = >( rows: T[], sort?: UiTableSort, ) => { if (!sort) return rows; return rows .map((row, originalIndex) => ({ row, originalIndex })) .sort((leftEntry, rightEntry) => { const leftValue = leftEntry.row[sort.key]; const rightValue = rightEntry.row[sort.key]; const leftEmpty = isEmptySortValue(leftValue); const rightEmpty = isEmptySortValue(rightValue); if (leftEmpty || rightEmpty) { if (leftEmpty && rightEmpty) return leftEntry.originalIndex - rightEntry.originalIndex; return leftEmpty ? 1 : -1; } const leftNumber = toComparableNumber(leftValue); const rightNumber = toComparableNumber(rightValue); const comparison = leftNumber !== undefined && rightNumber !== undefined ? leftNumber - rightNumber : TABLE_SORT_COLLATOR.compare(String(leftValue), String(rightValue)); if (comparison === 0) return leftEntry.originalIndex - rightEntry.originalIndex; return sort.direction === "asc" ? comparison : -comparison; }) .map(({ row }) => row); }; /** children으로 다단 헤더를 전달하는 UiTable에서도 동일한 3상태 정렬을 사용한다. */ export const getNextTableSort = ( head: UiHeadProps, currentSort?: UiTableSort, ): UiTableSort | undefined => { const key = head.sortKey ?? head.id; if (currentSort?.key !== key) return { key, direction: "asc" }; if (currentSort.direction === "asc") return { key, direction: "desc" }; return undefined; };