@adcops/autocore-react 3.3.59 → 3.3.63

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 (53) hide show
  1. package/dist/components/ams/AmsProvider.d.ts +45 -0
  2. package/dist/components/ams/AmsProvider.d.ts.map +1 -0
  3. package/dist/components/ams/AmsProvider.js +1 -0
  4. package/dist/components/ams/AssetDetailView.d.ts +3 -0
  5. package/dist/components/ams/AssetDetailView.d.ts.map +1 -0
  6. package/dist/components/ams/AssetDetailView.js +1 -0
  7. package/dist/components/ams/AssetRegistryTable.d.ts +3 -0
  8. package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -0
  9. package/dist/components/ams/AssetRegistryTable.js +1 -0
  10. package/dist/components/ams/CalibrationEntryDialog.d.ts +10 -0
  11. package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -0
  12. package/dist/components/ams/CalibrationEntryDialog.js +1 -0
  13. package/dist/components/ams/SubLocationPicker.d.ts +3 -0
  14. package/dist/components/ams/SubLocationPicker.d.ts.map +1 -0
  15. package/dist/components/ams/SubLocationPicker.js +1 -0
  16. package/dist/components/ams/index.d.ts +6 -0
  17. package/dist/components/ams/index.d.ts.map +1 -0
  18. package/dist/components/ams/index.js +1 -0
  19. package/dist/components/index.d.ts +9 -0
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components/index.js +1 -1
  22. package/dist/components/tis/ProjectSelector.d.ts +15 -0
  23. package/dist/components/tis/ProjectSelector.d.ts.map +1 -0
  24. package/dist/components/tis/ProjectSelector.js +1 -0
  25. package/dist/components/tis/TestDataView.d.ts +9 -1
  26. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  27. package/dist/components/tis/TestDataView.js +1 -1
  28. package/dist/components/tis/TestSetupForm.d.ts +8 -4
  29. package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
  30. package/dist/components/tis/TestSetupForm.js +1 -1
  31. package/dist/components/tis/TisProvider.d.ts +45 -0
  32. package/dist/components/tis/TisProvider.d.ts.map +1 -1
  33. package/dist/components/tis/TisProvider.js +1 -1
  34. package/dist/core/AutoCoreTagContext.d.ts +16 -0
  35. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  36. package/dist/core/AutoCoreTagContext.js +1 -1
  37. package/dist/themes/adc-dark/blue/theme.css +67 -37
  38. package/dist/themes/adc-dark/blue/theme.css.map +1 -1
  39. package/package.json +1 -1
  40. package/src/components/ams/AmsProvider.tsx +219 -0
  41. package/src/components/ams/AssetDetailView.tsx +101 -0
  42. package/src/components/ams/AssetRegistryTable.tsx +171 -0
  43. package/src/components/ams/CalibrationEntryDialog.tsx +197 -0
  44. package/src/components/ams/SubLocationPicker.tsx +146 -0
  45. package/src/components/ams/index.ts +12 -0
  46. package/src/components/index.ts +30 -0
  47. package/src/components/tis/ProjectSelector.tsx +190 -0
  48. package/src/components/tis/TestDataView.tsx +321 -28
  49. package/src/components/tis/TestSetupForm.tsx +66 -253
  50. package/src/components/tis/TisProvider.tsx +192 -1
  51. package/src/core/AutoCoreTagContext.tsx +114 -16
  52. package/src/themes/adc-dark/_extensions.scss +15 -0
  53. package/src/themes/adc-dark/blue/adc_theme.scss +56 -10
@@ -0,0 +1,171 @@
1
+ /*
2
+ * <AssetRegistryTable> — list every asset in the AMS, with quick
3
+ * filters by type / status and a "+ Add" button that pops a creation
4
+ * dialog. Click a row → pins selection.assetId so <AssetDetailView>
5
+ * can render the chosen asset.
6
+ */
7
+
8
+ import React, { useContext, useMemo, useState } from 'react';
9
+ import { Button } from 'primereact/button';
10
+ import { DataTable } from 'primereact/datatable';
11
+ import { Column } from 'primereact/column';
12
+ import { Dropdown } from 'primereact/dropdown';
13
+ import { InputText } from 'primereact/inputtext';
14
+ import { Dialog } from 'primereact/dialog';
15
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
16
+ import { MessageType } from '../../hub/CommandMessage';
17
+ import { useAms, type AmsAssetEntry } from './AmsProvider';
18
+
19
+ interface AddDialogState {
20
+ open: boolean;
21
+ assetType: string;
22
+ serial: string;
23
+ location: string;
24
+ }
25
+
26
+ const EMPTY_ADD: AddDialogState = { open: false, assetType: '', serial: '', location: '' };
27
+
28
+ export const AssetRegistryTable: React.FC = () => {
29
+ const { schemas, assets, refreshAssets, setSelection, selection } = useAms();
30
+ const { invoke } = useContext(EventEmitterContext);
31
+
32
+ const [filterType, setFilterType] = useState<string | null>(null);
33
+ const [filterStatus, setFilterStatus] = useState<string | null>(null);
34
+ const [addState, setAddState] = useState<AddDialogState>(EMPTY_ADD);
35
+
36
+ const typeOptions = useMemo(
37
+ () => Object.keys(schemas).map(k => ({ label: schemas[k]?.label ?? k, value: k })),
38
+ [schemas],
39
+ );
40
+
41
+ const filtered = useMemo(() => {
42
+ return assets.filter(a => {
43
+ if (filterType && a.asset_type !== filterType) return false;
44
+ if (filterStatus && a.status !== filterStatus) return false;
45
+ return true;
46
+ });
47
+ }, [assets, filterType, filterStatus]);
48
+
49
+ const onCreate = async () => {
50
+ if (!addState.assetType) return;
51
+ try {
52
+ const resp: any = await invoke('ams.create_asset' as any, MessageType.Request, {
53
+ asset_type: addState.assetType,
54
+ serial: addState.serial,
55
+ location: addState.location,
56
+ } as any);
57
+ if (resp?.success) {
58
+ setAddState(EMPTY_ADD);
59
+ await refreshAssets();
60
+ if (resp.data?.asset_id) {
61
+ setSelection({ assetType: addState.assetType, assetId: resp.data.asset_id });
62
+ }
63
+ } else {
64
+ console.warn('[AssetRegistryTable] create_asset failed:', resp?.error_message);
65
+ }
66
+ } catch (e) {
67
+ console.error('[AssetRegistryTable] create_asset threw:', e);
68
+ }
69
+ };
70
+
71
+ return (
72
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
73
+ <div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
74
+ <Dropdown
75
+ value={filterType}
76
+ options={[{ label: 'All Types', value: null }, ...typeOptions]}
77
+ onChange={(e) => setFilterType(e.value)}
78
+ placeholder="Filter by type"
79
+ />
80
+ <Dropdown
81
+ value={filterStatus}
82
+ options={[
83
+ { label: 'All Statuses', value: null },
84
+ { label: 'Active', value: 'active' },
85
+ { label: 'Out for Service', value: 'out_for_service' },
86
+ { label: 'Retired', value: 'retired' },
87
+ ]}
88
+ onChange={(e) => setFilterStatus(e.value)}
89
+ placeholder="Filter by status"
90
+ />
91
+ <span style={{ marginLeft: 'auto' }}>
92
+ <Button
93
+ icon="pi pi-plus"
94
+ label="Add Asset"
95
+ onClick={() => setAddState(s => ({ ...s, open: true }))}
96
+ disabled={typeOptions.length === 0}
97
+ />
98
+ </span>
99
+ </div>
100
+
101
+ <DataTable
102
+ value={filtered}
103
+ selectionMode="single"
104
+ selection={filtered.find(a => a.asset_id === selection.assetId) ?? null}
105
+ onSelectionChange={(e) => {
106
+ const row = e.value as AmsAssetEntry | null;
107
+ if (row) setSelection({ assetType: row.asset_type, assetId: row.asset_id });
108
+ }}
109
+ dataKey="asset_id"
110
+ emptyMessage={
111
+ typeOptions.length === 0
112
+ ? 'AMS not enabled in this project (no asset_types declared).'
113
+ : 'No assets registered yet.'
114
+ }
115
+ size="small"
116
+ stripedRows
117
+ >
118
+ <Column field="asset_id" header="Asset ID" />
119
+ <Column field="asset_type" header="Type"
120
+ body={(r: AmsAssetEntry) => schemas[r.asset_type]?.label ?? r.asset_type}
121
+ />
122
+ <Column field="serial" header="Serial" />
123
+ <Column field="location" header="Location" />
124
+ <Column field="status" header="Status" />
125
+ <Column header="Calibration"
126
+ body={(r: AmsAssetEntry) =>
127
+ r.current_calibration_id
128
+ ? <span title={r.current_calibration_id}>✓</span>
129
+ : <span style={{ color: '#f59e0b' }}>none</span>
130
+ }
131
+ />
132
+ </DataTable>
133
+
134
+ <Dialog
135
+ header="Add New Asset"
136
+ visible={addState.open}
137
+ style={{ width: '32rem' }}
138
+ onHide={() => setAddState(EMPTY_ADD)}
139
+ footer={
140
+ <>
141
+ <Button label="Cancel" severity="secondary" onClick={() => setAddState(EMPTY_ADD)} />
142
+ <Button label="Create" icon="pi pi-check"
143
+ onClick={onCreate}
144
+ disabled={!addState.assetType}
145
+ />
146
+ </>
147
+ }
148
+ >
149
+ <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '0.5rem 1rem', alignItems: 'center' }}>
150
+ <label>Type *</label>
151
+ <Dropdown
152
+ value={addState.assetType}
153
+ options={typeOptions}
154
+ onChange={(e) => setAddState(s => ({ ...s, assetType: e.value }))}
155
+ placeholder="Choose asset type"
156
+ />
157
+ <label>Serial</label>
158
+ <InputText value={addState.serial}
159
+ onChange={(e) => setAddState(s => ({ ...s, serial: e.target.value }))} />
160
+ <label>Location</label>
161
+ <InputText value={addState.location}
162
+ onChange={(e) => setAddState(s => ({ ...s, location: e.target.value }))} />
163
+ </div>
164
+ <p style={{ fontSize: '0.875rem', color: '#9ca3af', marginTop: '1rem' }}>
165
+ The asset_id is generated by the server (format: <code>{addState.assetType ? (schemas[addState.assetType]?.id_prefix ?? 'A-') : 'A-'}YYYYMMDDTHHMMSS</code>).
166
+ Manufacturer serial is recorded for traceability but is not used as the unique key.
167
+ </p>
168
+ </Dialog>
169
+ </div>
170
+ );
171
+ };
@@ -0,0 +1,197 @@
1
+ /*
2
+ * <CalibrationEntryDialog> — form for adding a calibration record to
3
+ * an asset. Renders a dynamic field set built from the asset_type's
4
+ * `calibration_fields` schema, plus a few standard meta fields.
5
+ *
6
+ * Triggered by <AssetDetailView>'s "+ Calibration" button. Submits via
7
+ * `ams.add_calibration`; on success the AmsProvider's
8
+ * `ams.calibration_added` subscription refreshes the registry.
9
+ */
10
+
11
+ import React, { useContext, useEffect, useState } from 'react';
12
+ import { Button } from 'primereact/button';
13
+ import { Dialog } from 'primereact/dialog';
14
+ import { InputText } from 'primereact/inputtext';
15
+ import { InputNumber } from 'primereact/inputnumber';
16
+ import { Calendar } from 'primereact/calendar';
17
+ import { Dropdown } from 'primereact/dropdown';
18
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
19
+ import { MessageType } from '../../hub/CommandMessage';
20
+ import { useAmsSchemas } from './AmsProvider';
21
+
22
+ export interface CalibrationEntryDialogProps {
23
+ visible: boolean;
24
+ assetId: string;
25
+ assetType: string;
26
+ onHide: () => void;
27
+ onAdded?: (calId: string) => void;
28
+ }
29
+
30
+ interface FieldSpec {
31
+ name: string;
32
+ type: string;
33
+ required?: boolean;
34
+ label?: string;
35
+ units?: string;
36
+ description?: string;
37
+ values?: string[]; // for enum
38
+ }
39
+
40
+ export const CalibrationEntryDialog: React.FC<CalibrationEntryDialogProps> = ({
41
+ visible, assetId, assetType, onHide, onAdded,
42
+ }) => {
43
+ const schemas = useAmsSchemas();
44
+ const { invoke } = useContext(EventEmitterContext);
45
+
46
+ const fields: FieldSpec[] = (schemas[assetType]?.calibration_fields ?? []) as FieldSpec[];
47
+
48
+ const [values, setValues] = useState<{ [k: string]: any }>({});
49
+ const [performedBy, setPerformedBy] = useState('');
50
+ const [expiresAt, setExpiresAt] = useState<Date | null>(null);
51
+ const [certRef, setCertRef] = useState('');
52
+ const [notes, setNotes] = useState('');
53
+ const [submitting, setSubmitting] = useState(false);
54
+ const [error, setError] = useState<string | null>(null);
55
+
56
+ useEffect(() => {
57
+ if (visible) {
58
+ setValues({});
59
+ setPerformedBy('');
60
+ setExpiresAt(null);
61
+ setCertRef('');
62
+ setNotes('');
63
+ setError(null);
64
+ }
65
+ }, [visible]);
66
+
67
+ const renderField = (f: FieldSpec) => {
68
+ const labelText = `${f.label ?? f.name}${f.units ? ` [${f.units}]` : ''}${f.required ? ' *' : ''}`;
69
+ const labelEl = (
70
+ <label title={f.description ?? ''}>
71
+ {labelText}
72
+ </label>
73
+ );
74
+
75
+ const onChange = (val: any) => setValues(v => ({ ...v, [f.name]: val }));
76
+
77
+ let input: React.ReactNode;
78
+ switch (f.type) {
79
+ case 'string':
80
+ input = <InputText value={values[f.name] ?? ''} onChange={(e) => onChange(e.target.value)} />;
81
+ break;
82
+ case 'enum':
83
+ input = (
84
+ <Dropdown
85
+ value={values[f.name] ?? null}
86
+ options={(f.values ?? []).map(v => ({ label: v, value: v }))}
87
+ onChange={(e) => onChange(e.value)}
88
+ placeholder="Choose…"
89
+ />
90
+ );
91
+ break;
92
+ case 'bool':
93
+ input = (
94
+ <Dropdown
95
+ value={values[f.name] ?? null}
96
+ options={[{ label: 'true', value: true }, { label: 'false', value: false }]}
97
+ onChange={(e) => onChange(e.value)}
98
+ />
99
+ );
100
+ break;
101
+ case 'u8': case 'u16': case 'u32': case 'u64':
102
+ case 'i8': case 'i16': case 'i32': case 'i64':
103
+ case 'f32': case 'f64':
104
+ input = (
105
+ <InputNumber
106
+ value={values[f.name] ?? null}
107
+ onValueChange={(e) => onChange(e.value)}
108
+ useGrouping={false}
109
+ maxFractionDigits={f.type.startsWith('f') ? 9 : 0}
110
+ />
111
+ );
112
+ break;
113
+ default:
114
+ input = <InputText value={values[f.name] ?? ''} onChange={(e) => onChange(e.target.value)} />;
115
+ break;
116
+ }
117
+ return [labelEl, input] as const;
118
+ };
119
+
120
+ const onSubmit = async () => {
121
+ // Basic required-field check.
122
+ for (const f of fields) {
123
+ if (f.required && (values[f.name] === undefined || values[f.name] === null || values[f.name] === '')) {
124
+ setError(`Field "${f.label ?? f.name}" is required.`);
125
+ return;
126
+ }
127
+ }
128
+ setSubmitting(true);
129
+ setError(null);
130
+ try {
131
+ const resp: any = await invoke('ams.add_calibration' as any, MessageType.Request, {
132
+ asset_id: assetId,
133
+ performed_by: performedBy,
134
+ expires_at: expiresAt ? expiresAt.toISOString() : null,
135
+ cert_ref: certRef,
136
+ notes,
137
+ values,
138
+ } as any);
139
+ if (resp?.success) {
140
+ onAdded?.(resp.data.cal_id);
141
+ onHide();
142
+ } else {
143
+ setError(resp?.error_message ?? 'add_calibration failed');
144
+ }
145
+ } catch (e: any) {
146
+ setError(String(e?.message ?? e));
147
+ } finally {
148
+ setSubmitting(false);
149
+ }
150
+ };
151
+
152
+ return (
153
+ <Dialog
154
+ header={`Add Calibration — ${assetId}`}
155
+ visible={visible}
156
+ onHide={onHide}
157
+ style={{ width: '40rem' }}
158
+ footer={
159
+ <>
160
+ <Button label="Cancel" severity="secondary" onClick={onHide} disabled={submitting} />
161
+ <Button label="Save" icon="pi pi-check" onClick={onSubmit} loading={submitting} />
162
+ </>
163
+ }
164
+ >
165
+ <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '0.5rem 1rem', alignItems: 'center' }}>
166
+ {fields.flatMap((f, i) => {
167
+ const [l, inp] = renderField(f);
168
+ return [
169
+ <React.Fragment key={`l-${i}`}>{l}</React.Fragment>,
170
+ <React.Fragment key={`i-${i}`}>{inp}</React.Fragment>,
171
+ ];
172
+ })}
173
+
174
+ <hr style={{ gridColumn: '1 / span 2', width: '100%' }} />
175
+
176
+ <label>Performed by</label>
177
+ <InputText value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
178
+
179
+ <label>Expires at</label>
180
+ <Calendar value={expiresAt} onChange={(e) => setExpiresAt((e.value as Date) ?? null)}
181
+ showIcon dateFormat="yy-mm-dd" />
182
+
183
+ <label>Certificate ref</label>
184
+ <InputText value={certRef} onChange={(e) => setCertRef(e.target.value)} />
185
+
186
+ <label>Notes</label>
187
+ <InputText value={notes} onChange={(e) => setNotes(e.target.value)} />
188
+ </div>
189
+
190
+ {error && (
191
+ <div style={{ marginTop: '1rem', color: '#ef4444' }}>
192
+ {error}
193
+ </div>
194
+ )}
195
+ </Dialog>
196
+ );
197
+ };
@@ -0,0 +1,146 @@
1
+ /*
2
+ * <SubLocationPicker> — grid view for an asset's sub_locations
3
+ * (typically surface lanes). Each cell shows the sub_location's status
4
+ * and lets the operator click to mark it in_use / worn / available, or
5
+ * select it for the active test.
6
+ *
7
+ * Pure context-driven: reads selection.assetId from <AmsProvider>.
8
+ */
9
+
10
+ import React, { useContext, useEffect, useState } from 'react';
11
+ import { Button } from 'primereact/button';
12
+ import { Dialog } from 'primereact/dialog';
13
+ import { Dropdown } from 'primereact/dropdown';
14
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
15
+ import { MessageType } from '../../hub/CommandMessage';
16
+ import { useAms } from './AmsProvider';
17
+
18
+ interface SubLocationItem {
19
+ id: string;
20
+ [k: string]: any;
21
+ }
22
+
23
+ interface SubLocations {
24
+ name: string;
25
+ items: SubLocationItem[];
26
+ }
27
+
28
+ export const SubLocationPicker: React.FC = () => {
29
+ const { selection } = useAms();
30
+ const { invoke } = useContext(EventEmitterContext);
31
+ const [subs, setSubs] = useState<SubLocations | null>(null);
32
+ const [editing, setEditing] = useState<{ id: string; status: string } | null>(null);
33
+
34
+ const refresh = async () => {
35
+ if (!selection.assetId) {
36
+ setSubs(null);
37
+ return;
38
+ }
39
+ try {
40
+ const resp: any = await invoke('ams.list_sub_locations' as any, MessageType.Request, {
41
+ asset_id: selection.assetId,
42
+ } as any);
43
+ if (resp?.success) {
44
+ const sl = resp.data?.sub_locations;
45
+ if (sl && Array.isArray(sl.items)) {
46
+ setSubs(sl as SubLocations);
47
+ } else {
48
+ setSubs(null);
49
+ }
50
+ }
51
+ } catch (e) {
52
+ console.error('[SubLocationPicker] list_sub_locations:', e);
53
+ }
54
+ };
55
+ useEffect(() => { refresh(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [selection.assetId]);
56
+
57
+ if (!selection.assetId) {
58
+ return null;
59
+ }
60
+ if (!subs) {
61
+ return null;
62
+ }
63
+
64
+ const onSave = async () => {
65
+ if (!editing) return;
66
+ try {
67
+ await invoke('ams.update_sub_location' as any, MessageType.Request, {
68
+ asset_id: selection.assetId!,
69
+ location_id: editing.id,
70
+ partial: { status: editing.status },
71
+ } as any);
72
+ setEditing(null);
73
+ await refresh();
74
+ } catch (e) {
75
+ console.error('[SubLocationPicker] update:', e);
76
+ }
77
+ };
78
+
79
+ const colorFor = (status: string): string => {
80
+ switch (status) {
81
+ case 'available': return '#22c55e';
82
+ case 'in_use': return '#3b82f6';
83
+ case 'worn': return '#f59e0b';
84
+ case 'retired': return '#6b7280';
85
+ default: return '#9ca3af';
86
+ }
87
+ };
88
+
89
+ return (
90
+ <div>
91
+ <h4 style={{ marginBottom: '0.5rem', textTransform: 'capitalize' }}>{subs.name}</h4>
92
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(7rem, 1fr))', gap: '0.5rem' }}>
93
+ {subs.items.map(item => {
94
+ const status = String(item.status ?? '');
95
+ return (
96
+ <button
97
+ key={item.id}
98
+ onClick={() => setEditing({ id: item.id, status })}
99
+ style={{
100
+ padding: '0.75rem',
101
+ border: `2px solid ${colorFor(status)}`,
102
+ borderRadius: 6,
103
+ background: 'transparent',
104
+ color: 'inherit',
105
+ cursor: 'pointer',
106
+ textAlign: 'left',
107
+ }}
108
+ >
109
+ <div style={{ fontWeight: 700 }}>{item.id}</div>
110
+ <div style={{ fontSize: '0.875rem', color: colorFor(status) }}>{status}</div>
111
+ {item.cycles_used !== undefined && (
112
+ <div style={{ fontSize: '0.75rem', color: '#9ca3af' }}>
113
+ {Number(item.cycles_used).toLocaleString()} cycles
114
+ </div>
115
+ )}
116
+ </button>
117
+ );
118
+ })}
119
+ </div>
120
+
121
+ <Dialog
122
+ header={editing ? `Edit ${editing.id}` : 'Edit'}
123
+ visible={!!editing}
124
+ onHide={() => setEditing(null)}
125
+ style={{ width: '24rem' }}
126
+ footer={
127
+ <>
128
+ <Button label="Cancel" severity="secondary" onClick={() => setEditing(null)} />
129
+ <Button label="Save" icon="pi pi-check" onClick={onSave} />
130
+ </>
131
+ }
132
+ >
133
+ {editing && (
134
+ <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '0.5rem 1rem' }}>
135
+ <label>Status</label>
136
+ <Dropdown
137
+ value={editing.status}
138
+ options={['available', 'in_use', 'worn', 'retired'].map(v => ({ label: v, value: v }))}
139
+ onChange={(e) => setEditing(s => s ? { ...s, status: e.value } : s)}
140
+ />
141
+ </div>
142
+ )}
143
+ </Dialog>
144
+ </div>
145
+ );
146
+ };
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Public surface of the AMS component family. Drop <AmsProvider> at
3
+ * the top of your HMI; the rest are zero-prop and read from context.
4
+ *
5
+ * See autocore-server/doc/ams_product_plan.md for the full design.
6
+ */
7
+
8
+ export { AmsProvider, useAms, useAmsSchemas, useAmsAlerts, useAmsAssets, useAmsSelection } from './AmsProvider';
9
+ export { AssetRegistryTable } from './AssetRegistryTable';
10
+ export { AssetDetailView } from './AssetDetailView';
11
+ export { CalibrationEntryDialog } from './CalibrationEntryDialog';
12
+ export { SubLocationPicker } from './SubLocationPicker';
@@ -27,6 +27,9 @@ export type {
27
27
  TisMethodSchema,
28
28
  } from './tis/TisProvider';
29
29
 
30
+ export { ProjectSelector } from './tis/ProjectSelector';
31
+ export type { ProjectSelectorProps } from './tis/ProjectSelector';
32
+
30
33
  export { TestSetupForm } from './tis/TestSetupForm';
31
34
  export type { TestSetupFormProps } from './tis/TestSetupForm';
32
35
 
@@ -44,3 +47,30 @@ export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape
44
47
 
45
48
  export { TestRawDataView } from './tis/TestRawDataView';
46
49
  export type { TestRawDataViewProps } from './tis/TestRawDataView';
50
+
51
+ // -----------------------------------------------------------------------
52
+ // Asset Management System — see autocore-server/doc/ams_product_plan.md
53
+ // Drop <AmsProvider> at the top of your HMI; the rest are zero-prop.
54
+ // -----------------------------------------------------------------------
55
+ export {
56
+ AmsProvider,
57
+ useAms,
58
+ useAmsSchemas,
59
+ useAmsAlerts,
60
+ useAmsAssets,
61
+ useAmsSelection,
62
+ } from './ams/AmsProvider';
63
+ export type {
64
+ AmsProviderProps,
65
+ AmsContextValue,
66
+ AmsAlerts,
67
+ AmsAssetEntry,
68
+ AmsSelection,
69
+ AmsSchemaRegistry,
70
+ AmsTypeSchema,
71
+ } from './ams/AmsProvider';
72
+ export { AssetRegistryTable } from './ams/AssetRegistryTable';
73
+ export { AssetDetailView } from './ams/AssetDetailView';
74
+ export { CalibrationEntryDialog } from './ams/CalibrationEntryDialog';
75
+ export type { CalibrationEntryDialogProps } from './ams/CalibrationEntryDialog';
76
+ export { SubLocationPicker } from './ams/SubLocationPicker';