@adcops/autocore-react 3.3.73 → 3.3.77

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 (75) hide show
  1. package/dist/assets/HomeMotor.d.ts +4 -0
  2. package/dist/assets/HomeMotor.d.ts.map +1 -0
  3. package/dist/assets/HomeMotor.js +1 -0
  4. package/dist/assets/svg/home_motor.svg +57 -0
  5. package/dist/components/Indicator.d.ts +29 -52
  6. package/dist/components/Indicator.d.ts.map +1 -1
  7. package/dist/components/Indicator.js +1 -1
  8. package/dist/components/ValueInput.d.ts +1 -1
  9. package/dist/components/ValueInput.d.ts.map +1 -1
  10. package/dist/components/ams/AmsProvider.d.ts +7 -0
  11. package/dist/components/ams/AmsProvider.d.ts.map +1 -1
  12. package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
  13. package/dist/components/ams/AssetDetailView.js +1 -1
  14. package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
  15. package/dist/components/ams/AssetRegistryTable.js +1 -1
  16. package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -1
  17. package/dist/components/ams/CalibrationEntryDialog.js +1 -1
  18. package/dist/components/ams/MissingAssetsBanner.d.ts +11 -0
  19. package/dist/components/ams/MissingAssetsBanner.d.ts.map +1 -0
  20. package/dist/components/ams/MissingAssetsBanner.js +1 -0
  21. package/dist/components/ams/PlaceholderHealthPanel.d.ts +3 -0
  22. package/dist/components/ams/PlaceholderHealthPanel.d.ts.map +1 -0
  23. package/dist/components/ams/PlaceholderHealthPanel.js +1 -0
  24. package/dist/components/ams/index.d.ts +2 -0
  25. package/dist/components/ams/index.d.ts.map +1 -1
  26. package/dist/components/ams/index.js +1 -1
  27. package/dist/components/index.d.ts +8 -0
  28. package/dist/components/index.d.ts.map +1 -1
  29. package/dist/components/index.js +1 -1
  30. package/dist/components/network/NetworkPanel.d.ts +8 -0
  31. package/dist/components/network/NetworkPanel.d.ts.map +1 -0
  32. package/dist/components/network/NetworkPanel.js +1 -0
  33. package/dist/components/network/NetworkProvider.d.ts +72 -0
  34. package/dist/components/network/NetworkProvider.d.ts.map +1 -0
  35. package/dist/components/network/NetworkProvider.js +1 -0
  36. package/dist/components/network/StagedChangeBanner.d.ts +8 -0
  37. package/dist/components/network/StagedChangeBanner.d.ts.map +1 -0
  38. package/dist/components/network/StagedChangeBanner.js +1 -0
  39. package/dist/components/network/index.d.ts +7 -0
  40. package/dist/components/network/index.d.ts.map +1 -0
  41. package/dist/components/network/index.js +1 -0
  42. package/dist/components/tis/ProjectManager.d.ts +7 -0
  43. package/dist/components/tis/ProjectManager.d.ts.map +1 -0
  44. package/dist/components/tis/ProjectManager.js +1 -0
  45. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  46. package/dist/components/tis/ResultHistoryTable.js +1 -1
  47. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  48. package/dist/components/tis/TestDataView.js +1 -1
  49. package/dist/components/tis/TestRawDataView.d.ts.map +1 -1
  50. package/dist/components/tis/TestRawDataView.js +1 -1
  51. package/dist/components/tis/TestSetupForm.d.ts +7 -0
  52. package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
  53. package/dist/components/tis/TestSetupForm.js +1 -1
  54. package/package.json +5 -1
  55. package/src/assets/HomeMotor.tsx +37 -0
  56. package/src/assets/svg/home_motor.svg +57 -0
  57. package/src/components/Indicator.tsx +166 -162
  58. package/src/components/ValueInput.tsx +2 -2
  59. package/src/components/ams/AmsProvider.tsx +7 -0
  60. package/src/components/ams/AssetDetailView.tsx +287 -4
  61. package/src/components/ams/AssetRegistryTable.tsx +325 -21
  62. package/src/components/ams/CalibrationEntryDialog.tsx +163 -30
  63. package/src/components/ams/MissingAssetsBanner.tsx +124 -0
  64. package/src/components/ams/PlaceholderHealthPanel.tsx +188 -0
  65. package/src/components/ams/index.ts +2 -0
  66. package/src/components/index.ts +26 -0
  67. package/src/components/network/NetworkPanel.tsx +363 -0
  68. package/src/components/network/NetworkProvider.tsx +349 -0
  69. package/src/components/network/StagedChangeBanner.tsx +101 -0
  70. package/src/components/network/index.ts +17 -0
  71. package/src/components/tis/ProjectManager.tsx +392 -0
  72. package/src/components/tis/ResultHistoryTable.tsx +125 -74
  73. package/src/components/tis/TestDataView.tsx +160 -14
  74. package/src/components/tis/TestRawDataView.tsx +118 -8
  75. package/src/components/tis/TestSetupForm.tsx +42 -1
@@ -6,19 +6,97 @@
6
6
  * <CalibrationEntryDialog>.
7
7
  */
8
8
 
9
- import React, { useEffect, useState } from 'react';
9
+ import React, { useContext, useEffect, useState } from 'react';
10
10
  import { Button } from 'primereact/button';
11
11
  import { DataTable } from 'primereact/datatable';
12
12
  import { Column } from 'primereact/column';
13
+ import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog';
14
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
15
+ import { MessageType } from '../../hub/CommandMessage';
13
16
  import { useAms } from './AmsProvider';
14
17
  import { CalibrationEntryDialog } from './CalibrationEntryDialog';
15
18
 
16
19
  export const AssetDetailView: React.FC = () => {
17
- const { selection, schemas, roles, readAsset, listCalibrations, readCalibration, readUsage } = useAms();
20
+ const { selection, schemas, roles, readAsset, listCalibrations, readCalibration, readUsage,
21
+ refreshAssets, setSelection } = useAms();
22
+ const { invoke } = useContext(EventEmitterContext);
18
23
  const [asset, setAsset] = useState<any | null>(null);
19
24
  const [cals, setCals] = useState<any[]>([]);
20
25
  const [usage, setUsage] = useState<any | null>(null);
21
26
  const [calDialogOpen, setCalDialogOpen] = useState(false);
27
+ const [deleting, setDeleting] = useState(false);
28
+
29
+ /** Two-step asset retirement: flip status to retired via update_asset.
30
+ * The hard delete (delete_asset) is a separate action, only visible
31
+ * after this completes. Both go through ams.* IPC. */
32
+ const retire = async () => {
33
+ if (!asset) return;
34
+ try {
35
+ const resp: any = await invoke('ams.update_asset' as any, MessageType.Request, {
36
+ asset_id: asset.asset_id,
37
+ status: 'retired',
38
+ } as any);
39
+ if (resp?.success) {
40
+ await refreshAssets();
41
+ await refresh();
42
+ } else {
43
+ console.warn('[AssetDetailView] retire failed:', resp?.error_message);
44
+ }
45
+ } catch (e) {
46
+ console.error('[AssetDetailView] retire threw:', e);
47
+ }
48
+ };
49
+
50
+ /** Permanent delete. Removes the AMS record (asset.json + cal
51
+ * history + usage). Historical test records still reference this
52
+ * asset_id but carry the full asset+calibration snapshot inline
53
+ * so the audit trail survives the purge. */
54
+ const performDelete = async () => {
55
+ if (!asset) return;
56
+ setDeleting(true);
57
+ try {
58
+ const resp: any = await invoke('ams.delete_asset' as any, MessageType.Request, {
59
+ asset_id: asset.asset_id,
60
+ } as any);
61
+ if (resp?.success) {
62
+ setSelection({ assetType: null, assetId: null });
63
+ await refreshAssets();
64
+ } else {
65
+ console.warn('[AssetDetailView] delete failed:', resp?.error_message);
66
+ alert(`Delete failed: ${resp?.error_message ?? 'unknown error'}`);
67
+ }
68
+ } catch (e: any) {
69
+ console.error('[AssetDetailView] delete threw:', e);
70
+ alert(`Delete threw: ${e?.message ?? e}`);
71
+ } finally {
72
+ setDeleting(false);
73
+ }
74
+ };
75
+
76
+ const confirmDelete = () => {
77
+ if (!asset) return;
78
+ confirmDialog({
79
+ message: (
80
+ <div>
81
+ <p style={{ marginTop: 0 }}>
82
+ Permanently delete asset <code>{asset.asset_id}</code>?
83
+ </p>
84
+ <p style={{ marginBottom: 0, fontSize: '0.875rem', color: '#9ca3af' }}>
85
+ This removes the AMS record (nameplate, calibration history,
86
+ usage counters). Historical test records that referenced this
87
+ asset are unaffected — they carry the full asset and
88
+ calibration data inline.
89
+ </p>
90
+ </div>
91
+ ),
92
+ header: 'Delete asset',
93
+ icon: 'pi pi-exclamation-triangle',
94
+ acceptLabel: 'Delete',
95
+ rejectLabel: 'Cancel',
96
+ acceptClassName: 'p-button-danger',
97
+ accept: () => { void performDelete(); },
98
+ });
99
+ };
22
100
 
23
101
  const refresh = async () => {
24
102
  if (!selection.assetId) {
@@ -56,6 +134,22 @@ export const AssetDetailView: React.FC = () => {
56
134
  const roleHit = roles[asset.asset_type]?.find(r => r.location === asset.location);
57
135
  const roleLabel = roleHit ? (roleHit.label ?? roleHit.location) : asset.location;
58
136
 
137
+ // Schema-declared nameplate fields (capacity_n, sensitivity_mv_v,
138
+ // etc.). Rendered in their own panel so operators can verify what
139
+ // the placeholder resolver will read for `${ams.by_location.*}`
140
+ // references at module-start time. Missing values render as a
141
+ // muted "(not set)" rather than empty space — that's the failure
142
+ // mode the resolver will block on, so we draw attention to it.
143
+ const schemaFields: any[] = Array.isArray(schemas[asset.asset_type]?.fields)
144
+ ? schemas[asset.asset_type].fields
145
+ : [];
146
+ const custom = (asset.custom ?? {}) as Record<string, any>;
147
+
148
+ // Modules that resolve placeholders against this role. Drawn from
149
+ // ams.list_roles so the operator can see "this load cell feeds NI
150
+ // bridge channel calibration" at a glance.
151
+ const modulesUsingRole = roleHit?.used_by_modules ?? [];
152
+
59
153
  return (
60
154
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
61
155
  <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto 1fr', gap: '0.5rem 1.5rem', alignItems: 'baseline' }}>
@@ -74,11 +168,76 @@ export const AssetDetailView: React.FC = () => {
74
168
  <strong>Cycles</strong> <span>{usage?.cycles ?? 0}</span>
75
169
  </div>
76
170
 
171
+ {modulesUsingRole.length > 0 && (
172
+ <div style={{ fontSize: '0.875rem', color: '#34d399' }}>
173
+ ✓ Feeds module {modulesUsingRole.length === 1 ? 'config' : 'configs'}:{' '}
174
+ <strong>{modulesUsingRole.join(', ')}</strong>
175
+ {' — '}their <code>${'${ams.by_location.' + asset.location + '.*}'}</code>{' '}
176
+ placeholders will resolve to the values below.
177
+ </div>
178
+ )}
179
+
180
+ {schemaFields.length > 0 && (
181
+ <div>
182
+ <h4 style={{ margin: '0 0 0.5rem 0' }}>Nameplate</h4>
183
+ <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr',
184
+ gap: '0.25rem 1rem', alignItems: 'baseline',
185
+ fontSize: '0.9rem' }}>
186
+ {schemaFields.map((f: any) => {
187
+ const v = custom[f.name];
188
+ const present = v !== undefined && v !== null && v !== '';
189
+ const label = f.label ?? f.name;
190
+ const display = present
191
+ ? (f.units ? `${v} ${f.units}` : String(v))
192
+ : '(not set)';
193
+ return (
194
+ <React.Fragment key={f.name}>
195
+ <strong title={f.description ?? undefined}>{label}</strong>
196
+ <span style={{ color: present ? undefined : '#f59e0b' }}>
197
+ {display}
198
+ </span>
199
+ </React.Fragment>
200
+ );
201
+ })}
202
+ </div>
203
+ </div>
204
+ )}
205
+
206
+ <SubLocationsPanel
207
+ schema={schemas[asset.asset_type]?.sub_locations}
208
+ values={asset.sub_locations}
209
+ />
210
+
211
+ <SubLocationsCalibrationPanel
212
+ schema={schemas[asset.asset_type]?.sub_locations}
213
+ currentCalibration={cals.find(c => c.cal_id === asset.current_calibration_id)}
214
+ />
215
+
77
216
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
78
217
  <h4 style={{ margin: 0 }}>Calibration History</h4>
79
- <Button label="+ Calibration" icon="pi pi-plus"
80
- onClick={() => setCalDialogOpen(true)} />
218
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
219
+ {asset.status === 'active' && (
220
+ <Button label="Retire" icon="pi pi-power-off"
221
+ severity="warning" outlined
222
+ onClick={retire}
223
+ tooltip="Mark inactive. The asset stays on disk; deletion is a separate action available after retirement."
224
+ tooltipOptions={{ position: 'left' }}
225
+ />
226
+ )}
227
+ {asset.status === 'retired' && (
228
+ <Button label="Delete" icon="pi pi-trash"
229
+ severity="danger" outlined
230
+ loading={deleting}
231
+ onClick={confirmDelete}
232
+ tooltip="Permanent. Removes the AMS record; historical test snapshots are preserved inline."
233
+ tooltipOptions={{ position: 'left' }}
234
+ />
235
+ )}
236
+ <Button label="+ Calibration" icon="pi pi-plus"
237
+ onClick={() => setCalDialogOpen(true)} />
238
+ </div>
81
239
  </div>
240
+ <ConfirmDialog />
82
241
  <DataTable value={cals} size="small" stripedRows
83
242
  emptyMessage="No calibrations recorded for this asset."
84
243
  >
@@ -106,3 +265,127 @@ export const AssetDetailView: React.FC = () => {
106
265
  </div>
107
266
  );
108
267
  };
268
+
269
+ // -------------------------------------------------------------------------
270
+ // Per-axis nameplate matrix. Renders when the asset's type declares a
271
+ // keyed-fields sub_locations schema (multi-axis transducers etc.).
272
+ // Read-only — registration / edits happen in the Add dialog.
273
+ // -------------------------------------------------------------------------
274
+ const SubLocationsPanel: React.FC<{ schema: any; values: any }> = ({ schema, values }) => {
275
+ if (!schema || !Array.isArray(schema.keys) || !Array.isArray(schema.fields)) {
276
+ return null;
277
+ }
278
+ const obj = (values && typeof values === 'object') ? values : {};
279
+ return (
280
+ <div>
281
+ <h4 style={{ margin: '0 0 0.5rem 0' }}>{schema.label ?? 'Sub-locations'}</h4>
282
+ <div style={{ overflowX: 'auto' }}>
283
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
284
+ <thead>
285
+ <tr>
286
+ <th style={{ textAlign: 'left', padding: '0.25rem 0.5rem',
287
+ borderBottom: '1px solid var(--surface-border)' }}>
288
+ {schema.key_label ?? 'Key'}
289
+ </th>
290
+ {schema.fields.map((f: any) => (
291
+ <th key={f.name}
292
+ title={f.description ?? undefined}
293
+ style={{ textAlign: 'left', padding: '0.25rem 0.5rem',
294
+ borderBottom: '1px solid var(--surface-border)' }}>
295
+ {(f.label ?? f.name)}{f.units ? ` [${f.units}]` : ''}
296
+ </th>
297
+ ))}
298
+ </tr>
299
+ </thead>
300
+ <tbody>
301
+ {schema.keys.map((key: string) => {
302
+ const row = (obj[key] ?? {}) as Record<string, any>;
303
+ return (
304
+ <tr key={key}>
305
+ <td style={{ padding: '0.25rem 0.5rem', fontWeight: 600 }}>{key}</td>
306
+ {schema.fields.map((f: any) => {
307
+ const v = row[f.name];
308
+ const present = v !== undefined && v !== null && v !== '';
309
+ return (
310
+ <td key={f.name}
311
+ style={{ padding: '0.25rem 0.5rem',
312
+ color: present ? undefined : '#f59e0b' }}>
313
+ {present ? String(v) : '(not set)'}
314
+ </td>
315
+ );
316
+ })}
317
+ </tr>
318
+ );
319
+ })}
320
+ </tbody>
321
+ </table>
322
+ </div>
323
+ </div>
324
+ );
325
+ };
326
+
327
+ // -------------------------------------------------------------------------
328
+ // Per-axis calibration matrix. Renders when the schema declares
329
+ // per-axis calibration_fields AND a current calibration is on file.
330
+ // Reads from `currentCalibration.values.<key>.<field>`. Read-only.
331
+ // -------------------------------------------------------------------------
332
+ const SubLocationsCalibrationPanel: React.FC<{
333
+ schema: any;
334
+ currentCalibration: any | undefined;
335
+ }> = ({ schema, currentCalibration }) => {
336
+ if (!schema || !Array.isArray(schema.keys) || !Array.isArray(schema.calibration_fields)
337
+ || schema.calibration_fields.length === 0) {
338
+ return null;
339
+ }
340
+ if (!currentCalibration) return null;
341
+ const values = (currentCalibration.values && typeof currentCalibration.values === 'object')
342
+ ? currentCalibration.values as Record<string, any>
343
+ : {};
344
+ return (
345
+ <div>
346
+ <h4 style={{ margin: '0 0 0.5rem 0' }}>
347
+ Current Calibration — Per-axis Values
348
+ </h4>
349
+ <div style={{ overflowX: 'auto' }}>
350
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
351
+ <thead>
352
+ <tr>
353
+ <th style={{ textAlign: 'left', padding: '0.25rem 0.5rem',
354
+ borderBottom: '1px solid var(--surface-border)' }}>
355
+ {schema.key_label ?? 'Key'}
356
+ </th>
357
+ {schema.calibration_fields.map((f: any) => (
358
+ <th key={f.name}
359
+ style={{ textAlign: 'left', padding: '0.25rem 0.5rem',
360
+ borderBottom: '1px solid var(--surface-border)' }}>
361
+ {(f.label ?? f.name)}{f.units ? ` [${f.units}]` : ''}
362
+ </th>
363
+ ))}
364
+ </tr>
365
+ </thead>
366
+ <tbody>
367
+ {schema.keys.map((key: string) => {
368
+ const row = (values[key] ?? {}) as Record<string, any>;
369
+ return (
370
+ <tr key={key}>
371
+ <td style={{ padding: '0.25rem 0.5rem', fontWeight: 600 }}>{key}</td>
372
+ {schema.calibration_fields.map((f: any) => {
373
+ const v = row[f.name];
374
+ const present = v !== undefined && v !== null && v !== '';
375
+ return (
376
+ <td key={f.name}
377
+ style={{ padding: '0.25rem 0.5rem',
378
+ color: present ? undefined : '#f59e0b' }}>
379
+ {present ? String(v) : '(not set)'}
380
+ </td>
381
+ );
382
+ })}
383
+ </tr>
384
+ );
385
+ })}
386
+ </tbody>
387
+ </table>
388
+ </div>
389
+ </div>
390
+ );
391
+ };