@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.
Files changed (50) hide show
  1. package/README.md +859 -0
  2. package/index.esm.d.ts +1 -0
  3. package/index.esm.js +3658 -0
  4. package/package.json +40 -0
  5. package/src/constants/z-index.d.ts +8 -0
  6. package/src/contexts/i18n-context.d.ts +12 -0
  7. package/src/contexts/i18n-provider.d.ts +13 -0
  8. package/src/hooks/use-auto-scroll-input.d.ts +6 -0
  9. package/src/hooks/use-i18n.d.ts +19 -0
  10. package/src/i18n/config.d.ts +8 -0
  11. package/src/i18n/locales/de.d.ts +131 -0
  12. package/src/i18n/locales/en.d.ts +131 -0
  13. package/src/i18n/locales/es.d.ts +131 -0
  14. package/src/i18n/locales/fr.d.ts +131 -0
  15. package/src/i18n/utils.d.ts +13 -0
  16. package/src/index.d.ts +27 -0
  17. package/src/lib/action-button/action-button.d.ts +14 -0
  18. package/src/lib/action-button/action-button.stories.d.ts +15 -0
  19. package/src/lib/file-upload-panel/file-upload-panel.d.ts +23 -0
  20. package/src/lib/file-upload-panel/file-upload-panel.stories.d.ts +12 -0
  21. package/src/lib/i18n-settings/i18n-settings.d.ts +8 -0
  22. package/src/lib/i18n-settings/i18n-settings.stories.d.ts +7 -0
  23. package/src/lib/logs/log-filter-form.d.ts +9 -0
  24. package/src/lib/logs/logs-pagination.d.ts +8 -0
  25. package/src/lib/logs/logs-panel.d.ts +59 -0
  26. package/src/lib/logs/logs-panel.stories.d.ts +6 -0
  27. package/src/lib/logs/logs-table.d.ts +15 -0
  28. package/src/lib/navigation-bar/navigation-bar-item.d.ts +17 -0
  29. package/src/lib/navigation-bar/navigation-bar.d.ts +20 -0
  30. package/src/lib/navigation-bar/navigation-bar.stories.d.ts +6 -0
  31. package/src/lib/navigation-bar/navigation-confirmation-modal.d.ts +7 -0
  32. package/src/lib/navigation-bar/password-protection-modal.d.ts +8 -0
  33. package/src/lib/navigation-bar/password-protection-modal.stories.d.ts +6 -0
  34. package/src/lib/navigation-bar/time-label.d.ts +6 -0
  35. package/src/lib/product-form-list/product-form-list.d.ts +27 -0
  36. package/src/lib/product-form-list/product-form-list.stories.d.ts +18 -0
  37. package/src/lib/settings-page/settings-page.d.ts +11 -0
  38. package/src/lib/settings-page/settings-page.stories.d.ts +9 -0
  39. package/src/lib/sidebar/sidebar.d.ts +18 -0
  40. package/src/lib/sidebar/sidebar.stories.d.ts +8 -0
  41. package/src/lib/status-top-bar/status-top-bar-button.d.ts +17 -0
  42. package/src/lib/status-top-bar/status-top-bar.d.ts +27 -0
  43. package/src/lib/status-top-bar/status-top-bar.stories.d.ts +10 -0
  44. package/src/lib/step-progress-circle/step-progress-circle.d.ts +14 -0
  45. package/src/lib/step-progress-circle/step-progress-circle.stories.d.ts +16 -0
  46. package/src/lib/utils/api-config-utils.d.ts +34 -0
  47. package/src/lib/utils/device-utils.d.ts +14 -0
  48. package/src/test-setup.d.ts +1 -0
  49. package/src/test-utils.d.ts +13 -0
  50. 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
+ ```