@trackunit/iris-app 1.12.9 → 1.12.11

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 (41) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +2 -2
  3. package/src/executors/submit/executor.js.map +1 -0
  4. package/src/executors/unpublish/executor.js.map +1 -0
  5. package/src/executors/utils/authentication.js.map +1 -0
  6. package/src/executors/utils/irisAppServerSettings.js.map +1 -0
  7. package/src/executors/utils/src/index.js.map +1 -0
  8. package/src/generators/ai-agent-sync/README.md +5 -2
  9. package/src/generators/ai-agent-sync/generator.d.ts +1 -1
  10. package/src/generators/ai-agent-sync/generator.js +29 -4
  11. package/src/generators/ai-agent-sync/generator.js.map +1 -0
  12. package/src/generators/create/generator.js.map +1 -0
  13. package/src/generators/extend/dependencies.js.map +1 -0
  14. package/src/generators/extend/generator.js.map +1 -0
  15. package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
  16. package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
  17. package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
  18. package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
  19. package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
  20. package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
  21. package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
  22. package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
  23. package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
  24. package/src/generators/preset/files/.cursor/mcp.json +4 -0
  25. package/src/generators/preset/generator.js.map +1 -0
  26. package/src/generators/preset/root-files/AGENTS.md +43 -0
  27. package/src/generators/preset/root-files/CLAUDE.md +17 -0
  28. package/src/index.js.map +1 -0
  29. package/src/utils/ast/astUtils.js.map +1 -0
  30. package/src/utils/fileUpdater.js.map +1 -0
  31. package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
  32. package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
  33. package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
  34. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
  35. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
  36. package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
  37. package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
  38. package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
  39. package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
  40. package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
  41. package/src/generators/preset/files/.cursor/rules/widget-extensions.md +0 -323
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: graphql-timeseries
3
+ description: Use when querying machine insights, engine metrics, fuel data, operating hours, or building charts with historical time-based data.
4
+ ---
5
+
6
+ # GraphQL Time Series API
7
+
8
+ ## Overview
9
+
10
+ Query time series data through GraphQL using PromQL (Prometheus Query Language). Use `rangeQuery` for time series charts, `instantQuery` for current values.
11
+
12
+ ## When to Use
13
+
14
+ - Querying engine metrics (speed, temperature, fuel rate)
15
+ - Building historical data charts
16
+ - Calculating increases over time periods (daily fuel usage, operating hours)
17
+ - Getting current sensor values
18
+
19
+ **Not for:** Static entity data (use `graphql` skill).
20
+
21
+ ## Query Structure
22
+
23
+ ### Range Query (Time Series Data)
24
+
25
+ ```graphql
26
+ query GetTimeSeriesData($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
27
+ asset(id: $assetId) {
28
+ timeSeries {
29
+ rangeQuery(query: "metric_name", start: $start, end: $end, step: $step) {
30
+ data {
31
+ ... on TimeSeriesMatrixData {
32
+ result {
33
+ values { timestamp value }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### Instant Query (Single Point in Time)
44
+
45
+ ```graphql
46
+ query GetInstantData($assetId: ID!, $time: DateTime!) {
47
+ asset(id: $assetId) {
48
+ timeSeries {
49
+ instantQuery(query: "metric_name", time: $time) {
50
+ data {
51
+ ... on TimeSeriesVectorData {
52
+ result { value { timestamp value } }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Common Metrics
62
+
63
+ ### Machine Insights
64
+
65
+ Not all assets have all insights. Common metrics include:
66
+
67
+ **Engine Metrics:**
68
+ - `machine_insight_engine_speed`
69
+ - `machine_insight_engine_coolant_temperature`
70
+ - `machine_insight_engine_oil_pressure`
71
+ - `machine_insight_engine_oil_temperature`
72
+ - `machine_insight_engine_fuel_rate`
73
+ - `machine_insight_engine_total_fuel_used`
74
+ - `machine_insight_engine_percent_load_at_current_speed`
75
+ - `machine_insight_cumulative_engine_hours`
76
+
77
+ **Fuel & Emissions:**
78
+ - `machine_insight_fuel_level`
79
+ - `machine_insight_fuel_tank_capacity`
80
+ - `machine_insight_fuel_used_last_24`
81
+ - `machine_insight_cumulative_co2_emissions`
82
+
83
+ **Operating Hours:**
84
+ - `machine_insight_cumulative_operating_hours`
85
+ - `machine_insight_cumulative_idle_hours`
86
+ - `machine_insight_cumulative_moving_hours`
87
+ - `machine_insight_cumulative_productive_hours`
88
+
89
+ **Battery & Electric:**
90
+ - `machine_insight_battery_state_of_charge_percent`
91
+ - `machine_insight_battery_state_of_health_percent`
92
+ - `machine_insight_battery_temperature`
93
+ - `machine_insight_battery_current`
94
+ - `machine_insight_battery_potential`
95
+
96
+ **Location & Movement:**
97
+ - `machine_insight_speed`
98
+ - `machine_insight_altitude`
99
+ - `machine_insight_total_vehicle_distance`
100
+
101
+ **Environmental:**
102
+ - `machine_insight_ambient_air_temperature`
103
+ - `machine_insight_barometric_pressure`
104
+
105
+ ## PromQL Examples
106
+
107
+ ### Calculate Daily Increases
108
+
109
+ ```graphql
110
+ query IncreaseOperatingHours($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
111
+ asset(id: $assetId) {
112
+ timeSeries {
113
+ rangeQuery(
114
+ query: "increase(machine_insight_cumulative_operating_hours[1d])",
115
+ start: $start,
116
+ end: $end,
117
+ step: $step
118
+ ) {
119
+ data {
120
+ ... on TimeSeriesMatrixData {
121
+ result { values { timestamp value } }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Filter with Conditions
131
+
132
+ ```graphql
133
+ query HighFuelConsumption($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
134
+ asset(id: $assetId) {
135
+ timeSeries {
136
+ rangeQuery(
137
+ query: "increase(machine_insight_engine_total_fuel_used[1d]) > 13",
138
+ start: $start,
139
+ end: $end,
140
+ step: $step
141
+ ) {
142
+ data {
143
+ ... on TimeSeriesMatrixData {
144
+ result { values { timestamp value } }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### Combine Metrics
154
+
155
+ ```graphql
156
+ query FuelWithHighIdle($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
157
+ asset(id: $assetId) {
158
+ timeSeries {
159
+ rangeQuery(
160
+ query: "(increase(machine_insight_engine_total_fuel_used[1d]) > 13) and (increase(machine_insight_cumulative_idle_hours[1d]) > 4)",
161
+ start: $start,
162
+ end: $end,
163
+ step: $step
164
+ ) {
165
+ data {
166
+ ... on TimeSeriesMatrixData {
167
+ result { values { timestamp value } }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Key Points
177
+
178
+ - **Always use fragments** for both `TimeSeriesMatrixData` and `TimeSeriesVectorData`
179
+ - **Use PromQL functions**: `increase()`, `rate()`, `avg_over_time()`
180
+ - **Filter at query level** with operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
181
+ - **Time windows** use brackets: `[1d]`, `[1h]`, `[15m]`
182
+ - **Step intervals**: Choose based on data granularity needed
183
+ - **Timestamps** are Unix timestamps in seconds
184
+ - **Values** are returned as strings - convert to numbers as needed
185
+
186
+ ## Common Mistakes
187
+
188
+ | Mistake | Fix |
189
+ |---------|-----|
190
+ | Missing fragment for data type | Use `... on TimeSeriesMatrixData` or `TimeSeriesVectorData` |
191
+ | Not handling missing metrics | Not all assets have all insights; handle empty results |
192
+ | Using values as numbers directly | Values are strings; convert with `parseFloat()` |
193
+ | Wrong step interval | Match step to desired chart granularity |
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: irisx-app-sdk
3
+ description: Use when creating apps, adding extensions, configuring CSP/scopes, or needing SDK command reference. Core SDK documentation.
4
+ ---
5
+
6
+ # IrisX App SDK Development
7
+
8
+ ## Overview
9
+
10
+ IrisX Apps are React applications that integrate into Trackunit Manager at defined extension points. Use SDK commands (not generic React/Node.js) for all project operations.
11
+
12
+ ## When to Use
13
+
14
+ - Creating new apps or extensions
15
+ - Configuring security (CSP headers, scopes)
16
+ - Looking up extension types or SDK commands
17
+ - Understanding runtime hooks availability
18
+
19
+ **Not for:** GraphQL setup (use `graphql` skill), browser testing (use `browser-testing` skill).
20
+
21
+ ## Extension Points
22
+
23
+ Extensions are React applications that integrate into specific points in Trackunit Manager:
24
+
25
+ | Extension Type | Location |
26
+ |----------------|----------|
27
+ | `ASSET_HOME_EXTENSION` | New tabs in Assets Home screen |
28
+ | `SITE_HOME_EXTENSION` | New tabs in Site Home screen |
29
+ | `FLEET_EXTENSION` | Menu items in Main Menu |
30
+ | `REPORT_EXTENSION` | New reports in Reports screen |
31
+ | `WIDGET_EXTENSION` | Dashboard widgets |
32
+ | `IRIS_APP_SETTINGS_EXTENSION` | Configuration UI in App library |
33
+ | `ADMIN_EXTENSION` | Admin UI tabs (admin-only) |
34
+ | `CUSTOMER_HOME_EXTENSION` | UI within Customer Home |
35
+ | `ASSET_EVENTS_ACTIONS_EXTENSION` | UI in Asset Home Events |
36
+ | `LIFECYCLE_EXTENSION` | Handles app install/uninstall events |
37
+ | `SERVERLESS_FUNCTION_EXTENSION` | Serverless backend functions |
38
+
39
+ ## Commands
40
+
41
+ ### Create New App
42
+
43
+ ```bash
44
+ npx nx generate @trackunit/iris-app:create [name-of-your-app]
45
+ ```
46
+
47
+ See: [Creating a new app](https://developers.trackunit.com/docs/creating-a-new-app)
48
+
49
+ ### Create New Extension
50
+
51
+ ```bash
52
+ npx nx g @trackunit/iris-app:extend --name=[my-extension-name] --app=[app-name] --directory=[feature] --type=[extension-type]
53
+ ```
54
+
55
+ See: [Creating a new extension](https://developers.trackunit.com/docs/creating-a-new-extension)
56
+
57
+ ### Run App
58
+
59
+ ```bash
60
+ npx nx run [name-of-your-app]:serve
61
+ ```
62
+
63
+ See: [Running the Iris App SDK](https://developers.trackunit.com/docs/running-the-iris-app-sdk)
64
+
65
+ ## Security Configuration
66
+
67
+ IrisX Apps are locked down by default. External API requests and embedded content require proper Content Security Policy (CSP) configuration in the app manifest.
68
+
69
+ ### CSP Headers Example
70
+
71
+ ```typescript
72
+ cspHeader: {
73
+ 'frame-src': ['https://api.example.com', 'https://dashboard.example.com'],
74
+ 'script-src': ['https://api.example.com'],
75
+ 'connect-src': ['https://api.example.com'],
76
+ },
77
+ ```
78
+
79
+ ### Scopes Example
80
+
81
+ ```typescript
82
+ scopes: [
83
+ {scope: "required.scope.1", optional: false},
84
+ {scope: "required.scope.2", optional: true},
85
+ ],
86
+ ```
87
+
88
+ Apps requiring API access need scopes configured in the manifest, and the app must be deployed with those scopes to work locally. Never submit an app automatically - instruct the user to do so.
89
+
90
+ See: [Embedding IrisX Dashboards in Apps](https://developers.trackunit.com/docs/analytics-dashboards-in-apps#troubleshooting)
91
+
92
+ ## Runtime Hooks
93
+
94
+ IrisX Apps have access to runtime hooks from `@trackunit/react-core-hooks`:
95
+
96
+ - `useAssetRuntime()` - Asset information in asset-scoped extensions
97
+ - `useCustomerRuntime()` - Customer data
98
+ - `useEventRuntime()` - Event details
99
+ - `useSiteRuntime()` - Site information
100
+ - Plus navigation, user context, and utility hooks
101
+
102
+ See the `react-core-hooks` skill for full details.
103
+
104
+ ## Quality Assurance
105
+
106
+ Before completing any task: Always use `read_lints` to check for and fix all ESLint and TypeScript errors in modified files.
107
+
108
+ ## Common Mistakes
109
+
110
+ | Mistake | Fix |
111
+ |---------|-----|
112
+ | Using `create-react-app` or `npm init` | Use `nx generate @trackunit/iris-app:create` |
113
+ | Mocking data without asking | Always use real GraphQL API |
114
+ | Missing scopes in manifest | Add required scopes; deploy to enable locally |
115
+ | External API blocked | Add domain to `cspHeader` in manifest |
116
+ | Auto-submitting app | Never auto-submit; instruct user to deploy |
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: react-core-hooks
3
+ description: Use when needing asset/site/customer runtime data, user context, navigation, toasts, or confirmation dialogs in IrisX Apps.
4
+ ---
5
+
6
+ # React Core Hooks
7
+
8
+ ## Overview
9
+
10
+ Reference for `@trackunit/react-core-hooks` - runtime context, navigation, user information, and UI interaction hooks for IrisX Apps.
11
+
12
+ ## When to Use
13
+
14
+ - Accessing current asset/site/customer context
15
+ - Getting user information or preferences
16
+ - Navigating within Trackunit Manager
17
+ - Showing toasts or confirmation dialogs
18
+ - Managing widget configuration
19
+
20
+ ## Runtime Hooks
21
+
22
+ ### useAssetRuntime
23
+
24
+ Access asset information in asset-scoped extensions.
25
+
26
+ ```typescript
27
+ const { assetInfo, loading, error } = useAssetRuntime();
28
+ // assetInfo.assetId, assetInfo.name, etc.
29
+ ```
30
+
31
+ ### useCustomerRuntime
32
+
33
+ Access customer information.
34
+
35
+ ```typescript
36
+ const { customerInfo, loading, error } = useCustomerRuntime();
37
+ // customerInfo.customerId, customerInfo.name, etc.
38
+ ```
39
+
40
+ ### useEventRuntime
41
+
42
+ Access event information in event-scoped extensions.
43
+
44
+ ```typescript
45
+ const { eventInfo, loading, error } = useEventRuntime();
46
+ // eventInfo.eventId, eventInfo.type, etc.
47
+ ```
48
+
49
+ ### useSiteRuntime
50
+
51
+ Access site information in site-scoped extensions.
52
+
53
+ ```typescript
54
+ const { siteInfo, loading, error } = useSiteRuntime();
55
+ // siteInfo.siteId, siteInfo.name, etc.
56
+ ```
57
+
58
+ ### useIrisAppName
59
+
60
+ Get current app name.
61
+
62
+ ```typescript
63
+ const { appName, loading, error } = useIrisAppName();
64
+ ```
65
+
66
+ ### useIrisAppId
67
+
68
+ Get current app ID.
69
+
70
+ ```typescript
71
+ const { irisAppId, loading, error } = useIrisAppId();
72
+ ```
73
+
74
+ ## Navigation & User Context
75
+
76
+ ### useNavigateInHost
77
+
78
+ Navigate within Trackunit Manager.
79
+
80
+ ```typescript
81
+ const { gotoAssetHome, gotoSiteHome } = useNavigateInHost();
82
+ gotoAssetHome(assetId);
83
+ gotoSiteHome(siteId);
84
+ ```
85
+
86
+ ### useHasAccessTo
87
+
88
+ Check user access to pages or asset-scoped pages.
89
+
90
+ ```typescript
91
+ const hasAccess = useHasAccessTo({ page: "sites" });
92
+ const hasAssetAccess = useHasAccessTo({ assetId, page: "asset-home" });
93
+ ```
94
+
95
+ ### useCurrentUser
96
+
97
+ Access current user information. Returns flat properties directly.
98
+
99
+ ```typescript
100
+ const { email, name, accountId, userId } = useCurrentUser();
101
+ ```
102
+
103
+ ### useCurrentUserLanguage
104
+
105
+ Get/set user language preference.
106
+
107
+ ```typescript
108
+ const { language, setLanguage } = useCurrentUserLanguage();
109
+ ```
110
+
111
+ ### useCurrentUserTimeZonePreference
112
+
113
+ Get/set user timezone preference.
114
+
115
+ ```typescript
116
+ const { timeZonePreference, setTimeZonePreference } = useCurrentUserTimeZonePreference();
117
+ ```
118
+
119
+ ### useToken
120
+
121
+ Access authentication token.
122
+
123
+ ```typescript
124
+ const { token } = useToken();
125
+ ```
126
+
127
+ ## UI & Interaction
128
+
129
+ ### useToast
130
+
131
+ Show toast notifications.
132
+
133
+ ```typescript
134
+ const { addToast } = useToast();
135
+ addToast({ intent: "success", title: "Operation completed" });
136
+ ```
137
+
138
+ ### useConfirmationDialog
139
+
140
+ Show confirmation dialogs.
141
+
142
+ ```typescript
143
+ const { confirm } = useConfirmationDialog();
144
+ const confirmed = await confirm({
145
+ title: "Confirm Action",
146
+ message: "Are you sure?",
147
+ primaryActionType: "danger",
148
+ primaryActionLabel: "Delete",
149
+ secondaryActionLabel: "Cancel"
150
+ });
151
+ ```
152
+
153
+ ### useModalDialogContext
154
+
155
+ Manage modal dialogs.
156
+
157
+ ```typescript
158
+ const { openModal, closeModal } = useModalDialogContext();
159
+ ```
160
+
161
+ ### useErrorHandler
162
+
163
+ Handle errors consistently.
164
+
165
+ ```typescript
166
+ const { logError } = useErrorHandler();
167
+ logError(new Error("Something went wrong"));
168
+ ```
169
+
170
+ ## Data & State Management
171
+
172
+ ### useAssetSorting
173
+
174
+ Asset sorting functionality.
175
+
176
+ ```typescript
177
+ const { sortingState, setSortBy } = useAssetSorting();
178
+ // sortingState.sortBy, sortingState.order
179
+ ```
180
+
181
+ ### useFilterBarContext
182
+
183
+ Filter bar state management. Provides domain-specific filter values.
184
+
185
+ ```typescript
186
+ const { assetsFilterBarValues, customersFilterBarValues } = useFilterBarContext();
187
+ ```
188
+
189
+ ### useTimeRange
190
+
191
+ Time range context (read-only).
192
+
193
+ ```typescript
194
+ const { timeRange, temporalPeriod } = useTimeRange();
195
+ ```
196
+
197
+ ## Widget Configuration
198
+
199
+ ### useWidgetConfig
200
+
201
+ Access widget configuration, filters, time range, and edit mode state.
202
+
203
+ ```typescript
204
+ const {
205
+ data, setData, dataVersion, loadingData,
206
+ title, setTitle,
207
+ filters, timeRange,
208
+ isEditMode, openEditMode, closeEditMode,
209
+ pollInterval, setLoadingState
210
+ } = useWidgetConfig();
211
+ ```
212
+
213
+ ## Note
214
+
215
+ All hooks require their respective providers to be set up in your app's component tree. IrisX App SDK handles this automatically for extensions.
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: tables-and-sorting
3
+ description: Use when building data tables with filtering, sorting, pagination, or displaying lists of assets, users, or other entities.
4
+ ---
5
+
6
+ # Tables with Filtering & Sorting
7
+
8
+ ## Overview
9
+
10
+ Create interactive data tables using `@trackunit/react-table`, `@trackunit/filters-filter-bar`, and server-side pagination with GraphQL. Always use `useMemo` and `manualSorting: true` for large datasets.
11
+
12
+ ## When to Use
13
+
14
+ - Building asset management interfaces
15
+ - Creating user administration tables
16
+ - Developing dashboard data grids
17
+ - Implementing reporting interfaces with large datasets
18
+
19
+ **Not for:** Simple lists without filtering (use standard components).
20
+
21
+ ## Required Imports
22
+
23
+ ```typescript
24
+ import { Table, useTable } from '@trackunit/react-table';
25
+ import { TextCell, DateTimeCell } from '@trackunit/react-table-base-components';
26
+ import { FilterBar, useFilterBar } from '@trackunit/filters-filter-bar';
27
+ import { useAssetQueryFilters } from '@trackunit/filters-filter-bar-asset-graphql';
28
+ import { usePaginationQuery } from '@trackunit/react-graphql-hooks';
29
+ import { createColumnHelper, SortingState } from '@tanstack/react-table';
30
+ import { useBrandFilter, useModelsFilter } from '@trackunit/filters-asset-filter-definitions';
31
+ ```
32
+
33
+ ## Valid Implementation
34
+
35
+ ```typescript
36
+ type Asset = { id: string; name: string; createdAt: string };
37
+ const columnHelper = createColumnHelper<Asset>();
38
+
39
+ const AssetTable = () => {
40
+ // Setup filters with proper naming
41
+ const filterBar = useFilterBar({
42
+ name: "assetFilters",
43
+ filterBarDefinition: {
44
+ brand: useBrandFilter(),
45
+ model: useModelsFilter()
46
+ }
47
+ });
48
+
49
+ // Convert filter values to GraphQL variables
50
+ const filterVariables = useAssetQueryFilters(filterBar.filterBarConfig.values);
51
+
52
+ // Server-side sorting state
53
+ const [sorting, setSorting] = useState<SortingState>([]);
54
+
55
+ // GraphQL query with filters
56
+ const { data, loading, pagination } = usePaginationQuery(GetAssetsDocument, {
57
+ variables: { first: 50, ...filterVariables },
58
+ pageSize: 50,
59
+ });
60
+
61
+ // Transform data with useMemo
62
+ const assets = useMemo(() =>
63
+ data?.assets.edges.map(edge => ({ ...edge.node, id: edge.node.id })) ?? [],
64
+ [data]
65
+ );
66
+
67
+ // Define columns with proper cell components
68
+ const columns = useMemo(() => [
69
+ columnHelper.accessor('name', {
70
+ cell: ({ row: { original } }) => <TextCell content={original.name} />,
71
+ header: "Asset Name",
72
+ enableSorting: true,
73
+ }),
74
+ columnHelper.accessor('createdAt', {
75
+ cell: ({ row: { original } }) => <DateTimeCell date={new Date(original.createdAt)} />,
76
+ header: "Created",
77
+ enableSorting: true,
78
+ }),
79
+ ], []);
80
+
81
+ // Setup table with manual sorting
82
+ const table = useTable({
83
+ data: assets,
84
+ columns,
85
+ enableSorting: true,
86
+ manualSorting: true,
87
+ state: { sorting },
88
+ onSortingChange: setSorting,
89
+ });
90
+
91
+ return (
92
+ <>
93
+ <FilterBar {...filterBar} />
94
+ <Table
95
+ {...table}
96
+ loading={loading}
97
+ pagination={pagination}
98
+ onRowClick={(row) => navigateToAsset(row.original.id)}
99
+ />
100
+ </>
101
+ );
102
+ };
103
+ ```
104
+
105
+ ## Common Mistakes
106
+
107
+ | Mistake | Fix |
108
+ |---------|-----|
109
+ | Missing `useMemo` for columns/data | Wrap in `useMemo` to prevent re-renders |
110
+ | `manualSorting: false` on large datasets | Use `manualSorting: true` for server-side sorting |
111
+ | Missing filter variables in query | Spread `filterVariables` into GraphQL variables |
112
+ | Using `getValue()` in cells | Use Trackunit cell components (`TextCell`, `DateTimeCell`) |
113
+ | No `onRowClick` handler | Add navigation or detail view on row click |
114
+
115
+ ## Key Requirements
116
+
117
+ - **Always use `useMemo`** for data transformation and column definitions
118
+ - **Enable `manualSorting: true`** for datasets > 1000 items
119
+ - **Integrate filter state** with GraphQL variables using spread operator
120
+ - **Use Trackunit cell components** (`TextCell`, `DateTimeCell`, `PlainDateCell`) for consistency
121
+ - **Implement `onRowClick`** for navigation or detail views
122
+ - **Sync state properly** between filters, sorting, and GraphQL queries