import type { ColumnType, Filter, FilterOperator, } from "@/types/table" import { type Column } from "@tanstack/react-table" import { dataTableConfig } from "@/config/data-table" import { FilterFn, Row } from "@tanstack/react-table" /** * Generate common pinning styles for a table column. * * This function calculates and returns CSS properties for pinned columns in a data table. * It handles both left and right pinning, applying appropriate styles for positioning, * shadows, and z-index. The function also considers whether the column is the last left-pinned * or first right-pinned column to apply specific shadow effects. * * @param options - The options for generating pinning styles. * @param options.column - The column object for which to generate styles. * @param options.withBorder - Whether to show a box shadow between pinned and scrollable columns. * @returns A React.CSSProperties object containing the calculated styles. */ export function getCommonPinningStylesWithBorder({ column, withBorder = true, isHeader = false, }: { column: Column withBorder?: boolean isHeader?: boolean }): React.CSSProperties { const pinnedSide = column.getIsPinned() as "left" | "right" | false // 안전한 방식으로 위치 확인 const isLastLeft = pinnedSide === "left" && column.getIsLastColumn?.("left") const isFirstRight = pinnedSide === "right" && column.getIsFirstColumn?.("right") const styles: React.CSSProperties = { /* ▒▒ sticky 위치 ▒▒ */ position: pinnedSide ? "sticky" : "relative", left: pinnedSide === "left" ? `${column.getStart("left")}px` : undefined, right: pinnedSide === "right" ? `${column.getAfter("right")}px` : undefined, /* ▒▒ 기타 스타일 ▒▒ */ width: column.getSize(), // 헤더는 항상 불투명, 셀은 핀된 경우에만 불투명 background: isHeader || pinnedSide ? "hsl(var(--background))" : "transparent", zIndex: pinnedSide ? 1 : 0, } // 구분선 및 그림자 추가 (선택사항) // if (withBorder && pinnedSide) { // if (pinnedSide === "left" && isLastLeft) { // // 좌측 핀의 마지막 컬럼에만 우측 그림자 추가 // styles.boxShadow = "2px 0 4px -2px rgba(0, 0, 0, 0.1)" // styles.borderRight = "1px solid hsl(var(--border))" // } else if (pinnedSide === "right" && isFirstRight) { // // 우측 핀의 첫 컬럼에만 좌측 그림자 추가 // styles.boxShadow = "-2px 0 4px -2px rgba(0, 0, 0, 0.1)" // styles.borderLeft = "1px solid hsl(var(--border))" // } // } return styles } // 디버깅을 위한 헬퍼 함수 export function debugPinningInfo(column: Column) { console.log("Column Debug Info:", { id: column.id, isPinned: column.getIsPinned(), start: column.getStart("left"), after: column.getAfter("right"), size: column.getSize(), isLastLeft: column.getIsLastColumn?.("left"), isFirstRight: column.getIsFirstColumn?.("right"), }) } /** * Determine the default filter operator for a given column type. * * This function returns the most appropriate default filter operator based on the * column's data type. For text columns, it returns 'iLike' (case-insensitive like), * while for all other types, it returns 'eq' (equality). * * @param columnType - The type of the column (e.g., 'text', 'number', 'date', etc.). * @returns The default FilterOperator for the given column type. */ export function getDefaultFilterOperator( columnType: ColumnType ): FilterOperator { if (columnType === "text") { return "iLike" } return "eq" } /** * Retrieve the list of applicable filter operators for a given column type. * * This function returns an array of filter operators that are relevant and applicable * to the specified column type. It uses a predefined mapping of column types to * operator lists, falling back to text operators if an unknown column type is provided. * * @param columnType - The type of the column for which to get filter operators. * @returns An array of objects, each containing a label and value for a filter operator. */ export function getFilterOperators(columnType: ColumnType) { const operatorMap: Record< ColumnType, { label: string; value: FilterOperator }[] > = { text: dataTableConfig.textOperators, number: dataTableConfig.numericOperators, select: dataTableConfig.selectOperators, "multi-select": dataTableConfig.selectOperators, boolean: dataTableConfig.booleanOperators, date: dataTableConfig.dateOperators, } return operatorMap[columnType] ?? dataTableConfig.textOperators } /** * Filters out invalid or empty filters from an array of filters. * * This function processes an array of filters and returns a new array * containing only the valid filters. A filter is considered valid if: * - It has an 'isEmpty' or 'isNotEmpty' operator, or * - Its value is not empty (for array values, at least one element must be present; * for other types, the value must not be an empty string, null, or undefined) * * @param filters - An array of Filter objects to be validated. * @returns A new array containing only the valid filters. */ export function getValidFilters( filters: Filter[] ): Filter[] { return filters?.filter( (filter) => filter.operator === "isEmpty" || filter.operator === "isNotEmpty" || (Array.isArray(filter.value) ? filter.value.length > 0 : filter.value !== "" && filter.value !== null && filter.value !== undefined) ) } interface NumericFilterValue { operator: string inputValue?: number } export const numericFilter: FilterFn = ( row: Row, columnId: string, filterValue: NumericFilterValue ) => { const rowValue = row.getValue(columnId) // handle "isEmpty" / "isNotEmpty" if (filterValue.operator === "isEmpty") { return rowValue == null || rowValue === "" } else if (filterValue.operator === "isNotEmpty") { return !(rowValue == null || rowValue === "") } // parse rowValue → numeric const numericRowVal = typeof rowValue === "number" ? rowValue : parseFloat(String(rowValue)) if (isNaN(numericRowVal)) { // rowValue not a number return false } // parse filterValue.inputValue const filterNum = filterValue.inputValue if (filterNum == null || isNaN(filterNum)) { // if user didn’t actually type a number, match everything or nothing (your choice) return true } // compare based on operator switch (filterValue.operator) { case "eq": return numericRowVal === filterNum case "ne": return numericRowVal !== filterNum case "lt": return numericRowVal < filterNum case "lte": return numericRowVal <= filterNum case "gt": return numericRowVal > filterNum case "gte": return numericRowVal >= filterNum default: return true } }