@trackunit/iris-app 1.12.7 → 1.12.10
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/CHANGELOG.md +30 -0
- package/package.json +3 -3
- package/src/generators/ai-agent-sync/README.md +5 -2
- package/src/generators/ai-agent-sync/generator.d.ts +1 -1
- package/src/generators/ai-agent-sync/generator.js +29 -4
- package/src/generators/ai-agent-sync/generator.js.map +1 -1
- package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
- package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
- package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
- package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
- package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
- package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
- package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
- package/src/generators/preset/files/.cursor/mcp.json +4 -0
- package/src/generators/preset/root-files/AGENTS.md +43 -0
- package/src/generators/preset/root-files/CLAUDE.md +17 -0
- package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
- package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
- package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
- package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
- package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
- package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
- package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
- package/src/generators/preset/files/.cursor/rules/widget-extensions.md +0 -323
|
@@ -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
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: widget-extensions
|
|
3
|
+
description: Use when creating dashboard widgets, KPI displays, configurable widgets with edit dialogs, or data visualizations for dashboards.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Widget Extensions
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Widget extensions are React components that integrate into Trackunit Manager dashboards. Use `useWidgetConfig` for data, filters, and edit mode. Supports KPI, CHART, LIST, MAP, and OTHER types.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Creating dashboard KPIs or metrics
|
|
15
|
+
- Building data visualization widgets
|
|
16
|
+
- Adding configurable widgets with edit dialogs
|
|
17
|
+
- Displaying filtered/time-ranged data in dashboards
|
|
18
|
+
|
|
19
|
+
**Not for:** Full-page extensions (use `FLEET_EXTENSION` or `ASSET_HOME_EXTENSION`).
|
|
20
|
+
|
|
21
|
+
## Creating Widget Extensions
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx nx g @trackunit/iris-app:extend --name=[my-widget-name] --app=[app-name] --directory=[feature] --type=WIDGET_EXTENSION
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This creates:
|
|
28
|
+
- `extension-manifest.ts` - Widget configuration and metadata
|
|
29
|
+
- `src/app.tsx` - Main widget component (handles both display and edit mode)
|
|
30
|
+
|
|
31
|
+
## Widget Manifest
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { WidgetExtensionManifest } from '@trackunit/iris-app-api';
|
|
35
|
+
|
|
36
|
+
const widgetExtensionManifest: WidgetExtensionManifest = {
|
|
37
|
+
id: "my-widget-id",
|
|
38
|
+
type: "WIDGET_EXTENSION",
|
|
39
|
+
sourceRoot: "libs/[feature]/[widget-name]/src",
|
|
40
|
+
|
|
41
|
+
widgetType: "CHART", // CHART | KPI | LIST | MAP | OTHER
|
|
42
|
+
|
|
43
|
+
size: {
|
|
44
|
+
default: { width: 2, height: 2 }, // 1-6 grid units
|
|
45
|
+
allowFullWidth: false,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
header: {
|
|
49
|
+
name: "My Widget",
|
|
50
|
+
image: { name: "ChartBar" }, // IconByName or IconByPath
|
|
51
|
+
hasEditDialog: true,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
footer: {
|
|
55
|
+
linkDescription: "View details",
|
|
56
|
+
link: "/my-app/details",
|
|
57
|
+
poweredByImage: { path: "/assets/logo.svg" },
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
preview: {
|
|
61
|
+
description: "Displays chart data for analysis",
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
supportedLocations: ["MY_HOME", "PLAYGROUND"],
|
|
65
|
+
|
|
66
|
+
supportedFilters: {
|
|
67
|
+
TIME_RANGE: { include: ["ALL"] },
|
|
68
|
+
ASSETS: { include: ["assetType", "brands", "models"] },
|
|
69
|
+
CUSTOMERS: { include: ["customerType", "criticality"] },
|
|
70
|
+
SITES: { include: ["siteType", "location"] },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Widget Types
|
|
76
|
+
|
|
77
|
+
| Type | Use Case |
|
|
78
|
+
|------|----------|
|
|
79
|
+
| `KPI` | Single metric displays (typically 1x1) |
|
|
80
|
+
| `CHART` | Data visualization (charts, graphs) |
|
|
81
|
+
| `LIST` | Tabular or list-based data |
|
|
82
|
+
| `MAP` | Geographic or spatial data |
|
|
83
|
+
| `OTHER` | Custom or specialized widgets |
|
|
84
|
+
|
|
85
|
+
## Supported Locations
|
|
86
|
+
|
|
87
|
+
- `MY_HOME` - User's personal dashboard
|
|
88
|
+
- `SITE_HOME` - Site-specific dashboards
|
|
89
|
+
- `PLAYGROUND` - Widget testing playground
|
|
90
|
+
|
|
91
|
+
## Widget Development
|
|
92
|
+
|
|
93
|
+
### Basic Widget Component
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
97
|
+
|
|
98
|
+
export const App = () => {
|
|
99
|
+
const {
|
|
100
|
+
data, setData, dataVersion, loadingData,
|
|
101
|
+
title, setTitle,
|
|
102
|
+
filters, timeRange,
|
|
103
|
+
isEditMode, openEditMode, closeEditMode,
|
|
104
|
+
pollInterval, setLoadingState
|
|
105
|
+
} = useWidgetConfig();
|
|
106
|
+
|
|
107
|
+
if (isEditMode) {
|
|
108
|
+
return <EditDialog />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<WidgetBody>
|
|
113
|
+
<h2>{title}</h2>
|
|
114
|
+
{/* Widget content */}
|
|
115
|
+
</WidgetBody>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### useWidgetConfig Hook
|
|
121
|
+
|
|
122
|
+
**Data Management:**
|
|
123
|
+
- `data: Record<string, unknown> | null` - Widget configuration data
|
|
124
|
+
- `setData(data, version)` - Update widget data
|
|
125
|
+
- `dataVersion: number | null` - Current data version for migrations
|
|
126
|
+
- `loadingData: boolean` - Whether widget data is loading
|
|
127
|
+
|
|
128
|
+
**Title Management:**
|
|
129
|
+
- `title: string` - Widget title
|
|
130
|
+
- `setTitle(title)` - Update widget title
|
|
131
|
+
|
|
132
|
+
**Context:**
|
|
133
|
+
- `filters` - Applied filters (assets, customers, sites)
|
|
134
|
+
- `timeRange` - Selected time range (startMsInEpoch, endMsInEpoch)
|
|
135
|
+
- `isEditMode: boolean` - Whether widget is in edit mode
|
|
136
|
+
|
|
137
|
+
**Edit Mode:**
|
|
138
|
+
- `openEditMode()` - Enter edit mode
|
|
139
|
+
- `closeEditMode()` - Exit edit mode
|
|
140
|
+
|
|
141
|
+
### Widget with Edit Dialog
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
145
|
+
import { WidgetEditBody } from '@trackunit/react-widgets';
|
|
146
|
+
|
|
147
|
+
export const App = () => {
|
|
148
|
+
const { isEditMode, data, closeEditMode } = useWidgetConfig();
|
|
149
|
+
|
|
150
|
+
if (isEditMode) {
|
|
151
|
+
return (
|
|
152
|
+
<WidgetEditBody
|
|
153
|
+
onSave={async () => {
|
|
154
|
+
await closeEditMode({ newData: { data: { demo: "yourDataHere"}, dataVersion: 1 }});
|
|
155
|
+
}}
|
|
156
|
+
onCancel={closeEditMode}
|
|
157
|
+
initialData={data}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return <MainWidgetContent />;
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Filter Support
|
|
167
|
+
|
|
168
|
+
### Using Filters
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const { filters, timeRange } = useWidgetConfig();
|
|
172
|
+
|
|
173
|
+
const selectedAssets = filters.assetsFilterBarValues?.assetType;
|
|
174
|
+
const selectedCustomers = filters.customersFilterBarValues?.customerType;
|
|
175
|
+
const selectedSites = filters.sitesFilterBarValues?.siteType;
|
|
176
|
+
|
|
177
|
+
const startTime = timeRange?.startMsInEpoch;
|
|
178
|
+
const endTime = timeRange?.endMsInEpoch;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## KPI Widget Example
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
185
|
+
import { useQuery } from '@trackunit/react-graphql-hooks';
|
|
186
|
+
|
|
187
|
+
export const App = () => {
|
|
188
|
+
const { filters, timeRange, pollInterval, setLoadingState } = useWidgetConfig();
|
|
189
|
+
|
|
190
|
+
const { data: kpiData, loading } = useQuery(MY_KPI_QUERY, {
|
|
191
|
+
variables: {
|
|
192
|
+
filters: {
|
|
193
|
+
assetIds: filters.assetsFilterBarValues?.assetIds,
|
|
194
|
+
timeRange: {
|
|
195
|
+
start: timeRange?.startMsInEpoch,
|
|
196
|
+
end: timeRange?.endMsInEpoch,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
pollInterval
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
void setLoadingState({ hasData: !!kpiData, isLoading: loading });
|
|
205
|
+
}, [kpiData, loading, setLoadingState]);
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="kpi-widget">
|
|
209
|
+
<div className="kpi-value">{kpiData?.totalCount}</div>
|
|
210
|
+
<div className="kpi-label">Total Assets</div>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Best Practices
|
|
217
|
+
|
|
218
|
+
### Widget Design
|
|
219
|
+
1. **Responsive Layout** - Design for different grid sizes
|
|
220
|
+
2. **Loading States** - Show loading indicators for async operations
|
|
221
|
+
3. **Error Handling** - Gracefully handle data loading errors
|
|
222
|
+
4. **Empty States** - Provide meaningful empty state messages
|
|
223
|
+
|
|
224
|
+
### Data Management
|
|
225
|
+
1. **Use Real Data** - Always fetch from Trackunit GraphQL API
|
|
226
|
+
2. **Respect Filters** - Apply user-selected filters to data queries
|
|
227
|
+
3. **Efficient Updates** - Only update widget data when necessary
|
|
228
|
+
4. **Version Control** - Use `dataVersion` for configuration migrations
|
|
229
|
+
|
|
230
|
+
### Configuration
|
|
231
|
+
1. **Meaningful Defaults** - Provide sensible default configurations
|
|
232
|
+
2. **Validation** - Validate user input in edit dialogs
|
|
233
|
+
3. **Persistence** - Use `setData()` to save configuration changes
|
|
234
|
+
4. **User Experience** - Keep configuration UI simple and intuitive
|
|
235
|
+
|
|
236
|
+
## Common Mistakes
|
|
237
|
+
|
|
238
|
+
| Mistake | Fix |
|
|
239
|
+
|---------|-----|
|
|
240
|
+
| Using generic widget libraries | Use Trackunit IrisX App SDK patterns |
|
|
241
|
+
| Not handling `isEditMode` | Check `isEditMode` and render edit dialog |
|
|
242
|
+
| Missing `setLoadingState` call | Call `setLoadingState` to show loading indicators |
|
|
243
|
+
| Ignoring `filters` from context | Apply filters to data queries |
|
|
244
|
+
| Not using `dataVersion` | Increment version when changing data schema |
|
|
245
|
+
| Missing empty/error states | Handle no data and error conditions gracefully |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# IrisX App SDK Workspace
|
|
2
|
+
|
|
3
|
+
This is an IrisX App workspace. Follow these rules for all development.
|
|
4
|
+
|
|
5
|
+
## Building Real Applications
|
|
6
|
+
|
|
7
|
+
You are building real applications that work with actual Trackunit data.
|
|
8
|
+
|
|
9
|
+
- Never mock data unless explicitly requested by the user
|
|
10
|
+
- Always use Trackunit's GraphQL API to fetch real data
|
|
11
|
+
- If data is not available via GraphQL, ask the user how they want to obtain the data
|
|
12
|
+
- Only use mock data if the user explicitly requests it for testing purposes
|
|
13
|
+
|
|
14
|
+
When building any feature:
|
|
15
|
+
1. Identify what data you need
|
|
16
|
+
2. Check if it's available via Trackunit's GraphQL API
|
|
17
|
+
3. If available, use the GraphQL API
|
|
18
|
+
4. If not available, ask the user
|
|
19
|
+
|
|
20
|
+
## IrisX App Commands
|
|
21
|
+
|
|
22
|
+
**ALWAYS use these specific commands** (never generic React/Node.js commands):
|
|
23
|
+
|
|
24
|
+
- **Create new app:** `npx nx generate @trackunit/iris-app:create [name-of-your-app]`
|
|
25
|
+
- **Create new extension:** `npx nx g @trackunit/iris-app:extend --name=[my-extension-name] --app=[app-name] --directory=[feature] --type=[extension-type]`
|
|
26
|
+
- **Run app:** `npx nx run [name-of-your-app]:serve`
|
|
27
|
+
|
|
28
|
+
Available extension types:
|
|
29
|
+
- `ASSET_HOME_EXTENSION` - Tabs in Asset Home screen
|
|
30
|
+
- `SITE_HOME_EXTENSION` - Tabs in Site Home screen
|
|
31
|
+
- `FLEET_EXTENSION` - Menu items in Main Menu
|
|
32
|
+
- `REPORT_EXTENSION` - Reports in Reports screen
|
|
33
|
+
- `WIDGET_EXTENSION` - Dashboard widgets
|
|
34
|
+
- `IRIS_APP_SETTINGS_EXTENSION` - Configuration in App library
|
|
35
|
+
- `ADMIN_EXTENSION` - Admin UI tabs (admin-only)
|
|
36
|
+
- `CUSTOMER_HOME_EXTENSION` - UI within Customer Home
|
|
37
|
+
- `ASSET_EVENTS_ACTIONS_EXTENSION` - UI in Asset Home Events
|
|
38
|
+
- `LIFECYCLE_EXTENSION` - Handles app install/uninstall events
|
|
39
|
+
- `SERVERLESS_FUNCTION_EXTENSION` - Serverless backend functions
|
|
40
|
+
|
|
41
|
+
## Quality Assurance
|
|
42
|
+
|
|
43
|
+
Before completing any task: Always check for and fix all ESLint and TypeScript errors in modified files.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
@AGENTS.md
|
|
2
|
+
|
|
3
|
+
# IrisX App Workspace
|
|
4
|
+
|
|
5
|
+
This is an IrisX App project generated by the Trackunit SDK. Follow AGENTS.md for all development rules.
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
SDK-specific skills are located in `.agents/skills/`. When a skill is referenced in AGENTS.md, read it from `.agents/skills/[skill-name]/SKILL.md`.
|
|
10
|
+
|
|
11
|
+
If skills from `.agents/skills/` are not appearing in your available skills list, suggest the user symlink them for automatic discovery:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ln -s .agents/skills .claude/skills
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Note: If the user already has a `.claude/skills` folder, they should symlink individual skills or the contents instead.
|