diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
| commit | 9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch) | |
| tree | 4188cb7e6bf2c862d9c86a59d79946bd41217227 /components/client-data-table/table-filters.ts | |
| parent | b67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff) | |
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'components/client-data-table/table-filters.ts')
| -rw-r--r-- | components/client-data-table/table-filters.ts | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/components/client-data-table/table-filters.ts b/components/client-data-table/table-filters.ts new file mode 100644 index 00000000..44391999 --- /dev/null +++ b/components/client-data-table/table-filters.ts @@ -0,0 +1,344 @@ +import { Row } from "@tanstack/react-table" + +export type FilterOperator = + | "iLike" | "notILike" | "eq" | "ne" | "isEmpty" | "isNotEmpty" + | "lt" | "lte" | "gt" | "gte" | "isBetween" | "isRelativeToToday" + +export type ColumnType = "text" | "number" | "date" | "boolean" | "select" | "multi-select" + +interface FilterValue { + operator: FilterOperator + value: any +} + +/** + * 글로벌 필터 함수 생성 + * @param type - 컬럼 타입 + * @returns 필터 함수 + */ +export const createFilterFn = (type: ColumnType) => { + return <TData>(row: Row<TData>, columnId: string, filterValue: FilterValue) => { + const cellValue = row.getValue(columnId) + const { operator, value } = filterValue + + // 공통 처리: isEmpty/isNotEmpty + if (operator === "isEmpty") { + if (type === "multi-select") { + return !cellValue || (Array.isArray(cellValue) && cellValue.length === 0) + } + return cellValue == null || cellValue === "" || cellValue === undefined + } + + if (operator === "isNotEmpty") { + if (type === "multi-select") { + return cellValue != null && Array.isArray(cellValue) && cellValue.length > 0 + } + return cellValue != null && cellValue !== "" && cellValue !== undefined + } + + // value가 없고 isEmpty/isNotEmpty가 아닌 경우 + if ((value === "" || value == null) && operator !== "isEmpty" && operator !== "isNotEmpty") { + return true + } + + // 타입별 처리 + switch (type) { + case "text": { + const cellStr = String(cellValue || "").toLowerCase() + const filterStr = String(value || "").toLowerCase() + + switch (operator) { + case "iLike": + return cellStr.includes(filterStr) + case "notILike": + return !cellStr.includes(filterStr) + case "eq": + return cellStr === filterStr + case "ne": + return cellStr !== filterStr + default: + return true + } + } + + case "number": { + const cellNum = cellValue != null ? Number(cellValue) : null + const filterNum = value != null ? Number(value) : null + + if (cellNum == null || filterNum == null) { + return false + } + + switch (operator) { + case "eq": + return cellNum === filterNum + case "ne": + return cellNum !== filterNum + case "lt": + return cellNum < filterNum + case "lte": + return cellNum <= filterNum + case "gt": + return cellNum > filterNum + case "gte": + return cellNum >= filterNum + default: + return true + } + } + + case "date": { + const cellDate = cellValue ? new Date(cellValue as string | Date) : null + + if (!cellDate || isNaN(cellDate.getTime())) { + return false + } + + switch (operator) { + case "eq": { + if (!value) return false + const filterDate = new Date(value) + return cellDate.toDateString() === filterDate.toDateString() + } + + case "ne": { + if (!value) return true + const filterDate = new Date(value) + return cellDate.toDateString() !== filterDate.toDateString() + } + + case "lt": { + if (!value) return false + const filterDate = new Date(value) + return cellDate < filterDate + } + + case "lte": { + if (!value) return false + const filterDate = new Date(value) + filterDate.setHours(23, 59, 59, 999) // 그 날의 끝까지 포함 + return cellDate <= filterDate + } + + case "gt": { + if (!value) return false + const filterDate = new Date(value) + return cellDate > filterDate + } + + case "gte": { + if (!value) return false + const filterDate = new Date(value) + filterDate.setHours(0, 0, 0, 0) // 그 날의 시작부터 포함 + return cellDate >= filterDate + } + + case "isBetween": { + if (!Array.isArray(value) || value.length !== 2) return false + const [startDate, endDate] = value + if (!startDate || !endDate) return false + const start = new Date(startDate) + const end = new Date(endDate) + start.setHours(0, 0, 0, 0) + end.setHours(23, 59, 59, 999) + return cellDate >= start && cellDate <= end + } + + case "isRelativeToToday": { + const today = new Date() + today.setHours(0, 0, 0, 0) + const tomorrow = new Date(today) + tomorrow.setDate(tomorrow.getDate() + 1) + + // value는 상대적 날짜 지정자 (예: "today", "yesterday", "thisWeek", "lastWeek", "thisMonth", "lastMonth") + switch (value) { + case "today": + return cellDate >= today && cellDate < tomorrow + + case "yesterday": { + const yesterday = new Date(today) + yesterday.setDate(yesterday.getDate() - 1) + return cellDate >= yesterday && cellDate < today + } + + case "tomorrow": { + const dayAfterTomorrow = new Date(tomorrow) + dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1) + return cellDate >= tomorrow && cellDate < dayAfterTomorrow + } + + case "thisWeek": { + const startOfWeek = new Date(today) + startOfWeek.setDate(today.getDate() - today.getDay()) // 일요일부터 시작 + const endOfWeek = new Date(startOfWeek) + endOfWeek.setDate(startOfWeek.getDate() + 6) + endOfWeek.setHours(23, 59, 59, 999) + return cellDate >= startOfWeek && cellDate <= endOfWeek + } + + case "lastWeek": { + const startOfLastWeek = new Date(today) + startOfLastWeek.setDate(today.getDate() - today.getDay() - 7) + const endOfLastWeek = new Date(startOfLastWeek) + endOfLastWeek.setDate(startOfLastWeek.getDate() + 6) + endOfLastWeek.setHours(23, 59, 59, 999) + return cellDate >= startOfLastWeek && cellDate <= endOfLastWeek + } + + case "thisMonth": { + const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) + const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0) + endOfMonth.setHours(23, 59, 59, 999) + return cellDate >= startOfMonth && cellDate <= endOfMonth + } + + case "lastMonth": { + const startOfLastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1) + const endOfLastMonth = new Date(today.getFullYear(), today.getMonth(), 0) + endOfLastMonth.setHours(23, 59, 59, 999) + return cellDate >= startOfLastMonth && cellDate <= endOfLastMonth + } + + case "thisYear": { + const startOfYear = new Date(today.getFullYear(), 0, 1) + const endOfYear = new Date(today.getFullYear(), 11, 31) + endOfYear.setHours(23, 59, 59, 999) + return cellDate >= startOfYear && cellDate <= endOfYear + } + + case "lastYear": { + const startOfLastYear = new Date(today.getFullYear() - 1, 0, 1) + const endOfLastYear = new Date(today.getFullYear() - 1, 11, 31) + endOfLastYear.setHours(23, 59, 59, 999) + return cellDate >= startOfLastYear && cellDate <= endOfLastYear + } + + default: + // 숫자가 오면 일 단위 상대 날짜로 처리 (예: "7" = 7일 이내, "-7" = 7일 전) + const days = parseInt(value) + if (!isNaN(days)) { + if (days > 0) { + // 미래 n일 이내 + const futureDate = new Date(today) + futureDate.setDate(futureDate.getDate() + days) + return cellDate >= today && cellDate <= futureDate + } else if (days < 0) { + // 과거 n일 이내 + const pastDate = new Date(today) + pastDate.setDate(pastDate.getDate() + days) + return cellDate >= pastDate && cellDate <= today + } + } + return true + } + } + + default: + return true + } + } + + case "boolean": { + const cellBool = cellValue === true || cellValue === "true" || cellValue === 1 + const filterBool = value === true || value === "true" + + switch (operator) { + case "eq": + return cellBool === filterBool + case "ne": + return cellBool !== filterBool + default: + return true + } + } + + case "select": { + const cellStr = String(cellValue || "") + const filterStr = String(value || "") + + switch (operator) { + case "eq": + return cellStr === filterStr + case "ne": + return cellStr !== filterStr + default: + return true + } + } + + case "multi-select": { + const cellArray = Array.isArray(cellValue) ? cellValue : [] + const filterArray = Array.isArray(value) ? value : [] + + switch (operator) { + case "eq": + // 선택된 모든 값들이 포함되어 있는지 확인 + return filterArray.every(v => cellArray.includes(v)) + case "ne": + // 선택된 값들 중 하나라도 포함되어 있지 않은지 확인 + return !filterArray.some(v => cellArray.includes(v)) + default: + return true + } + } + + default: + return true + } + } +} + +/** + * AND/OR 조건으로 여러 필터 결합 + * @param filters - 필터 배열 + * @param joinOperator - 결합 연산자 ("and" | "or") + */ +export const combineFilters = <TData>( + row: Row<TData>, + filters: Array<{ + columnId: string + filterValue: FilterValue + type: ColumnType + }>, + joinOperator: "and" | "or" = "and" +): boolean => { + if (filters.length === 0) return true + + if (joinOperator === "and") { + return filters.every(filter => { + const filterFn = createFilterFn(filter.type) + return filterFn(row, filter.columnId, filter.filterValue) + }) + } else { + return filters.some(filter => { + const filterFn = createFilterFn(filter.type) + return filterFn(row, filter.columnId, filter.filterValue) + }) + } +} + +/** + * 테이블 전체에 대한 커스텀 필터 함수 + * ClientDataTableAdvancedFilter와 함께 사용 + */ +export const globalFilterFn = <TData>( + row: Row<TData>, + columnId: string, + filterValue: any +): boolean => { + // filterValue가 객체 형태로 전달되는 경우를 처리 + if (filterValue && typeof filterValue === 'object' && 'filters' in filterValue) { + const { filters, joinOperator } = filterValue + return combineFilters(row, filters, joinOperator) + } + + // 단일 필터의 경우 + if (filterValue && typeof filterValue === 'object' && 'operator' in filterValue) { + // 컬럼 타입을 추론하거나 전달받아야 함 + // 기본적으로 text로 처리 + const filterFn = createFilterFn("text") + return filterFn(row, columnId, filterValue) + } + + return true +}
\ No newline at end of file |
