@kronor/dtv 0.2.9

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.
Files changed (97) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/copilot-instructions.md +64 -0
  3. package/.github/workflows/ci.yml +51 -0
  4. package/.husky/pre-commit +8 -0
  5. package/README.md +63 -0
  6. package/docs/api/README.md +32 -0
  7. package/docs/api/cell-renderers.md +121 -0
  8. package/docs/api/no-rows-component.md +71 -0
  9. package/docs/api/runtime.md +78 -0
  10. package/e2e/app.spec.ts +6 -0
  11. package/e2e/cell-renderer-setfilterstate.spec.ts +63 -0
  12. package/e2e/filter-sharing.spec.ts +113 -0
  13. package/e2e/filter-url-persistence.spec.ts +36 -0
  14. package/e2e/graphqlMock.ts +144 -0
  15. package/e2e/multi-field-filters.spec.ts +95 -0
  16. package/e2e/pagination.spec.ts +38 -0
  17. package/e2e/payment-request-email-filter.spec.ts +67 -0
  18. package/e2e/save-filter-splitbutton.spec.ts +68 -0
  19. package/e2e/simple-view-email-filter.spec.ts +67 -0
  20. package/e2e/simple-view-transforms.spec.ts +171 -0
  21. package/e2e/simple-view.spec.ts +104 -0
  22. package/e2e/transform-regression.spec.ts +108 -0
  23. package/eslint.config.js +30 -0
  24. package/index.html +17 -0
  25. package/jest.config.js +10 -0
  26. package/package.json +45 -0
  27. package/playwright.config.ts +54 -0
  28. package/public/vite.svg +1 -0
  29. package/src/App.externalRuntime.test.ts +190 -0
  30. package/src/App.tsx +540 -0
  31. package/src/assets/react.svg +1 -0
  32. package/src/components/AIAssistantForm.tsx +241 -0
  33. package/src/components/FilterForm.test.ts +82 -0
  34. package/src/components/FilterForm.tsx +375 -0
  35. package/src/components/PhoneNumberFilter.tsx +102 -0
  36. package/src/components/SavedFilterList.tsx +181 -0
  37. package/src/components/SpeechInput.tsx +67 -0
  38. package/src/components/Table.tsx +119 -0
  39. package/src/components/TablePagination.tsx +40 -0
  40. package/src/components/aiAssistant.test.ts +270 -0
  41. package/src/components/aiAssistant.ts +291 -0
  42. package/src/framework/cell-renderer-components/CurrencyAmount.tsx +30 -0
  43. package/src/framework/cell-renderer-components/LayoutHelpers.tsx +74 -0
  44. package/src/framework/cell-renderer-components/Link.tsx +28 -0
  45. package/src/framework/cell-renderer-components/Mapping.tsx +11 -0
  46. package/src/framework/cell-renderer-components.test.ts +353 -0
  47. package/src/framework/column-definition.tsx +85 -0
  48. package/src/framework/currency.test.ts +46 -0
  49. package/src/framework/currency.ts +62 -0
  50. package/src/framework/data.staticConditions.test.ts +46 -0
  51. package/src/framework/data.test.ts +167 -0
  52. package/src/framework/data.ts +162 -0
  53. package/src/framework/filter-form-state.test.ts +189 -0
  54. package/src/framework/filter-form-state.ts +185 -0
  55. package/src/framework/filter-sharing.test.ts +135 -0
  56. package/src/framework/filter-sharing.ts +118 -0
  57. package/src/framework/filters.ts +194 -0
  58. package/src/framework/graphql.buildHasuraConditions.test.ts +473 -0
  59. package/src/framework/graphql.paginationKey.test.ts +29 -0
  60. package/src/framework/graphql.test.ts +286 -0
  61. package/src/framework/graphql.ts +462 -0
  62. package/src/framework/native-runtime/index.tsx +33 -0
  63. package/src/framework/native-runtime/nativeComponents.test.ts +108 -0
  64. package/src/framework/runtime-reference.test.ts +172 -0
  65. package/src/framework/runtime.ts +15 -0
  66. package/src/framework/saved-filters.test.ts +422 -0
  67. package/src/framework/saved-filters.ts +293 -0
  68. package/src/framework/state.test.ts +86 -0
  69. package/src/framework/state.ts +148 -0
  70. package/src/framework/transform.test.ts +51 -0
  71. package/src/framework/view-parser-initialvalues.test.ts +228 -0
  72. package/src/framework/view-parser.ts +714 -0
  73. package/src/framework/view.test.ts +1805 -0
  74. package/src/framework/view.ts +38 -0
  75. package/src/index.css +6 -0
  76. package/src/main.tsx +99 -0
  77. package/src/views/index.ts +12 -0
  78. package/src/views/payment-requests/components/NoRowsExtendDateRange.tsx +37 -0
  79. package/src/views/payment-requests/components/PaymentMethod.tsx +184 -0
  80. package/src/views/payment-requests/components/PaymentStatusTag.tsx +61 -0
  81. package/src/views/payment-requests/index.ts +1 -0
  82. package/src/views/payment-requests/runtime.tsx +145 -0
  83. package/src/views/payment-requests/view.json +692 -0
  84. package/src/views/payment-requests-initial-values.test.ts +73 -0
  85. package/src/views/request-log/index.ts +2 -0
  86. package/src/views/request-log/runtime.tsx +47 -0
  87. package/src/views/request-log/view.json +123 -0
  88. package/src/views/simple-test-view/index.ts +3 -0
  89. package/src/views/simple-test-view/runtime.tsx +85 -0
  90. package/src/views/simple-test-view/view.json +191 -0
  91. package/src/vite-env.d.ts +1 -0
  92. package/tailwind.config.js +7 -0
  93. package/tsconfig.app.json +26 -0
  94. package/tsconfig.jest.json +6 -0
  95. package/tsconfig.json +7 -0
  96. package/tsconfig.node.json +24 -0
  97. package/vite.config.ts +11 -0
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*.{ts, tsx, json}]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,64 @@
1
+ # Copilot Instructions for dtv
2
+
3
+ ## Project Overview
4
+ - This is a React + TypeScript monorepo using Vite for development/build, Playwright for E2E tests, and Jest for unit tests.
5
+ - The core domain is a declarative, schema-driven table view system for filtering, displaying, and interacting with data collections.
6
+ - Major code is in `src/`, with key subfolders:
7
+ - `src/framework/`: Table/view schema, filter logic, state, and data fetching.
8
+ - `src/components/`: UI components, including filter forms, AI assistant, and table rendering.
9
+ - `src/views/`: View definitions, each exporting a `View` object with schema, columns, and query config.
10
+
11
+ ## Key Patterns & Conventions
12
+ - **Filter Schema**: Filters are defined in `FilterFieldSchema` objects, with each filter requiring an `aiGenerated: boolean` field. See `src/framework/filters.ts` for types and helpers.
13
+ - **AI Integration**: The AI assistant (see `src/components/AIAssistantForm.tsx` and `src/components/aiAssistant.ts`) can generate filters, which must set `aiGenerated: true`.
14
+ - **View Registration**: Views can be defined in two formats:
15
+ - **TSX Format** (legacy): Each view exports a `View` object with schema, columns, and query config
16
+ - **JSON Format** (new): Views are organized in folders with `view.json` (schema) and `runtime.tsx` (cell renderers)
17
+ - See `src/views/simple-test-view/` and `src/views/request-log/` for JSON format examples
18
+ - See `src/views/payment-requests/` for TSX format example
19
+ - **Type Safety**: All filter and view schemas are strongly typed. When adding new filters, always specify all required fields.
20
+ - **Cell Renderers**: All cell renderers receive `setFilterState` as a required prop, allowing them to programmatically update filter state when users interact with table cells.
21
+
22
+ ## Testing & Code Quality
23
+ - **Unit Tests**: Run with `npm run test-unit` (Jest)
24
+ - **E2E Tests**: Run with `npm test` or `npm run test` (Playwright)
25
+ - **Linting**: Run with `npm run lint` (ESLint with TypeScript)
26
+ - E2E test files are located in `e2e/` directory
27
+ - Unit test files use `.test.ts` or `.test.tsx` extensions
28
+ - The file `COPILOT_TEST_COMMAND.txt` in the repo root also specifies the canonical unit test command for AI tools.
29
+
30
+ ### Linting & Code Standards
31
+ - ESLint is configured with TypeScript, React hooks, and React refresh rules
32
+ - Pre-commit hooks automatically run linting and tests before commits
33
+ - CI pipeline runs linting, unit tests, and E2E tests on push/PR
34
+ - **EditorConfig**: Follow `.editorconfig` formatting rules for all files:
35
+ - Use 4 spaces for indentation (TypeScript, TSX, JSON)
36
+ - UTF-8 encoding with LF line endings
37
+ - Insert final newline and trim trailing whitespace
38
+ - When generating or editing JSON files, always use 4-space indentation to match project standards
39
+ - Key rules:
40
+ - `@typescript-eslint/no-explicit-any` is disabled to allow `any` types when needed
41
+ - Use `@ts-expect-error` with descriptive comments instead of `@ts-ignore`
42
+ - React Hook dependency warnings can be suppressed with `// eslint-disable-next-line react-hooks/exhaustive-deps` when intentional
43
+ - Fast refresh warnings are acceptable (non-blocking) for files that export both components and utilities
44
+
45
+ ## Integration & Data Flow
46
+ - Data is fetched via GraphQL using `graphql-request` (see `src/framework/data.ts`).
47
+ - Views define their own GraphQL queries and filter schemas.
48
+ - Filter expressions are serialized/deserialized using helpers in `src/framework/filters.ts`.
49
+
50
+ ## Project-Specific Advice
51
+ - Always update all usages of schema types when changing filter/view schema fields.
52
+ - When adding new filters, ensure `aiGenerated` is set appropriately.
53
+ - Use the helpers in `src/framework/filters.ts` for building filter expressions and controls.
54
+ - All cell renderers must accept `setFilterState` as a required prop for programmatic filter updates.
55
+ - For new E2E tests, add Playwright specs in `e2e/`.
56
+ - For new unit tests, add Jest specs with `.test.ts` or `.test.tsx` extensions.
57
+
58
+ ## Examples
59
+ - See `src/views/paymentRequest.tsx` for a full-featured view definition.
60
+ - See `src/components/AIAssistantForm.tsx` for AI-driven filter generation.
61
+ - See `src/framework/filters.ts` for filter schema/type definitions and utilities.
62
+
63
+ ---
64
+ If you are unsure about a workflow or convention, check for a helper or type in `src/framework/` or look for examples in `src/views/` and `src/components/`.
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ timeout-minutes: 60
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ node-version: [20.x]
17
+
18
+ steps:
19
+ - name: Checkout code
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Setup Node.js ${{ matrix.node-version }}
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: lts/*
26
+ cache: 'npm'
27
+
28
+ - name: Install dependencies
29
+ run: npm ci
30
+
31
+ - name: Build application
32
+ run: npm run build
33
+
34
+ - name: Run linting
35
+ run: npm run lint
36
+
37
+ - name: Run unit tests
38
+ run: npm run test-unit
39
+
40
+ # - name: Install Playwright browsers
41
+ # run: npx playwright install --with-deps
42
+
43
+ # - name: Run Playwright tests
44
+ # run: npx playwright test
45
+
46
+ - uses: actions/upload-artifact@v4
47
+ if: ${{ !cancelled() }}
48
+ with:
49
+ name: playwright-report
50
+ path: playwright-report/
51
+ retention-days: 30
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ # Run linting first
5
+ npm run lint
6
+
7
+ # Run tests if linting passes
8
+ npm test && npm run test-unit
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Declarative Table View System
2
+
3
+ This project is a React + TypeScript monorepo for building schema-driven, declarative table views with advanced filtering, data fetching, and AI-assisted filter generation.
4
+
5
+ ## Project Overview
6
+ - **Framework:** React, TypeScript, Vite
7
+ - **Testing:** Jest (unit), Playwright (E2E)
8
+ - **Core Domain:** Declarative, schema-driven table view system for filtering, displaying, and interacting with data collections.
9
+ - **Key Directories:**
10
+ - `src/framework/`: Table/view schema, filter logic, state, and data fetching.
11
+ - `src/components/`: UI components, including filter forms, AI assistant, and table rendering.
12
+ - `src/views/`: View definitions, each exporting a `View` object with schema, columns, and query config.
13
+
14
+ ## Key Patterns & Conventions
15
+ - **Filter Schema:** Filters are defined in `FilterFieldSchema` objects. Each filter requires an `aiGenerated: boolean` field. See `src/framework/filters.ts` for types and helpers.
16
+ - **AI Integration:** The AI assistant (see `src/components/AIAssistantForm.tsx` and `src/components/aiAssistant.ts`) can generate filters, which must set `aiGenerated: true`.
17
+ - **View Registration:** Each view (e.g., `paymentRequest.tsx`) exports a `View` object with a `filterSchema`, `columnDefinitions`, and a GraphQL query.
18
+ - **Type Safety:** All filter and view schemas are strongly typed. When adding new filters, always specify all required fields.
19
+
20
+ ## Integration & Data Flow
21
+ - Data is fetched via GraphQL using `graphql-request` (see `src/framework/data.ts`).
22
+ - Views define their own GraphQL queries and filter schemas.
23
+ - Filter expressions are serialized/deserialized using helpers in `src/framework/filters.ts`.
24
+ - Unified URL Filter Param: Both share links and persistence use a single base64 URL-safe encoded parameter `dtv-filter-state`. Enable syncing by passing `syncFilterStateToUrl: true` to `renderTableView` (or `?sync-filter-state-to-url=true` in dev). The param is updated only when filters are applied (not on every change). When disabled, a one-off link is consumed (param removed after load).
25
+
26
+ ## Development
27
+
28
+ ### Install dependencies
29
+ ```sh
30
+ npm install
31
+ ```
32
+
33
+ ### Environment Variables
34
+ Create a `.env.development` file in the project root to set environment variables for local development (e.g., API endpoints, feature flags, secrets).
35
+
36
+ Example:
37
+ ```env
38
+ VITE_GRAPHQL_HOST=https://your-graphql-host.example.com
39
+ VITE_GRAPHQL_TOKEN=your-graphql-token-here
40
+ VITE_GEMINI_API_KEY=your-gemini-api-key-here
41
+ ```
42
+
43
+ ### Start development server
44
+ ```sh
45
+ npm run dev
46
+ ```
47
+
48
+ ### Run unit tests
49
+ ```sh
50
+ npm run test-unit
51
+ ```
52
+
53
+ ### Run E2E tests (Playwright)
54
+ ```sh
55
+ npm test
56
+ # or
57
+ npm run test
58
+ ```
59
+
60
+ ## Examples
61
+ - See `src/views/paymentRequest.tsx` for a full-featured view definition.
62
+ - See `src/components/AIAssistantForm.tsx` for AI-driven filter generation.
63
+ - See `src/framework/filters.ts` for filter schema/type definitions and utilities.
@@ -0,0 +1,32 @@
1
+ # View DSL API Documentation
2
+
3
+ This folder contains detailed API documentation for the runtime extension points of the View DSL:
4
+
5
+ - Cell Renderers (`cellRenderers`)
6
+ - Runtime Object (`Runtime`)
7
+ - No Rows Components (`noRowsComponents` and per-view `noRowsComponent`)
8
+ - Custom Filter Components (`customFilterComponents`)
9
+ - Query Transforms (`queryTransforms`)
10
+ - Initial Values (`initialValues`)
11
+ - Static Conditions (`staticConditions` on a View)
12
+
13
+ Each extension point is referenced from JSON view definitions using a Runtime Reference object:
14
+
15
+ ```jsonc
16
+ {
17
+ "section": "cellRenderers", // one of: cellRenderers, noRowsComponents, customFilterComponents, queryTransforms, initialValues
18
+ "key": "transaction" // the key inside the runtime section
19
+ }
20
+ ```
21
+
22
+ Resolution precedence: External (per-app) runtime overrides Built-in runtime. If the key is missing in both, an error is thrown listing available keys.
23
+
24
+ ---
25
+
26
+ ## Files
27
+ - `cell-renderers.md` — How to write cell renderer functions and available helper components.
28
+ - `runtime.md` — Structure of the `Runtime` object and how runtime references are resolved.
29
+ - `no-rows-component.md` — Authoring components shown when a view returns zero rows.
30
+ - `static-conditions.md` — Defining always-on GraphQL boolean expressions via `staticConditions`.
31
+
32
+ Add additional docs here for transforms, custom filters, etc. as needed.
@@ -0,0 +1,121 @@
1
+ # Cell Renderer API
2
+
3
+ Cell renderers are React functions responsible for rendering the contents of each table cell.
4
+ They are referenced from JSON view definitions via a runtime reference of the form:
5
+
6
+ ```jsonc
7
+ {
8
+ "section": "cellRenderers",
9
+ "key": "transaction"
10
+ }
11
+ ```
12
+
13
+ The runtime must expose a matching function under `runtime.cellRenderers.transaction`.
14
+
15
+ ## Type Signature
16
+ ```
17
+ import { CellRenderer } from "src/framework/column-definition";
18
+
19
+ export type CellRenderer = (props: CellRendererProps) => React.ReactNode;
20
+
21
+ export type CellRendererProps = {
22
+ data: Record<string, any>;
23
+ setFilterState: (updater: (current: FilterState) => FilterState) => void;
24
+ applyFilters: () => void;
25
+ updateFilterById: (filterId: string, updater: (currentValue: any) => any) => void;
26
+ createElement: typeof React.createElement;
27
+ components: { Badge; FlexRow; FlexColumn; Mapping; DateTime; CurrencyAmount; Link; };
28
+ currency: { majorToMinor: (major: number, code: string, locale?: string) => number; minorToMajor: (minor: number, code: string, locale?: string) => number };
29
+ };
30
+ ```
31
+
32
+ ### Provided Props
33
+ - `data`: Object containing the resolved data fields for this column. Its structure is derived from the column's `data` field array in the JSON definition. For nested/query configs additional keys may be present (e.g. `attempts.cardType`).
34
+ - `setFilterState(updater)`: Low-level state setter for the full filter map. Prefer `updateFilterById` for targeted updates.
35
+ - `applyFilters()`: Triggers a new data fetch after mutating filter state programmatically.
36
+ - `updateFilterById(filterId, updater)`: Focused helper to update the internal form state of a single filter (tree structure) in-place.
37
+ - `createElement`: Re-exported `React.createElement` for advanced dynamic element factories (rarely needed).
38
+ - `components`: Convenience bundle of commonly used primitives:
39
+ - `Badge` (`Tag` from PrimeReact) — for status-like labels
40
+ - `FlexRow` / `FlexColumn` — layout helpers with gap/align/justify props
41
+ - `Mapping` — map raw values to labels (`<Mapping value={merchantId} map={{1: 'Boozt'}} />`)
42
+ - `DateTime` — localized date/time formatting
43
+ - `CurrencyAmount` — currency formatting with `Intl.NumberFormat`
44
+ - (prop) `currency` — helpers for unit conversion (major/minor units)
45
+ - `Link` — styled anchor element
46
+
47
+ ## Creating a Cell Renderer
48
+ ```tsx
49
+ // runtime.tsx
50
+ export const myRuntime: Runtime = {
51
+ cellRenderers: {
52
+ // Example where the GraphQL field "amountMinor" is stored in minor units (e.g. cents)
53
+ amount: ({
54
+ data: { currency, amountMinor },
55
+ components: { FlexRow, CurrencyAmount },
56
+ currency: { minorToMajor }
57
+ }) => {
58
+ // Convert minor (integer) units to major for display using provided helper
59
+ const majorAmount = minorToMajor(amountMinor, currency);
60
+ return (
61
+ <FlexRow justify="end">
62
+ <CurrencyAmount amount={majorAmount} currency={currency} />
63
+ </FlexRow>
64
+ );
65
+ }
66
+ },
67
+ queryTransforms: {},
68
+ noRowsComponents: {},
69
+ customFilterComponents: {},
70
+ initialValues: {}
71
+ };
72
+ ```
73
+
74
+ If your amount field already arrives as a major unit (e.g. 123.45 for USD) you can skip the `minorToMajor` call and pass it directly to `CurrencyAmount`.
75
+
76
+ ## Referencing in JSON View
77
+ ```jsonc
78
+ {
79
+ "data": [ { "type": "field", "path": "currency" }, { "type": "field", "path": "amount" } ],
80
+ "name": "Amount",
81
+ "cellRenderer": { "section": "cellRenderers", "key": "amount" }
82
+ }
83
+ ```
84
+
85
+ ## Programmatic Filtering Example
86
+ A cell renderer can trigger a filter update upon interaction:
87
+ ```tsx
88
+ initiatedBy: ({ data, updateFilterById, applyFilters, components: { FlexRow, FlexColumn } }) => {
89
+ const handleEmailClick = () => {
90
+ updateFilterById('customer-email', current => ({
91
+ ...current,
92
+ value: { operator: '_eq', value: data['customer.email'] }
93
+ }));
94
+ applyFilters();
95
+ };
96
+ return (
97
+ <FlexRow align="center">
98
+ <FlexColumn>
99
+ <span className="tw:font-bold">{data['customer.name']}</span>
100
+ <button className="tw:text-blue-500 tw:underline" onClick={handleEmailClick}>
101
+ {data['customer.email']}
102
+ </button>
103
+ </FlexColumn>
104
+ </FlexRow>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ## Data Shape Notes
110
+ - Each `data` entry in the column definition becomes a property on the `data` object using its `path`.
111
+ - For `queryConfigs`, nested results may be flattened (e.g. `attempts.cardType`). The exact flattening logic mirrors the GraphQL query builder.
112
+
113
+ ## Best Practices
114
+ - Keep renderers pure (avoid side effects except in event handlers).
115
+ - Pull layout primitives from `components` to stay consistent.
116
+ - Guard against missing fields (`data.someKey ?? '-'`).
117
+ - Limit expensive computations; precompute in transforms/query if possible.
118
+ - Use `updateFilterById` + `applyFilters` for instant filtering interactions.
119
+
120
+ ## Error Handling
121
+ If a reference key is missing, parsing the JSON view will throw an error listing available keys, preventing runtime ambiguity.
@@ -0,0 +1,71 @@
1
+ # No Rows Component API
2
+
3
+ A No Rows Component renders contextual UI when a view's data query returns zero rows.
4
+ It can guide the user to broaden filters, change date ranges, or provide helpful links.
5
+
6
+ ## Type Signature
7
+ Defined in `src/framework/view.ts`:
8
+ ```
9
+ export type NoRowsComponentProps = {
10
+ setFilterState: (updater: (current: FilterState) => FilterState) => void;
11
+ filterState: FilterState;
12
+ applyFilters: () => void;
13
+ updateFilterById: (filterId: string, updater: (current: FilterFormState) => FilterFormState) => void;
14
+ };
15
+ export type NoRowsComponent = (props: NoRowsComponentProps) => React.ReactNode;
16
+ ```
17
+
18
+ In a runtime, entries live under `runtime.noRowsComponents`:
19
+ ```ts
20
+ noRowsComponents: {
21
+ noRowsExtendDateRange: NoRowsExtendDateRange
22
+ }
23
+ ```
24
+
25
+ ## Referencing in View JSON
26
+ ```jsonc
27
+ "noRowsComponent": { "section": "noRowsComponents", "key": "noRowsExtendDateRange" }
28
+ ```
29
+
30
+ ## Example Component
31
+ `src/views/payment-requests/components/NoRowsExtendDateRange.tsx`:
32
+ ```tsx
33
+ const NoRowsExtendDateRange = ({ updateFilterById, applyFilters }) => {
34
+ const handleExtend = () => {
35
+ updateFilterById('date-range', currentFilter => {
36
+ if (currentFilter.type === 'and' && currentFilter.children.length > 0) {
37
+ const firstChild = currentFilter.children[0];
38
+ if (firstChild.type === 'leaf') {
39
+ const d = new Date(firstChild.value); d.setMonth(d.getMonth() - 1);
40
+ return { ...currentFilter, children: [{ ...firstChild, value: d }, ...currentFilter.children.slice(1)] };
41
+ }
42
+ }
43
+ return currentFilter;
44
+ });
45
+ applyFilters();
46
+ };
47
+ return (
48
+ <FlexColumn align="center" justify="center" className="py-8 text-gray-400">
49
+ <span>No data rows match the current filter.</span>
50
+ <Button label="Extend the date range back by 1 month" onClick={handleExtend} size="small" />
51
+ </FlexColumn>
52
+ );
53
+ };
54
+ ```
55
+
56
+ ## When to Use
57
+ - Suggesting filter adjustments (expand date range, clear a value).
58
+ - Offering quick actions (e.g. buttons to reset filters).
59
+ - Providing guidance (links to documentation or onboarding steps).
60
+
61
+ ## Best Practices
62
+ - Keep height modest; avoid pushing layout drastically.
63
+ - Provide at most 1–2 primary actions.
64
+ - Don't auto-modify filters without explicit user interaction.
65
+ - Use neutral/secondary styling to differentiate from loaded data states.
66
+
67
+ ## Advanced Interactions
68
+ You have full access to the current filter tree via `filterState` if deeper introspection is required (e.g. determining which specific filter is most restrictive). Modify only what is necessary for clarity.
69
+
70
+ ## Error Handling
71
+ If the referenced key is missing in the runtime, parsing the view JSON will throw with available keys to aid discovery.
@@ -0,0 +1,78 @@
1
+ # Runtime Object
2
+
3
+ The `Runtime` aggregates all pluggable functions and components that a JSON view can reference.
4
+ It is defined in `src/framework/runtime.ts`:
5
+
6
+ ```ts
7
+ export type Runtime = {
8
+ cellRenderers: Record<string, CellRenderer | React.ComponentType<any>>;
9
+ queryTransforms: Record<string, { toQuery: (input: any) => TransformResult }>;
10
+ noRowsComponents: Record<string, NoRowsComponent | React.ComponentType<any>>;
11
+ customFilterComponents: Record<string, React.ComponentType<any>>;
12
+ initialValues: Record<string, any>;
13
+ };
14
+ ```
15
+
16
+ ## Sections
17
+ | Section | Purpose | Reference Usage | Value Type |
18
+ |---------|---------|-----------------|------------|
19
+ | `cellRenderers` | Render table cells | Column `cellRenderer` | `(props) => ReactNode` |
20
+ | `queryTransforms` | Preprocess raw filter form values into GraphQL-ready values | Filter expression `transform` | `{ toQuery(input) => { value, operator?, ... } }` |
21
+ | `noRowsComponents` | UI when a data query yields zero rows | View `noRowsComponent` | `(props) => ReactNode` |
22
+ | `customFilterComponents` | Custom input widgets inside filter forms | Filter control `{ type: 'custom' }` | React component |
23
+ | `initialValues` | Dynamic initial values resolved at parse time | Any `initialValue` field | any |
24
+
25
+ ## Resolution Precedence
26
+ When parsing a view JSON, runtime references are resolved via:
27
+ 1. External runtime passed to the <ViewRenderer /> (if any)
28
+ 2. Built-in (view-specific) runtime exported alongside the JSON
29
+
30
+ If a key does not exist in either location an error is thrown with a list of known keys for that section.
31
+
32
+ ## Example Runtime
33
+ ```tsx
34
+ export const paymentRequestsRuntime: Runtime = {
35
+ cellRenderers: { /* ... */ },
36
+ queryTransforms: {
37
+ reference: { toQuery: input => input.operator === '_like' ? { value: { value: `${input.value}%` } } : { value: input } },
38
+ amount: { toQuery: input => input ? { value: input * 100 } : { value: input } }
39
+ },
40
+ noRowsComponents: { noRowsExtendDateRange: NoRowsExtendDateRange },
41
+ customFilterComponents: { phoneNumberFilter: PhoneNumberFilter },
42
+ initialValues: {
43
+ dateRangeStart: (() => { const d = new Date(); d.setMonth(d.getMonth()-1); return d; })(),
44
+ dateRangeEnd: new Date()
45
+ }
46
+ };
47
+ ```
48
+
49
+ ## Adding a New Section Entry
50
+ ```ts
51
+ // Add a transform
52
+ runtime.queryTransforms.orderId = {
53
+ toQuery: (input) => input ? { value: input.trim().toUpperCase() } : { value: input }
54
+ };
55
+ ```
56
+
57
+ ## Transform Contract
58
+ A transform receives the raw leaf value (or structured operator object for custom operator controls) and returns an object merged into the GraphQL boolean expression builder. Common pattern:
59
+ ```ts
60
+ { toQuery: (input) => ({ value: input }) }
61
+ ```
62
+ You can wrap/reshape the value as needed (e.g. apply wildcards, multiply for minor units, split strings, etc.).
63
+
64
+ ## Initial Values via Runtime Reference
65
+ Any `initialValue` field inside filter controls can be replaced with a runtime reference:
66
+ ```jsonc
67
+ "initialValue": { "section": "initialValues", "key": "dateRangeStart" }
68
+ ```
69
+ This allows dynamic (computed at parse time) defaults.
70
+
71
+ ## Debugging Missing References
72
+ If parsing fails with: `Reference "foo" not found in cellRenderers. Available keys: a,b,c` verify:
73
+ - The key exists in either runtime instance.
74
+ - The JSON `section` name matches the correct section.
75
+ - There are no typos (keys are case-sensitive).
76
+
77
+ ## Testing
78
+ See `runtime-reference.test.ts` for validation tests around resolution and error messages.
@@ -0,0 +1,6 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test('basic test', async ({ page }) => {
4
+ await page.goto('/');
5
+ await expect(page.locator('body')).toBeVisible(); // Replace with a more specific selector for your app
6
+ });
@@ -0,0 +1,63 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { mockPaginationGraphQL } from './graphqlMock';
3
+
4
+ test.describe('Cell Renderer setFilterState', () => {
5
+ test('should allow cell renderers to programmatically set filter state', async ({ page }) => {
6
+ // Intercept the GraphQL request and mock the response
7
+ await page.route('**/v1/graphql', mockPaginationGraphQL);
8
+
9
+ // Navigate to the page
10
+ await page.goto('/?test-view=simple-test-view');
11
+
12
+ // Wait for the table to be present and visible
13
+ const table = page.getByRole('table');
14
+ await expect(table).toBeVisible();
15
+
16
+ // Verify initial state - all rows should be visible
17
+ await expect(table.getByText('Test 30', { exact: true })).toBeVisible();
18
+ await expect(table.getByText('Test 29', { exact: true })).toBeVisible();
19
+ await expect(table.getByText('Test 28', { exact: true })).toBeVisible();
20
+
21
+ // Note: This test verifies that the table renders correctly with the new required prop
22
+ // In a real implementation, you would:
23
+ // 1. Create a cell renderer that has a clickable element
24
+ // 2. Click that element to trigger setFilterState
25
+ // 3. Verify that the filter state changes accordingly
26
+
27
+ // Test that filters work normally (ensuring our changes don't break existing functionality)
28
+ // Show filters first
29
+ await page.getByText('Filters', { exact: true }).click();
30
+
31
+ // Find the Amount input and apply a filter (simple-test-view shows filters by default)
32
+ const amountLabel = page.getByText('Amount', { exact: true });
33
+ const amountInput = amountLabel.locator('..').locator('~ div input');
34
+ await amountInput.fill('260');
35
+ await amountInput.press('Enter');
36
+
37
+ // Assert that only the filtered rows are visible
38
+ await expect(table.getByText('Test 30', { exact: true })).toBeVisible();
39
+ await expect(table.getByText('Test 27', { exact: true })).toBeVisible();
40
+ await expect(table.getByText('Test 25', { exact: true })).not.toBeVisible();
41
+ });
42
+
43
+ test('should maintain table functionality with required setFilterState prop', async ({ page }) => {
44
+ // Intercept the GraphQL request and mock the response
45
+ await page.route('**/v1/graphql', mockPaginationGraphQL);
46
+
47
+ // Navigate to the payment request view which has more complex cell renderers
48
+ await page.goto('/?test-view=payment-requests');
49
+
50
+ // Wait for the table to be present and visible
51
+ const table = page.getByRole('table');
52
+ await expect(table).toBeVisible();
53
+
54
+ // Verify that the table headers are rendered correctly
55
+ await expect(table.getByText('Transaction')).toBeVisible();
56
+ await expect(table.getByText('Status')).toBeVisible();
57
+ await expect(table.getByText('Amount')).toBeVisible();
58
+
59
+ // Verify that cell renderers are working (they now receive setFilterState as required prop)
60
+ // The fact that the page loads without errors indicates our changes are working
61
+ await expect(table).toBeVisible();
62
+ });
63
+ });