@vention/machine-apps-components 0.2.8

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 ADDED
@@ -0,0 +1,819 @@
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 that can be shown/hidden
99
+ - Button enable/disable states
100
+ - Custom styling for buttons
101
+
102
+ #### Props
103
+
104
+ ```typescript
105
+ interface ButtonConfig {
106
+ id: string // Unique identifier for the button
107
+ label: string // Button text
108
+ onClick?: () => void // Click handler
109
+ backgroundColor?: string // Button background color (e.g., "#4CAF50")
110
+ backgroundColorHover?: string // Hover background color
111
+ borderColor?: string // Border color
112
+ textColor?: string // Text color
113
+ width?: number // Button width in pixels (default: 208)
114
+ height?: number // Button height in pixels (default: 80)
115
+ icon?: ReactNode // Icon to display when enabled
116
+ iconDisabled?: ReactNode // Icon to display when disabled
117
+ }
118
+
119
+ interface StatusTopBarProps {
120
+ statusLabel?: string // Label for the status indicator
121
+ dotColor?: string // Color of the status dot
122
+ buttonConfigs?: ButtonConfig[] // Array of button configurations
123
+ visibleButtons?: string[] // Button IDs to show (controlled)
124
+ disabledButtons?: string[] // Button IDs to disable (controlled)
125
+ }
126
+ ```
127
+
128
+ #### API Methods (via ref)
129
+
130
+ ```typescript
131
+ interface StatusTopBarHandle {
132
+ showButtons: (buttonIds: string[]) => void // Show specific buttons
133
+ hideAllButtons: () => void // Hide all buttons
134
+ disableButton: (buttonId: string) => void // Disable a specific button
135
+ enableButton: (buttonId: string) => void // Enable a specific button
136
+ disableAllButtons: () => void // Disable all buttons
137
+ enableAllButtons: () => void // Enable all buttons
138
+ }
139
+ ```
140
+
141
+ #### Example Usage
142
+
143
+ ##### Basic Usage
144
+
145
+ ```tsx
146
+ import { StatusTopBar } from "@vention/machine-apps-components"
147
+ import { useRef } from "react"
148
+ import type { StatusTopBarHandle } from "@vention/machine-apps-components"
149
+
150
+ const BUTTON_CONFIGS = [
151
+ {
152
+ id: "enableFreeDrive",
153
+ label: "Enable Free Drive",
154
+ onClick: () => console.log("Free Drive enabled"),
155
+ backgroundColor: "#4CAF50",
156
+ backgroundColorHover: "#45A049",
157
+ textColor: "#FFFFFF",
158
+ },
159
+ {
160
+ id: "start",
161
+ label: "Start",
162
+ onClick: () => console.log("Start clicked"),
163
+ backgroundColor: "#2196F3",
164
+ textColor: "#FFFFFF",
165
+ },
166
+ {
167
+ id: "stop",
168
+ label: "Stop",
169
+ onClick: () => console.log("Stop clicked"),
170
+ backgroundColor: "#F44336",
171
+ textColor: "#FFFFFF",
172
+ },
173
+ ]
174
+
175
+ function App() {
176
+ const statusBarRef = useRef<StatusTopBarHandle>(null)
177
+
178
+ return (
179
+ <StatusTopBar
180
+ ref={statusBarRef}
181
+ statusLabel="Ready"
182
+ dotColor="#4CAF50"
183
+ buttonConfigs={BUTTON_CONFIGS}
184
+ visibleButtons={["start"]}
185
+ />
186
+ )
187
+ }
188
+ ```
189
+
190
+ ##### Controlling Buttons Programmatically
191
+
192
+ ```tsx
193
+ const statusBarRef = useRef<StatusTopBarHandle>(null)
194
+
195
+ // Show specific buttons
196
+ statusBarRef.current?.showButtons(["enableFreeDrive", "start"])
197
+
198
+ // Hide all buttons
199
+ statusBarRef.current?.hideAllButtons()
200
+
201
+ // Disable a specific button
202
+ statusBarRef.current?.disableButton("start")
203
+
204
+ // Enable a specific button
205
+ statusBarRef.current?.enableButton("start")
206
+
207
+ // Disable all buttons
208
+ statusBarRef.current?.disableAllButtons()
209
+
210
+ // Enable all buttons
211
+ statusBarRef.current?.enableAllButtons()
212
+ ```
213
+
214
+ #### Console Testing Commands
215
+
216
+ For development and debugging, the StatusTopBar component exposes its API methods on the `window` object. You can test button visibility and states directly from the browser console:
217
+
218
+ ##### StatusTopBar Commands
219
+
220
+ ```javascript
221
+ // Show specific buttons (pass an array of button IDs)
222
+ window.showButtons(["enableFreeDrive", "start"])
223
+
224
+ // Show a single button
225
+ window.showButtons(["enableFreeDrive"])
226
+
227
+ // Hide all buttons
228
+ window.hideAllButtons()
229
+
230
+ // Disable a specific button
231
+ window.disableButton("start")
232
+
233
+ // Enable a specific button
234
+ window.enableButton("start")
235
+
236
+ // Disable all buttons
237
+ window.disableAllButtons()
238
+
239
+ // Enable all buttons
240
+ window.enableAllButtons()
241
+ ```
242
+
243
+ **Important:** The `showButtons` function requires an **array** of button IDs, not a single string. This is a common mistake when testing in the console.
244
+
245
+ ✅ **Correct:**
246
+
247
+ ```javascript
248
+ window.showButtons(["enableFreeDrive"])
249
+ window.showButtons(["enableFreeDrive", "start", "stop"])
250
+ ```
251
+
252
+ ❌ **Incorrect:**
253
+
254
+ ```javascript
255
+ window.showButtons("enableFreeDrive") // Will cause "visibleButtons.map is not a function" error
256
+ ```
257
+
258
+ ##### NavigationBar Console Testing
259
+
260
+ The NavigationBar doesn't expose window commands because it's controlled by React Router. To test navigation:
261
+
262
+ ```javascript
263
+ // In browser console, navigate programmatically
264
+ window.location.hash = "#/operation"
265
+ window.location.hash = "#/settings"
266
+ window.location.hash = "#/control-center"
267
+ ```
268
+
269
+ Or use React Router's imperative navigation in your component:
270
+
271
+ ```tsx
272
+ import { useNavigate } from "react-router-dom"
273
+
274
+ const navigate = useNavigate()
275
+ navigate("/operation")
276
+ ```
277
+
278
+ ### Logs
279
+
280
+ Reusable logs components for filtering, sorting, and displaying machine logs.
281
+
282
+ #### Exports
283
+
284
+ ```typescript
285
+ import { LogsPanel, LogsTable, LogFilterForm, LogsPagination } from "@vention/machine-apps-components"
286
+ import type {
287
+ LogEntry,
288
+ LogType,
289
+ LogFilterFormValues,
290
+ SortOrder,
291
+ LogsPanelHandle,
292
+ PaginationConfig,
293
+ PaginationMode,
294
+ FetchParams,
295
+ FetchResult,
296
+ } from "@vention/machine-apps-components"
297
+ ```
298
+
299
+ #### Types
300
+
301
+ ```typescript
302
+ type LogType = "error" | "warning" | "info"
303
+
304
+ interface LogEntry {
305
+ id: string
306
+ date: string // ISO or locale string parsable by Date
307
+ type: LogType
308
+ code: string
309
+ message: string
310
+ description: string
311
+ }
312
+
313
+ type SortOrder = "latest" | "oldest"
314
+
315
+ interface LogFilterFormValues {
316
+ fromDate?: string // YYYY-MM-DD
317
+ toDate?: string // YYYY-MM-DD
318
+ logType?: LogType
319
+ sortOrder: SortOrder
320
+ }
321
+
322
+ type PaginationMode = "pagination" | "infinite-scroll" | "none"
323
+
324
+ interface PaginationConfig {
325
+ mode: PaginationMode
326
+ pageSize?: number // Default: 10
327
+ initialPage?: number // Default: 1
328
+ }
329
+
330
+ // Parameters passed to the dataFetcher
331
+ interface FetchParams {
332
+ filters: LogFilterFormValues // Current filter state
333
+ page: number // Current page number
334
+ pageSize: number // Items per page
335
+ }
336
+
337
+ // Expected return type from dataFetcher
338
+ interface FetchResult {
339
+ logs: LogEntry[] // The log entries for this page
340
+ totalCount: number // Total number of logs (after filtering)
341
+ totalPages: number // Total number of pages
342
+ currentPage: number // Current page number
343
+ hasMore: boolean // Whether there are more pages available
344
+ }
345
+
346
+ // Type definition for data fetcher
347
+ type DataFetcher = (params: FetchParams) => Promise<FetchResult>
348
+
349
+ interface LogsPanelHandle {
350
+ refresh: () => Promise<void>
351
+ resetFilters: () => void
352
+ applyFilters: (filters: Partial<LogFilterFormValues>) => void
353
+ getCurrentFilters: () => LogFilterFormValues
354
+ exportLogs: () => LogEntry[]
355
+ }
356
+ ```
357
+
358
+ #### LogsPanel
359
+
360
+ 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.
361
+
362
+ **Features:**
363
+
364
+ - 🔄 Automatic loading state with spinner when fetching data
365
+ - ❌ Built-in error display with VentionAlert
366
+ - 🔍 Filter UI for date range and log type
367
+ - ↕️ Sort UI for latest/oldest
368
+ - 📅 Date formatting (YYYY-MM-DD h:mm:ssa)
369
+ - 🎨 Type-based icons (error, warning, info)
370
+ - 📄 Pagination support (standard pagination or infinite scroll)
371
+ - 🎯 Imperative API for programmatic control
372
+ - 🔔 Event callbacks for integration
373
+ - 🎨 Customizable styling and empty states
374
+ - ⚡ Performance optimized with React.memo
375
+
376
+ ##### How It Works
377
+
378
+ 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:
379
+
380
+ ```tsx
381
+ import { LogsPanel } from "@vention/machine-apps-components"
382
+ import type { FetchParams, FetchResult } from "@vention/machine-apps-components"
383
+
384
+ const fetchLogs = async (params: FetchParams): Promise<FetchResult> => {
385
+ // params contains:
386
+ // - params.filters.fromDate
387
+ // - params.filters.toDate
388
+ // - params.filters.logType
389
+ // - params.filters.sortOrder
390
+ // - params.page
391
+ // - params.pageSize
392
+
393
+ const response = await fetch(
394
+ `/api/logs?${new URLSearchParams({
395
+ fromDate: params.filters.fromDate || "",
396
+ toDate: params.filters.toDate || "",
397
+ logType: params.filters.logType || "",
398
+ sortOrder: params.filters.sortOrder,
399
+ page: params.page.toString(),
400
+ pageSize: params.pageSize.toString(),
401
+ })}`
402
+ )
403
+
404
+ const data = await response.json()
405
+
406
+ return {
407
+ logs: data.logs,
408
+ totalCount: data.total,
409
+ totalPages: Math.ceil(data.total / params.pageSize),
410
+ currentPage: params.page,
411
+ hasMore: params.page < Math.ceil(data.total / params.pageSize),
412
+ }
413
+ }
414
+
415
+ ;<LogsPanel
416
+ dataFetcher={fetchLogs}
417
+ pagination={{
418
+ mode: "pagination",
419
+ pageSize: 20,
420
+ }}
421
+ />
422
+ ```
423
+
424
+ **Important:** When using `dataFetcher`, you are responsible for:
425
+
426
+ - ✅ Filtering logs by date range and type
427
+ - ✅ Sorting logs by date (latest/oldest)
428
+ - ✅ Paginating the results
429
+ - ✅ Returning pagination metadata (totalCount, totalPages, hasMore)
430
+
431
+ The component handles:
432
+
433
+ - ✅ Rendering the filter UI
434
+ - ✅ Managing filter state
435
+ - ✅ Calling your `dataFetcher` when filters/page changes
436
+ - ✅ Loading and error states
437
+ - ✅ Displaying the results
438
+
439
+ ##### Complete Client-Side Example
440
+
441
+ Here's a complete example showing how to implement filtering, sorting, and pagination on the client-side:
442
+
443
+ ```tsx
444
+ import { useCallback, useMemo } from "react"
445
+ import { LogsPanel } from "@vention/machine-apps-components"
446
+ import type { FetchParams, FetchResult, LogEntry } from "@vention/machine-apps-components"
447
+ import dayjs from "dayjs"
448
+
449
+ function LogsPage() {
450
+ // Your data source (could come from props, context, etc.)
451
+ const allLogs: LogEntry[] = useMemo(
452
+ () => [
453
+ { id: "1", date: "2025-10-07T14:00:00Z", type: "error", code: "ERR_001", message: "Error", description: "..." },
454
+ { id: "2", date: "2025-10-07T13:00:00Z", type: "warning", code: "WARN_001", message: "Warning", description: "..." },
455
+ { id: "3", date: "2025-10-07T12:00:00Z", type: "info", code: "INFO_001", message: "Info", description: "..." },
456
+ // ... more logs
457
+ ],
458
+ []
459
+ )
460
+
461
+ const fetchLogs = useCallback(
462
+ async (params: FetchParams): Promise<FetchResult> => {
463
+ // Simulate network delay (optional)
464
+ await new Promise(resolve => setTimeout(resolve, 500))
465
+
466
+ // 1. Apply filtering
467
+ let filtered = [...allLogs]
468
+
469
+ // Filter by date range
470
+ if (params.filters.fromDate) {
471
+ const fromTimestamp = dayjs(params.filters.fromDate).valueOf()
472
+ filtered = filtered.filter(log => dayjs(log.date).valueOf() >= fromTimestamp)
473
+ }
474
+
475
+ if (params.filters.toDate) {
476
+ const toTimestamp = dayjs(params.filters.toDate).endOf("day").valueOf()
477
+ filtered = filtered.filter(log => dayjs(log.date).valueOf() <= toTimestamp)
478
+ }
479
+
480
+ // Filter by log type
481
+ if (params.filters.logType) {
482
+ filtered = filtered.filter(log => log.type === params.filters.logType)
483
+ }
484
+
485
+ // 2. Apply sorting
486
+ filtered.sort((a, b) => {
487
+ const aTime = dayjs(a.date).valueOf()
488
+ const bTime = dayjs(b.date).valueOf()
489
+ return params.filters.sortOrder === "latest" ? bTime - aTime : aTime - bTime
490
+ })
491
+
492
+ // 3. Apply pagination
493
+ const totalCount = filtered.length
494
+ const totalPages = Math.ceil(totalCount / params.pageSize)
495
+ const start = (params.page - 1) * params.pageSize
496
+ const end = start + params.pageSize
497
+ const paginated = filtered.slice(start, end)
498
+
499
+ // 4. Return result
500
+ return {
501
+ logs: paginated,
502
+ totalCount,
503
+ totalPages,
504
+ currentPage: params.page,
505
+ hasMore: end < totalCount,
506
+ }
507
+ },
508
+ [allLogs]
509
+ )
510
+
511
+ return (
512
+ <LogsPanel
513
+ dataFetcher={fetchLogs}
514
+ pagination={{
515
+ mode: "pagination",
516
+ pageSize: 10,
517
+ }}
518
+ />
519
+ )
520
+ }
521
+ ```
522
+
523
+ ##### Props
524
+
525
+ ```typescript
526
+ interface LogsPanelProps {
527
+ // Required - Data fetcher function
528
+ dataFetcher: (params: FetchParams) => Promise<FetchResult>
529
+
530
+ // Optional - Initial state
531
+ initialFilters?: Partial<LogFilterFormValues>
532
+
533
+ // Optional - Error handling
534
+ onError?: (error: unknown) => void
535
+
536
+ // Optional - Pagination
537
+ pagination?: PaginationConfig
538
+
539
+ // Optional - Event callbacks
540
+ onFilterChange?: (filters: LogFilterFormValues) => void
541
+ onLogClick?: (log: LogEntry) => void
542
+
543
+ // Optional - Customization
544
+ className?: string
545
+ tableHeight?: string | number
546
+ emptyStateMessage?: string
547
+ emptyStateIcon?: ReactNode
548
+ }
549
+ ```
550
+
551
+ ##### States
552
+
553
+ - **Loading**: Shows VentionSpinner with "Loading logs..." message
554
+ - **Error**: Displays VentionAlert with error title and description
555
+ - **Empty**: Shows customizable empty state message
556
+ - **Success**: Renders filterable/sortable table with data
557
+
558
+ ##### Initial Filters
559
+
560
+ ```tsx
561
+ <LogsPanel
562
+ data={logs}
563
+ initialFilters={{
564
+ fromDate: "2025-09-01",
565
+ toDate: "2025-09-30",
566
+ logType: "error",
567
+ sortOrder: "latest",
568
+ }}
569
+ />
570
+ ```
571
+
572
+ ##### Pagination
573
+
574
+ ```tsx
575
+ // Standard pagination (default: 10 items per page)
576
+ <LogsPanel
577
+ data={logs}
578
+ pagination={{
579
+ mode: "pagination",
580
+ pageSize: 20,
581
+ initialPage: 1,
582
+ }}
583
+ />
584
+
585
+ // Infinite scroll (loads more as you scroll)
586
+ <LogsPanel
587
+ data={logs}
588
+ pagination={{
589
+ mode: "infinite-scroll",
590
+ pageSize: 10,
591
+ }}
592
+ />
593
+
594
+ // No pagination (show all logs)
595
+ <LogsPanel
596
+ data={logs}
597
+ pagination={{ mode: "none" }}
598
+ />
599
+
600
+ // Default behavior (no pagination prop = show all logs)
601
+ <LogsPanel data={logs} />
602
+ ```
603
+
604
+ ##### Event Callbacks
605
+
606
+ Get notified when user interacts with the component:
607
+
608
+ ```tsx
609
+ import { LogsPanel } from "@vention/machine-apps-components"
610
+ import type { LogEntry, LogFilterFormValues } from "@vention/machine-apps-components"
611
+ ;<LogsPanel
612
+ data={logs}
613
+ onFilterChange={(filters: LogFilterFormValues) => {
614
+ console.log("User changed filters:", filters)
615
+ // Track analytics, sync to URL params, etc.
616
+ }}
617
+ onLogClick={(log: LogEntry) => {
618
+ console.log("User clicked log:", log)
619
+ // Show details modal, navigate to details page, etc.
620
+ }}
621
+ />
622
+ ```
623
+
624
+ ##### Imperative API
625
+
626
+ Control the component programmatically using a ref:
627
+
628
+ ```tsx
629
+ import { useRef } from "react"
630
+ import { LogsPanel } from "@vention/machine-apps-components"
631
+ import type { LogsPanelHandle } from "@vention/machine-apps-components"
632
+
633
+ function MyComponent() {
634
+ const logsPanelRef = useRef<LogsPanelHandle>(null)
635
+
636
+ const handleRefresh = async () => {
637
+ // Manually refresh async data
638
+ await logsPanelRef.current?.refresh()
639
+ }
640
+
641
+ const handleResetFilters = () => {
642
+ // Reset all filters to default
643
+ logsPanelRef.current?.resetFilters()
644
+ }
645
+
646
+ const handleShowErrors = () => {
647
+ // Programmatically apply filters
648
+ logsPanelRef.current?.applyFilters({ logType: "error" })
649
+ }
650
+
651
+ const handleExport = () => {
652
+ // Get current filtered/sorted logs
653
+ const logs = logsPanelRef.current?.exportLogs()
654
+ if (logs) {
655
+ // Export to CSV, JSON, etc.
656
+ downloadAsCSV(logs)
657
+ }
658
+ }
659
+
660
+ const handleGetFilters = () => {
661
+ // Get current filter state
662
+ const filters = logsPanelRef.current?.getCurrentFilters()
663
+ console.log("Current filters:", filters)
664
+ }
665
+
666
+ return (
667
+ <>
668
+ <div>
669
+ <button onClick={handleRefresh}>Refresh</button>
670
+ <button onClick={handleResetFilters}>Reset Filters</button>
671
+ <button onClick={handleShowErrors}>Show Errors Only</button>
672
+ <button onClick={handleExport}>Export Logs</button>
673
+ </div>
674
+ <LogsPanel ref={logsPanelRef} data={fetchLogs} />
675
+ </>
676
+ )
677
+ }
678
+ ```
679
+
680
+ **Available Methods:**
681
+
682
+ - `refresh()` - Manually re-fetch data with current filters/page
683
+ - `resetFilters()` - Reset all filters to their default values
684
+ - `applyFilters(filters)` - Programmatically set filters
685
+ - `getCurrentFilters()` - Get the current filter state
686
+ - `exportLogs()` - Get currently displayed logs array
687
+
688
+ ##### Customization
689
+
690
+ Customize the appearance and behavior:
691
+
692
+ ```tsx
693
+ import { LogsPanel } from "@vention/machine-apps-components"
694
+ import { VentionIcon } from "@ventionco/machine-ui"
695
+ ;<LogsPanel
696
+ data={logs}
697
+ className="my-custom-logs-panel"
698
+ tableHeight="600px"
699
+ emptyStateMessage="No logs match your filters"
700
+ emptyStateIcon={<VentionIcon type="inbox" size={48} color="gray" />}
701
+ />
702
+ ```
703
+
704
+ **Customization Props:**
705
+
706
+ - `className` - Add custom CSS class to the root container
707
+ - `tableHeight` - Set custom table height (e.g., "500px", 600)
708
+ - `emptyStateMessage` - Custom message when no logs (default: "You have no logs")
709
+ - `emptyStateIcon` - Custom React node to display above empty message
710
+
711
+ #### LogsTable
712
+
713
+ Just the table component. Useful if you want to handle filtering/sorting yourself or compose your own custom layout with filters.
714
+
715
+ ```tsx
716
+ import { LogsTable } from "@vention/machine-apps-components"
717
+ import type { LogEntry } from "@vention/machine-apps-components"
718
+
719
+ <LogsTable
720
+ logs={logs}
721
+ onLogClick={log => showDetailsModal(log)}
722
+ tableHeight="500px"
723
+ emptyStateMessage="No logs available"
724
+ />
725
+ ```
726
+
727
+ **Props:**
728
+
729
+ ```typescript
730
+ interface LogsTableProps {
731
+ logs?: LogEntry[]
732
+ isLoading?: boolean
733
+ error?: string | null
734
+ onLoadMoreLogs?: () => void // For infinite scroll
735
+ hasMoreLogs?: boolean // For infinite scroll
736
+ onLogClick?: (log: LogEntry) => void
737
+ tableHeight?: string | number
738
+ emptyStateMessage?: string
739
+ emptyStateIcon?: ReactNode
740
+ }
741
+ ```
742
+
743
+ #### LogFilterForm
744
+
745
+ Just the filter UI. Use this when you want full control over the filtering logic.
746
+
747
+ ```tsx
748
+ import { LogFilterForm } from "@vention/machine-apps-components"
749
+ import type { LogFilterFormValues } from "@vention/machine-apps-components"
750
+
751
+ const handleFilterChange = (filters: LogFilterFormValues) => {
752
+ // Apply filters to your own data source
753
+ const filtered = myLogs.filter(log => {
754
+ // Your custom filter logic
755
+ })
756
+ }
757
+
758
+ const handleReset = () => {
759
+ // Handle reset
760
+ }
761
+
762
+ ;<LogFilterForm onFilterChange={handleFilterChange} onReset={handleReset} initialFilters={{ logType: "error" }} />
763
+ ```
764
+
765
+ #### LogsPagination
766
+
767
+ Just the pagination controls. Use this for custom pagination implementations.
768
+
769
+ ```tsx
770
+ import { LogsPagination } from "@vention/machine-apps-components"
771
+ ;<LogsPagination currentPage={currentPage} totalPages={totalPages} onPageChange={page => setCurrentPage(page)} />
772
+ ```
773
+
774
+ ## Development
775
+
776
+ ### Running Tests
777
+
778
+ ```bash
779
+ # Run tests
780
+ nx test machine-apps-components
781
+
782
+ # Run tests with coverage
783
+ nx test machine-apps-components --coverage
784
+
785
+ # Run tests in watch mode
786
+ cd projects/machine-code/libs/machine-apps-components
787
+ npx vitest
788
+ ```
789
+
790
+ ### Testing Setup
791
+
792
+ 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:
793
+
794
+ - `ThemeProvider` with `machineUiTheme`
795
+ - `MemoryRouter` for routing
796
+
797
+ Example test:
798
+
799
+ ```tsx
800
+ import { renderWithProviders } from "../../test-utils"
801
+ import { NavigationBar } from "./navigation-bar"
802
+
803
+ it("should render correctly", () => {
804
+ renderWithProviders(<NavigationBar navigationItems={mockNavigationItems} />)
805
+ expect(screen.getByText("Operation")).toBeDefined()
806
+ })
807
+ ```
808
+
809
+ ## Building
810
+
811
+ ```bash
812
+ nx build machine-apps-components
813
+ ```
814
+
815
+ ## Linting
816
+
817
+ ```bash
818
+ nx lint machine-apps-components
819
+ ```