import type { ColumnDef } from "../components/data-table/types" import type { FilterOperator } from "./table-properties-types" import { filterPresetForCellKind } from "./column-cell-kind" export type { ColumnCellKind } from "./column-cell-kind" export { COLUMN_CELL_KIND_FILTER_PRESETS, filterPresetForCellKind, } from "./column-cell-kind" const NON_FILTERABLE_COLUMN_KEYS = new Set(["select", "actions"]) const DEFAULT_TEXT_FILTER_OPERATORS = ["contains", "not_contains"] as const satisfies readonly FilterOperator[] const FAVORITE_FILTER_OPERATORS = ["is", "is_not"] as const satisfies readonly FilterOperator[] export type ResolvedColumnFilter = { type: "select" | "text" | "date" | "date-range" | "range" icon?: string options?: NonNullable>["filter"]>["options"] operators?: FilterOperator[] selectVariant?: "default" | "person" dataBounds?: boolean textMask?: NonNullable>["filter"]>["textMask"] rangeMin?: number rangeMax?: number rangeStep?: number rangeUnit?: string } export type ColumnFavoriteFilter = | boolean | { /** Row field — default `isStarred`. */ fieldKey?: string label?: string } function resolveFavoriteFilterConfig( favoriteFilter: ColumnFavoriteFilter | undefined, ): { fieldKey: string; label: string } | null { if (!favoriteFilter) return null if (favoriteFilter === true) { return { fieldKey: "isStarred", label: "Favorite" } } return { fieldKey: favoriteFilter.fieldKey ?? "isStarred", label: favoriteFilter.label ?? "Favorite", } } /** Synthetic filter-only columns (e.g. Favorite when star is embedded in Name column). */ export function buildCompanionFilterColumns( cols: ColumnDef[], ): ColumnDef[] { const companions: ColumnDef[] = [] const seen = new Set() for (const col of cols) { const fav = resolveFavoriteFilterConfig(col.favoriteFilter) if (!fav) continue if (seen.has(fav.fieldKey) || cols.some(c => c.key === fav.fieldKey)) continue seen.add(fav.fieldKey) companions.push({ key: fav.fieldKey, label: fav.label, filterOnly: true, filter: { type: "select", icon: "fa-star", operators: [...FAVORITE_FILTER_OPERATORS], options: [ { value: "true", label: "Favorited" }, { value: "false", label: "Not favorited" }, ], }, }) } return companions } /** Column defs used for filter chips, Properties drawer, and row matching. */ export function augmentColumnsWithCompanionFilters( cols: ColumnDef[], ): ColumnDef[] { return [...cols, ...buildCompanionFilterColumns(cols)] } export function isFilterableColumn(col: ColumnDef): boolean { if (NON_FILTERABLE_COLUMN_KEYS.has(col.key)) return false if (col.filterOnly) return Boolean(col.filter ?? col.cellKind) return true } function mergeFilterWithCellKind( col: ColumnDef, ): ResolvedColumnFilter | null { const preset = col.cellKind ? filterPresetForCellKind(col.cellKind) : null const explicit = col.filter if (!preset && !explicit) { return { type: "text", icon: "fa-filter", operators: [...DEFAULT_TEXT_FILTER_OPERATORS], } } if (!preset) { return { type: explicit!.type ?? "text", icon: explicit!.icon ?? "fa-filter", operators: explicit!.operators ?? [...DEFAULT_TEXT_FILTER_OPERATORS], options: explicit!.options, selectVariant: explicit!.selectVariant, dataBounds: explicit!.dataBounds, textMask: explicit!.textMask, rangeMin: explicit!.rangeMin, rangeMax: explicit!.rangeMax, rangeStep: explicit!.rangeStep, rangeUnit: explicit!.rangeUnit, } } if (!explicit) { return { type: preset.type, icon: preset.icon, operators: preset.operators ? [...preset.operators] : undefined, options: preset.options, selectVariant: preset.selectVariant, dataBounds: preset.dataBounds, rangeMin: preset.rangeMin, rangeMax: preset.rangeMax, rangeStep: preset.rangeStep, rangeUnit: preset.rangeUnit, } } return { type: explicit.type ?? preset.type, icon: explicit.icon ?? preset.icon, operators: explicit.operators ?? (preset.operators ? [...preset.operators] : undefined), options: explicit.options ?? preset.options, selectVariant: explicit.selectVariant ?? preset.selectVariant, dataBounds: explicit.dataBounds ?? preset.dataBounds, textMask: explicit.textMask, rangeMin: explicit.rangeMin ?? preset.rangeMin, rangeMax: explicit.rangeMax ?? preset.rangeMax, rangeStep: explicit.rangeStep ?? preset.rangeStep, rangeUnit: explicit.rangeUnit ?? preset.rangeUnit, } } /** Explicit `ColumnDef.filter`, `cellKind` preset, or a text fallback for every data column. */ export function resolveColumnFilter( col: ColumnDef, ): ResolvedColumnFilter | null { if (!isFilterableColumn(col)) return null return mergeFilterWithCellKind(col) } export function filterableColumns(cols: ColumnDef[]): ColumnDef[] { return augmentColumnsWithCompanionFilters(cols).filter(isFilterableColumn) }