@adcops/autocore-react 3.3.87 → 3.3.90
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/dist/assets/AxisC.d.ts +4 -0
- package/dist/assets/AxisC.d.ts.map +1 -0
- package/dist/assets/AxisC.js +1 -0
- package/dist/assets/AxisX.js +1 -1
- package/dist/assets/AxisY.js +1 -1
- package/dist/assets/AxisZ.js +1 -1
- package/dist/assets/JogXNeg.d.ts +4 -0
- package/dist/assets/JogXNeg.d.ts.map +1 -0
- package/dist/assets/JogXNeg.js +1 -0
- package/dist/assets/JogXPos.d.ts +4 -0
- package/dist/assets/JogXPos.d.ts.map +1 -0
- package/dist/assets/JogXPos.js +1 -0
- package/dist/assets/JogYNeg.d.ts +4 -0
- package/dist/assets/JogYNeg.d.ts.map +1 -0
- package/dist/assets/JogYNeg.js +1 -0
- package/dist/assets/JogYPos.d.ts +4 -0
- package/dist/assets/JogYPos.d.ts.map +1 -0
- package/dist/assets/JogYPos.js +1 -0
- package/dist/assets/JogZNeg.d.ts +4 -0
- package/dist/assets/JogZNeg.d.ts.map +1 -0
- package/dist/assets/JogZNeg.js +1 -0
- package/dist/assets/JogZPos.d.ts +4 -0
- package/dist/assets/JogZPos.d.ts.map +1 -0
- package/dist/assets/JogZPos.js +1 -0
- package/dist/assets/Off.d.ts +4 -0
- package/dist/assets/Off.d.ts.map +1 -0
- package/dist/assets/Off.js +1 -0
- package/dist/assets/On.d.ts +4 -0
- package/dist/assets/On.d.ts.map +1 -0
- package/dist/assets/On.js +1 -0
- package/dist/assets/index.d.ts +6 -0
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -1
- package/dist/assets/svg/off.svg +2 -0
- package/dist/assets/svg/on.svg +11 -0
- package/dist/components/JogPanel.d.ts +2 -2
- package/dist/components/JogPanel.d.ts.map +1 -1
- package/dist/components/JogPanel.js +1 -1
- package/dist/components/ams/AmsProvider.d.ts +10 -0
- package/dist/components/ams/AmsProvider.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.js +1 -1
- package/dist/components/ams/AssetEditDialog.d.ts.map +1 -1
- package/dist/components/ams/AssetEditDialog.js +1 -1
- package/dist/components/ams/AssetRegistryTable.css +12 -0
- package/dist/components/ams/AssetRegistryTable.d.ts +1 -0
- package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
- package/dist/components/ams/AssetRegistryTable.js +1 -1
- package/dist/components/forms/FormRow.js +1 -1
- package/dist/components/forms/FormSection.js +1 -1
- package/dist/components/forms/forms.css +18 -18
- package/dist/components/tis/ConfigurationDialog.d.ts +21 -0
- package/dist/components/tis/ConfigurationDialog.d.ts.map +1 -0
- package/dist/components/tis/ConfigurationDialog.js +1 -0
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/dist/components/tis/TestDataView.d.ts +27 -0
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis/TestSetupForm.d.ts +37 -0
- package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
- package/dist/components/tis/TestSetupForm.js +1 -1
- package/dist/components/tis/TisProvider.d.ts +25 -0
- package/dist/components/tis/TisProvider.d.ts.map +1 -1
- package/dist/components/tis/TisProvider.js +1 -1
- package/dist/components/tis-editor/TisConfigEditor.css +20 -0
- package/dist/components/tis-editor/editor/ConfigurationsEditor.d.ts +19 -0
- package/dist/components/tis-editor/editor/ConfigurationsEditor.d.ts.map +1 -0
- package/dist/components/tis-editor/editor/ConfigurationsEditor.js +1 -0
- package/dist/components/tis-editor/editor/MethodFormEditor.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/MethodFormEditor.js +1 -1
- package/dist/components/tis-editor/types.d.ts +13 -0
- package/dist/components/tis-editor/types.d.ts.map +1 -1
- package/dist/components/tis-editor/validation.d.ts.map +1 -1
- package/dist/components/tis-editor/validation.js +1 -1
- package/dist/themes/adc-dark/blue/theme.css +3 -2
- package/dist/themes/adc-dark/blue/theme.css.map +1 -1
- package/package.json +2 -1
- package/src/assets/AxisC.tsx +38 -0
- package/src/assets/AxisX.tsx +32 -32
- package/src/assets/AxisY.tsx +34 -34
- package/src/assets/AxisZ.tsx +31 -31
- package/src/assets/JogXNeg.tsx +30 -0
- package/src/assets/JogXPos.tsx +30 -0
- package/src/assets/JogYNeg.tsx +30 -0
- package/src/assets/JogYPos.tsx +30 -0
- package/src/assets/JogZNeg.tsx +30 -0
- package/src/assets/JogZPos.tsx +30 -0
- package/src/assets/Off.tsx +14 -0
- package/src/assets/On.tsx +26 -0
- package/src/assets/index.ts +6 -0
- package/src/assets/svg/off.svg +2 -0
- package/src/assets/svg/on.svg +11 -0
- package/src/components/JogPanel.tsx +18 -28
- package/src/components/ams/AmsProvider.tsx +10 -0
- package/src/components/ams/AssetDetailView.tsx +1 -1
- package/src/components/ams/AssetEditDialog.tsx +25 -10
- package/src/components/ams/AssetRegistryTable.css +12 -0
- package/src/components/ams/AssetRegistryTable.tsx +68 -12
- package/src/components/forms/FormRow.tsx +6 -6
- package/src/components/forms/FormSection.tsx +6 -6
- package/src/components/forms/forms.css +18 -18
- package/src/components/tis/ConfigurationDialog.tsx +128 -0
- package/src/components/tis/ResultHistoryTable.tsx +2 -2
- package/src/components/tis/TestDataView.tsx +83 -1
- package/src/components/tis/TestSetupForm.tsx +167 -10
- package/src/components/tis/TisProvider.tsx +53 -0
- package/src/components/tis-editor/TisConfigEditor.css +20 -0
- package/src/components/tis-editor/editor/ConfigurationsEditor.tsx +242 -0
- package/src/components/tis-editor/editor/MethodFormEditor.tsx +4 -0
- package/src/components/tis-editor/types.ts +14 -0
- package/src/components/tis-editor/validation.ts +29 -0
- package/src/themes/adc-dark/_extensions.scss +1 -0
- package/src/themes/theme-base/components/panel/_fieldset.scss +2 -2
|
@@ -15,6 +15,7 @@ import { Dialog } from 'primereact/dialog';
|
|
|
15
15
|
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
16
16
|
import { MessageType } from '../../hub/CommandMessage';
|
|
17
17
|
import { useAms, type AmsAssetEntry, type AmsRole } from './AmsProvider';
|
|
18
|
+
import './AssetRegistryTable.css';
|
|
18
19
|
|
|
19
20
|
// Sentinel value for the "Other..." dropdown option, which lets the
|
|
20
21
|
// operator type a free-form role for the rare case (custom builds,
|
|
@@ -111,6 +112,32 @@ function subLocationsFor(schemas: any, assetType: string): SubLocationsSchema |
|
|
|
111
112
|
return sl as SubLocationsSchema;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/** Build a fresh `customFields` seed from a role's `defaults` map,
|
|
116
|
+
* narrowed to fields the asset_type schema actually declares. Server
|
|
117
|
+
* validation rejects unknown default keys at project load, so in
|
|
118
|
+
* normal operation every key will match a declared field; the
|
|
119
|
+
* narrowing here just protects against schema/defaults drift while a
|
|
120
|
+
* project is being edited.
|
|
121
|
+
*
|
|
122
|
+
* Returns string-typed values because the dialog's inputs are text-
|
|
123
|
+
* based; the existing `coerceField` helper converts back on submit.
|
|
124
|
+
* Empty / missing defaults are skipped (rather than emitting `""`) so
|
|
125
|
+
* required-field gating still fires for fields the role didn't seed. */
|
|
126
|
+
function seedCustomFromRoleDefaults(
|
|
127
|
+
role: AmsRole | undefined,
|
|
128
|
+
fields: SchemaField[],
|
|
129
|
+
): Record<string, string> {
|
|
130
|
+
const out: Record<string, string> = {};
|
|
131
|
+
if (!role || !role.defaults) return out;
|
|
132
|
+
const declared = new Set(fields.map(f => f.name));
|
|
133
|
+
for (const [k, v] of Object.entries(role.defaults)) {
|
|
134
|
+
if (!declared.has(k)) continue;
|
|
135
|
+
if (v === undefined || v === null) continue;
|
|
136
|
+
out[k] = String(v);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
/** Coerce a per-cell string from the matrix input back to its declared
|
|
115
142
|
* type. Numeric strings → JSON numbers; booleans → bool; everything
|
|
116
143
|
* else stays as a string. Matches the top-level fields coercion. */
|
|
@@ -142,6 +169,8 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
142
169
|
// when an operator clicks Register on one of its rows. Catch it
|
|
143
170
|
// here and open the Add dialog pre-populated with the asset_type
|
|
144
171
|
// and role — the operator just fills in the nameplate values.
|
|
172
|
+
// If the matching role declares `defaults`, those seed the
|
|
173
|
+
// nameplate inputs too (same path as onRoleChange).
|
|
145
174
|
useEffect(() => {
|
|
146
175
|
const handler = (e: Event) => {
|
|
147
176
|
const detail = (e as CustomEvent).detail as
|
|
@@ -149,17 +178,20 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
149
178
|
const assetType = detail?.assetType ?? '';
|
|
150
179
|
const location = detail?.location ?? '';
|
|
151
180
|
if (!assetType) return;
|
|
181
|
+
const role = (roles[assetType] ?? []).find(r => r.location === location);
|
|
182
|
+
const declaredFields = fieldsFor(schemas, assetType);
|
|
152
183
|
setAddState({
|
|
153
184
|
...EMPTY_ADD,
|
|
154
185
|
open: true,
|
|
155
186
|
assetType,
|
|
156
187
|
roleSelection: location,
|
|
157
188
|
location,
|
|
189
|
+
customFields: seedCustomFromRoleDefaults(role, declaredFields),
|
|
158
190
|
});
|
|
159
191
|
};
|
|
160
192
|
window.addEventListener('ams:prefill-add', handler);
|
|
161
193
|
return () => window.removeEventListener('ams:prefill-add', handler);
|
|
162
|
-
}, []);
|
|
194
|
+
}, [roles, schemas]);
|
|
163
195
|
|
|
164
196
|
const typeOptions = useMemo(
|
|
165
197
|
() => Object.keys(schemas).map(k => ({ label: schemas[k]?.label ?? k, value: k })),
|
|
@@ -285,15 +317,20 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
285
317
|
* hardware), or leave it empty so they make an explicit choice
|
|
286
318
|
* when there are multiple. Hide the field entirely when none.
|
|
287
319
|
* Also resets the nameplate-field map — fields are type-specific
|
|
288
|
-
* and the previous type's inputs would not be relevant.
|
|
320
|
+
* and the previous type's inputs would not be relevant. When the
|
|
321
|
+
* auto-selected role declares `defaults`, seed those into the
|
|
322
|
+
* nameplate inputs so the operator only has to confirm or edit. */
|
|
289
323
|
const onAssetTypeChange = (newType: string) => {
|
|
290
324
|
const list = roles[newType] ?? [];
|
|
291
|
-
const
|
|
292
|
-
|
|
325
|
+
const autoRole = list.length === 1 ? list[0] : undefined;
|
|
326
|
+
const base = autoRole
|
|
327
|
+
? { roleSelection: autoRole.location, location: autoRole.location }
|
|
293
328
|
: { roleSelection: '', location: '' };
|
|
329
|
+
const newFields = fieldsFor(schemas, newType);
|
|
294
330
|
setAddState(s => ({
|
|
295
331
|
...s, assetType: newType, ...base,
|
|
296
|
-
customFields:
|
|
332
|
+
customFields: seedCustomFromRoleDefaults(autoRole, newFields),
|
|
333
|
+
subLocationFields: {},
|
|
297
334
|
}));
|
|
298
335
|
};
|
|
299
336
|
|
|
@@ -315,13 +352,22 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
315
352
|
);
|
|
316
353
|
|
|
317
354
|
/** Role dropdown change handler. ROLE_OTHER puts us into free-form
|
|
318
|
-
* mode where the operator types into a text field.
|
|
355
|
+
* mode where the operator types into a text field. When picking a
|
|
356
|
+
* known role with declared `defaults`, seed the nameplate inputs
|
|
357
|
+
* from those values so the operator only has to confirm or edit.
|
|
358
|
+
* Switching role is treated as an explicit reseed; values typed
|
|
359
|
+
* into the previous role's inputs are replaced — switching role
|
|
360
|
+
* is a discrete intent, not a continuous edit. */
|
|
319
361
|
const onRoleChange = (value: string) => {
|
|
320
362
|
if (value === ROLE_OTHER) {
|
|
321
363
|
setAddState(s => ({ ...s, roleSelection: ROLE_OTHER, location: '' }));
|
|
322
|
-
|
|
323
|
-
setAddState(s => ({ ...s, roleSelection: value, location: value }));
|
|
364
|
+
return;
|
|
324
365
|
}
|
|
366
|
+
const role = rolesForType.find(r => r.location === value);
|
|
367
|
+
setAddState(s => ({
|
|
368
|
+
...s, roleSelection: value, location: value,
|
|
369
|
+
customFields: seedCustomFromRoleDefaults(role, fieldsForType),
|
|
370
|
+
}));
|
|
325
371
|
};
|
|
326
372
|
|
|
327
373
|
/** Disable Create until the operator has picked a role (or supplied
|
|
@@ -388,8 +434,11 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
388
434
|
? 'AMS not enabled in this project (no asset_types declared).'
|
|
389
435
|
: 'No assets registered yet.'
|
|
390
436
|
}
|
|
391
|
-
size="small"
|
|
392
437
|
stripedRows
|
|
438
|
+
/* Extra vertical padding on body cells (see the CSS) enlarges
|
|
439
|
+
the touch target for each row — operators on the shop-floor
|
|
440
|
+
touchscreen were mis-tapping the dense default rows. */
|
|
441
|
+
className="ams-asset-table"
|
|
393
442
|
>
|
|
394
443
|
<Column field="asset_id" header="Asset ID" />
|
|
395
444
|
<Column field="asset_type" header="Type"
|
|
@@ -436,9 +485,16 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
436
485
|
onChange={(e) => onAssetTypeChange(e.value)}
|
|
437
486
|
placeholder="Choose asset type"
|
|
438
487
|
/>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
488
|
+
{/* Serial: hidden until a type is chosen. Showing it
|
|
489
|
+
before the type led operators to fill it in for a
|
|
490
|
+
not-yet-selected asset, which read as confusing. */}
|
|
491
|
+
{addState.assetType && (
|
|
492
|
+
<>
|
|
493
|
+
<label>Serial</label>
|
|
494
|
+
<InputText value={addState.serial}
|
|
495
|
+
onChange={(e) => setAddState(s => ({ ...s, serial: e.target.value }))} />
|
|
496
|
+
</>
|
|
497
|
+
)}
|
|
442
498
|
|
|
443
499
|
{/* Role field: only shown when this asset_type has at
|
|
444
500
|
least one declared role in project.json. Asset
|
|
@@ -20,15 +20,15 @@ export interface FormRowProps {
|
|
|
20
20
|
|
|
21
21
|
export const FormRow: React.FC<FormRowProps> = ({ label, required, hint, error, htmlFor, children }) => {
|
|
22
22
|
return (
|
|
23
|
-
<div className={`ac-
|
|
24
|
-
<label className="ac-
|
|
23
|
+
<div className={`ac-formrow${error ? ' ac-formrow--error' : ''}`}>
|
|
24
|
+
<label className="ac-formrow__label" htmlFor={htmlFor}>
|
|
25
25
|
{label}
|
|
26
|
-
{required && <span className="ac-
|
|
27
|
-
{hint && <small className="ac-
|
|
26
|
+
{required && <span className="ac-formrow__required" aria-hidden> *</span>}
|
|
27
|
+
{hint && <small className="ac-formrow__hint">{hint}</small>}
|
|
28
28
|
</label>
|
|
29
|
-
<div className="ac-
|
|
29
|
+
<div className="ac-formrow__field">
|
|
30
30
|
{children}
|
|
31
|
-
{error && <small className="ac-
|
|
31
|
+
{error && <small className="ac-formrow__error">{error}</small>}
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
);
|
|
@@ -19,17 +19,17 @@ export interface FormSectionProps {
|
|
|
19
19
|
|
|
20
20
|
export const FormSection: React.FC<FormSectionProps> = ({ title, description, actions, children }) => {
|
|
21
21
|
return (
|
|
22
|
-
<section className="ac-
|
|
22
|
+
<section className="ac-formsection">
|
|
23
23
|
{(title || actions) && (
|
|
24
|
-
<header className="ac-
|
|
24
|
+
<header className="ac-formsection__header">
|
|
25
25
|
<div>
|
|
26
|
-
{title && <h3 className="ac-
|
|
27
|
-
{description && <small className="ac-
|
|
26
|
+
{title && <h3 className="ac-formsection__title">{title}</h3>}
|
|
27
|
+
{description && <small className="ac-formsection__desc">{description}</small>}
|
|
28
28
|
</div>
|
|
29
|
-
{actions && <div className="ac-
|
|
29
|
+
{actions && <div className="ac-formsection__actions">{actions}</div>}
|
|
30
30
|
</header>
|
|
31
31
|
)}
|
|
32
|
-
<div className="ac-
|
|
32
|
+
<div className="ac-formsection__body">
|
|
33
33
|
{children}
|
|
34
34
|
</div>
|
|
35
35
|
</section>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
.ac-
|
|
1
|
+
.ac-formsection {
|
|
2
2
|
border: 1px solid var(--surface-d, #e2e8f0);
|
|
3
3
|
border-radius: 6px;
|
|
4
4
|
background: var(--surface-card, #fff);
|
|
5
5
|
margin-bottom: 1rem;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
.ac-
|
|
8
|
+
.ac-formsection__header {
|
|
9
9
|
display: flex;
|
|
10
10
|
align-items: flex-start;
|
|
11
11
|
justify-content: space-between;
|
|
@@ -16,74 +16,74 @@
|
|
|
16
16
|
border-top-right-radius: 6px;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
.ac-
|
|
19
|
+
.ac-formsection__title {
|
|
20
20
|
margin: 0;
|
|
21
21
|
font-size: 0.95rem;
|
|
22
22
|
font-weight: 600;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
.ac-
|
|
25
|
+
.ac-formsection__desc {
|
|
26
26
|
color: var(--text-color-secondary, #64748b);
|
|
27
27
|
display: block;
|
|
28
28
|
margin-top: 0.15rem;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
.ac-
|
|
31
|
+
.ac-formsection__actions {
|
|
32
32
|
display: flex;
|
|
33
33
|
gap: 0.5rem;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
.ac-
|
|
36
|
+
.ac-formsection__body {
|
|
37
37
|
padding: 0.75rem 1rem;
|
|
38
38
|
display: flex;
|
|
39
39
|
flex-direction: column;
|
|
40
40
|
gap: 0.5rem;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
.ac-
|
|
43
|
+
.ac-formrow {
|
|
44
44
|
display: grid;
|
|
45
45
|
grid-template-columns: minmax(8rem, 14rem) 1fr;
|
|
46
46
|
gap: 0.5rem 1rem;
|
|
47
47
|
align-items: start;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
.ac-
|
|
51
|
-
.ac-
|
|
50
|
+
.ac-formrow--error .ac-formrow__field input,
|
|
51
|
+
.ac-formrow--error .ac-formrow__field .p-inputtext {
|
|
52
52
|
border-color: #dc2626;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.ac-
|
|
55
|
+
.ac-formrow__label {
|
|
56
56
|
font-weight: 500;
|
|
57
57
|
padding-top: 0.4rem;
|
|
58
58
|
display: flex;
|
|
59
59
|
flex-direction: column;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
.ac-
|
|
62
|
+
.ac-formrow__required {
|
|
63
63
|
color: #dc2626;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
.ac-
|
|
66
|
+
.ac-formrow__hint {
|
|
67
67
|
color: var(--text-color-secondary, #64748b);
|
|
68
68
|
font-weight: 400;
|
|
69
69
|
font-size: 0.75rem;
|
|
70
70
|
margin-top: 0.15rem;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
.ac-
|
|
73
|
+
.ac-formrow__field {
|
|
74
74
|
display: flex;
|
|
75
75
|
flex-direction: column;
|
|
76
76
|
gap: 0.25rem;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
.ac-
|
|
80
|
-
.ac-
|
|
81
|
-
.ac-
|
|
82
|
-
.ac-
|
|
79
|
+
.ac-formrow__field > input,
|
|
80
|
+
.ac-formrow__field > .p-inputtext,
|
|
81
|
+
.ac-formrow__field > .p-dropdown,
|
|
82
|
+
.ac-formrow__field > .p-inputtextarea {
|
|
83
83
|
width: 100%;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
.ac-
|
|
86
|
+
.ac-formrow__error {
|
|
87
87
|
color: #dc2626;
|
|
88
88
|
font-size: 0.75rem;
|
|
89
89
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* <ConfigurationDialog> — picker UI for choosing a method's named
|
|
5
|
+
* configuration (see `TestMethod.configurations`). Mirrors
|
|
6
|
+
* <TestMethodDialog>: a dropdown of the configurations declared on the
|
|
7
|
+
* active method plus the long-form description for whichever entry is
|
|
8
|
+
* highlighted. OK applies the choice via the supplied callback (which
|
|
9
|
+
* writes the configuration's `defaults` into the config_fields); Cancel
|
|
10
|
+
* discards.
|
|
11
|
+
*
|
|
12
|
+
* Only mounted by <TestSetupForm> when the active method declares one or
|
|
13
|
+
* more configurations, so the empty case is informational only.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
17
|
+
import { Button } from 'primereact/button';
|
|
18
|
+
import { Dialog } from 'primereact/dialog';
|
|
19
|
+
import { Dropdown } from 'primereact/dropdown';
|
|
20
|
+
import type { TestConfiguration } from './TestSetupForm';
|
|
21
|
+
|
|
22
|
+
/** Display name for one configuration: prefer `label`, fall back to `name`. */
|
|
23
|
+
export const configLabelOf = (cfg: TestConfiguration | undefined): string =>
|
|
24
|
+
(cfg?.label && cfg.label.length > 0) ? cfg.label : (cfg?.name ?? '');
|
|
25
|
+
|
|
26
|
+
export interface ConfigurationDialogProps {
|
|
27
|
+
visible: boolean;
|
|
28
|
+
onHide: () => void;
|
|
29
|
+
/** Configurations declared on the active method. */
|
|
30
|
+
configurations: TestConfiguration[];
|
|
31
|
+
/** `name` of the configuration currently applied on the form. The
|
|
32
|
+
* dropdown opens pointing at this so the dialog reflects state. */
|
|
33
|
+
currentConfigName: string;
|
|
34
|
+
/**
|
|
35
|
+
* Called with the chosen configuration `name` when the operator
|
|
36
|
+
* clicks OK. Cancel does not fire this. The parent applies the
|
|
37
|
+
* configuration's defaults to the fields.
|
|
38
|
+
*/
|
|
39
|
+
onSelected: (configName: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const ConfigurationDialog: React.FC<ConfigurationDialogProps> = ({
|
|
43
|
+
visible, onHide, configurations, currentConfigName, onSelected,
|
|
44
|
+
}) => {
|
|
45
|
+
// Local "draft" selection — the dropdown writes here; OK applies it,
|
|
46
|
+
// so a Cancel really cancels (matches <TestMethodDialog>).
|
|
47
|
+
const [draftName, setDraftName] = useState<string>(currentConfigName);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (visible) setDraftName(currentConfigName);
|
|
51
|
+
}, [visible, currentConfigName]);
|
|
52
|
+
|
|
53
|
+
const options = useMemo(
|
|
54
|
+
() => configurations.map(cfg => ({ label: configLabelOf(cfg), value: cfg.name })),
|
|
55
|
+
[configurations],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const draftCfg = configurations.find(c => c.name === draftName);
|
|
59
|
+
const draftDescription =
|
|
60
|
+
(draftCfg?.description && draftCfg.description.length > 0)
|
|
61
|
+
? draftCfg.description
|
|
62
|
+
: null;
|
|
63
|
+
|
|
64
|
+
const handleOk = () => {
|
|
65
|
+
if (draftName && draftName !== currentConfigName) {
|
|
66
|
+
onSelected(draftName);
|
|
67
|
+
}
|
|
68
|
+
onHide();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const footer = (
|
|
72
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem' }}>
|
|
73
|
+
<Button label="Cancel" icon="pi pi-times" onClick={onHide} text />
|
|
74
|
+
<Button label="OK" icon="pi pi-check" onClick={handleOk} disabled={!draftName} />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Dialog
|
|
80
|
+
header="Select Configuration"
|
|
81
|
+
visible={visible}
|
|
82
|
+
onHide={onHide}
|
|
83
|
+
footer={footer}
|
|
84
|
+
modal
|
|
85
|
+
style={{ width: 'min(560px, 90vw)' }}
|
|
86
|
+
>
|
|
87
|
+
{options.length === 0 ? (
|
|
88
|
+
<p style={{ color: 'var(--text-secondary-color)' }}>
|
|
89
|
+
This test method declares no configurations.
|
|
90
|
+
</p>
|
|
91
|
+
) : (
|
|
92
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
93
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
|
94
|
+
<label htmlFor="acConfigurationDropdown" style={{ flexShrink: 0 }}>
|
|
95
|
+
Configuration:
|
|
96
|
+
</label>
|
|
97
|
+
<Dropdown
|
|
98
|
+
inputId="acConfigurationDropdown"
|
|
99
|
+
value={draftName}
|
|
100
|
+
options={options}
|
|
101
|
+
onChange={(e) => setDraftName(e.value)}
|
|
102
|
+
placeholder="Select a configuration"
|
|
103
|
+
style={{ flex: 1 }}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
{/* Stable-height description region, matching
|
|
107
|
+
<TestMethodDialog>: render a muted placeholder
|
|
108
|
+
rather than collapsing the dialog. */}
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
padding: '0.75rem 1rem',
|
|
112
|
+
background: 'var(--surface-100)',
|
|
113
|
+
borderRadius: '6px',
|
|
114
|
+
minHeight: '4.5rem',
|
|
115
|
+
color: draftDescription
|
|
116
|
+
? 'var(--text-color)'
|
|
117
|
+
: 'var(--text-secondary-color)',
|
|
118
|
+
fontStyle: draftDescription ? 'normal' : 'italic',
|
|
119
|
+
whiteSpace: 'pre-wrap',
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{draftDescription ?? 'No description provided for this configuration.'}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</Dialog>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
@@ -300,7 +300,7 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
|
|
|
300
300
|
<div style={{ display: 'flex', gap: '0.4rem' }}>
|
|
301
301
|
<Button
|
|
302
302
|
icon={isDataBusy ? 'pi pi-spin pi-spinner' : 'pi pi-download'}
|
|
303
|
-
label="
|
|
303
|
+
label="Raw"
|
|
304
304
|
size="small"
|
|
305
305
|
outlined
|
|
306
306
|
disabled={anyBusy}
|
|
@@ -310,7 +310,7 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
|
|
|
310
310
|
/>
|
|
311
311
|
<Button
|
|
312
312
|
icon={isReportBusy ? 'pi pi-spin pi-spinner' : 'pi pi-file'}
|
|
313
|
-
label="
|
|
313
|
+
label="Results"
|
|
314
314
|
size="small"
|
|
315
315
|
outlined
|
|
316
316
|
disabled={anyBusy}
|
|
@@ -37,6 +37,7 @@ import { Chart as ChartJS,
|
|
|
37
37
|
Title, Tooltip, Legend,
|
|
38
38
|
} from 'chart.js';
|
|
39
39
|
import zoomPlugin from 'chartjs-plugin-zoom';
|
|
40
|
+
import annotationPlugin from 'chartjs-plugin-annotation';
|
|
40
41
|
import { Line } from 'react-chartjs-2';
|
|
41
42
|
|
|
42
43
|
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
@@ -46,7 +47,7 @@ import { useRawCycleData } from './useRawCycleData';
|
|
|
46
47
|
|
|
47
48
|
ChartJS.register(
|
|
48
49
|
CategoryScale, LinearScale, PointElement, LineElement,
|
|
49
|
-
Title, Tooltip, Legend, zoomPlugin,
|
|
50
|
+
Title, Tooltip, Legend, zoomPlugin, annotationPlugin,
|
|
50
51
|
);
|
|
51
52
|
|
|
52
53
|
// -------------------------------------------------------------------------
|
|
@@ -69,11 +70,38 @@ export interface TestFieldDef {
|
|
|
69
70
|
|
|
70
71
|
export interface ChartAxis { field?: string; column?: string; label?: string; }
|
|
71
72
|
export interface ChartSeries { field?: string; column?: string; label?: string; y_axis?: 'left' | 'right'; }
|
|
73
|
+
/**
|
|
74
|
+
* A shaded X-range band drawn over the plot (rendered as a
|
|
75
|
+
* chartjs-plugin-annotation `box` annotation spanning the full Y height).
|
|
76
|
+
* `xMin`/`xMax` are in the same units as the view's x data — for a
|
|
77
|
+
* `raw_trace` that's the x column's value, for a `cycle_scatter` it's the
|
|
78
|
+
* category index. Used to indicate a processed / region-of-interest span.
|
|
79
|
+
*/
|
|
80
|
+
export interface ChartRegion {
|
|
81
|
+
/** Start of the band on the X axis. */
|
|
82
|
+
xMin: number;
|
|
83
|
+
/** End of the band on the X axis. */
|
|
84
|
+
xMax: number;
|
|
85
|
+
/** Optional caption drawn at the top-center of the band. */
|
|
86
|
+
label?: string;
|
|
87
|
+
/** Fill color (any CSS color). Default: translucent theme accent. */
|
|
88
|
+
color?: string;
|
|
89
|
+
/** Optional band-edge border color. Default: no border. */
|
|
90
|
+
borderColor?: string;
|
|
91
|
+
}
|
|
72
92
|
export interface ChartView {
|
|
73
93
|
title?: string;
|
|
74
94
|
type: 'cycle_scatter' | 'raw_trace';
|
|
75
95
|
x: ChartAxis;
|
|
76
96
|
y: ChartSeries[];
|
|
97
|
+
/**
|
|
98
|
+
* Optional shaded X-range bands drawn over the plot. Declared per view
|
|
99
|
+
* in project.json (static). The dynamic case — bands computed at
|
|
100
|
+
* runtime from processed/results data — would feed the same
|
|
101
|
+
* `buildRegionAnnotations()` helper via a future prop; see its seam in
|
|
102
|
+
* the `chartOptions` memo.
|
|
103
|
+
*/
|
|
104
|
+
regions?: ChartRegion[];
|
|
77
105
|
}
|
|
78
106
|
export interface RawColumn { source: string; }
|
|
79
107
|
export interface RawDataShape {
|
|
@@ -375,6 +403,12 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
375
403
|
mode: 'xy' as const,
|
|
376
404
|
},
|
|
377
405
|
},
|
|
406
|
+
// Region bands. Today these come from the view's static
|
|
407
|
+
// `regions` declaration; a future dynamic source (processed
|
|
408
|
+
// data) can merge its own annotations into this same map.
|
|
409
|
+
annotation: {
|
|
410
|
+
annotations: buildRegionAnnotations(selectedViewDef?.regions),
|
|
411
|
+
},
|
|
378
412
|
},
|
|
379
413
|
};
|
|
380
414
|
}, [selectedViewDef, usesRightAxis]);
|
|
@@ -1010,6 +1044,54 @@ const CHART_COLORS = [
|
|
|
1010
1044
|
];
|
|
1011
1045
|
const palette = (i: number) => CHART_COLORS[i % CHART_COLORS.length];
|
|
1012
1046
|
|
|
1047
|
+
// Default translucent fill for region bands (theme accent at low alpha).
|
|
1048
|
+
const DEFAULT_REGION_FILL = 'rgba(78, 168, 222, 0.15)';
|
|
1049
|
+
// Label color must be a concrete canvas color — CSS variables don't
|
|
1050
|
+
// resolve when chart.js paints to the 2D context.
|
|
1051
|
+
const REGION_LABEL_COLOR = 'rgba(226, 232, 240, 0.85)';
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Translate the view's `regions` into chartjs-plugin-annotation `box`
|
|
1055
|
+
* annotations keyed by a stable id. Each band spans the full Y height
|
|
1056
|
+
* (yMin/yMax left unset) between `xMin` and `xMax` on the x scale.
|
|
1057
|
+
* Returns an empty map when there are no regions, which the plugin
|
|
1058
|
+
* treats as "draw nothing".
|
|
1059
|
+
*
|
|
1060
|
+
* This is the single seam through which both the static schema path and a
|
|
1061
|
+
* future dynamic (processed-data) path produce annotations.
|
|
1062
|
+
*/
|
|
1063
|
+
function buildRegionAnnotations(regions?: ChartRegion[]): Record<string, any> {
|
|
1064
|
+
if (!regions || regions.length === 0) return {};
|
|
1065
|
+
const out: Record<string, any> = {};
|
|
1066
|
+
regions.forEach((r, i) => {
|
|
1067
|
+
const hasBorder = !!r.borderColor;
|
|
1068
|
+
out[`region-${i}`] = {
|
|
1069
|
+
type: 'box',
|
|
1070
|
+
xScaleID: 'x',
|
|
1071
|
+
xMin: r.xMin,
|
|
1072
|
+
xMax: r.xMax,
|
|
1073
|
+
// yMin/yMax intentionally omitted → band spans the plot height.
|
|
1074
|
+
backgroundColor: r.color ?? DEFAULT_REGION_FILL,
|
|
1075
|
+
borderColor: hasBorder ? r.borderColor : 'transparent',
|
|
1076
|
+
borderWidth: hasBorder ? 1 : 0,
|
|
1077
|
+
// Sit behind the data line so the trace stays readable.
|
|
1078
|
+
drawTime: 'beforeDatasetsDraw' as const,
|
|
1079
|
+
...(r.label
|
|
1080
|
+
? {
|
|
1081
|
+
label: {
|
|
1082
|
+
display: true,
|
|
1083
|
+
content: r.label,
|
|
1084
|
+
position: { x: 'center' as const, y: 'start' as const },
|
|
1085
|
+
color: REGION_LABEL_COLOR,
|
|
1086
|
+
font: { size: 11 },
|
|
1087
|
+
},
|
|
1088
|
+
}
|
|
1089
|
+
: {}),
|
|
1090
|
+
};
|
|
1091
|
+
});
|
|
1092
|
+
return out;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1013
1095
|
// Loading / error wash drawn over the chart area while a raw_trace
|
|
1014
1096
|
// fetch is in flight. Centered, pointer-events-none so the operator
|
|
1015
1097
|
// can still interact with the dropdown above.
|