@vention/machine-apps-components 0.0.0-dev.4527.a3d343ca7
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/README.md +859 -0
- package/index.esm.d.ts +1 -0
- package/index.esm.js +3658 -0
- package/package.json +40 -0
- package/src/constants/z-index.d.ts +8 -0
- package/src/contexts/i18n-context.d.ts +12 -0
- package/src/contexts/i18n-provider.d.ts +13 -0
- package/src/hooks/use-auto-scroll-input.d.ts +6 -0
- package/src/hooks/use-i18n.d.ts +19 -0
- package/src/i18n/config.d.ts +8 -0
- package/src/i18n/locales/de.d.ts +131 -0
- package/src/i18n/locales/en.d.ts +131 -0
- package/src/i18n/locales/es.d.ts +131 -0
- package/src/i18n/locales/fr.d.ts +131 -0
- package/src/i18n/utils.d.ts +13 -0
- package/src/index.d.ts +27 -0
- package/src/lib/action-button/action-button.d.ts +14 -0
- package/src/lib/action-button/action-button.stories.d.ts +15 -0
- package/src/lib/file-upload-panel/file-upload-panel.d.ts +23 -0
- package/src/lib/file-upload-panel/file-upload-panel.stories.d.ts +12 -0
- package/src/lib/i18n-settings/i18n-settings.d.ts +8 -0
- package/src/lib/i18n-settings/i18n-settings.stories.d.ts +7 -0
- package/src/lib/logs/log-filter-form.d.ts +9 -0
- package/src/lib/logs/logs-pagination.d.ts +8 -0
- package/src/lib/logs/logs-panel.d.ts +59 -0
- package/src/lib/logs/logs-panel.stories.d.ts +6 -0
- package/src/lib/logs/logs-table.d.ts +15 -0
- package/src/lib/navigation-bar/navigation-bar-item.d.ts +17 -0
- package/src/lib/navigation-bar/navigation-bar.d.ts +20 -0
- package/src/lib/navigation-bar/navigation-bar.stories.d.ts +6 -0
- package/src/lib/navigation-bar/navigation-confirmation-modal.d.ts +7 -0
- package/src/lib/navigation-bar/password-protection-modal.d.ts +8 -0
- package/src/lib/navigation-bar/password-protection-modal.stories.d.ts +6 -0
- package/src/lib/navigation-bar/time-label.d.ts +6 -0
- package/src/lib/product-form-list/product-form-list.d.ts +27 -0
- package/src/lib/product-form-list/product-form-list.stories.d.ts +18 -0
- package/src/lib/settings-page/settings-page.d.ts +11 -0
- package/src/lib/settings-page/settings-page.stories.d.ts +9 -0
- package/src/lib/sidebar/sidebar.d.ts +18 -0
- package/src/lib/sidebar/sidebar.stories.d.ts +8 -0
- package/src/lib/status-top-bar/status-top-bar-button.d.ts +17 -0
- package/src/lib/status-top-bar/status-top-bar.d.ts +27 -0
- package/src/lib/status-top-bar/status-top-bar.stories.d.ts +10 -0
- package/src/lib/step-progress-circle/step-progress-circle.d.ts +14 -0
- package/src/lib/step-progress-circle/step-progress-circle.stories.d.ts +16 -0
- package/src/lib/utils/api-config-utils.d.ts +34 -0
- package/src/lib/utils/device-utils.d.ts +14 -0
- package/src/test-setup.d.ts +1 -0
- package/src/test-utils.d.ts +13 -0
- package/src/types/user-level.d.ts +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
# Machine Apps Components
|
|
2
|
+
|
|
3
|
+
Reusable components for machine applications.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### NavigationBar
|
|
8
|
+
|
|
9
|
+
A navigation bar component with support for:
|
|
10
|
+
|
|
11
|
+
- Multiple navigation items with icons
|
|
12
|
+
- Control Center button
|
|
13
|
+
- Support button
|
|
14
|
+
- Optional timer display
|
|
15
|
+
|
|
16
|
+
#### Props
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
interface NavigationItem {
|
|
20
|
+
label: string
|
|
21
|
+
path: string
|
|
22
|
+
icon: ReactNode
|
|
23
|
+
onClick?: () => void // Optional: Custom click handler (overrides default navigation)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface NavigationBarProps {
|
|
27
|
+
navigationItems: NavigationItem[]
|
|
28
|
+
showTimer?: boolean // Default: true
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Example Usage
|
|
33
|
+
|
|
34
|
+
##### Basic Usage
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { NavigationBar } from "@vention/machine-apps-components"
|
|
38
|
+
import { VentionIcon } from "@ventionco/machine-ui"
|
|
39
|
+
|
|
40
|
+
const NAV_ITEMS = [
|
|
41
|
+
{
|
|
42
|
+
label: "Operation",
|
|
43
|
+
path: "/operation",
|
|
44
|
+
icon: <VentionIcon size={32} type="category-filled" color="white" />,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: "Settings",
|
|
48
|
+
path: "/settings",
|
|
49
|
+
icon: <VentionIcon size={32} type="tool-1" color="white" />,
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
// With timer (default)
|
|
54
|
+
<NavigationBar navigationItems={NAV_ITEMS} />
|
|
55
|
+
|
|
56
|
+
// Without timer
|
|
57
|
+
<NavigationBar navigationItems={NAV_ITEMS} showTimer={false} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
##### Custom Click Handlers
|
|
61
|
+
|
|
62
|
+
You can override the default navigation behavior by providing custom click handlers:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Custom handler for navigation items
|
|
66
|
+
const NAV_ITEMS = [
|
|
67
|
+
{
|
|
68
|
+
label: "Settings",
|
|
69
|
+
path: "/settings",
|
|
70
|
+
icon: <VentionIcon size={32} type="tool-1" color="white" />,
|
|
71
|
+
onClick: () => {
|
|
72
|
+
// Show confirmation before navigating
|
|
73
|
+
if (confirm("You have unsaved changes. Continue?")) {
|
|
74
|
+
navigate("/settings")
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: "Logs",
|
|
80
|
+
path: "/logs",
|
|
81
|
+
icon: <VentionIcon size={32} type="alarm-bell-filled" color="white" />,
|
|
82
|
+
onClick: () => {
|
|
83
|
+
// Do something before navigating
|
|
84
|
+
localStorage.setItem("lastView", "operation")
|
|
85
|
+
navigate("/logs")
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Note:** When a custom `onClick` handler is provided, it completely overrides the default navigation behavior. The handler is responsible for any navigation logic if needed.
|
|
92
|
+
|
|
93
|
+
### StatusTopBar
|
|
94
|
+
|
|
95
|
+
A status bar component positioned at the top of the screen that displays:
|
|
96
|
+
|
|
97
|
+
- Status indicator with label and colored dot
|
|
98
|
+
- Configurable action buttons with visibility and disabled states
|
|
99
|
+
- Custom styling for buttons
|
|
100
|
+
|
|
101
|
+
**Key Features:**
|
|
102
|
+
|
|
103
|
+
- ✅ Declarative, config-based API
|
|
104
|
+
- ✅ Fully controlled component (no internal state)
|
|
105
|
+
- ✅ Type-safe button configurations
|
|
106
|
+
- ✅ Easy to test and reason about
|
|
107
|
+
|
|
108
|
+
#### Props
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface ButtonConfig {
|
|
112
|
+
id: string // Unique identifier for the button
|
|
113
|
+
label: string // Button text
|
|
114
|
+
onClick?: () => void // Click handler
|
|
115
|
+
visible?: boolean // Show/hide button (default: true)
|
|
116
|
+
disabled?: boolean // Enable/disable button (default: false)
|
|
117
|
+
backgroundColor?: string // Button background color (e.g., "#4CAF50")
|
|
118
|
+
backgroundColorHover?: string // Hover background color
|
|
119
|
+
borderColor?: string // Border color
|
|
120
|
+
textColor?: string // Text color
|
|
121
|
+
width?: number // Button width in pixels (default: 208)
|
|
122
|
+
height?: number // Button height in pixels (default: 80)
|
|
123
|
+
icon?: ReactNode // Icon to display when enabled
|
|
124
|
+
iconDisabled?: ReactNode // Icon to display when disabled
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface StatusTopBarProps {
|
|
128
|
+
status?: {
|
|
129
|
+
label: string // Status label text
|
|
130
|
+
color: string // Color of the status dot
|
|
131
|
+
}
|
|
132
|
+
buttonConfigs?: ButtonConfig[] // Array of button configurations
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Example Usage
|
|
137
|
+
|
|
138
|
+
##### Basic Usage
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { StatusTopBar, ButtonConfig } from "@vention/machine-apps-components"
|
|
142
|
+
import { VentionIcon } from "@ventionco/machine-ui"
|
|
143
|
+
|
|
144
|
+
function App() {
|
|
145
|
+
const buttonConfigs: ButtonConfig[] = [
|
|
146
|
+
{
|
|
147
|
+
id: "start",
|
|
148
|
+
label: "Start",
|
|
149
|
+
onClick: () => console.log("Start clicked"),
|
|
150
|
+
backgroundColor: "#2196F3",
|
|
151
|
+
textColor: "white",
|
|
152
|
+
icon: <VentionIcon size={32} type="player-play-filled" color="white" />,
|
|
153
|
+
visible: true,
|
|
154
|
+
disabled: false,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "stop",
|
|
158
|
+
label: "Stop",
|
|
159
|
+
onClick: () => console.log("Stop clicked"),
|
|
160
|
+
backgroundColor: "#F44336",
|
|
161
|
+
textColor: "white",
|
|
162
|
+
icon: <VentionIcon size={32} type="player-stop-filled" color="white" />,
|
|
163
|
+
visible: false, // Hidden by default
|
|
164
|
+
disabled: false,
|
|
165
|
+
},
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<StatusTopBar
|
|
170
|
+
status={{
|
|
171
|
+
label: "Machine state: Ready",
|
|
172
|
+
color: "#4CAF50",
|
|
173
|
+
}}
|
|
174
|
+
buttonConfigs={buttonConfigs}
|
|
175
|
+
/>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
##### Dynamic State Management
|
|
181
|
+
|
|
182
|
+
The component is fully controlled - all button states are derived from your application state:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { StatusTopBar, ButtonConfig } from "@vention/machine-apps-components"
|
|
186
|
+
import { useMemo } from "react"
|
|
187
|
+
|
|
188
|
+
function MachineApp() {
|
|
189
|
+
const { machineState, isRunning, canStart } = useMachineState()
|
|
190
|
+
|
|
191
|
+
const buttonConfigs: ButtonConfig[] = useMemo(() => {
|
|
192
|
+
const configs: ButtonConfig[] = []
|
|
193
|
+
|
|
194
|
+
// Start button - visible when not running
|
|
195
|
+
if (!isRunning) {
|
|
196
|
+
configs.push({
|
|
197
|
+
id: "start",
|
|
198
|
+
label: "Start",
|
|
199
|
+
onClick: handleStart,
|
|
200
|
+
backgroundColor: "#2196F3",
|
|
201
|
+
textColor: "white",
|
|
202
|
+
visible: true,
|
|
203
|
+
disabled: !canStart, // Disabled if prerequisites not met
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Stop button - visible when running
|
|
208
|
+
if (isRunning) {
|
|
209
|
+
configs.push({
|
|
210
|
+
id: "stop",
|
|
211
|
+
label: "Stop",
|
|
212
|
+
onClick: handleStop,
|
|
213
|
+
backgroundColor: "#F44336",
|
|
214
|
+
textColor: "white",
|
|
215
|
+
visible: true,
|
|
216
|
+
disabled: false,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Home button - always visible
|
|
221
|
+
configs.push({
|
|
222
|
+
id: "home",
|
|
223
|
+
label: "Home",
|
|
224
|
+
onClick: handleHome,
|
|
225
|
+
borderColor: "#E2E8F0",
|
|
226
|
+
textColor: "#1A202C",
|
|
227
|
+
visible: true,
|
|
228
|
+
disabled: isRunning, // Can't home while running
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return configs
|
|
232
|
+
}, [isRunning, canStart])
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<StatusTopBar
|
|
236
|
+
status={{
|
|
237
|
+
label: `Machine state: ${machineState}`,
|
|
238
|
+
color: getStatusColor(machineState),
|
|
239
|
+
}}
|
|
240
|
+
buttonConfigs={buttonConfigs}
|
|
241
|
+
/>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
##### Conditional Buttons
|
|
247
|
+
|
|
248
|
+
Easily show/hide buttons based on application state:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
const buttonConfigs: ButtonConfig[] = useMemo(() => {
|
|
252
|
+
const configs: ButtonConfig[] = []
|
|
253
|
+
|
|
254
|
+
// Operation page buttons
|
|
255
|
+
if (currentPage === "operation") {
|
|
256
|
+
configs.push({
|
|
257
|
+
id: "start",
|
|
258
|
+
label: isRunning ? "Stop" : "Start",
|
|
259
|
+
onClick: isRunning ? handleStop : handleStart,
|
|
260
|
+
backgroundColor: isRunning ? "#F44336" : "#2196F3",
|
|
261
|
+
textColor: "white",
|
|
262
|
+
visible: true,
|
|
263
|
+
disabled: false,
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Calibration page buttons
|
|
268
|
+
if (currentPage === "calibration") {
|
|
269
|
+
configs.push({
|
|
270
|
+
id: "freedrive",
|
|
271
|
+
label: isFreeDriveEnabled ? "Disable Free Drive" : "Enable Free Drive",
|
|
272
|
+
onClick: toggleFreeDrive,
|
|
273
|
+
backgroundColor: isFreeDriveEnabled ? "#CBD5E0" : undefined,
|
|
274
|
+
borderColor: !isFreeDriveEnabled ? "#E2E8F0" : undefined,
|
|
275
|
+
textColor: "black",
|
|
276
|
+
visible: true,
|
|
277
|
+
disabled: false,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return configs
|
|
282
|
+
}, [currentPage, isRunning, isFreeDriveEnabled])
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Migration from Imperative API
|
|
286
|
+
|
|
287
|
+
If you're migrating from the old compound component pattern:
|
|
288
|
+
|
|
289
|
+
**Before (compound components):**
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
<StatusTopBar statusLabel="Ready" dotColor="#4CAF50" visibleButtons={["start"]} disabledButtons={["stop"]}>
|
|
293
|
+
<StatusTopBar.Button id="start" label="Start" onClick={handleStart} backgroundColor="#2196F3" textColor="white" />
|
|
294
|
+
<StatusTopBar.Button id="stop" label="Stop" onClick={handleStop} backgroundColor="#F44336" textColor="white" />
|
|
295
|
+
</StatusTopBar>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**After (config-based):**
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
<StatusTopBar
|
|
302
|
+
status={{ label: "Ready", color: "#4CAF50" }}
|
|
303
|
+
buttonConfigs={[
|
|
304
|
+
{ id: "start", label: "Start", onClick: handleStart, backgroundColor: "#2196F3", textColor: "white", visible: true },
|
|
305
|
+
{ id: "stop", label: "Stop", onClick: handleStop, backgroundColor: "#F44336", textColor: "white", visible: false },
|
|
306
|
+
]}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Benefits of the new API:**
|
|
311
|
+
|
|
312
|
+
- Single source of truth for button state
|
|
313
|
+
- No need for refs or imperative methods
|
|
314
|
+
- Easier to test (just pass different configs)
|
|
315
|
+
- Better TypeScript support
|
|
316
|
+
- More predictable behavior
|
|
317
|
+
|
|
318
|
+
### Logs
|
|
319
|
+
|
|
320
|
+
Reusable logs components for filtering, sorting, and displaying machine logs.
|
|
321
|
+
|
|
322
|
+
#### Exports
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { LogsPanel, LogsTable, LogFilterForm, LogsPagination } from "@vention/machine-apps-components"
|
|
326
|
+
import type {
|
|
327
|
+
LogEntry,
|
|
328
|
+
LogType,
|
|
329
|
+
LogFilterFormValues,
|
|
330
|
+
SortOrder,
|
|
331
|
+
LogsPanelHandle,
|
|
332
|
+
PaginationConfig,
|
|
333
|
+
PaginationMode,
|
|
334
|
+
FetchParams,
|
|
335
|
+
FetchResult,
|
|
336
|
+
} from "@vention/machine-apps-components"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Types
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
type LogType = "error" | "warning" | "info"
|
|
343
|
+
|
|
344
|
+
interface LogEntry {
|
|
345
|
+
id: string
|
|
346
|
+
date: string // ISO or locale string parsable by Date
|
|
347
|
+
level: LogType
|
|
348
|
+
code: string
|
|
349
|
+
message: string
|
|
350
|
+
description: string
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
type SortOrder = "latest" | "oldest"
|
|
354
|
+
|
|
355
|
+
interface LogFilterFormValues {
|
|
356
|
+
fromDate?: string // YYYY-MM-DD
|
|
357
|
+
toDate?: string // YYYY-MM-DD
|
|
358
|
+
logType?: LogType
|
|
359
|
+
sortOrder: SortOrder
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
type PaginationMode = "pagination" | "infinite-scroll" | "none"
|
|
363
|
+
|
|
364
|
+
interface PaginationConfig {
|
|
365
|
+
mode: PaginationMode
|
|
366
|
+
pageSize?: number // Default: 10
|
|
367
|
+
initialPage?: number // Default: 1
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Parameters passed to the dataFetcher
|
|
371
|
+
interface FetchParams {
|
|
372
|
+
filters: LogFilterFormValues // Current filter state
|
|
373
|
+
page: number // Current page number
|
|
374
|
+
pageSize: number // Items per page
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Expected return type from dataFetcher
|
|
378
|
+
interface FetchResult {
|
|
379
|
+
logs: LogEntry[] // The log entries for this page
|
|
380
|
+
totalCount: number // Total number of logs (after filtering)
|
|
381
|
+
totalPages: number // Total number of pages
|
|
382
|
+
currentPage: number // Current page number
|
|
383
|
+
hasMore: boolean // Whether there are more pages available
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Type definition for data fetcher
|
|
387
|
+
type DataFetcher = (params: FetchParams) => Promise<FetchResult>
|
|
388
|
+
|
|
389
|
+
interface LogsPanelHandle {
|
|
390
|
+
refresh: () => Promise<void>
|
|
391
|
+
resetFilters: () => void
|
|
392
|
+
applyFilters: (filters: Partial<LogFilterFormValues>) => void
|
|
393
|
+
getCurrentFilters: () => LogFilterFormValues
|
|
394
|
+
exportLogs: () => LogEntry[]
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### LogsPanel
|
|
399
|
+
|
|
400
|
+
A composite component that combines filter UI and table display with loading states and error handling. The component is a **pure presentation component** - it displays data and manages UI state, while delegating all data operations (filtering, sorting, pagination) to your `dataFetcher` function.
|
|
401
|
+
|
|
402
|
+
**Features:**
|
|
403
|
+
|
|
404
|
+
- 🔄 Automatic loading state with spinner when fetching data
|
|
405
|
+
- ❌ Built-in error display with VentionAlert
|
|
406
|
+
- 🔍 Filter UI for date range and log type
|
|
407
|
+
- ↕️ Sort UI for latest/oldest
|
|
408
|
+
- 📅 Date formatting (YYYY-MM-DD h:mm:ssa)
|
|
409
|
+
- 🎨 Type-based icons (error, warning, info)
|
|
410
|
+
- 📄 Pagination support (standard pagination or infinite scroll)
|
|
411
|
+
- 🎯 Imperative API for programmatic control
|
|
412
|
+
- 🔔 Event callbacks for integration
|
|
413
|
+
- 🎨 Customizable styling and empty states
|
|
414
|
+
- ⚡ Performance optimized with React.memo
|
|
415
|
+
|
|
416
|
+
##### How It Works
|
|
417
|
+
|
|
418
|
+
The component calls your `dataFetcher` function whenever filters, sort order, or page changes. You implement the filtering, sorting, and pagination logic, and return the results:
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
import { LogsPanel } from "@vention/machine-apps-components"
|
|
422
|
+
import type { FetchParams, FetchResult } from "@vention/machine-apps-components"
|
|
423
|
+
|
|
424
|
+
const fetchLogs = async (params: FetchParams): Promise<FetchResult> => {
|
|
425
|
+
// params contains:
|
|
426
|
+
// - params.filters.fromDate
|
|
427
|
+
// - params.filters.toDate
|
|
428
|
+
// - params.filters.logType
|
|
429
|
+
// - params.filters.sortOrder
|
|
430
|
+
// - params.page
|
|
431
|
+
// - params.pageSize
|
|
432
|
+
|
|
433
|
+
const response = await fetch(
|
|
434
|
+
`/api/logs?${new URLSearchParams({
|
|
435
|
+
fromDate: params.filters.fromDate || "",
|
|
436
|
+
toDate: params.filters.toDate || "",
|
|
437
|
+
logType: params.filters.logType || "",
|
|
438
|
+
sortOrder: params.filters.sortOrder,
|
|
439
|
+
page: params.page.toString(),
|
|
440
|
+
pageSize: params.pageSize.toString(),
|
|
441
|
+
})}`
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
const data = await response.json()
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
logs: data.logs,
|
|
448
|
+
totalCount: data.total,
|
|
449
|
+
totalPages: Math.ceil(data.total / params.pageSize),
|
|
450
|
+
currentPage: params.page,
|
|
451
|
+
hasMore: params.page < Math.ceil(data.total / params.pageSize),
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
;<LogsPanel
|
|
456
|
+
dataFetcher={fetchLogs}
|
|
457
|
+
pagination={{
|
|
458
|
+
mode: "pagination",
|
|
459
|
+
pageSize: 20,
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Important:** When using `dataFetcher`, you are responsible for:
|
|
465
|
+
|
|
466
|
+
- ✅ Filtering logs by date range and type
|
|
467
|
+
- ✅ Sorting logs by date (latest/oldest)
|
|
468
|
+
- ✅ Paginating the results
|
|
469
|
+
- ✅ Returning pagination metadata (totalCount, totalPages, hasMore)
|
|
470
|
+
|
|
471
|
+
The component handles:
|
|
472
|
+
|
|
473
|
+
- ✅ Rendering the filter UI
|
|
474
|
+
- ✅ Managing filter state
|
|
475
|
+
- ✅ Calling your `dataFetcher` when filters/page changes
|
|
476
|
+
- ✅ Loading and error states
|
|
477
|
+
- ✅ Displaying the results
|
|
478
|
+
|
|
479
|
+
##### Complete Client-Side Example
|
|
480
|
+
|
|
481
|
+
Here's a complete example showing how to implement filtering, sorting, and pagination on the client-side:
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import { useCallback, useMemo } from "react"
|
|
485
|
+
import { LogsPanel } from "@vention/machine-apps-components"
|
|
486
|
+
import type { FetchParams, FetchResult, LogEntry } from "@vention/machine-apps-components"
|
|
487
|
+
import dayjs from "dayjs"
|
|
488
|
+
|
|
489
|
+
function LogsPage() {
|
|
490
|
+
// Your data source (could come from props, context, etc.)
|
|
491
|
+
const allLogs: LogEntry[] = useMemo(
|
|
492
|
+
() => [
|
|
493
|
+
{ id: "1", date: "2025-10-07T14:00:00Z", type: "error", code: "ERR_001", message: "Error", description: "..." },
|
|
494
|
+
{ id: "2", date: "2025-10-07T13:00:00Z", type: "warning", code: "WARN_001", message: "Warning", description: "..." },
|
|
495
|
+
{ id: "3", date: "2025-10-07T12:00:00Z", type: "info", code: "INFO_001", message: "Info", description: "..." },
|
|
496
|
+
// ... more logs
|
|
497
|
+
],
|
|
498
|
+
[]
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
const fetchLogs = useCallback(
|
|
502
|
+
async (params: FetchParams): Promise<FetchResult> => {
|
|
503
|
+
// Simulate network delay (optional)
|
|
504
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
505
|
+
|
|
506
|
+
// 1. Apply filtering
|
|
507
|
+
let filtered = [...allLogs]
|
|
508
|
+
|
|
509
|
+
// Filter by date range
|
|
510
|
+
if (params.filters.fromDate) {
|
|
511
|
+
const fromTimestamp = dayjs(params.filters.fromDate).valueOf()
|
|
512
|
+
filtered = filtered.filter(log => dayjs(log.date).valueOf() >= fromTimestamp)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (params.filters.toDate) {
|
|
516
|
+
const toTimestamp = dayjs(params.filters.toDate).endOf("day").valueOf()
|
|
517
|
+
filtered = filtered.filter(log => dayjs(log.date).valueOf() <= toTimestamp)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Filter by log type
|
|
521
|
+
if (params.filters.logType) {
|
|
522
|
+
filtered = filtered.filter(log => log.type === params.filters.logType)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 2. Apply sorting
|
|
526
|
+
filtered.sort((a, b) => {
|
|
527
|
+
const aTime = dayjs(a.date).valueOf()
|
|
528
|
+
const bTime = dayjs(b.date).valueOf()
|
|
529
|
+
return params.filters.sortOrder === "latest" ? bTime - aTime : aTime - bTime
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
// 3. Apply pagination
|
|
533
|
+
const totalCount = filtered.length
|
|
534
|
+
const totalPages = Math.ceil(totalCount / params.pageSize)
|
|
535
|
+
const start = (params.page - 1) * params.pageSize
|
|
536
|
+
const end = start + params.pageSize
|
|
537
|
+
const paginated = filtered.slice(start, end)
|
|
538
|
+
|
|
539
|
+
// 4. Return result
|
|
540
|
+
return {
|
|
541
|
+
logs: paginated,
|
|
542
|
+
totalCount,
|
|
543
|
+
totalPages,
|
|
544
|
+
currentPage: params.page,
|
|
545
|
+
hasMore: end < totalCount,
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
[allLogs]
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
return (
|
|
552
|
+
<LogsPanel
|
|
553
|
+
dataFetcher={fetchLogs}
|
|
554
|
+
pagination={{
|
|
555
|
+
mode: "pagination",
|
|
556
|
+
pageSize: 10,
|
|
557
|
+
}}
|
|
558
|
+
/>
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
##### Props
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
interface LogsPanelProps {
|
|
567
|
+
// Required - Data fetcher function
|
|
568
|
+
dataFetcher: (params: FetchParams) => Promise<FetchResult>
|
|
569
|
+
|
|
570
|
+
// Optional - Initial state
|
|
571
|
+
initialFilters?: Partial<LogFilterFormValues>
|
|
572
|
+
|
|
573
|
+
// Optional - Error handling
|
|
574
|
+
onError?: (error: unknown) => void
|
|
575
|
+
|
|
576
|
+
// Optional - Pagination
|
|
577
|
+
pagination?: PaginationConfig
|
|
578
|
+
|
|
579
|
+
// Optional - Event callbacks
|
|
580
|
+
onFilterChange?: (filters: LogFilterFormValues) => void
|
|
581
|
+
onLogClick?: (log: LogEntry) => void
|
|
582
|
+
|
|
583
|
+
// Optional - Customization
|
|
584
|
+
className?: string
|
|
585
|
+
tableHeight?: string | number
|
|
586
|
+
emptyStateMessage?: string
|
|
587
|
+
emptyStateIcon?: ReactNode
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
##### States
|
|
592
|
+
|
|
593
|
+
- **Loading**: Shows VentionSpinner with "Loading logs..." message
|
|
594
|
+
- **Error**: Displays VentionAlert with error title and description
|
|
595
|
+
- **Empty**: Shows customizable empty state message
|
|
596
|
+
- **Success**: Renders filterable/sortable table with data
|
|
597
|
+
|
|
598
|
+
##### Initial Filters
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
<LogsPanel
|
|
602
|
+
data={logs}
|
|
603
|
+
initialFilters={{
|
|
604
|
+
fromDate: "2025-09-01",
|
|
605
|
+
toDate: "2025-09-30",
|
|
606
|
+
logType: "error",
|
|
607
|
+
sortOrder: "latest",
|
|
608
|
+
}}
|
|
609
|
+
/>
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
##### Pagination
|
|
613
|
+
|
|
614
|
+
```tsx
|
|
615
|
+
// Standard pagination (default: 10 items per page)
|
|
616
|
+
<LogsPanel
|
|
617
|
+
data={logs}
|
|
618
|
+
pagination={{
|
|
619
|
+
mode: "pagination",
|
|
620
|
+
pageSize: 20,
|
|
621
|
+
initialPage: 1,
|
|
622
|
+
}}
|
|
623
|
+
/>
|
|
624
|
+
|
|
625
|
+
// Infinite scroll (loads more as you scroll)
|
|
626
|
+
<LogsPanel
|
|
627
|
+
data={logs}
|
|
628
|
+
pagination={{
|
|
629
|
+
mode: "infinite-scroll",
|
|
630
|
+
pageSize: 10,
|
|
631
|
+
}}
|
|
632
|
+
/>
|
|
633
|
+
|
|
634
|
+
// No pagination (show all logs)
|
|
635
|
+
<LogsPanel
|
|
636
|
+
data={logs}
|
|
637
|
+
pagination={{ mode: "none" }}
|
|
638
|
+
/>
|
|
639
|
+
|
|
640
|
+
// Default behavior (no pagination prop = show all logs)
|
|
641
|
+
<LogsPanel data={logs} />
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
##### Event Callbacks
|
|
645
|
+
|
|
646
|
+
Get notified when user interacts with the component:
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
import { LogsPanel } from "@vention/machine-apps-components"
|
|
650
|
+
import type { LogEntry, LogFilterFormValues } from "@vention/machine-apps-components"
|
|
651
|
+
;<LogsPanel
|
|
652
|
+
data={logs}
|
|
653
|
+
onFilterChange={(filters: LogFilterFormValues) => {
|
|
654
|
+
console.log("User changed filters:", filters)
|
|
655
|
+
// Track analytics, sync to URL params, etc.
|
|
656
|
+
}}
|
|
657
|
+
onLogClick={(log: LogEntry) => {
|
|
658
|
+
console.log("User clicked log:", log)
|
|
659
|
+
// Show details modal, navigate to details page, etc.
|
|
660
|
+
}}
|
|
661
|
+
/>
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
##### Imperative API
|
|
665
|
+
|
|
666
|
+
Control the component programmatically using a ref:
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
import { useRef } from "react"
|
|
670
|
+
import { LogsPanel } from "@vention/machine-apps-components"
|
|
671
|
+
import type { LogsPanelHandle } from "@vention/machine-apps-components"
|
|
672
|
+
|
|
673
|
+
function MyComponent() {
|
|
674
|
+
const logsPanelRef = useRef<LogsPanelHandle>(null)
|
|
675
|
+
|
|
676
|
+
const handleRefresh = async () => {
|
|
677
|
+
// Manually refresh async data
|
|
678
|
+
await logsPanelRef.current?.refresh()
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const handleResetFilters = () => {
|
|
682
|
+
// Reset all filters to default
|
|
683
|
+
logsPanelRef.current?.resetFilters()
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const handleShowErrors = () => {
|
|
687
|
+
// Programmatically apply filters
|
|
688
|
+
logsPanelRef.current?.applyFilters({ logType: "error" })
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const handleExport = () => {
|
|
692
|
+
// Get current filtered/sorted logs
|
|
693
|
+
const logs = logsPanelRef.current?.exportLogs()
|
|
694
|
+
if (logs) {
|
|
695
|
+
// Export to CSV, JSON, etc.
|
|
696
|
+
downloadAsCSV(logs)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const handleGetFilters = () => {
|
|
701
|
+
// Get current filter state
|
|
702
|
+
const filters = logsPanelRef.current?.getCurrentFilters()
|
|
703
|
+
console.log("Current filters:", filters)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return (
|
|
707
|
+
<>
|
|
708
|
+
<div>
|
|
709
|
+
<button onClick={handleRefresh}>Refresh</button>
|
|
710
|
+
<button onClick={handleResetFilters}>Reset Filters</button>
|
|
711
|
+
<button onClick={handleShowErrors}>Show Errors Only</button>
|
|
712
|
+
<button onClick={handleExport}>Export Logs</button>
|
|
713
|
+
</div>
|
|
714
|
+
<LogsPanel ref={logsPanelRef} data={fetchLogs} />
|
|
715
|
+
</>
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**Available Methods:**
|
|
721
|
+
|
|
722
|
+
- `refresh()` - Manually re-fetch data with current filters/page
|
|
723
|
+
- `resetFilters()` - Reset all filters to their default values
|
|
724
|
+
- `applyFilters(filters)` - Programmatically set filters
|
|
725
|
+
- `getCurrentFilters()` - Get the current filter state
|
|
726
|
+
- `exportLogs()` - Get currently displayed logs array
|
|
727
|
+
|
|
728
|
+
##### Customization
|
|
729
|
+
|
|
730
|
+
Customize the appearance and behavior:
|
|
731
|
+
|
|
732
|
+
```tsx
|
|
733
|
+
import { LogsPanel } from "@vention/machine-apps-components"
|
|
734
|
+
import { VentionIcon } from "@ventionco/machine-ui"
|
|
735
|
+
;<LogsPanel
|
|
736
|
+
data={logs}
|
|
737
|
+
className="my-custom-logs-panel"
|
|
738
|
+
tableHeight="600px"
|
|
739
|
+
emptyStateMessage="No logs match your filters"
|
|
740
|
+
emptyStateIcon={<VentionIcon type="inbox" size={48} color="gray" />}
|
|
741
|
+
/>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Customization Props:**
|
|
745
|
+
|
|
746
|
+
- `className` - Add custom CSS class to the root container
|
|
747
|
+
- `tableHeight` - Set custom table height (e.g., "500px", 600)
|
|
748
|
+
- `emptyStateMessage` - Custom message when no logs (default: "You have no logs")
|
|
749
|
+
- `emptyStateIcon` - Custom React node to display above empty message
|
|
750
|
+
|
|
751
|
+
#### LogsTable
|
|
752
|
+
|
|
753
|
+
Just the table component. Useful if you want to handle filtering/sorting yourself or compose your own custom layout with filters.
|
|
754
|
+
|
|
755
|
+
```tsx
|
|
756
|
+
import { LogsTable } from "@vention/machine-apps-components"
|
|
757
|
+
import type { LogEntry } from "@vention/machine-apps-components"
|
|
758
|
+
|
|
759
|
+
<LogsTable
|
|
760
|
+
logs={logs}
|
|
761
|
+
onLogClick={log => showDetailsModal(log)}
|
|
762
|
+
tableHeight="500px"
|
|
763
|
+
emptyStateMessage="No logs available"
|
|
764
|
+
/>
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
**Props:**
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
interface LogsTableProps {
|
|
771
|
+
logs?: LogEntry[]
|
|
772
|
+
isLoading?: boolean
|
|
773
|
+
error?: string | null
|
|
774
|
+
onLoadMoreLogs?: () => void // For infinite scroll
|
|
775
|
+
hasMoreLogs?: boolean // For infinite scroll
|
|
776
|
+
onLogClick?: (log: LogEntry) => void
|
|
777
|
+
tableHeight?: string | number
|
|
778
|
+
emptyStateMessage?: string
|
|
779
|
+
emptyStateIcon?: ReactNode
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
#### LogFilterForm
|
|
784
|
+
|
|
785
|
+
Just the filter UI. Use this when you want full control over the filtering logic.
|
|
786
|
+
|
|
787
|
+
```tsx
|
|
788
|
+
import { LogFilterForm } from "@vention/machine-apps-components"
|
|
789
|
+
import type { LogFilterFormValues } from "@vention/machine-apps-components"
|
|
790
|
+
|
|
791
|
+
const handleFilterChange = (filters: LogFilterFormValues) => {
|
|
792
|
+
// Apply filters to your own data source
|
|
793
|
+
const filtered = myLogs.filter(log => {
|
|
794
|
+
// Your custom filter logic
|
|
795
|
+
})
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const handleReset = () => {
|
|
799
|
+
// Handle reset
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
;<LogFilterForm onFilterChange={handleFilterChange} onReset={handleReset} initialFilters={{ logType: "error" }} />
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
#### LogsPagination
|
|
806
|
+
|
|
807
|
+
Just the pagination controls. Use this for custom pagination implementations.
|
|
808
|
+
|
|
809
|
+
```tsx
|
|
810
|
+
import { LogsPagination } from "@vention/machine-apps-components"
|
|
811
|
+
;<LogsPagination currentPage={currentPage} totalPages={totalPages} onPageChange={page => setCurrentPage(page)} />
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
## Development
|
|
815
|
+
|
|
816
|
+
### Running Tests
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
# Run tests
|
|
820
|
+
nx test machine-apps-components
|
|
821
|
+
|
|
822
|
+
# Run tests with coverage
|
|
823
|
+
nx test machine-apps-components --coverage
|
|
824
|
+
|
|
825
|
+
# Run tests in watch mode
|
|
826
|
+
cd projects/machine-code/libs/machine-apps-components
|
|
827
|
+
npx vitest
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### Testing Setup
|
|
831
|
+
|
|
832
|
+
This library uses Vitest with jsdom environment for testing React components. Test utilities are provided in `src/test-utils.tsx` that wrap components with necessary providers:
|
|
833
|
+
|
|
834
|
+
- `ThemeProvider` with `machineUiTheme`
|
|
835
|
+
- `MemoryRouter` for routing
|
|
836
|
+
|
|
837
|
+
Example test:
|
|
838
|
+
|
|
839
|
+
```tsx
|
|
840
|
+
import { renderWithProviders } from "../../test-utils"
|
|
841
|
+
import { NavigationBar } from "./navigation-bar"
|
|
842
|
+
|
|
843
|
+
it("should render correctly", () => {
|
|
844
|
+
renderWithProviders(<NavigationBar navigationItems={mockNavigationItems} />)
|
|
845
|
+
expect(screen.getByText("Operation")).toBeDefined()
|
|
846
|
+
})
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
## Building
|
|
850
|
+
|
|
851
|
+
```bash
|
|
852
|
+
nx build machine-apps-components
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
## Linting
|
|
856
|
+
|
|
857
|
+
```bash
|
|
858
|
+
nx lint machine-apps-components
|
|
859
|
+
```
|