@trackunit/iris-app 1.12.9 → 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 +12 -0
- package/package.json +1 -1
- package/src/executors/submit/executor.js.map +1 -0
- package/src/executors/unpublish/executor.js.map +1 -0
- package/src/executors/utils/authentication.js.map +1 -0
- package/src/executors/utils/irisAppServerSettings.js.map +1 -0
- package/src/executors/utils/src/index.js.map +1 -0
- 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 -0
- package/src/generators/create/generator.js.map +1 -0
- package/src/generators/extend/dependencies.js.map +1 -0
- package/src/generators/extend/generator.js.map +1 -0
- 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/generator.js.map +1 -0
- package/src/generators/preset/root-files/AGENTS.md +43 -0
- package/src/generators/preset/root-files/CLAUDE.md +17 -0
- package/src/index.js.map +1 -0
- package/src/utils/ast/astUtils.js.map +1 -0
- package/src/utils/fileUpdater.js.map +1 -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
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
alwaysApply: false
|
|
3
|
-
---
|
|
4
|
-
# 📊 Trackunit Table with Filtering & Sorting
|
|
5
|
-
|
|
6
|
-
Create interactive data tables with server-side filtering, sorting, and pagination using Trackunit components and GraphQL.
|
|
7
|
-
|
|
8
|
-
## 🎯 When to Apply
|
|
9
|
-
- Building asset management interfaces
|
|
10
|
-
- Creating user administration tables
|
|
11
|
-
- Developing dashboard data grids
|
|
12
|
-
- Implementing reporting interfaces with large datasets
|
|
13
|
-
- Any UI requiring interactive table features (filter/sort/paginate)
|
|
14
|
-
|
|
15
|
-
## 🔧 Required Imports
|
|
16
|
-
```typescript
|
|
17
|
-
import { Table, useTable, TextCell, DateCell } from '@trackunit/react-table';
|
|
18
|
-
import { FilterBar, useFilterBar } from '@trackunit/filters-filter-bar';
|
|
19
|
-
import { usePaginationQuery } from '@trackunit/react-graphql-hooks';
|
|
20
|
-
import { createColumnHelper, SortingState } from '@tanstack/react-table';
|
|
21
|
-
import { useBrandFilter, useModelFilter } from '@trackunit/filters-asset-filters';
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## ✅ Valid Implementation
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
const AssetTable = () => {
|
|
28
|
-
// Setup filters with proper naming
|
|
29
|
-
const filterBar = useFilterBar({
|
|
30
|
-
name: "assetFilters",
|
|
31
|
-
filterBarDefinition: {
|
|
32
|
-
brand: useBrandFilter(),
|
|
33
|
-
model: useModelFilter()
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Server-side sorting state
|
|
38
|
-
const [sorting, setSorting] = useState<SortingState>([]);
|
|
39
|
-
|
|
40
|
-
// GraphQL query with filters
|
|
41
|
-
const { data, loading, error, pagination } = usePaginationQuery(GetAssetsDocument, {
|
|
42
|
-
variables: { first: 50, ...filterBar.selectedFilters },
|
|
43
|
-
pageSize: 50,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Transform data with useMemo
|
|
47
|
-
const assets = useMemo(() =>
|
|
48
|
-
data?.assets.edges.map(edge => ({ ...edge.node, id: edge.node.id })) ?? [],
|
|
49
|
-
[data]
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Define columns with proper cell components
|
|
53
|
-
const columns = useMemo(() => [
|
|
54
|
-
columnHelper.accessor('name', {
|
|
55
|
-
cell: ({ row: { original } }) => <TextCell text={original.name} />,
|
|
56
|
-
header: "Asset Name",
|
|
57
|
-
enableSorting: true,
|
|
58
|
-
}),
|
|
59
|
-
columnHelper.accessor('createdAt', {
|
|
60
|
-
cell: ({ row: { original } }) => <DateCell date={original.createdAt} />,
|
|
61
|
-
header: "Created",
|
|
62
|
-
enableSorting: true,
|
|
63
|
-
}),
|
|
64
|
-
], []);
|
|
65
|
-
|
|
66
|
-
// Setup table with manual sorting
|
|
67
|
-
const { table } = useTable({
|
|
68
|
-
data: assets,
|
|
69
|
-
columns,
|
|
70
|
-
enableSorting: true,
|
|
71
|
-
manualSorting: true,
|
|
72
|
-
state: { sorting },
|
|
73
|
-
onSortingChange: setSorting,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<>
|
|
78
|
-
<FilterBar filterBar={filterBar} />
|
|
79
|
-
<Table
|
|
80
|
-
table={table}
|
|
81
|
-
loading={loading}
|
|
82
|
-
error={error}
|
|
83
|
-
pagination={pagination}
|
|
84
|
-
onRowClick={(row) => navigateToAsset(row.original.id)}
|
|
85
|
-
/>
|
|
86
|
-
</>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## ❌ Invalid Implementation
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// DON'T: Missing useMemo optimization
|
|
95
|
-
const columns = [
|
|
96
|
-
columnHelper.accessor('name', {
|
|
97
|
-
cell: ({ getValue }) => getValue(), // Wrong: Use TextCell component
|
|
98
|
-
header: "Name"
|
|
99
|
-
})
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
// DON'T: Client-side sorting on large datasets
|
|
103
|
-
const { table } = useTable({
|
|
104
|
-
data: assets,
|
|
105
|
-
columns,
|
|
106
|
-
enableSorting: true,
|
|
107
|
-
manualSorting: false, // Wrong: Should be true for server-side
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// DON'T: Missing filter integration
|
|
111
|
-
const { data } = usePaginationQuery(GetAssetsDocument, {
|
|
112
|
-
variables: { first: 50 }, // Wrong: Missing filter variables
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// DON'T: No row click handler
|
|
116
|
-
<Table table={table} loading={loading} />
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## 🎯 Key Requirements
|
|
120
|
-
|
|
121
|
-
- **Always use `useMemo`** for data transformation and column definitions
|
|
122
|
-
- **Enable `manualSorting: true`** for datasets > 1000 items
|
|
123
|
-
- **Integrate filter state** with GraphQL variables using spread operator
|
|
124
|
-
- **Use Trackunit cell components** (`TextCell`, `DateCell`) for consistency
|
|
125
|
-
- **Implement `onRowClick`** for navigation or detail views
|
|
126
|
-
- **Sync state properly** between filters, sorting, and GraphQL queries
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
# Widget Extensions
|
|
2
|
-
|
|
3
|
-
Widget extensions are specialized components that integrate into Trackunit Manager's dashboard systems, providing customizable UI elements for data visualization and user interaction.
|
|
4
|
-
|
|
5
|
-
## Key Concepts
|
|
6
|
-
|
|
7
|
-
1. **Widget Extensions** are React applications that render within widget containers on various pages
|
|
8
|
-
2. **Supported Locations** define where widgets can be placed in Trackunit Manager
|
|
9
|
-
3. **Widget Types** categorize widgets by their primary purpose and visual style
|
|
10
|
-
4. **Filter Support** enables widgets to respond to user-applied filters
|
|
11
|
-
5. **Configuration** allows widgets to store and manage their own settings
|
|
12
|
-
|
|
13
|
-
## Creating Widget Extensions
|
|
14
|
-
|
|
15
|
-
### Generate a new widget extension:
|
|
16
|
-
```bash
|
|
17
|
-
npx nx g @trackunit/iris-app:extend --name=[my-widget-name] --app=[app-name] --directory=[feature] --type=WIDGET_EXTENSION
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
This creates:
|
|
21
|
-
- `extension-manifest.ts` - Widget configuration and metadata
|
|
22
|
-
- `src/app.tsx` - Main widget component
|
|
23
|
-
- `src/editDialog.tsx` - Configuration dialog (if `hasEditDialog: true`)
|
|
24
|
-
|
|
25
|
-
## Widget Extension Manifest
|
|
26
|
-
|
|
27
|
-
The manifest defines widget metadata, appearance, and behavior:
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { WidgetExtensionManifest } from '@trackunit/iris-app-api';
|
|
31
|
-
|
|
32
|
-
const widgetExtensionManifest: WidgetExtensionManifest = {
|
|
33
|
-
id: "my-widget-id",
|
|
34
|
-
type: "WIDGET_EXTENSION",
|
|
35
|
-
sourceRoot: "libs/[feature]/[widget-name]/src",
|
|
36
|
-
|
|
37
|
-
widgetType: "CHART", // Required: Widget classification
|
|
38
|
-
|
|
39
|
-
size: {
|
|
40
|
-
default: {
|
|
41
|
-
width: 2, // 1-6 grid units
|
|
42
|
-
height: 2, // 1-6 grid units
|
|
43
|
-
},
|
|
44
|
-
allowFullWidth: false, // Optional: Allow full-width expansion
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
header: {
|
|
48
|
-
name: "My Widget", // Display name
|
|
49
|
-
image: { name: "ChartBar" }, // Icon (IconByName or IconByPath)
|
|
50
|
-
hasEditDialog: true, // Optional: Enable edit button
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
footer: { // Optional: Footer with link
|
|
54
|
-
linkDescription: "View details",
|
|
55
|
-
link: "/my-app/details",
|
|
56
|
-
poweredByImage: { path: "/assets/logo.svg" },
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
preview: { // Optional: Description for widget drawer
|
|
60
|
-
description: "Displays chart data for analysis",
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
supportedLocations: ["MY_HOME", "PLAYGROUND"],
|
|
64
|
-
|
|
65
|
-
supportedFilters: { // Optional: Enable filter support
|
|
66
|
-
TIME_RANGE: { include: ["ALL"] },
|
|
67
|
-
ASSETS: { include: ["assetType", "brands", "models"] },
|
|
68
|
-
CUSTOMERS: { include: ["customerType", "criticality"] },
|
|
69
|
-
SITES: { include: ["siteType", "location"] },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default widgetExtensionManifest;
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Widget Types
|
|
77
|
-
|
|
78
|
-
Available widget types that determine visual presentation:
|
|
79
|
-
|
|
80
|
-
- **`"KPI"`** - Single metric displays (typically 1x1 size)
|
|
81
|
-
- **`"CHART"`** - Data visualization widgets (charts, graphs)
|
|
82
|
-
- **`"LIST"`** - Tabular or list-based data presentation
|
|
83
|
-
- **`"MAP"`** - Geographic or spatial data visualization
|
|
84
|
-
- **`"OTHER"`** - Custom or specialized widgets
|
|
85
|
-
|
|
86
|
-
## Supported Locations
|
|
87
|
-
|
|
88
|
-
Widgets can be placed in these locations:
|
|
89
|
-
|
|
90
|
-
- **`"MY_HOME"`** - User's personal dashboard
|
|
91
|
-
- **`"SITE_HOME"`** - Site-specific dashboards
|
|
92
|
-
|
|
93
|
-
## Widget Development
|
|
94
|
-
|
|
95
|
-
### Basic Widget Component
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
99
|
-
|
|
100
|
-
export const App = () => {
|
|
101
|
-
const {
|
|
102
|
-
data,
|
|
103
|
-
setData,
|
|
104
|
-
dataVersion,
|
|
105
|
-
title,
|
|
106
|
-
setTitle,
|
|
107
|
-
filters,
|
|
108
|
-
timeRange,
|
|
109
|
-
isEditMode,
|
|
110
|
-
openEditMode,
|
|
111
|
-
closeEditMode
|
|
112
|
-
pollInterval,
|
|
113
|
-
setLoadingState
|
|
114
|
-
} = useWidgetConfig();
|
|
115
|
-
|
|
116
|
-
if (isEditMode) {
|
|
117
|
-
return <EditDialog />;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<WidgetBody>
|
|
122
|
-
<h2>{title}</h2>
|
|
123
|
-
{/* Widget content */}
|
|
124
|
-
</WidgetBody>
|
|
125
|
-
);
|
|
126
|
-
};
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Widget Configuration Hook
|
|
130
|
-
|
|
131
|
-
The `useWidgetConfig()` hook provides:
|
|
132
|
-
|
|
133
|
-
**Data Management:**
|
|
134
|
-
- `data: Record<string, unknown>` - Widget configuration data
|
|
135
|
-
- `setData: (data: Record<string, unknown>, version: number) => Promise<void>` - Update widget data
|
|
136
|
-
- `dataVersion: number` - Current data version for migrations
|
|
137
|
-
|
|
138
|
-
**Title Management:**
|
|
139
|
-
- `title: string` - Widget title
|
|
140
|
-
- `setTitle: (title: string) => Promise<void>` - Update widget title
|
|
141
|
-
|
|
142
|
-
**Context Information:**
|
|
143
|
-
- `filters: { assetsFilterBarValues, customersFilterBarValues, sitesFilterBarValues }` - Applied filters
|
|
144
|
-
- `timeRange: { startMsInEpoch, endMsInEpoch }` - Selected time range
|
|
145
|
-
- `isEditMode: boolean` - Whether widget is in edit mode
|
|
146
|
-
|
|
147
|
-
**Edit Mode Control:**
|
|
148
|
-
- `openEditMode: () => Promise<void>` - Enter edit mode
|
|
149
|
-
- `closeEditMode: () => Promise<void>` - Exit edit mode
|
|
150
|
-
|
|
151
|
-
### Widget with Edit Dialog
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
155
|
-
import { WidgetEditBody } from '@trackunit/react-widgets';
|
|
156
|
-
|
|
157
|
-
export const App = () => {
|
|
158
|
-
const { isEditMode, data, closeEditMode } = useWidgetConfig();
|
|
159
|
-
|
|
160
|
-
if (isEditMode) {
|
|
161
|
-
return (
|
|
162
|
-
<WidgetEditBody
|
|
163
|
-
onSave={async () => {
|
|
164
|
-
await closeEditMode({ newData: { data: { demo: "youDataHere"}, dataVersion: 1 });
|
|
165
|
-
}}
|
|
166
|
-
onCancel={closeEditMode}
|
|
167
|
-
initialData={data}
|
|
168
|
-
/>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return <MainWidgetContent />;
|
|
173
|
-
};
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Filter Support
|
|
177
|
-
|
|
178
|
-
### Supported Filter Types
|
|
179
|
-
|
|
180
|
-
Configure which filters your widget responds to:
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
supportedFilters: {
|
|
184
|
-
// Time range filtering
|
|
185
|
-
TIME_RANGE: { include: ["ALL"] },
|
|
186
|
-
|
|
187
|
-
// Asset filtering
|
|
188
|
-
ASSETS: {
|
|
189
|
-
include: [
|
|
190
|
-
"assetType", "brands", "models", "types",
|
|
191
|
-
"activity", "criticality", "lastSeen", "groups",
|
|
192
|
-
"siteIds", "customerIds", "ownerAccountIds",
|
|
193
|
-
// Add custom fields support
|
|
194
|
-
...allAssetFilterKeysWithCustomFields
|
|
195
|
-
]
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
// Customer filtering
|
|
199
|
-
CUSTOMERS: {
|
|
200
|
-
include: [
|
|
201
|
-
"customerType", "criticality", "servicePlan",
|
|
202
|
-
...allCustomerFilterKeysWithCustomFields
|
|
203
|
-
]
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
// Site filtering
|
|
207
|
-
SITES: {
|
|
208
|
-
include: [
|
|
209
|
-
"siteType", "location", "area",
|
|
210
|
-
...allSiteFilterKeysWithCustomFields
|
|
211
|
-
]
|
|
212
|
-
},
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Using Filters in Widgets
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
const { filters, timeRange } = useWidgetConfig();
|
|
220
|
-
|
|
221
|
-
// Access filter values
|
|
222
|
-
const selectedAssets = filters.assetsFilterBarValues?.assetType;
|
|
223
|
-
const selectedCustomers = filters.customersFilterBarValues?.customerType;
|
|
224
|
-
const selectedSites = filters.sitesFilterBarValues?.siteType;
|
|
225
|
-
|
|
226
|
-
// Use time range
|
|
227
|
-
const startTime = timeRange?.startMsInEpoch;
|
|
228
|
-
const endTime = timeRange?.endMsInEpoch;
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
## Widget Lifecycle
|
|
232
|
-
|
|
233
|
-
### 1. Widget Creation
|
|
234
|
-
- Generated via `@trackunit/iris-app:extend` command
|
|
235
|
-
- Manifest defines widget metadata and capabilities
|
|
236
|
-
- React component handles rendering and user interaction
|
|
237
|
-
|
|
238
|
-
### 2. Widget Registration
|
|
239
|
-
- Widget appears in the widget drawer based on `supportedLocations`
|
|
240
|
-
- Users can add widgets to their dashboards
|
|
241
|
-
- Widget configuration is persisted automatically
|
|
242
|
-
|
|
243
|
-
### 3. Widget Runtime
|
|
244
|
-
- Widget receives context (filters, time range, etc.)
|
|
245
|
-
- Widget can store and retrieve configuration data
|
|
246
|
-
- Widget responds to user interactions and filter changes
|
|
247
|
-
|
|
248
|
-
## Best Practices
|
|
249
|
-
|
|
250
|
-
### Widget Design
|
|
251
|
-
1. **Responsive Layout** - Design for different grid sizes
|
|
252
|
-
2. **Loading States** - Show loading indicators for async operations
|
|
253
|
-
3. **Error Handling** - Gracefully handle data loading errors
|
|
254
|
-
4. **Empty States** - Provide meaningful empty state messages
|
|
255
|
-
|
|
256
|
-
### Data Management
|
|
257
|
-
1. **Use Real Data** - Always fetch from Trackunit GraphQL API
|
|
258
|
-
2. **Respect Filters** - Apply user-selected filters to your data queries
|
|
259
|
-
3. **Efficient Updates** - Only update widget data when necessary
|
|
260
|
-
4. **Version Control** - Use `dataVersion` for configuration migrations
|
|
261
|
-
|
|
262
|
-
### Configuration
|
|
263
|
-
1. **Meaningful Defaults** - Provide sensible default configurations
|
|
264
|
-
2. **Validation** - Validate user input in edit dialogs
|
|
265
|
-
3. **Persistence** - Use `setData()` to save configuration changes
|
|
266
|
-
4. **User Experience** - Keep configuration UI simple and intuitive
|
|
267
|
-
|
|
268
|
-
## Example: KPI Widget
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
import { useWidgetConfig } from '@trackunit/react-core-hooks';
|
|
272
|
-
import { useQuery } from '@trackunit/react-graphql-hooks';
|
|
273
|
-
|
|
274
|
-
export const App = () => {
|
|
275
|
-
const { filters, timeRange, data, pollInterval, setLoadingState} = useWidgetConfig();
|
|
276
|
-
|
|
277
|
-
const { data: kpiData, loading } = useQuery(MY_KPI_QUERY, {
|
|
278
|
-
variables: {
|
|
279
|
-
filters: {
|
|
280
|
-
assetIds: filters.assetsFilterBarValues?.assetIds,
|
|
281
|
-
timeRange: {
|
|
282
|
-
start: timeRange?.startMsInEpoch,
|
|
283
|
-
end: timeRange?.endMsInEpoch,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
pollInterval
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
useEffect(() => {
|
|
291
|
-
void setLoadingState({ hasData: !!kpiData, isLoading: loading });
|
|
292
|
-
}, [data, loading, setLoadingState]);
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<div className="kpi-widget">
|
|
296
|
-
<div className="kpi-value">{kpiData?.totalCount}</div>
|
|
297
|
-
<div className="kpi-label">Total Assets</div>
|
|
298
|
-
</div>
|
|
299
|
-
);
|
|
300
|
-
};
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## Common Patterns
|
|
304
|
-
|
|
305
|
-
### Chart Widget
|
|
306
|
-
- Use `widgetType: "CHART"`
|
|
307
|
-
- Implement responsive chart sizing
|
|
308
|
-
- Support time range filtering
|
|
309
|
-
- Show data trends and comparisons
|
|
310
|
-
|
|
311
|
-
### List Widget
|
|
312
|
-
- Use `widgetType: "LIST"`
|
|
313
|
-
- Implement pagination for large datasets
|
|
314
|
-
- Support asset/customer filtering
|
|
315
|
-
- Provide links to detailed views
|
|
316
|
-
|
|
317
|
-
### Map Widget
|
|
318
|
-
- Use `widgetType: "MAP"`
|
|
319
|
-
- Set `allowFullWidth: true` for better visibility
|
|
320
|
-
- Support location-based filtering
|
|
321
|
-
- Show asset positions and movement
|
|
322
|
-
|
|
323
|
-
DO NOT use generic React widget libraries - always use the Trackunit IrisX App SDK widget patterns and components.
|