@open-mercato/ui 0.6.8-develop.6910.1.560e304de2 → 0.6.8-develop.6912.1.36161dc9ac
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/DataTable.js +3 -2
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/utils/customFieldColumns.js.map +2 -2
- package/dist/backend/utils/useAutoDiscoveredFields.js.map +2 -2
- package/jest.config.cjs +1 -1
- package/package.json +3 -3
- package/src/backend/DataTable.tsx +5 -4
- package/src/backend/__tests__/DataTable.extensions.test.tsx +1 -1
- package/src/backend/__tests__/DataTable.legacyTableShim.test.tsx +81 -0
- package/src/backend/__tests__/DataTable.metaHiddenLateColumns.test.tsx +1 -1
- package/src/backend/__tests__/DataTable.perspectiveGuardedMutation.test.tsx +1 -1
- package/src/backend/__tests__/DataTable.render.test.tsx +1 -1
- package/src/backend/__tests__/DataTable.stickyResponsive.test.tsx +1 -1
- package/src/backend/__tests__/DataTable.virtualizedHooks.test.tsx +1 -1
- package/src/backend/utils/customFieldColumns.ts +3 -2
- package/src/backend/utils/useAutoDiscoveredFields.ts +7 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/utils/customFieldColumns.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\nimport type { CustomFieldDefDto, CustomFieldVisibility } from './customFieldDefs'\nimport { isDefVisible } from './customFieldDefs'\n\ntype RawCustomFieldOption = string | number | { value?: unknown; label?: unknown }\n\nexport function supportsCustomFieldColumn(def: CustomFieldDefDto): boolean {\n return def.kind !== 'attachment'\n}\n\nexport function mapCustomFieldKindToFilterType(kind: string): FilterFieldType {\n switch (kind) {\n case 'boolean':\n return 'boolean'\n case 'integer':\n case 'float':\n return 'number'\n case 'date':\n case 'datetime':\n return 'date'\n case 'select':\n case 'dictionary':\n case 'currency':\n case 'relation':\n return 'select'\n default:\n return 'text'\n }\n}\n\nexport function normalizeCustomFieldFilterOptions(options?: RawCustomFieldOption[]): FilterOption[] {\n if (!Array.isArray(options)) return []\n return options.map((option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n const rawValue = option.value\n const rawLabel = option.label ?? rawValue\n return {\n value: String(rawValue),\n label: typeof rawLabel === 'string' ? rawLabel : String(rawLabel),\n }\n }\n const value = String(option)\n return {\n value,\n label: value.charAt(0).toUpperCase() + value.slice(1),\n }\n })\n}\n\n// Filters and annotates columns with custom-field definitions:\n// - Drops cf_* columns when no definition exists or listVisible === false\n// - Uses definition label as header when header is missing\nexport function applyCustomFieldVisibility<T>(columns: ColumnDef<T, any>[], defs: CustomFieldDefDto[], mode: CustomFieldVisibility = 'list'): ColumnDef<T, any>[] {\n const byKey = new Map(defs.map((d) => [d.key, d]))\n // First, filter and annotate headers\n const filtered = columns.filter((c) => {\n const key = String((c as any).accessorKey || '')\n if (!key.startsWith('cf_')) return true\n const cfKey = key.slice(3)\n const def = byKey.get(cfKey)\n if (!def) return false\n if (!supportsCustomFieldColumn(def)) return false\n if (!isDefVisible(def, mode)) return false\n const currentHeader = (c as any).header\n const fallbackHeader = typeof currentHeader === 'string' && currentHeader.trim().length ? currentHeader : key\n const label = def.label && def.label.trim().length ? def.label : fallbackHeader\n if (currentHeader == null || typeof currentHeader === 'string') {\n (c as any).header = label\n }\n const existingMeta = ((c as any).meta || {}) as Record<string, unknown>\n const nextMeta = Object.assign({}, existingMeta, { label })\n ;(c as any).meta = nextMeta\n return true\n })\n\n // Then, reorder only the cf_* columns by definition priority while preserving\n // the positions of non-cf columns and the count of cf slots.\n const cfEntries: Array<{ col: ColumnDef<T, any>; key: string; prio: number }> = []\n filtered.forEach((c) => {\n const key = String((c as any).accessorKey || '')\n if (key.startsWith('cf_')) {\n const cfKey = key.slice(3)\n const def = byKey.get(cfKey)\n cfEntries.push({ col: c, key: cfKey, prio: def?.priority ?? 0 })\n }\n })\n cfEntries.sort((a, b) => a.prio - b.prio)\n let cfIdx = 0\n const result = filtered.map((c) => {\n const key = String((c as any).accessorKey || '')\n if (!key.startsWith('cf_')) return c\n const next = cfEntries[cfIdx++]?.col ?? c\n return next\n })\n\n // Append any missing cf columns (defs visible but not present in incoming columns)\n const existingCfKeys = new Set<string>(result\n .map((c) => String((c as any).accessorKey || ''))\n .filter((k) => k.startsWith('cf_'))\n .map((k) => k.slice(3)))\n\n const visibleSorted = defs\n .filter((d) => supportsCustomFieldColumn(d) && isDefVisible(d, mode))\n .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))\n\n const missing = visibleSorted.filter((d) => !existingCfKeys.has(d.key))\n for (const d of missing) {\n const col: ColumnDef<T, any> = {\n accessorKey: `cf_${d.key}` as any,\n header: d.label || `cf_${d.key}`,\n // Respect responsive priority when provided; default leaves it visible\n meta: { priority: (d as any).priority, label: d.label || `cf_${d.key}` } as any,\n }\n result.push(col)\n }\n return result\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { RowData } from '@tanstack/react-table'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\nimport type { CustomFieldDefDto, CustomFieldVisibility } from './customFieldDefs'\nimport { isDefVisible } from './customFieldDefs'\n\ntype RawCustomFieldOption = string | number | { value?: unknown; label?: unknown }\n\nexport function supportsCustomFieldColumn(def: CustomFieldDefDto): boolean {\n return def.kind !== 'attachment'\n}\n\nexport function mapCustomFieldKindToFilterType(kind: string): FilterFieldType {\n switch (kind) {\n case 'boolean':\n return 'boolean'\n case 'integer':\n case 'float':\n return 'number'\n case 'date':\n case 'datetime':\n return 'date'\n case 'select':\n case 'dictionary':\n case 'currency':\n case 'relation':\n return 'select'\n default:\n return 'text'\n }\n}\n\nexport function normalizeCustomFieldFilterOptions(options?: RawCustomFieldOption[]): FilterOption[] {\n if (!Array.isArray(options)) return []\n return options.map((option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n const rawValue = option.value\n const rawLabel = option.label ?? rawValue\n return {\n value: String(rawValue),\n label: typeof rawLabel === 'string' ? rawLabel : String(rawLabel),\n }\n }\n const value = String(option)\n return {\n value,\n label: value.charAt(0).toUpperCase() + value.slice(1),\n }\n })\n}\n\n// Filters and annotates columns with custom-field definitions:\n// - Drops cf_* columns when no definition exists or listVisible === false\n// - Uses definition label as header when header is missing\nexport function applyCustomFieldVisibility<T extends RowData = RowData>(columns: ColumnDef<T, any>[], defs: CustomFieldDefDto[], mode: CustomFieldVisibility = 'list'): ColumnDef<T, any>[] {\n const byKey = new Map(defs.map((d) => [d.key, d]))\n // First, filter and annotate headers\n const filtered = columns.filter((c) => {\n const key = String((c as any).accessorKey || '')\n if (!key.startsWith('cf_')) return true\n const cfKey = key.slice(3)\n const def = byKey.get(cfKey)\n if (!def) return false\n if (!supportsCustomFieldColumn(def)) return false\n if (!isDefVisible(def, mode)) return false\n const currentHeader = (c as any).header\n const fallbackHeader = typeof currentHeader === 'string' && currentHeader.trim().length ? currentHeader : key\n const label = def.label && def.label.trim().length ? def.label : fallbackHeader\n if (currentHeader == null || typeof currentHeader === 'string') {\n (c as any).header = label\n }\n const existingMeta = ((c as any).meta || {}) as Record<string, unknown>\n const nextMeta = Object.assign({}, existingMeta, { label })\n ;(c as any).meta = nextMeta\n return true\n })\n\n // Then, reorder only the cf_* columns by definition priority while preserving\n // the positions of non-cf columns and the count of cf slots.\n const cfEntries: Array<{ col: ColumnDef<T, any>; key: string; prio: number }> = []\n filtered.forEach((c) => {\n const key = String((c as any).accessorKey || '')\n if (key.startsWith('cf_')) {\n const cfKey = key.slice(3)\n const def = byKey.get(cfKey)\n cfEntries.push({ col: c, key: cfKey, prio: def?.priority ?? 0 })\n }\n })\n cfEntries.sort((a, b) => a.prio - b.prio)\n let cfIdx = 0\n const result = filtered.map((c) => {\n const key = String((c as any).accessorKey || '')\n if (!key.startsWith('cf_')) return c\n const next = cfEntries[cfIdx++]?.col ?? c\n return next\n })\n\n // Append any missing cf columns (defs visible but not present in incoming columns)\n const existingCfKeys = new Set<string>(result\n .map((c) => String((c as any).accessorKey || ''))\n .filter((k) => k.startsWith('cf_'))\n .map((k) => k.slice(3)))\n\n const visibleSorted = defs\n .filter((d) => supportsCustomFieldColumn(d) && isDefVisible(d, mode))\n .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))\n\n const missing = visibleSorted.filter((d) => !existingCfKeys.has(d.key))\n for (const d of missing) {\n const col: ColumnDef<T, any> = {\n accessorKey: `cf_${d.key}` as any,\n header: d.label || `cf_${d.key}`,\n // Respect responsive priority when provided; default leaves it visible\n meta: { priority: (d as any).priority, label: d.label || `cf_${d.key}` } as any,\n }\n result.push(col)\n }\n return result\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/utils/useAutoDiscoveredFields.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterFieldDef as AdvancedFilterFieldDef, FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\nimport type { ColumnChooserField } from '../columns/ColumnChooserPanel'\nimport type { CustomFieldDefDto } from './customFieldDefs'\nimport {\n mapCustomFieldKindToFilterType,\n normalizeCustomFieldFilterOptions,\n supportsCustomFieldColumn,\n} from './customFieldColumns'\n\ntype ColumnMeta = {\n filterKey?: string\n filterType?: FilterFieldType\n filterOptions?: FilterOption[]\n filterLoadOptions?: (query?: string) => Promise<FilterOption[]>\n filterable?: boolean\n filterGroup?: string\n filterIconName?: string\n columnChooserGroup?: string\n alwaysVisible?: boolean\n hidden?: boolean\n truncate?: boolean\n maxWidth?: string\n priority?: number\n tooltipContent?: (row: unknown) => string | undefined\n}\n\nfunction resolveHeaderLabel(column: ColumnDef<
|
|
5
|
-
"mappings": ";AACA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type { RowData } from '@tanstack/react-table'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { FilterFieldDef as AdvancedFilterFieldDef, FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\nimport type { ColumnChooserField } from '../columns/ColumnChooserPanel'\nimport type { CustomFieldDefDto } from './customFieldDefs'\nimport {\n mapCustomFieldKindToFilterType,\n normalizeCustomFieldFilterOptions,\n supportsCustomFieldColumn,\n} from './customFieldColumns'\n\ntype ColumnMeta = {\n filterKey?: string\n filterType?: FilterFieldType\n filterOptions?: FilterOption[]\n filterLoadOptions?: (query?: string) => Promise<FilterOption[]>\n filterable?: boolean\n filterGroup?: string\n filterIconName?: string\n columnChooserGroup?: string\n alwaysVisible?: boolean\n hidden?: boolean\n truncate?: boolean\n maxWidth?: string\n priority?: number\n tooltipContent?: (row: unknown) => string | undefined\n}\n\nfunction resolveHeaderLabel<T extends RowData>(column: ColumnDef<T, unknown>): string {\n const header = (column as any).header\n if (typeof header === 'string') return header\n const accessorKey = (column as any).accessorKey as string | undefined\n if (!accessorKey) return ''\n return accessorKey\n .replace(/^cf_/, '')\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (c: string) => c.toUpperCase())\n}\n\nfunction inferFilterType(accessorKey: string, meta?: ColumnMeta): FilterFieldType {\n if (meta?.filterType) return meta.filterType\n if (accessorKey.endsWith('_at') || accessorKey === 'createdAt' || accessorKey === 'updatedAt' || accessorKey === 'expectedCloseAt' || accessorKey.endsWith('At')) return 'date'\n if (accessorKey === 'probability' || accessorKey === 'valueAmount' || accessorKey === 'value_amount') return 'number'\n if (meta?.filterOptions) return 'select'\n return 'text'\n}\n\nexport type UseAutoDiscoveredFieldsInput<T extends RowData = RowData> = {\n columns: ColumnDef<T, unknown>[]\n customFieldDefs: CustomFieldDefDto[]\n}\n\nexport type UseAutoDiscoveredFieldsResult = {\n advancedFilterFields: AdvancedFilterFieldDef[]\n columnChooserFields: ColumnChooserField[]\n}\n\nexport function useAutoDiscoveredFields<T extends RowData = RowData>({\n columns,\n customFieldDefs,\n}: UseAutoDiscoveredFieldsInput<T>): UseAutoDiscoveredFieldsResult {\n return React.useMemo(() => {\n const filterFields: AdvancedFilterFieldDef[] = []\n const chooserFields: ColumnChooserField[] = []\n const seenFilterKeys = new Set<string>()\n const seenChooserKeys = new Set<string>()\n\n for (let i = 0; i < columns.length; i++) {\n const col = columns[i]\n const accessorKey = (col as any).accessorKey as string | undefined\n if (!accessorKey) continue\n\n const meta = (col as any).meta as ColumnMeta | undefined\n const label = resolveHeaderLabel(col)\n if (!label) continue\n const filterKey = meta?.filterKey ?? accessorKey\n\n // Advanced filter field\n if (meta?.filterable !== false && filterKey && !seenFilterKeys.has(filterKey)) {\n seenFilterKeys.add(filterKey)\n const type = inferFilterType(filterKey, meta)\n const field: AdvancedFilterFieldDef = {\n key: filterKey,\n label,\n type,\n group: meta?.filterGroup ?? meta?.columnChooserGroup,\n }\n if (meta?.filterIconName) field.iconName = meta.filterIconName\n if (meta?.filterOptions) field.options = meta.filterOptions\n if (meta?.filterLoadOptions) field.loadOptions = meta.filterLoadOptions\n filterFields.push(field)\n }\n\n // Column chooser field\n if (!seenChooserKeys.has(accessorKey)) {\n seenChooserKeys.add(accessorKey)\n chooserFields.push({\n key: accessorKey,\n label,\n group: meta?.columnChooserGroup ?? 'Columns',\n alwaysVisible: meta?.alwaysVisible ?? i === 0,\n defaultVisible: true,\n })\n }\n }\n\n // Append ALL custom field definitions (not just filterable/listVisible)\n // so that every entity field appears in both the column chooser and advanced filter\n for (const def of customFieldDefs) {\n if (!supportsCustomFieldColumn(def)) continue\n const filterKey = `cf_${def.key}`\n if (!seenFilterKeys.has(filterKey)) {\n seenFilterKeys.add(filterKey)\n const type = mapCustomFieldKindToFilterType(def.kind)\n const field: AdvancedFilterFieldDef = {\n key: filterKey,\n label: def.label || def.key,\n type,\n group: 'Custom Fields',\n }\n if (type === 'select' && Array.isArray(def.options) && def.options.length) {\n field.options = normalizeCustomFieldFilterOptions(def.options)\n }\n filterFields.push(field)\n }\n if (!seenChooserKeys.has(filterKey)) {\n seenChooserKeys.add(filterKey)\n chooserFields.push({\n key: filterKey,\n label: def.label || def.key,\n group: def.group?.title ?? 'Custom Fields',\n defaultVisible: false,\n })\n }\n }\n\n return { advancedFilterFields: filterFields, columnChooserFields: chooserFields }\n }, [columns, customFieldDefs])\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/jest.config.cjs
CHANGED
|
@@ -39,7 +39,7 @@ module.exports = {
|
|
|
39
39
|
},
|
|
40
40
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
|
41
41
|
transformIgnorePatterns: [
|
|
42
|
-
'node_modules/(?!(@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema)/)',
|
|
42
|
+
'node_modules/(?!(@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema|@tanstack/react-table|@tanstack/table-core|@tanstack/react-store|@tanstack/store)/)',
|
|
43
43
|
],
|
|
44
44
|
testMatch: [
|
|
45
45
|
'<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6912.1.36161dc9ac",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,14 +155,14 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.8-develop.6912.1.36161dc9ac",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
164
|
"@figma/code-connect": "^1.3.4",
|
|
165
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
165
|
+
"@open-mercato/shared": "0.6.8-develop.6912.1.36161dc9ac",
|
|
166
166
|
"@testing-library/dom": "^10.4.1",
|
|
167
167
|
"@testing-library/jest-dom": "^7.0.0",
|
|
168
168
|
"@testing-library/react": "^16.3.1",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { useRouter } from 'next/navigation'
|
|
4
|
-
import {
|
|
4
|
+
import { flexRender, type RowData, type SortingState, type ColumnVisibilityState as VisibilityState, type RowSelectionState } from '@tanstack/react-table'
|
|
5
|
+
import { useLegacyTable, getCoreRowModel, getSortedRowModel, type LegacyColumnDef as ColumnDef, type LegacyColumn as TableColumn } from '@tanstack/react-table/legacy'
|
|
5
6
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
6
7
|
import { RefreshCw, Loader2, SlidersHorizontal, MoreHorizontal, Circle, Filter, Columns3, ChevronUp, ChevronDown, ChevronsUpDown, Check, Inbox } from 'lucide-react'
|
|
7
8
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../primitives/table'
|
|
@@ -223,7 +224,7 @@ export type BulkAction<T = Record<string, unknown>> = {
|
|
|
223
224
|
onExecute: (selectedRows: T[]) => Promise<void | boolean | BulkActionExecuteResult> | void | boolean | BulkActionExecuteResult
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
export type DataTableProps<T> = {
|
|
227
|
+
export type DataTableProps<T extends RowData> = {
|
|
227
228
|
columns: ColumnDef<T, any>[]
|
|
228
229
|
data: T[]
|
|
229
230
|
toolbar?: React.ReactNode
|
|
@@ -1112,7 +1113,7 @@ function ViewSwitcherDropdown({
|
|
|
1112
1113
|
)
|
|
1113
1114
|
}
|
|
1114
1115
|
|
|
1115
|
-
export function DataTable<T>({
|
|
1116
|
+
export function DataTable<T extends RowData>({
|
|
1116
1117
|
columns,
|
|
1117
1118
|
data,
|
|
1118
1119
|
toolbar,
|
|
@@ -1628,7 +1629,7 @@ export function DataTable<T>({
|
|
|
1628
1629
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
|
1629
1630
|
const selectionScopeKeyRef = React.useRef<string | undefined>(selectionScopeKey)
|
|
1630
1631
|
const enableClientSorting = sortable && !manualSorting
|
|
1631
|
-
const table =
|
|
1632
|
+
const table = useLegacyTable<T>({
|
|
1632
1633
|
data: clientFilteredData,
|
|
1633
1634
|
columns: mergedColumns,
|
|
1634
1635
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { renderToString } from 'react-dom/server'
|
|
6
6
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
7
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
7
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
8
8
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
9
9
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
10
10
|
import { DataTable } from '../DataTable'
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { DataTable } from '../DataTable'
|
|
4
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
5
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
6
|
+
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
8
|
+
|
|
9
|
+
jest.mock('next/navigation', () => ({
|
|
10
|
+
useRouter: () => ({ push: jest.fn(), replace: jest.fn(), prefetch: jest.fn() }),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
jest.mock('../injection/useInjectionDataWidgets', () => ({
|
|
14
|
+
useInjectionDataWidgets: () => ({ widgets: [], isLoading: false }),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
type Row = { id: string; name: string }
|
|
18
|
+
|
|
19
|
+
const columns: ColumnDef<Row>[] = [{ accessorKey: 'name', header: 'Name' }]
|
|
20
|
+
|
|
21
|
+
const rows: Row[] = [
|
|
22
|
+
{ id: '2', name: 'Zed' },
|
|
23
|
+
{ id: '1', name: 'Ada' },
|
|
24
|
+
{ id: '3', name: 'Mia' },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
function renderTable(props: Record<string, unknown>) {
|
|
28
|
+
const queryClient = new QueryClient({ defaultOptions: { queries: { gcTime: 0 } } })
|
|
29
|
+
const result = render(
|
|
30
|
+
<QueryClientProvider client={queryClient}>
|
|
31
|
+
<I18nProvider locale="en" dict={{}}>
|
|
32
|
+
<DataTable columns={columns} data={rows} {...(props as any)} />
|
|
33
|
+
</I18nProvider>
|
|
34
|
+
</QueryClientProvider>,
|
|
35
|
+
)
|
|
36
|
+
return { ...result, queryClient }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* TanStack Table v9 moved the v8 hook and row-model factories behind the
|
|
41
|
+
* `@tanstack/react-table/legacy` entry point. DataTable consumes that shim, so
|
|
42
|
+
* these cover the two v8 behaviours whose wiring the shim owns — the sorted row
|
|
43
|
+
* model and the row-selection feature — rather than re-testing the library.
|
|
44
|
+
*/
|
|
45
|
+
describe('DataTable on the TanStack Table v9 legacy shim', () => {
|
|
46
|
+
it('sorts rows client-side through the legacy sorted row model', () => {
|
|
47
|
+
const { container, queryClient } = renderTable({ sortable: true })
|
|
48
|
+
try {
|
|
49
|
+
const initial = container.textContent ?? ''
|
|
50
|
+
expect(initial.indexOf('Zed')).toBeLessThan(initial.indexOf('Ada'))
|
|
51
|
+
|
|
52
|
+
fireEvent.click(screen.getByRole('button', { name: /name/i }))
|
|
53
|
+
|
|
54
|
+
const sorted = container.textContent ?? ''
|
|
55
|
+
expect(sorted.indexOf('Ada')).toBeLessThan(sorted.indexOf('Mia'))
|
|
56
|
+
expect(sorted.indexOf('Mia')).toBeLessThan(sorted.indexOf('Zed'))
|
|
57
|
+
} finally {
|
|
58
|
+
queryClient.clear()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('passes the selected originals to a bulk action through the legacy row-selection feature', async () => {
|
|
63
|
+
const onExecute = jest.fn().mockResolvedValue(undefined)
|
|
64
|
+
const { queryClient } = renderTable({
|
|
65
|
+
bulkActions: [{ id: 'archive', label: 'Archive', onExecute }],
|
|
66
|
+
})
|
|
67
|
+
try {
|
|
68
|
+
const checkboxes = screen.getAllByRole('checkbox')
|
|
69
|
+
// The first checkbox is the header select-all; the rest are per row.
|
|
70
|
+
expect(checkboxes.length).toBe(rows.length + 1)
|
|
71
|
+
|
|
72
|
+
fireEvent.click(checkboxes[1])
|
|
73
|
+
fireEvent.click(await screen.findByRole('button', { name: /archive/i }))
|
|
74
|
+
|
|
75
|
+
await waitFor(() => expect(onExecute).toHaveBeenCalledTimes(1))
|
|
76
|
+
expect(onExecute.mock.calls[0][0]).toEqual([rows[0]])
|
|
77
|
+
} finally {
|
|
78
|
+
queryClient.clear()
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
6
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
6
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
7
7
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
8
8
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
9
9
|
import { DataTable } from '../DataTable'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { DataTable } from '../DataTable'
|
|
4
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
4
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
5
5
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
6
6
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { renderToString } from 'react-dom/server'
|
|
4
4
|
import { DataTable } from '../DataTable'
|
|
5
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
5
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
6
6
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
7
7
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
8
8
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { DataTable } from '../DataTable'
|
|
4
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
4
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
5
5
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
6
6
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { render } from '@testing-library/react'
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import * as React from 'react'
|
|
11
11
|
import { render } from '@testing-library/react'
|
|
12
12
|
import { DataTable } from '../DataTable'
|
|
13
|
-
import type { ColumnDef } from '@tanstack/react-table'
|
|
13
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
14
14
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
15
15
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
16
16
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RowData } from '@tanstack/react-table'
|
|
2
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
2
3
|
import type { FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'
|
|
3
4
|
import type { CustomFieldDefDto, CustomFieldVisibility } from './customFieldDefs'
|
|
4
5
|
import { isDefVisible } from './customFieldDefs'
|
|
@@ -51,7 +52,7 @@ export function normalizeCustomFieldFilterOptions(options?: RawCustomFieldOption
|
|
|
51
52
|
// Filters and annotates columns with custom-field definitions:
|
|
52
53
|
// - Drops cf_* columns when no definition exists or listVisible === false
|
|
53
54
|
// - Uses definition label as header when header is missing
|
|
54
|
-
export function applyCustomFieldVisibility<T>(columns: ColumnDef<T, any>[], defs: CustomFieldDefDto[], mode: CustomFieldVisibility = 'list'): ColumnDef<T, any>[] {
|
|
55
|
+
export function applyCustomFieldVisibility<T extends RowData = RowData>(columns: ColumnDef<T, any>[], defs: CustomFieldDefDto[], mode: CustomFieldVisibility = 'list'): ColumnDef<T, any>[] {
|
|
55
56
|
const byKey = new Map(defs.map((d) => [d.key, d]))
|
|
56
57
|
// First, filter and annotate headers
|
|
57
58
|
const filtered = columns.filter((c) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import * as React from 'react'
|
|
3
|
-
import type {
|
|
3
|
+
import type { RowData } from '@tanstack/react-table'
|
|
4
|
+
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
4
5
|
import type { FilterFieldDef as AdvancedFilterFieldDef, FilterFieldType, FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'
|
|
5
6
|
import type { ColumnChooserField } from '../columns/ColumnChooserPanel'
|
|
6
7
|
import type { CustomFieldDefDto } from './customFieldDefs'
|
|
@@ -27,7 +28,7 @@ type ColumnMeta = {
|
|
|
27
28
|
tooltipContent?: (row: unknown) => string | undefined
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function resolveHeaderLabel(column: ColumnDef<
|
|
31
|
+
function resolveHeaderLabel<T extends RowData>(column: ColumnDef<T, unknown>): string {
|
|
31
32
|
const header = (column as any).header
|
|
32
33
|
if (typeof header === 'string') return header
|
|
33
34
|
const accessorKey = (column as any).accessorKey as string | undefined
|
|
@@ -46,8 +47,8 @@ function inferFilterType(accessorKey: string, meta?: ColumnMeta): FilterFieldTyp
|
|
|
46
47
|
return 'text'
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export type UseAutoDiscoveredFieldsInput = {
|
|
50
|
-
columns: ColumnDef<
|
|
50
|
+
export type UseAutoDiscoveredFieldsInput<T extends RowData = RowData> = {
|
|
51
|
+
columns: ColumnDef<T, unknown>[]
|
|
51
52
|
customFieldDefs: CustomFieldDefDto[]
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -56,10 +57,10 @@ export type UseAutoDiscoveredFieldsResult = {
|
|
|
56
57
|
columnChooserFields: ColumnChooserField[]
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
export function useAutoDiscoveredFields({
|
|
60
|
+
export function useAutoDiscoveredFields<T extends RowData = RowData>({
|
|
60
61
|
columns,
|
|
61
62
|
customFieldDefs,
|
|
62
|
-
}: UseAutoDiscoveredFieldsInput): UseAutoDiscoveredFieldsResult {
|
|
63
|
+
}: UseAutoDiscoveredFieldsInput<T>): UseAutoDiscoveredFieldsResult {
|
|
63
64
|
return React.useMemo(() => {
|
|
64
65
|
const filterFields: AdvancedFilterFieldDef[] = []
|
|
65
66
|
const chooserFields: ColumnChooserField[] = []
|