@adcops/autocore-react 3.6.7 → 3.6.10
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/components/ams/AssetEditDialog.d.ts.map +1 -1
- package/dist/components/ams/AssetEditDialog.js +1 -1
- package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
- package/dist/components/ams/AssetRegistryTable.js +1 -1
- package/dist/components/ams/index.d.ts +2 -0
- package/dist/components/ams/index.d.ts.map +1 -1
- package/dist/components/ams/index.js +1 -1
- package/dist/components/ams/schemaField.d.ts +95 -0
- package/dist/components/ams/schemaField.d.ts.map +1 -0
- package/dist/components/ams/schemaField.js +1 -0
- package/dist/components/tis/ResultHistoryTable.css +22 -9
- package/dist/components/tis/ResultHistoryTable.d.ts +6 -0
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/package.json +1 -1
- package/src/components/ams/AssetEditDialog.tsx +22 -77
- package/src/components/ams/AssetRegistryTable.tsx +21 -70
- package/src/components/ams/index.ts +5 -0
- package/src/components/ams/schemaField.tsx +166 -0
- package/src/components/tis/ResultHistoryTable.css +22 -9
- package/src/components/tis/ResultHistoryTable.tsx +244 -58
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetEditDialog.d.ts","sourceRoot":"","sources":["../../../src/components/ams/AssetEditDialog.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AssetEditDialog.d.ts","sourceRoot":"","sources":["../../../src/components/ams/AssetEditDialog.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAmD,MAAM,OAAO,CAAC;AA6BxE,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB;;yDAEqD;IACrD,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AA4CD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAuoB1D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useContext,useEffect,useMemo,useState}from"react";import{Button}from"primereact/button";import{Calendar}from"primereact/calendar";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{InputText}from"primereact/inputtext";import{InputTextarea}from"primereact/inputtextarea";import{TabPanel,TabView}from"primereact/tabview";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useAms}from"./AmsProvider";const ROLE_OTHER="__other__";function fieldsFor(e,t){const r=e?.[t]?.fields;return Array.isArray(r)?r:[]}function subLocationsFor(e,t){const r=e?.[t]?.sub_locations;return r&&Array.isArray(r.keys)&&Array.isArray(r.fields)?r:null}function calibrationFieldsFor(e,t){const r=e?.[t]?.calibration_fields;return Array.isArray(r)?r:[]}function perAxisCalibrationFieldsFor(e){return e?.calibration_fields??[]}function coerceField(e,t){if(void 0!==t&&""!==t)switch(e.type){case"f32":case"f64":case"i8":case"i16":case"i32":case"i64":case"u8":case"u16":case"u32":case"u64":{const e=Number(t);return Number.isFinite(e)?e:void 0}case"bool":return"true"===t||"1"===t;default:return t}}function seedFromCustom(e,t){if(!e)return"";const r=e[t];return null==r?"":String(r)}export const AssetEditDialog=({visible:e,asset:t,onHide:r,onSaved:s})=>{const{schemas:n,roles:a,readCalibration:o}=useAms(),{invoke:i}=useContext(EventEmitterContext),l=t?.asset_type??"",d=useMemo(()=>fieldsFor(n,l),[n,l]),c=useMemo(()=>subLocationsFor(n,l),[n,l]),u=useMemo(()=>calibrationFieldsFor(n,l),[n,l]),m=useMemo(()=>perAxisCalibrationFieldsFor(c),[c]),p=m.length>0,_=p||u.length>0,f=useMemo(()=>l?a[l]??[]:[],[l,a]),x=f.length>0,h=useMemo(()=>{const e=f.map(e=>({label:e.label??e.location,value:e.location}));return e.push({label:"Other (advanced — type a custom role)",value:ROLE_OTHER}),e},[f]),[g,y]=useState(""),[b,v]=useState(""),[j,C]=useState(""),[T,S]=useState({}),[F,E]=useState({}),[w,A]=useState(!1),[R,k]=useState(null),[I,O]=useState(0),[$,L]=useState(null),[M,q]=useState(""),[D,H]=useState(null),[B,P]=useState(""),[z,N]=useState(""),[K,V]=useState({}),[W,X]=useState({});useEffect(()=>{if(!e||!t)return;k(null),A(!1),O(0),C("string"==typeof t.serial?t.serial:"");const r="string"==typeof t.location?t.location:"",s=f.find(e=>e.location===r);""===r?(y(""),v("")):s?(y(r),v(r)):(y(ROLE_OTHER),v(r));const n={};for(const e of d)n[e.name]=seedFromCustom(t.custom,e.name);S(n);const a={};if(c){const e=t.sub_locations&&"object"==typeof t.sub_locations?t.sub_locations:{};for(const t of c.keys){const r=e[t]&&"object"==typeof e[t]?e[t]:{},s={};for(const e of c.fields){const t=r[e.name];s[e.name]=null==t?"":String(t)}a[t]=s}}E(a),L(null),q(""),H(null),P(""),N(""),V({}),X({});const i="string"==typeof t.current_calibration_id?t.current_calibration_id:"";if(!i)return;let l=!1;return(async()=>{const e=await o(t.asset_id,i);if(l||!e)return;L(e),q("string"==typeof e.performed_by?e.performed_by:""),P("string"==typeof e.cert_ref?e.cert_ref:""),N("string"==typeof e.notes?e.notes:""),H(e.expires_at?new Date(e.expires_at):null);const r=e.values&&"object"==typeof e.values?e.values:{};if(p&&c){const e={};for(const t of c.keys){const s=r[t]&&"object"==typeof r[t]?r[t]:{},n={};for(const e of m){const t=s[e.name];n[e.name]=null==t?"":String(t)}e[t]=n}X(e)}else{const e={};for(const t of u){const s=r[t.name];e[t.name]=null==s?"":String(s)}V(e)}})(),()=>{l=!0}},[e,t,d,c,f,u,m,p,o]);const G=()=>{if(p&&c){const e={};for(const t of c.keys){const r={};for(const e of m){const s=coerceField(e,W[t]?.[e.name]??"");void 0!==s&&(r[e.name]=s)}e[t]=r}return e}const e={};for(const t of u){const r=coerceField(t,K[t.name]??"");void 0!==r&&(e[t.name]=r)}return e},J=!t?.asset_id||w||x&&(""===g||g===ROLE_OTHER&&!b.trim())||d.some(e=>e.required&&!T[e.name]?.toString().trim())||(c?.keys.some(e=>c.fields.some(t=>t.required&&!F[e]?.[t.name]?.toString().trim()))??!1);if(!t)return null;const Q=n[l]?.label??l;return _jsxs(Dialog,{header:`Edit Asset — ${t.asset_id}`,visible:e,style:{width:"32rem"},onHide:()=>{w||r()},footer:_jsxs(_Fragment,{children:[_jsx(Button,{label:"Cancel",severity:"secondary",onClick:r,disabled:w}),_jsx(Button,{label:"Save",icon:"pi pi-check",onClick:async()=>{if(!t?.asset_id)return;A(!0),k(null);const e={};for(const t of d){const r=coerceField(t,T[t.name]??"");void 0!==r&&(e[t.name]=r)}let n;if(c){n={};for(const e of c.keys){const t={};for(const r of c.fields){const s=coerceField(r,F[e]?.[r.name]??"");void 0!==s&&(t[r.name]=s)}n[e]=t}}const a={asset_id:t.asset_id,location:b,serial:j,custom:e};n&&(a.sub_locations=n);try{const e=await i("ams.update_asset",MessageType.Request,a);if(!e?.success)return void k(e?.error_message??"update_asset failed");if($){const e={asset_id:t.asset_id,cal_id:$.cal_id,performed_by:M,expires_at:D?D.toISOString():null,cert_ref:B,notes:z,values:G()},r=await i("ams.update_calibration",MessageType.Request,e);if(!r?.success)return k(r?.error_message??"update_calibration failed"),void O(1)}s?.(),r()}catch(e){k(String(e?.message??e))}finally{A(!1)}},loading:w,disabled:J})]}),children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.25rem 1rem",fontSize:"0.875rem",color:"var(--text-secondary-color)",marginBottom:"0.75rem"},children:[_jsx("span",{children:"Type"})," ",_jsx("span",{children:Q}),t.install_date&&_jsxs(_Fragment,{children:[_jsx("span",{children:"Installed"}),_jsx("span",{children:new Date(t.install_date).toLocaleString()})]})]}),_jsxs(TabView,{activeIndex:I,onTabChange:e=>O(e.index),children:[_jsxs(TabPanel,{header:"Asset",children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Serial"}),_jsx(InputText,{value:j,placeholder:"(none)",onChange:e=>C(e.target.value)}),x&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Role *"}),_jsx(Dropdown,{value:g,options:h,onChange:e=>{var t;(t=e.value)===ROLE_OTHER?y(ROLE_OTHER):(y(t),v(t))},placeholder:"Choose where this asset is mounted"}),g===ROLE_OTHER&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Custom role"}),_jsx(InputText,{value:b,placeholder:"e.g. tsdr_secondary",onChange:e=>v(e.target.value)})]})]}),d.map(e=>{const t=e.label??e.name,r=e.units?`${t} [${e.units}]${e.required?" *":""}`:`${t}${e.required?" *":""}`,s="string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:r}),"bool"===e.type?_jsx(Dropdown,{value:T[e.name]??"",options:[{label:"true",value:"true"},{label:"false",value:"false"}],onChange:t=>S(r=>({...r,[e.name]:t.value})),placeholder:"—"}):_jsx(InputText,{value:T[e.name]??"",keyfilter:s?"num":void 0,placeholder:e.description??void 0,onChange:t=>S(r=>({...r,[e.name]:t.target.value}))})]},e.name)})]}),c&&_jsxs("div",{style:{marginTop:"1.5rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:c.label??"Sub-locations"}),_jsx("div",{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:c.key_label??"Key"}),c.fields.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:c.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),c.fields.map(t=>{const r=F[e]?.[t.name]??"",s="string"!==t.type&&"bool"!==t.type;return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(InputText,{value:r,keyfilter:s?"num":void 0,onChange:r=>{const s=r.target.value;E(r=>({...r,[e]:{...r[e]??{},[t.name]:s}}))},style:{width:"100%"}})},t.name)})]},e))})]})})]})]}),$&&_jsxs(TabPanel,{header:`Calibration (${$.cal_id})`,children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Performed by"}),_jsx(InputText,{value:M,onChange:e=>q(e.target.value)}),_jsx("label",{children:"Expires at"}),_jsx(Calendar,{value:D,onChange:e=>H(e.value??null),showIcon:!0,dateFormat:"yy-mm-dd"}),_jsx("label",{children:"Certificate ref"}),_jsx(InputText,{value:B,onChange:e=>P(e.target.value),placeholder:"e.g. ADC-CAL-2026-1043"}),_jsx("label",{children:"Notes"}),_jsx(InputTextarea,{value:z,rows:3,onChange:e=>N(e.target.value),autoResize:!0})]}),_&&_jsxs("div",{style:{marginTop:"1rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:p?c?.label??"Per-axis calibration":"Calibration values"}),_jsx("div",p&&c?{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:c.key_label??"Key"}),m.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:c.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),m.map(t=>{const r=W[e]?.[t.name]??"",s="string"!==t.type&&"bool"!==t.type;return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(InputText,{value:r,keyfilter:s?"num":void 0,onChange:r=>{const s=r.target.value;X(r=>({...r,[e]:{...r[e]??{},[t.name]:s}}))},style:{width:"100%"}})},t.name)})]},e))})]})}:{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:u.map(e=>{const t=e.label??e.name,r=e.units?`${t} [${e.units}]${e.required?" *":""}`:`${t}${e.required?" *":""}`,s="string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:r}),_jsx(InputText,{value:K[e.name]??"",keyfilter:s?"num":void 0,onChange:t=>V(r=>({...r,[e.name]:t.target.value}))})]},e.name)})})]})]})]}),R&&_jsx("div",{style:{marginTop:"1rem",color:"#ef4444"},children:R})]})};
|
|
1
|
+
import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useContext,useEffect,useMemo,useState}from"react";import{Button}from"primereact/button";import{Calendar}from"primereact/calendar";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{InputText}from"primereact/inputtext";import{InputTextarea}from"primereact/inputtextarea";import{TabPanel,TabView}from"primereact/tabview";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useAms}from"./AmsProvider";import{SchemaFieldInput,coerceField,fieldLabel}from"./schemaField";const ROLE_OTHER="__other__";function fieldsFor(e,t){const r=e?.[t]?.fields;return Array.isArray(r)?r:[]}function subLocationsFor(e,t){const r=e?.[t]?.sub_locations;return r&&Array.isArray(r.keys)&&Array.isArray(r.fields)?r:null}function calibrationFieldsFor(e,t){const r=e?.[t]?.calibration_fields;return Array.isArray(r)?r:[]}function perAxisCalibrationFieldsFor(e){return e?.calibration_fields??[]}function seedFromCustom(e,t){if(!e)return"";const r=e[t];return null==r?"":String(r)}export const AssetEditDialog=({visible:e,asset:t,onHide:r,onSaved:s})=>{const{schemas:n,roles:a,readCalibration:o}=useAms(),{invoke:i}=useContext(EventEmitterContext),l=t?.asset_type??"",d=useMemo(()=>fieldsFor(n,l),[n,l]),c=useMemo(()=>subLocationsFor(n,l),[n,l]),m=useMemo(()=>calibrationFieldsFor(n,l),[n,l]),u=useMemo(()=>perAxisCalibrationFieldsFor(c),[c]),_=u.length>0,p=_||m.length>0,f=useMemo(()=>l?a[l]??[]:[],[l,a]),h=f.length>0,x=useMemo(()=>{const e=f.map(e=>({label:e.label??e.location,value:e.location}));return e.push({label:"Other (advanced — type a custom role)",value:ROLE_OTHER}),e},[f]),[g,b]=useState(""),[y,j]=useState(""),[v,C]=useState(""),[S,F]=useState({}),[T,E]=useState({}),[A,w]=useState(!1),[R,I]=useState(null),[O,k]=useState(0),[L,M]=useState(null),[H,B]=useState(""),[D,q]=useState(null),[P,z]=useState(""),[$,K]=useState(""),[V,W]=useState({}),[X,N]=useState({});useEffect(()=>{if(!e||!t)return;I(null),w(!1),k(0),C("string"==typeof t.serial?t.serial:"");const r="string"==typeof t.location?t.location:"",s=f.find(e=>e.location===r);""===r?(b(""),j("")):s?(b(r),j(r)):(b(ROLE_OTHER),j(r));const n={};for(const e of d)n[e.name]=seedFromCustom(t.custom,e.name);F(n);const a={};if(c){const e=t.sub_locations&&"object"==typeof t.sub_locations?t.sub_locations:{};for(const t of c.keys){const r=e[t]&&"object"==typeof e[t]?e[t]:{},s={};for(const e of c.fields){const t=r[e.name];s[e.name]=null==t?"":String(t)}a[t]=s}}E(a),M(null),B(""),q(null),z(""),K(""),W({}),N({});const i="string"==typeof t.current_calibration_id?t.current_calibration_id:"";if(!i)return;let l=!1;return(async()=>{const e=await o(t.asset_id,i);if(l||!e)return;M(e),B("string"==typeof e.performed_by?e.performed_by:""),z("string"==typeof e.cert_ref?e.cert_ref:""),K("string"==typeof e.notes?e.notes:""),q(e.expires_at?new Date(e.expires_at):null);const r=e.values&&"object"==typeof e.values?e.values:{};if(_&&c){const e={};for(const t of c.keys){const s=r[t]&&"object"==typeof r[t]?r[t]:{},n={};for(const e of u){const t=s[e.name];n[e.name]=null==t?"":String(t)}e[t]=n}N(e)}else{const e={};for(const t of m){const s=r[t.name];e[t.name]=null==s?"":String(s)}W(e)}})(),()=>{l=!0}},[e,t,d,c,f,m,u,_,o]);const G=()=>{if(_&&c){const e={};for(const t of c.keys){const r={};for(const e of u){const s=X[t]?.[e.name]??"",n=coerceField(e,s);void 0!==n&&(r[e.name]=n)}e[t]=r}return e}const e={};for(const t of m){const r=V[t.name],s=coerceField(t,r??"");void 0!==s&&(e[t.name]=s)}return e},J=!t?.asset_id||A||h&&(""===g||g===ROLE_OTHER&&!y.trim())||d.some(e=>e.required&&!S[e.name]?.toString().trim())||(c?.keys.some(e=>c.fields.some(t=>t.required&&!T[e]?.[t.name]?.toString().trim()))??!1);if(!t)return null;const Q=n[l]?.label??l;return _jsxs(Dialog,{header:`Edit Asset — ${t.asset_id}`,visible:e,style:{width:"32rem"},onHide:()=>{A||r()},footer:_jsxs(_Fragment,{children:[_jsx(Button,{label:"Cancel",severity:"secondary",onClick:r,disabled:A}),_jsx(Button,{label:"Save",icon:"pi pi-check",onClick:async()=>{if(!t?.asset_id)return;w(!0),I(null);const e={};for(const t of d){const r=S[t.name],s=coerceField(t,r??"");void 0!==s&&(e[t.name]=s)}let n;if(c){n={};for(const e of c.keys){const t={};for(const r of c.fields){const s=T[e]?.[r.name]??"",n=coerceField(r,s);void 0!==n&&(t[r.name]=n)}n[e]=t}}const a={asset_id:t.asset_id,location:y,serial:v,custom:e};n&&(a.sub_locations=n);try{const e=await i("ams.update_asset",MessageType.Request,a);if(!e?.success)return void I(e?.error_message??"update_asset failed");if(L){const e={asset_id:t.asset_id,cal_id:L.cal_id,performed_by:H,expires_at:D?D.toISOString():null,cert_ref:P,notes:$,values:G()},r=await i("ams.update_calibration",MessageType.Request,e);if(!r?.success)return I(r?.error_message??"update_calibration failed"),void k(1)}s?.(),r()}catch(e){I(String(e?.message??e))}finally{w(!1)}},loading:A,disabled:J})]}),children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.25rem 1rem",fontSize:"0.875rem",color:"var(--text-secondary-color)",marginBottom:"0.75rem"},children:[_jsx("span",{children:"Type"})," ",_jsx("span",{children:Q}),t.install_date&&_jsxs(_Fragment,{children:[_jsx("span",{children:"Installed"}),_jsx("span",{children:new Date(t.install_date).toLocaleString()})]})]}),_jsxs(TabView,{activeIndex:O,onTabChange:e=>k(e.index),children:[_jsxs(TabPanel,{header:"Asset",children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Serial"}),_jsx(InputText,{value:v,placeholder:"(none)",onChange:e=>C(e.target.value)}),h&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Role *"}),_jsx(Dropdown,{value:g,options:x,onChange:e=>{var t;(t=e.value)===ROLE_OTHER?b(ROLE_OTHER):(b(t),j(t))},placeholder:"Choose where this asset is mounted"}),g===ROLE_OTHER&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Custom role"}),_jsx(InputText,{value:y,placeholder:"e.g. tsdr_secondary",onChange:e=>j(e.target.value)})]})]}),d.map(e=>_jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:fieldLabel(e)}),_jsx(SchemaFieldInput,{field:e,value:S[e.name]??"",onChange:t=>F(r=>({...r,[e.name]:t}))})]},e.name))]}),c&&_jsxs("div",{style:{marginTop:"1.5rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:c.label??"Sub-locations"}),_jsx("div",{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:c.key_label??"Key"}),c.fields.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:c.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),c.fields.map(t=>{const r=T[e]?.[t.name]??"";return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(SchemaFieldInput,{field:t,value:r,onChange:r=>{E(s=>({...s,[e]:{...s[e]??{},[t.name]:r}}))},style:{width:"100%"}})},t.name)})]},e))})]})})]})]}),L&&_jsxs(TabPanel,{header:`Calibration (${L.cal_id})`,children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Performed by"}),_jsx(InputText,{value:H,onChange:e=>B(e.target.value)}),_jsx("label",{children:"Expires at"}),_jsx(Calendar,{value:D,onChange:e=>q(e.value??null),showIcon:!0,dateFormat:"yy-mm-dd"}),_jsx("label",{children:"Certificate ref"}),_jsx(InputText,{value:P,onChange:e=>z(e.target.value),placeholder:"e.g. ADC-CAL-2026-1043"}),_jsx("label",{children:"Notes"}),_jsx(InputTextarea,{value:$,rows:3,onChange:e=>K(e.target.value),autoResize:!0})]}),p&&_jsxs("div",{style:{marginTop:"1rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:_?c?.label??"Per-axis calibration":"Calibration values"}),_jsx("div",_&&c?{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:c.key_label??"Key"}),u.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:c.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),u.map(t=>{const r=X[e]?.[t.name]??"";return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(SchemaFieldInput,{field:t,value:r,onChange:r=>{N(s=>({...s,[e]:{...s[e]??{},[t.name]:r}}))},style:{width:"100%"}})},t.name)})]},e))})]})}:{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:m.map(e=>_jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:fieldLabel(e)}),_jsx(SchemaFieldInput,{field:e,value:V[e.name]??"",onChange:t=>W(r=>({...r,[e.name]:t}))})]},e.name))})]})]})]}),R&&_jsx("div",{style:{marginTop:"1rem",color:"#ef4444"},children:R})]})};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetRegistryTable.d.ts","sourceRoot":"","sources":["../../../src/components/ams/AssetRegistryTable.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AssetRegistryTable.d.ts","sourceRoot":"","sources":["../../../src/components/ams/AssetRegistryTable.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,0BAA0B,CAAC;AAoHlC,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAuhBtC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useContext,useEffect,useMemo,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Dropdown}from"primereact/dropdown";import{InputText}from"primereact/inputtext";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useAms}from"./AmsProvider";import"./AssetRegistryTable.css";const ROLE_OTHER="__other__",EMPTY_ADD={open:!1,assetType:"",serial:"",roleSelection:"",location:"",customFields:{},subLocationFields:{}};function roleLabel(e){return e.label??e.location}function roleUsageSummary(e){const s=[];return e.used_by.length>0&&s.push(`Used by test method${1===e.used_by.length?"":"s"}: ${e.used_by.join(", ")}`),e.used_by_modules.length>0&&s.push(`Used by module${1===e.used_by_modules.length?"":"s"}: ${e.used_by_modules.join(", ")}`),0===s.length?"Not referenced by any test method or module.":s.join(" • ")}function fieldsFor(e,s){const t=e?.[s]?.fields;return Array.isArray(t)?t:[]}function subLocationsFor(e,s){const t=e?.[s]?.sub_locations;return t&&Array.isArray(t.keys)&&Array.isArray(t.fields)?t:null}function seedCustomFromRoleDefaults(e,s){const t={};if(!e||!e.defaults)return t;const o=new Set(s.map(e=>e.name));for(const[s,l]of Object.entries(e.defaults))o.has(s)&&null!=l&&(t[s]=String(l));return t}function coerceField(e,s){if(void 0!==s&&""!==s)switch(e.type){case"f32":case"f64":case"i8":case"i16":case"i32":case"i64":case"u8":case"u16":case"u32":case"u64":{const e=Number(s);return Number.isFinite(e)?e:void 0}case"bool":return"true"===s||"1"===s;default:return s}}export const AssetRegistryTable=()=>{const{schemas:e,roles:s,assets:t,refreshAssets:o,setSelection:l,selection:a}=useAms(),{invoke:n}=useContext(EventEmitterContext),[i,r]=useState(null),[c,d]=useState(null),[u,m]=useState(EMPTY_ADD),[p,y]=useState(!1);useEffect(()=>{const t=t=>{const o=t.detail,l=o?.assetType??"",a=o?.location??"";if(!l)return;const n=(s[l]??[]).find(e=>e.location===a),i=fieldsFor(e,l);m({...EMPTY_ADD,open:!0,assetType:l,roleSelection:a,location:a,customFields:seedCustomFromRoleDefaults(n,i)})};return window.addEventListener("ams:prefill-add",t),()=>window.removeEventListener("ams:prefill-add",t)},[s,e]);const _=useMemo(()=>Object.keys(e).map(s=>({label:e[s]?.label??s,value:s})),[e]),h=useMemo(()=>u.assetType?s[u.assetType]??[]:[],[u.assetType,s]),f=h.length>0,b=useMemo(()=>{const e=h.map(e=>({label:roleLabel(e),value:e.location}));return e.push({label:"Other (advanced — type a custom role)",value:ROLE_OTHER}),e},[h]),x=useMemo(()=>u.roleSelection===ROLE_OTHER?null:h.find(e=>e.location===u.roleSelection)??null,[u.roleSelection,h]),g=useMemo(()=>t.filter(e=>(!i||e.asset_type===i)&&(!c||e.status===c)),[t,i,c]),j=useMemo(()=>fieldsFor(e,u.assetType),[e,u.assetType]),v=useMemo(()=>subLocationsFor(e,u.assetType),[e,u.assetType]),T=!u.assetType||f&&(""===u.roleSelection||u.roleSelection===ROLE_OTHER&&!u.location.trim())||j.some(e=>e.required&&!u.customFields[e.name]?.toString().trim())||(v?.keys.some(e=>v.fields.some(s=>s.required&&!u.subLocationFields[e]?.[s.name]?.toString().trim()))??!1);return _jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"0.5rem"},children:[_jsxs("div",{style:{display:"flex",gap:"0.75rem",alignItems:"center"},children:[_jsx(Dropdown,{value:i,options:[{label:"All Types",value:null},..._],onChange:e=>r(e.value),placeholder:"Filter by type"}),_jsx(Dropdown,{value:c,options:[{label:"All Statuses",value:null},{label:"Active",value:"active"},{label:"Out for Service",value:"out_for_service"},{label:"Retired",value:"retired"}],onChange:e=>d(e.value),placeholder:"Filter by status"}),_jsx("span",{style:{marginLeft:"auto"},children:_jsx(Button,{icon:"pi pi-plus",label:"Add Asset",onClick:()=>m(e=>({...e,open:!0})),disabled:0===_.length})})]}),_jsxs(DataTable,{value:g,selectionMode:"single",selection:g.find(e=>e.asset_id===a.assetId)??null,onSelectionChange:e=>{const s=e.value;s&&l({assetType:s.asset_type,assetId:s.asset_id})},dataKey:"asset_id",emptyMessage:0===_.length?"AMS not enabled in this project (no asset_types declared).":"No assets registered yet.",stripedRows:!0,className:"ams-asset-table",children:[_jsx(Column,{field:"asset_id",header:"Asset ID"}),_jsx(Column,{field:"asset_type",header:"Type",body:s=>e[s.asset_type]?.label??s.asset_type}),_jsx(Column,{field:"serial",header:"Serial"}),_jsx(Column,{field:"location",header:"Role",body:e=>e.location?((e,t)=>{const o=(s[e]??[]).find(e=>e.location===t);return o?roleLabel(o):t})(e.asset_type,e.location):_jsx("span",{style:{color:"#9ca3af"},children:"—"})}),_jsx(Column,{field:"status",header:"Status"}),_jsx(Column,{header:"Calibration",body:e=>e.current_calibration_id?_jsx("span",{title:e.current_calibration_id,children:"✓"}):_jsx("span",{style:{color:"#f59e0b"},children:"none"})})]}),_jsxs(Dialog,{header:"Add New Asset",visible:u.open,style:{width:"32rem"},closable:!p,closeOnEscape:!p,onHide:()=>{p||m(EMPTY_ADD)},footer:p?_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",alignItems:"center",gap:"0.5rem"},children:[_jsx("i",{className:"pi pi-spin pi-spinner"}),_jsx("span",{children:"Creating asset…"})]}):_jsxs(_Fragment,{children:[_jsx(Button,{label:"Cancel",severity:"secondary",onClick:()=>m(EMPTY_ADD)}),_jsx(Button,{label:"Create",icon:"pi pi-check",onClick:async()=>{if(!u.assetType||p)return;const e={};for(const s of j){const t=u.customFields[s.name];if(void 0!==t&&""!==t)switch(s.type){case"f32":case"f64":case"i8":case"i16":case"i32":case"i64":case"u8":case"u16":case"u32":case"u64":{const o=Number(t);Number.isFinite(o)&&(e[s.name]=o);break}case"bool":e[s.name]="true"===t||"1"===t;break;default:e[s.name]=t}}const s={};if(v)for(const e of v.keys){const t={};for(const s of v.fields){const o=coerceField(s,u.subLocationFields[e]?.[s.name]??"");void 0!==o&&(t[s.name]=o)}s[e]=t}const t={asset_type:u.assetType,serial:u.serial,location:u.location,custom:e};v&&(t.sub_locations=s),y(!0);try{const e=await n("ams.create_asset",MessageType.Request,t);e?.success&&(m(EMPTY_ADD),await o(),e.data?.asset_id&&l({assetType:u.assetType,assetId:e.data.asset_id}))}catch(e){}finally{y(!1)}},disabled:T})]}),children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Type *"}),_jsx(Dropdown,{value:u.assetType,options:_,onChange:t=>(t=>{const o=s[t]??[],l=1===o.length?o[0]:void 0,a=l?{roleSelection:l.location,location:l.location}:{roleSelection:"",location:""},n=fieldsFor(e,t);m(e=>({...e,assetType:t,...a,customFields:seedCustomFromRoleDefaults(l,n),subLocationFields:{}}))})(t.value),placeholder:"Choose asset type"}),u.assetType&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Serial"}),_jsx(InputText,{value:u.serial,onChange:e=>m(s=>({...s,serial:e.target.value}))})]}),f&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Role *"}),_jsx(Dropdown,{value:u.roleSelection,options:b,onChange:e=>(e=>{if(e===ROLE_OTHER)return void m(e=>({...e,roleSelection:ROLE_OTHER,location:""}));const s=h.find(s=>s.location===e);m(t=>({...t,roleSelection:e,location:e,customFields:seedCustomFromRoleDefaults(s,j)}))})(e.value),placeholder:"Choose where this asset is mounted"}),u.roleSelection===ROLE_OTHER&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Custom role"}),_jsx(InputText,{value:u.location,placeholder:"e.g. tsdr_secondary",onChange:e=>m(s=>({...s,location:e.target.value}))})]})]}),j.map(e=>{const s=e.label??e.name,t=e.units?`${s} [${e.units}]${e.required?" *":""}`:`${s}${e.required?" *":""}`,o="string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:t}),"bool"===e.type?_jsx(Dropdown,{value:u.customFields[e.name]??"",options:[{label:"true",value:"true"},{label:"false",value:"false"}],onChange:s=>m(t=>({...t,customFields:{...t.customFields,[e.name]:s.value}})),placeholder:"—"}):_jsx(InputText,{value:u.customFields[e.name]??"",keyfilter:o?"num":void 0,placeholder:e.description??void 0,onChange:s=>m(t=>({...t,customFields:{...t.customFields,[e.name]:s.target.value}}))})]},e.name)})]}),v&&_jsxs("div",{style:{marginTop:"1.5rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:v.label??"Sub-locations"}),_jsx("div",{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:v.key_label??"Key"}),v.fields.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:v.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),v.fields.map(s=>{const t=u.subLocationFields[e]?.[s.name]??"",o="string"!==s.type&&"bool"!==s.type;return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(InputText,{value:t,keyfilter:o?"num":void 0,onChange:t=>{const o=t.target.value;m(t=>({...t,subLocationFields:{...t.subLocationFields,[e]:{...t.subLocationFields[e]??{},[s.name]:o}}}))},style:{width:"100%"}})},s.name)})]},e))})]})})]}),_jsxs("div",{style:{fontSize:"0.875rem",color:"#9ca3af",marginTop:"1rem"},children:[f&&x&&_jsxs(_Fragment,{children:[x.description&&_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:x.description}),_jsxs("p",{style:{margin:"0 0 0.5rem 0",color:"#34d399"},children:["✓ ",roleUsageSummary(x)]})]}),f&&u.roleSelection===ROLE_OTHER&&_jsx("p",{style:{margin:"0 0 0.5rem 0",color:"#f59e0b"},children:"⚠ No test method on this machine references this custom role yet. The asset will be registered but won't be picked up at start_test unless you add an asset_ref to a test method's project.json."}),!f&&u.assetType&&_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"This asset type isn't selected by physical role on this machine — test methods reference it by ID instead. Just register the asset and pick it by ID on the Test Setup form when running a test."}),_jsxs("p",{style:{margin:0},children:["The asset_id is generated by the server (format:"," ",_jsxs("code",{children:[u.assetType?e[u.assetType]?.id_prefix??"A-":"A-","YYYYMMDDTHHMMSS"]}),"). Manufacturer serial is recorded for traceability but is not used as the unique key."]})]})]})]})};
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useContext,useEffect,useMemo,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Dropdown}from"primereact/dropdown";import{InputText}from"primereact/inputtext";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useAms}from"./AmsProvider";import{SchemaFieldInput,coerceField,fieldLabel}from"./schemaField";import"./AssetRegistryTable.css";const ROLE_OTHER="__other__",EMPTY_ADD={open:!1,assetType:"",serial:"",roleSelection:"",location:"",customFields:{},subLocationFields:{}};function roleLabel(e){return e.label??e.location}function roleUsageSummary(e){const s=[];return e.used_by.length>0&&s.push(`Used by test method${1===e.used_by.length?"":"s"}: ${e.used_by.join(", ")}`),e.used_by_modules.length>0&&s.push(`Used by module${1===e.used_by_modules.length?"":"s"}: ${e.used_by_modules.join(", ")}`),0===s.length?"Not referenced by any test method or module.":s.join(" • ")}function fieldsFor(e,s){const t=e?.[s]?.fields;return Array.isArray(t)?t:[]}function subLocationsFor(e,s){const t=e?.[s]?.sub_locations;return t&&Array.isArray(t.keys)&&Array.isArray(t.fields)?t:null}function seedCustomFromRoleDefaults(e,s){const t={};if(!e||!e.defaults)return t;const o=new Set(s.map(e=>e.name));for(const[s,l]of Object.entries(e.defaults))o.has(s)&&null!=l&&(t[s]=String(l));return t}export const AssetRegistryTable=()=>{const{schemas:e,roles:s,assets:t,refreshAssets:o,setSelection:l,selection:a}=useAms(),{invoke:i}=useContext(EventEmitterContext),[n,r]=useState(null),[c,d]=useState(null),[u,m]=useState(EMPTY_ADD),[p,_]=useState(!1);useEffect(()=>{const t=t=>{const o=t.detail,l=o?.assetType??"",a=o?.location??"";if(!l)return;const i=(s[l]??[]).find(e=>e.location===a),n=fieldsFor(e,l);m({...EMPTY_ADD,open:!0,assetType:l,roleSelection:a,location:a,customFields:seedCustomFromRoleDefaults(i,n)})};return window.addEventListener("ams:prefill-add",t),()=>window.removeEventListener("ams:prefill-add",t)},[s,e]);const h=useMemo(()=>Object.keys(e).map(s=>({label:e[s]?.label??s,value:s})),[e]),y=useMemo(()=>u.assetType?s[u.assetType]??[]:[],[u.assetType,s]),f=y.length>0,b=useMemo(()=>{const e=y.map(e=>({label:roleLabel(e),value:e.location}));return e.push({label:"Other (advanced — type a custom role)",value:ROLE_OTHER}),e},[y]),x=useMemo(()=>u.roleSelection===ROLE_OTHER?null:y.find(e=>e.location===u.roleSelection)??null,[u.roleSelection,y]),g=useMemo(()=>t.filter(e=>(!n||e.asset_type===n)&&(!c||e.status===c)),[t,n,c]),j=useMemo(()=>fieldsFor(e,u.assetType),[e,u.assetType]),v=useMemo(()=>subLocationsFor(e,u.assetType),[e,u.assetType]),T=!u.assetType||f&&(""===u.roleSelection||u.roleSelection===ROLE_OTHER&&!u.location.trim())||j.some(e=>e.required&&!u.customFields[e.name]?.toString().trim())||(v?.keys.some(e=>v.fields.some(s=>s.required&&!u.subLocationFields[e]?.[s.name]?.toString().trim()))??!1);return _jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"0.5rem"},children:[_jsxs("div",{style:{display:"flex",gap:"0.75rem",alignItems:"center"},children:[_jsx(Dropdown,{value:n,options:[{label:"All Types",value:null},...h],onChange:e=>r(e.value),placeholder:"Filter by type"}),_jsx(Dropdown,{value:c,options:[{label:"All Statuses",value:null},{label:"Active",value:"active"},{label:"Out for Service",value:"out_for_service"},{label:"Retired",value:"retired"}],onChange:e=>d(e.value),placeholder:"Filter by status"}),_jsx("span",{style:{marginLeft:"auto"},children:_jsx(Button,{icon:"pi pi-plus",label:"Add Asset",onClick:()=>m(e=>({...e,open:!0})),disabled:0===h.length})})]}),_jsxs(DataTable,{value:g,selectionMode:"single",selection:g.find(e=>e.asset_id===a.assetId)??null,onSelectionChange:e=>{const s=e.value;s&&l({assetType:s.asset_type,assetId:s.asset_id})},dataKey:"asset_id",emptyMessage:0===h.length?"AMS not enabled in this project (no asset_types declared).":"No assets registered yet.",stripedRows:!0,className:"ams-asset-table",children:[_jsx(Column,{field:"asset_id",header:"Asset ID"}),_jsx(Column,{field:"asset_type",header:"Type",body:s=>e[s.asset_type]?.label??s.asset_type}),_jsx(Column,{field:"serial",header:"Serial"}),_jsx(Column,{field:"location",header:"Role",body:e=>e.location?((e,t)=>{const o=(s[e]??[]).find(e=>e.location===t);return o?roleLabel(o):t})(e.asset_type,e.location):_jsx("span",{style:{color:"#9ca3af"},children:"—"})}),_jsx(Column,{field:"status",header:"Status"}),_jsx(Column,{header:"Calibration",body:e=>e.current_calibration_id?_jsx("span",{title:e.current_calibration_id,children:"✓"}):_jsx("span",{style:{color:"#f59e0b"},children:"none"})})]}),_jsxs(Dialog,{header:"Add New Asset",visible:u.open,style:{width:"32rem"},closable:!p,closeOnEscape:!p,onHide:()=>{p||m(EMPTY_ADD)},footer:p?_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",alignItems:"center",gap:"0.5rem"},children:[_jsx("i",{className:"pi pi-spin pi-spinner"}),_jsx("span",{children:"Creating asset…"})]}):_jsxs(_Fragment,{children:[_jsx(Button,{label:"Cancel",severity:"secondary",onClick:()=>m(EMPTY_ADD)}),_jsx(Button,{label:"Create",icon:"pi pi-check",onClick:async()=>{if(!u.assetType||p)return;const e={};for(const s of j){const t=u.customFields[s.name];if(void 0!==t&&""!==t)switch(s.type){case"f32":case"f64":case"i8":case"i16":case"i32":case"i64":case"u8":case"u16":case"u32":case"u64":{const o=Number(t);Number.isFinite(o)&&(e[s.name]=o);break}case"bool":e[s.name]="true"===t||"1"===t;break;default:e[s.name]=t}}const s={};if(v)for(const e of v.keys){const t={};for(const s of v.fields){const o=u.subLocationFields[e]?.[s.name]??"",l=coerceField(s,o);void 0!==l&&(t[s.name]=l)}s[e]=t}const t={asset_type:u.assetType,serial:u.serial,location:u.location,custom:e};v&&(t.sub_locations=s),_(!0);try{const e=await i("ams.create_asset",MessageType.Request,t);e?.success&&(m(EMPTY_ADD),await o(),e.data?.asset_id&&l({assetType:u.assetType,assetId:e.data.asset_id}))}catch(e){}finally{_(!1)}},disabled:T})]}),children:[_jsxs("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",alignItems:"center"},children:[_jsx("label",{children:"Type *"}),_jsx(Dropdown,{value:u.assetType,options:h,onChange:t=>(t=>{const o=s[t]??[],l=1===o.length?o[0]:void 0,a=l?{roleSelection:l.location,location:l.location}:{roleSelection:"",location:""},i=fieldsFor(e,t);m(e=>({...e,assetType:t,...a,customFields:seedCustomFromRoleDefaults(l,i),subLocationFields:{}}))})(t.value),placeholder:"Choose asset type"}),u.assetType&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Serial"}),_jsx(InputText,{value:u.serial,onChange:e=>m(s=>({...s,serial:e.target.value}))})]}),f&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Role *"}),_jsx(Dropdown,{value:u.roleSelection,options:b,onChange:e=>(e=>{if(e===ROLE_OTHER)return void m(e=>({...e,roleSelection:ROLE_OTHER,location:""}));const s=y.find(s=>s.location===e);m(t=>({...t,roleSelection:e,location:e,customFields:seedCustomFromRoleDefaults(s,j)}))})(e.value),placeholder:"Choose where this asset is mounted"}),u.roleSelection===ROLE_OTHER&&_jsxs(_Fragment,{children:[_jsx("label",{children:"Custom role"}),_jsx(InputText,{value:u.location,placeholder:"e.g. tsdr_secondary",onChange:e=>m(s=>({...s,location:e.target.value}))})]})]}),j.map(e=>_jsxs(React.Fragment,{children:[_jsx("label",{title:e.description??void 0,children:fieldLabel(e)}),_jsx(SchemaFieldInput,{field:e,value:u.customFields[e.name]??"",onChange:s=>m(t=>({...t,customFields:{...t.customFields,[e.name]:s}}))})]},e.name))]}),v&&_jsxs("div",{style:{marginTop:"1.5rem"},children:[_jsx("h4",{style:{margin:"0 0 0.5rem 0"},children:v.label??"Sub-locations"}),_jsx("div",{style:{overflowX:"auto"},children:_jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.875rem"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx("th",{style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:v.key_label??"Key"}),v.fields.map(e=>_jsxs("th",{title:e.description??void 0,style:{textAlign:"left",padding:"0.25rem 0.5rem",borderBottom:"1px solid var(--surface-border)"},children:[e.label??e.name,e.units?` [${e.units}]`:"",e.required?" *":""]},e.name))]})}),_jsx("tbody",{children:v.keys.map(e=>_jsxs("tr",{children:[_jsx("td",{style:{padding:"0.25rem 0.5rem",fontWeight:600},children:e}),v.fields.map(s=>{const t=u.subLocationFields[e]?.[s.name]??"";return _jsx("td",{style:{padding:"0.25rem 0.5rem"},children:_jsx(SchemaFieldInput,{field:s,value:t,onChange:t=>{m(o=>({...o,subLocationFields:{...o.subLocationFields,[e]:{...o.subLocationFields[e]??{},[s.name]:t}}}))},style:{width:"100%"}})},s.name)})]},e))})]})})]}),_jsxs("div",{style:{fontSize:"0.875rem",color:"#9ca3af",marginTop:"1rem"},children:[f&&x&&_jsxs(_Fragment,{children:[x.description&&_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:x.description}),_jsxs("p",{style:{margin:"0 0 0.5rem 0",color:"#34d399"},children:["✓ ",roleUsageSummary(x)]})]}),f&&u.roleSelection===ROLE_OTHER&&_jsx("p",{style:{margin:"0 0 0.5rem 0",color:"#f59e0b"},children:"⚠ No test method on this machine references this custom role yet. The asset will be registered but won't be picked up at start_test unless you add an asset_ref to a test method's project.json."}),!f&&u.assetType&&_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"This asset type isn't selected by physical role on this machine — test methods reference it by ID instead. Just register the asset and pick it by ID on the Test Setup form when running a test."}),_jsxs("p",{style:{margin:0},children:["The asset_id is generated by the server (format:"," ",_jsxs("code",{children:[u.assetType?e[u.assetType]?.id_prefix??"A-":"A-","YYYYMMDDTHHMMSS"]}),"). Manufacturer serial is recorded for traceability but is not used as the unique key."]})]})]})]})};
|
|
@@ -10,6 +10,8 @@ export { MissingAssetsBanner } from './MissingAssetsBanner';
|
|
|
10
10
|
export { InstalledAssetPicker } from './InstalledAssetPicker';
|
|
11
11
|
export type { InstalledAssetPickerProps } from './InstalledAssetPicker';
|
|
12
12
|
export { AssetNameplateGrid } from './AssetNameplateGrid';
|
|
13
|
+
export { SchemaFieldInput, coerceField, fieldChoices, fieldLabel, isNumericField } from './schemaField';
|
|
14
|
+
export type { SchemaField, FieldOption, Choice, SchemaFieldInputProps } from './schemaField';
|
|
13
15
|
export { useInstalledAsset } from './useInstalledAsset';
|
|
14
16
|
export type { InstalledAssetState } from './useInstalledAsset';
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ams/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7H,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ams/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7H,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI1D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{AmsProvider,useAms,useAmsSchemas,useAmsRoles,useAmsAlerts,useAmsAssets,useAmsSelection}from"./AmsProvider";export{AssetRegistryTable}from"./AssetRegistryTable";export{AssetDetailView}from"./AssetDetailView";export{CalibrationEntryDialog}from"./CalibrationEntryDialog";export{AssetEditDialog}from"./AssetEditDialog";export{SubLocationPicker}from"./SubLocationPicker";export{PlaceholderHealthPanel}from"./PlaceholderHealthPanel";export{MissingAssetsBanner}from"./MissingAssetsBanner";export{InstalledAssetPicker}from"./InstalledAssetPicker";export{AssetNameplateGrid}from"./AssetNameplateGrid";export{useInstalledAsset}from"./useInstalledAsset";
|
|
1
|
+
export{AmsProvider,useAms,useAmsSchemas,useAmsRoles,useAmsAlerts,useAmsAssets,useAmsSelection}from"./AmsProvider";export{AssetRegistryTable}from"./AssetRegistryTable";export{AssetDetailView}from"./AssetDetailView";export{CalibrationEntryDialog}from"./CalibrationEntryDialog";export{AssetEditDialog}from"./AssetEditDialog";export{SubLocationPicker}from"./SubLocationPicker";export{PlaceholderHealthPanel}from"./PlaceholderHealthPanel";export{MissingAssetsBanner}from"./MissingAssetsBanner";export{InstalledAssetPicker}from"./InstalledAssetPicker";export{AssetNameplateGrid}from"./AssetNameplateGrid";export{SchemaFieldInput,coerceField,fieldChoices,fieldLabel,isNumericField}from"./schemaField";export{useInstalledAsset}from"./useInstalledAsset";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* One asset-schema field: its shape, how a typed value is coerced out of the
|
|
4
|
+
* string the form holds, and the input control that edits it.
|
|
5
|
+
*
|
|
6
|
+
* Every AMS form renders the same thing in seven places — the asset create
|
|
7
|
+
* form, the asset edit form, the per-axis sub-location matrix in each of those,
|
|
8
|
+
* the per-axis calibration matrix, the flat calibration values, and the
|
|
9
|
+
* standalone calibration dialog. They had drifted into seven copies of the same
|
|
10
|
+
* `label ?? name` / `[units]` / `*` / bool-Dropdown-else-InputText block, plus
|
|
11
|
+
* three copies of `SchemaField` and two of `coerceField`.
|
|
12
|
+
*
|
|
13
|
+
* That duplication is why a field could not gain a behaviour: adding one meant
|
|
14
|
+
* finding all seven. `capacity_units` is the case that forced this — it is a
|
|
15
|
+
* free-text string today, so an operator typed "Newtons" where the consuming
|
|
16
|
+
* machine matched on "N", and the fix is a dropdown in every one of those
|
|
17
|
+
* places or it is not a fix.
|
|
18
|
+
*/
|
|
19
|
+
import React from 'react';
|
|
20
|
+
/**
|
|
21
|
+
* One choice in a field's dropdown, as authored in the asset-type schema.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors `FieldOption` in autocore-ams's `config.rs`: either a bare scalar,
|
|
24
|
+
* where the stored value and the operator-facing label are the same, or an
|
|
25
|
+
* explicit pair when they should differ.
|
|
26
|
+
*
|
|
27
|
+
* ```jsonc
|
|
28
|
+
* "options": ["N", "kN", "lbf"]
|
|
29
|
+
* "options": [{ "label": "Newtons (N)", "value": "N" }]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type FieldOption = string | number | boolean | {
|
|
33
|
+
label: string;
|
|
34
|
+
value: unknown;
|
|
35
|
+
};
|
|
36
|
+
/** One field of an asset type's `fields` / `calibration_fields` schema. */
|
|
37
|
+
export interface SchemaField {
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
units?: string;
|
|
41
|
+
label?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
required?: boolean;
|
|
44
|
+
/** Fixed set of choices. When present the field is a dropdown, not a
|
|
45
|
+
* free-form input — see the file header. */
|
|
46
|
+
options?: FieldOption[];
|
|
47
|
+
}
|
|
48
|
+
/** A choice normalised for PrimeReact's `Dropdown`. */
|
|
49
|
+
export interface Choice {
|
|
50
|
+
label: string;
|
|
51
|
+
value: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The choices this field offers, or `null` if it is free-form.
|
|
55
|
+
*
|
|
56
|
+
* Booleans are a fixed two-choice dropdown, as they always were. An explicit
|
|
57
|
+
* `options` list wins over the type's own rendering, which is what lets a
|
|
58
|
+
* `string` field be constrained without inventing a new type.
|
|
59
|
+
*
|
|
60
|
+
* Values are carried as STRINGS because that is what the form state holds;
|
|
61
|
+
* `coerceField` converts back on submit.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fieldChoices(field: SchemaField): Choice[] | null;
|
|
64
|
+
/** True when the field takes digits only, so the input can keyfilter. A field
|
|
65
|
+
* with `options` is never free-typed, so it never keyfilters. */
|
|
66
|
+
export declare function isNumericField(field: SchemaField): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Coerce the form's string back to the field's declared type.
|
|
69
|
+
*
|
|
70
|
+
* Numeric strings become JSON numbers and booleans become bools, because the
|
|
71
|
+
* placeholder resolver and the SDO writers downstream expect native types — a
|
|
72
|
+
* string `"5000"` does not satisfy a `${ams.*}` reference asking for a numeric
|
|
73
|
+
* `max_val`. Empty stays `undefined` so an untouched field is omitted from the
|
|
74
|
+
* record rather than stored blank.
|
|
75
|
+
*/
|
|
76
|
+
export declare function coerceField(field: SchemaField, raw: string | undefined): any;
|
|
77
|
+
/** The label a form shows for this field: pretty name, units, required mark. */
|
|
78
|
+
export declare function fieldLabel(field: SchemaField): string;
|
|
79
|
+
export interface SchemaFieldInputProps {
|
|
80
|
+
field: SchemaField;
|
|
81
|
+
/** Current value, as the string the form state holds. */
|
|
82
|
+
value: string;
|
|
83
|
+
/** Called with the new string. Coercion happens on submit, not here. */
|
|
84
|
+
onChange: (next: string) => void;
|
|
85
|
+
style?: React.CSSProperties;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The input control for one schema field: a dropdown when the field has a
|
|
89
|
+
* fixed set of choices, a text box otherwise.
|
|
90
|
+
*
|
|
91
|
+
* Deliberately uncontrolled about layout — callers place it in a grid or a
|
|
92
|
+
* table cell — so this owns only the decision that must be the same everywhere.
|
|
93
|
+
*/
|
|
94
|
+
export declare const SchemaFieldInput: React.FC<SchemaFieldInputProps>;
|
|
95
|
+
//# sourceMappingURL=schemaField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaField.d.ts","sourceRoot":"","sources":["../../../src/components/ams/schemaField.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;iDAC6C;IAC7C,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CAC3B;AAED,uDAAuD;AACvD,MAAM,WAAW,MAAM;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,EAAE,GAAG,IAAI,CAchE;AAED;kEACkE;AAClE,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAG1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAc5E;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAIrD;AAED,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,WAAW,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwB5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as _jsx}from"react/jsx-runtime";import React from"react";import{Dropdown}from"primereact/dropdown";import{InputText}from"primereact/inputtext";export function fieldChoices(e){return e.options&&e.options.length>0?e.options.map(e=>null!==e&&"object"==typeof e&&"value"in e?{label:String(e.label),value:String(e.value)}:{label:String(e),value:String(e)}):"bool"===e.type?[{label:"true",value:"true"},{label:"false",value:"false"}]:null}export function isNumericField(e){return!fieldChoices(e)&&("string"!==e.type&&"bool"!==e.type)}export function coerceField(e,t){if(void 0!==t&&""!==t)switch(e.type){case"f32":case"f64":case"i8":case"i16":case"i32":case"i64":case"u8":case"u16":case"u32":case"u64":{const e=Number(t);return Number.isFinite(e)?e:void 0}case"bool":return"true"===t||"1"===t;default:return t}}export function fieldLabel(e){const t=e.label??e.name,i=e.required?" *":"";return e.units?`${t} [${e.units}]${i}`:`${t}${i}`}export const SchemaFieldInput=({field:e,value:t,onChange:i,style:o})=>{const r=fieldChoices(e);return r?_jsx(Dropdown,{value:t??"",options:r,onChange:e=>i(e.value),placeholder:"—",style:o}):_jsx(InputText,{value:t??"",keyfilter:isNumericField(e)?"num":void 0,placeholder:e.description??void 0,onChange:e=>i(e.target.value),style:o})};
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
/* Runs excluded from cross-test aggregation are kept visible but dimmed
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Runs excluded from cross-test aggregation are kept visible but dimmed, so an
|
|
2
|
+
operator can see at a glance which rows don't feed the avg/min/max/stddev/
|
|
3
|
+
count results for their sample group. The row's Status column says the word
|
|
4
|
+
"Excluded" outright, and its Details dialog says what that means — the dim
|
|
5
|
+
is a scanning aid, never the only signal.
|
|
6
|
+
|
|
7
|
+
NO line-through. It used to be here and it read as "deleted": an excluded
|
|
8
|
+
run is intact, still downloadable, and one click from being counted again.
|
|
9
|
+
|
|
10
|
+
Nor is there an exception rule for the action buttons. There was one —
|
|
11
|
+
`.tis-row-excluded > td .p-button { opacity: 1; text-decoration: none }` —
|
|
12
|
+
and neither half of it could ever have worked, because both properties come
|
|
13
|
+
from the ancestor `td`:
|
|
14
|
+
|
|
15
|
+
• `opacity` on the td composites its whole subtree as one group. A
|
|
16
|
+
descendant's `opacity: 1` cannot escape an ancestor's 0.55.
|
|
17
|
+
• `text-decoration` propagates from an ancestor to its in-flow
|
|
18
|
+
descendants and cannot be cancelled by a descendant at all.
|
|
19
|
+
|
|
20
|
+
So anything that must be exempt has to be exempt at the td: put the effect
|
|
21
|
+
on the text cells, not on every cell. Kept as one rule here because 0.55
|
|
22
|
+
leaves the buttons legible and they are what the operator reaches for. */
|
|
4
23
|
.tis-row-excluded > td {
|
|
5
24
|
opacity: 0.55;
|
|
6
|
-
text-decoration: line-through;
|
|
7
|
-
}
|
|
8
|
-
/* Keep the action buttons (Include / Download) legible and clickable. */
|
|
9
|
-
.tis-row-excluded > td .p-button {
|
|
10
|
-
opacity: 1;
|
|
11
|
-
text-decoration: none;
|
|
12
25
|
}
|
|
@@ -16,5 +16,11 @@ export interface ResultHistoryTableProps {
|
|
|
16
16
|
*/
|
|
17
17
|
methodId?: string;
|
|
18
18
|
}
|
|
19
|
+
type ChipSeverity = 'success' | 'info' | 'warning' | 'danger' | 'secondary' | undefined;
|
|
20
|
+
export interface StateChip {
|
|
21
|
+
label: string;
|
|
22
|
+
severity: ChipSeverity;
|
|
23
|
+
}
|
|
19
24
|
export declare const ResultHistoryTable: React.FC<ResultHistoryTableProps>;
|
|
25
|
+
export {};
|
|
20
26
|
//# sourceMappingURL=ResultHistoryTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAavE,OAAO,0BAA0B,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AA8DD,KAAK,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AACxF,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE;AAkNpE,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAyoBhE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useRef}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Badge}from"primereact/badge";import{Menu}from"primereact/menu";import{Toast}from"primereact/toast";import{confirmDialog}from"primereact/confirmdialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{SampleSummaryPanel}from"./SampleSummaryPanel";import"./ResultHistoryTable.css";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(i)},downloadViaUrl=(e,t)=>{const o=document.createElement("a");o.href=e,o.download=t,document.body.appendChild(o),o.click(),o.remove()},LIFECYCLE_DONE={complete:{summary:"Test closed",detail:e=>`${e} is now Completed. It can still be reopened.`},abandon:{summary:"Test abandoned",detail:e=>`${e} is out of the history and out of the sample averages. Its data is still on disk — use Show abandoned to see it.`},reopen:{summary:"Test reopened",detail:e=>`${e} is open again — new cycles are added to it.`}};export const ResultHistoryTable=e=>{const t=useTis(),o=e.projectId??t.selection.projectId,i=e.methodId,[s,a]=useState([]),[r,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),[u,m]=useState(null),f=useRef(null),[h,_]=useState(null),b=useRef(0),[y,g]=useState(null),[w,x]=useState(!1),v=useRef(null),{invoke:C}=useContext(EventEmitterContext),j=useRef(0),T=async()=>{if(!o)return void a([]);const e=++j.current;n(!0);try{const t={project_id:o};i&&(t.method_id=i);const s=await C("tis.list_tests",MessageType.Request,t);if(e!==j.current)return;s.success&&s.data&&s.data.tests&&a(s.data.tests)}catch(e){}e===j.current&&n(!1)};useEffect(()=>{T()},[o,i,t.state.activeRunId,t.state.active]),useEffect(()=>{h&&f.current?.show({currentTarget:h.anchor})},[h]);const $=async(e,t)=>{const s=e?.run_id,a=e?.method_id??i;if(!s||!a||!o)return;const r="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",n="report"===t?"test report":"test data";d({runId:s,kind:t});try{const e=await C(r,MessageType.Request,{project_id:o,method_id:a,run_id:s});if(!e?.success)return void alert(`Failed to build ${n} for ${s}`+(e?.error_message?`: ${e.error_message}`:""));const i="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${a}_${s}_${t}.csv`;if("report"===t){const t="string"==typeof e.data?.csv?e.data.csv:"";if(!t)return void alert(`${n} for ${s} is empty.`);downloadCsvBlob(i,t)}else{const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert(`Server did not return a download URL for ${n}.`);downloadViaUrl(t,i)}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},R=async(e,t)=>{const s=e?.run_id,a=e?.method_id??i;if(!s||!a||!o)return;const r="complete"===t?"tis.finish_test":"abandon"===t?"tis.reset_test":"tis.resume_test",n=e?.sample_id||s;g(s);try{const e=await C(r,MessageType.Request,{project_id:o,method_id:a,run_id:s});if(!e?.success)return void alert(`Could not ${t} ${s}`+(e?.error_message?`:\n\n${e.error_message}`:""));await T(),v.current?.show({severity:"abandon"===t?"warn":"success",summary:LIFECYCLE_DONE[t].summary,detail:LIFECYCLE_DONE[t].detail(n),life:4e3})}catch(e){alert(`${t} failed: ${e instanceof Error?e.message:String(e)}`)}finally{g(null)}},S=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",i="report"===e?"project report":"project archive";p(e);try{const s=await C(t,MessageType.Request,{project_id:o});if(!s?.success)return void alert(`Failed to build ${i}`+(s?.error_message?`: ${s.error_message}`:""));const a="string"==typeof s.data?.download_url?s.data.download_url:"";if(!a)return void alert(`Server did not return a download URL for ${i}.`);const r="report"===e?`${o}_project_report.csv`:`${o}_project_archive.zip`,n="string"==typeof s.data?.filename&&s.data.filename?s.data.filename:r;downloadViaUrl(a,n)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},k=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const o=e?.config;return o&&"object"==typeof o&&"string"==typeof o.sample_id?o.sample_id:""},I=s.filter(e=>!0===e?.finalized).length,E=w?s:s.filter(e=>!0!==e?.finalized);return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:o?`Test History: ${o}${i?` / ${i}`:""}`:"Test History (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center"},children:[I>0&&_jsx(Button,{icon:w?"pi pi-eye-slash":"pi pi-eye",label:w?"Hide abandoned":`Show abandoned (${I})`,size:"small",outlined:!0,severity:"secondary",onClick:()=>x(e=>!e)}),_jsx(Button,{icon:"report"===c?"pi pi-spin pi-spinner":"pi pi-file",label:"Download Report",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>S("report"),tooltip:"Download a CSV report of every test in this project",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"archive"===c?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>S("archive"),tooltip:"Download a ZIP of the entire project directory (all tests, raw data, configs)",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:T,disabled:r})]})]}),_jsx(SampleSummaryPanel,{projectId:o,methodId:i}),_jsxs(DataTable,{value:E,loading:r,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},rowClassName:e=>!0===e?.excluded?"tis-row-excluded":"",selectionMode:"single",onSelectionChange:e=>{const o=e.value;o?.run_id&&t.setSelection({projectId:o.project_id??null,methodId:o.method_id??null,runId:o.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:k,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>k(t).localeCompare(k(o))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{header:"Status",field:"status",sortable:!0,body:e=>{const t=(e=>{if(!0===e?.finalized)return{label:"Abandoned",severity:"danger"};const t="string"==typeof e?.status?e.status:"completed";if("completed"===t&&!0===e?.auto_closed)return{label:"Interrupted",severity:"warning"};switch(t){case"in_progress":return{label:"In Progress",severity:"info"};case"paused":return{label:"Paused",severity:"warning"};case"completed":return{label:"Completed",severity:"success"};default:return{label:t,severity:void 0}}})(e);return _jsx(Badge,{value:t.label,severity:t.severity})},style:{minWidth:"6rem"}}),_jsx(Column,{header:"Incl.",align:"center",alignHeader:"center",style:{width:"4.5rem"},body:e=>{const t=!0===e?.excluded,s=u===e.run_id;return _jsx(Button,{icon:s?"pi pi-spin pi-spinner":t?"pi pi-eye-slash":"pi pi-check",rounded:!0,text:!0,size:"small",severity:t?"warning":"success",disabled:null!==u,onClick:()=>(async e=>{const t=e?.run_id,s=e?.method_id??i;if(!t||!s||!o)return;const a=!0!==e?.excluded;m(t);try{const e=await C("tis.set_test_excluded",MessageType.Request,{project_id:o,method_id:s,run_id:t,excluded:a});if(!e?.success)return void alert(`Failed to ${a?"exclude":"include"} ${t}`+(e?.error_message?`: ${e.error_message}`:""));await T()}catch(e){alert(`Exclude toggle failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(null)}})(e),"aria-label":t?"Excluded — click to include":"Included — click to exclude",tooltip:t?"Excluded from cross-test results — click to include":"Included in cross-test results — click to exclude",tooltipOptions:{position:"left"}})}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=l?.runId===e.run_id&&"data"===l?.kind,o=l?.runId===e.run_id&&"report"===l?.kind,i=null!==l;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Raw",size:"small",outlined:!0,disabled:i,onClick:()=>$(e,"data"),tooltip:"Download raw + filtered trace data as one CSV",tooltipOptions:{position:"left"}}),_jsx(Button,{icon:o?"pi pi-spin pi-spinner":"pi pi-file",label:"Results",size:"small",outlined:!0,disabled:i,onClick:()=>$(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}}),_jsx(Column,{header:"",style:{width:"3rem"},body:e=>_jsx(Button,{icon:y===e.run_id?"pi pi-spin pi-spinner":"pi pi-ellipsis-v",text:!0,rounded:!0,size:"small",disabled:null!==y,"aria-label":"Test actions",tooltip:"Complete / abandon / reopen this test",tooltipOptions:{position:"left"},onClick:t=>{b.current+=1,_({row:e,anchor:t.currentTarget,seq:b.current})}})})]}),_jsx(Menu,{model:h?(e=>{const t=e?.status,o=!0===e?.finalized,i="in_progress"===t||"paused"===t,s="completed"===t&&!0===e?.auto_closed&&!o,a=[];return i?(a.push({label:"Complete Test",icon:"pi pi-check-circle",command:()=>R(e,"complete")}),a.push({label:"Abandon Test",icon:"pi pi-ban",command:()=>confirmDialog({header:"Abandon Test",message:`Mark ${e?.sample_id||e?.run_id} as abandoned? Recorded data is kept, but the test can no longer be reopened.`,icon:"pi pi-exclamation-triangle",acceptClassName:"p-button-danger",acceptLabel:"Abandon",accept:()=>R(e,"abandon")})})):s?(a.push({label:"Close Test",icon:"pi pi-check-circle",command:()=>confirmDialog({header:"Close Interrupted Test",message:`Close ${e?.sample_id||e?.run_id}? It was closed automatically when the software restarted mid-test. Closing it marks it Completed and keeps the recorded end time. Recorded data is not changed, and the test can still be reopened later.`,icon:"pi pi-check-circle",acceptLabel:"Close Test",accept:()=>R(e,"complete")})}),a.push({label:"Reopen Test",icon:"pi pi-replay",command:()=>confirmDialog({header:"Reopen Test",message:`Reopen ${e?.sample_id||e?.run_id} so new cycles are added to it? Cycle numbering continues from where it stopped. This changes where data is recorded — it does not start the machine.`,icon:"pi pi-replay",acceptLabel:"Reopen",accept:()=>R(e,"reopen")})}),a.push({label:"Abandon Test",icon:"pi pi-ban",command:()=>confirmDialog({header:"Abandon Test",message:`Mark ${e?.sample_id||e?.run_id} as abandoned? Recorded data is kept, but the test can no longer be reopened.`,icon:"pi pi-exclamation-triangle",acceptClassName:"p-button-danger",acceptLabel:"Abandon",accept:()=>R(e,"abandon")})})):"completed"!==t||o?a.push({label:o?"Abandoned — cannot reopen":"No actions available",icon:"pi pi-info-circle",disabled:!0}):a.push({label:"Reopen Test",icon:"pi pi-replay",command:()=>confirmDialog({header:"Reopen Test",message:`Reopen ${e?.sample_id||e?.run_id} so new cycles are added to it? Cycle numbering continues from where it stopped. This changes where data is recorded — it does not start the machine.`,icon:"pi pi-replay",acceptLabel:"Reopen",accept:()=>R(e,"reopen")})}),a})(h.row):[],popup:!0,ref:f}),_jsx(Toast,{ref:v,position:"top-right"})]})};
|
|
1
|
+
import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useRef}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Badge}from"primereact/badge";import{Menu}from"primereact/menu";import{Toast}from"primereact/toast";import{Dialog}from"primereact/dialog";import{confirmDialog}from"primereact/confirmdialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{SampleSummaryPanel}from"./SampleSummaryPanel";import"./ResultHistoryTable.css";const downloadCsvBlob=(e,t)=>{const a=new Blob([t],{type:"text/csv;charset=utf-8;"}),i=URL.createObjectURL(a),s=document.createElement("a");s.href=i,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(i)},downloadViaUrl=(e,t)=>{const a=document.createElement("a");a.href=e,a.download=t,document.body.appendChild(a),a.click(),a.remove()},LIFECYCLE_DONE={complete:{summary:"Test closed",detail:e=>`${e} is now Completed. It can still be reopened.`},abandon:{summary:"Test abandoned",detail:e=>`${e} is out of the history and out of the sample averages. Its data is still on disk — use Show abandoned to see it.`},reopen:{summary:"Test reopened",detail:e=>`${e} is open again — new cycles are added to it.`}},stateOf=e=>{const t="string"==typeof e?.status?e.status:"completed",a=!0===e?.finalized,i="completed"===t&&!0===e?.auto_closed,s="in_progress"===t||"paused"===t,o="completed"===t&&!i&&!a;return{lifecycle:a?{label:"Abandoned",severity:"danger"}:"in_progress"===t?{label:"In Progress",severity:"info"}:"paused"===t?{label:"Paused",severity:"warning"}:i?{label:"Interrupted",severity:"warning"}:"completed"===t?{label:"Completed",severity:"success"}:{label:t,severity:void 0},excluded:!0===e?.excluded?{label:"Excluded",severity:"secondary"}:null,live:s,abandoned:a,interrupted:i,settled:o}},stateChipsOf=e=>{const{lifecycle:t,excluded:a}=stateOf(e);return a?[t,a]:[t]},statusOf=e=>{const{lifecycle:t,excluded:a,settled:i}=stateOf(e);return a&&i?a:t},sampleIdOf=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const a=e?.config;return a&&"object"==typeof a&&"string"==typeof a.sample_id?a.sample_id:""},formatDate=e=>e?new Date(e).toLocaleString():"",DetailRow=({label:e,value:t,mono:a})=>null==t||""===t?null:_jsxs(_Fragment,{children:[_jsx("dt",{style:{color:"var(--text-color-secondary)",fontSize:"0.9em"},children:e}),_jsx("dd",{style:{margin:0,fontFamily:a?"monospace":void 0},children:t})]}),DetailGrid=({title:e,children:t})=>_jsxs("section",{style:{marginTop:"1rem"},children:[_jsx("h4",{style:{margin:"0 0 0.4rem",fontSize:"0.95em"},children:e}),_jsx("dl",{style:{display:"grid",gridTemplateColumns:"minmax(9rem, max-content) 1fr",gap:"0.3rem 1rem",margin:0},children:t})]}),configValueText=e=>null==e?"—":"number"==typeof e?Number.isFinite(e)?String(Number.parseFloat(e.toPrecision(6))):String(e):"boolean"==typeof e?e?"Yes":"No":"object"==typeof e?JSON.stringify(e):String(e),TestDetails=({row:e})=>{const t=stateChipsOf(e),{abandoned:a}=stateOf(e),i=e?.config&&"object"==typeof e.config?e.config:{},s=Object.entries(i).filter(([e])=>"sample_id"!==e);return _jsxs("div",{children:[_jsx("div",{style:{display:"flex",gap:"0.4rem",flexWrap:"wrap"},children:t.map(e=>_jsx(Badge,{value:e.label,severity:e.severity},e.label))}),_jsxs(DetailGrid,{title:"Identity",children:[_jsx(DetailRow,{label:"Sample ID",value:sampleIdOf(e)}),_jsx(DetailRow,{label:"Test Method",value:e?.method_id}),_jsx(DetailRow,{label:"Project",value:e?.project_id}),_jsx(DetailRow,{label:"Test ID",value:e?.run_id,mono:!0})]}),_jsxs(DetailGrid,{title:"Timing",children:[_jsx(DetailRow,{label:"Started",value:formatDate(e?.start_time)}),_jsx(DetailRow,{label:"Ended",value:formatDate(e?.end_time)}),_jsx(DetailRow,{label:"Paused",value:formatDate(e?.paused_time)}),_jsx(DetailRow,{label:"Resumed",value:formatDate(e?.resumed_time)})]}),(e?.close_reason||!0===e?.excluded||a)&&_jsxs(DetailGrid,{title:"State",children:[_jsx(DetailRow,{label:"Interrupted",value:e?.close_reason}),_jsx(DetailRow,{label:"Excluded",value:!0===e?.excluded?`Not counted in the ${sampleIdOf(e)||"sample"} results`+(e?.excluded_at?` · since ${formatDate(e.excluded_at)}`:"")+". Use the Incl. column to put it back.":void 0}),_jsx(DetailRow,{label:"Abandoned",value:a?"Kept on disk, out of the results, and cannot be reopened.":void 0})]}),s.length>0&&_jsx(DetailGrid,{title:"Configuration as recorded",children:s.map(([e,t])=>_jsx(DetailRow,{label:e,value:configValueText(t)},e))})]})};export const ResultHistoryTable=e=>{const t=useTis(),a=e.projectId??t.selection.projectId,i=e.methodId,[s,o]=useState([]),[r,l]=useState(!1),[n,d]=useState(null),[c,p]=useState(null),[m,u]=useState(null),f=useRef(null),[_,b]=useState(null),h=useRef(0),[y,g]=useState(null),[x,j]=useState(!1),[v,w]=useState(null),C=useRef(null),{invoke:D}=useContext(EventEmitterContext),R=useRef(0),T=async()=>{if(!a)return void o([]);const e=++R.current;l(!0);try{const t={project_id:a};i&&(t.method_id=i);const s=await D("tis.list_tests",MessageType.Request,t);if(e!==R.current)return;s.success&&s.data&&s.data.tests&&o(s.data.tests)}catch(e){}e===R.current&&l(!1)};useEffect(()=>{T()},[a,i,t.state.activeRunId,t.state.active]),useEffect(()=>{_&&f.current?.show({currentTarget:_.anchor})},[_]);const S=async(e,t)=>{const s=e?.run_id,o=e?.method_id??i;if(!s||!o||!a)return;const r="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",l="report"===t?"test report":"test data";d({runId:s,kind:t});try{const e=await D(r,MessageType.Request,{project_id:a,method_id:o,run_id:s});if(!e?.success)return void alert(`Failed to build ${l} for ${s}`+(e?.error_message?`: ${e.error_message}`:""));const i="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${a}_${o}_${s}_${t}.csv`;if("report"===t){const t="string"==typeof e.data?.csv?e.data.csv:"";if(!t)return void alert(`${l} for ${s} is empty.`);downloadCsvBlob(i,t)}else{const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert(`Server did not return a download URL for ${l}.`);downloadViaUrl(t,i)}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},$=async(e,t)=>{const s=e?.run_id,o=e?.method_id??i;if(!s||!o||!a)return;const r="complete"===t?"tis.finish_test":"abandon"===t?"tis.reset_test":"tis.resume_test",l=e?.sample_id||s;g(s);try{const e=await D(r,MessageType.Request,{project_id:a,method_id:o,run_id:s});if(!e?.success)return void alert(`Could not ${t} ${s}`+(e?.error_message?`:\n\n${e.error_message}`:""));await T(),C.current?.show({severity:"abandon"===t?"warn":"success",summary:LIFECYCLE_DONE[t].summary,detail:LIFECYCLE_DONE[t].detail(l),life:4e3})}catch(e){alert(`${t} failed: ${e instanceof Error?e.message:String(e)}`)}finally{g(null)}},I=async e=>{if(!a)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",i="report"===e?"project report":"project archive";p(e);try{const s=await D(t,MessageType.Request,{project_id:a});if(!s?.success)return void alert(`Failed to build ${i}`+(s?.error_message?`: ${s.error_message}`:""));const o="string"==typeof s.data?.download_url?s.data.download_url:"";if(!o)return void alert(`Server did not return a download URL for ${i}.`);const r="report"===e?`${a}_project_report.csv`:`${a}_project_archive.zip`,l="string"==typeof s.data?.filename&&s.data.filename?s.data.filename:r;downloadViaUrl(o,l)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},k=s.filter(e=>!0===e?.finalized).length,E=x?s:s.filter(e=>!0!==e?.finalized);return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:a?`Test History: ${a}${i?` / ${i}`:""}`:"Test History (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center"},children:[k>0&&_jsx(Button,{icon:x?"pi pi-eye-slash":"pi pi-eye",label:x?"Hide abandoned":`Show abandoned (${k})`,size:"small",outlined:!0,severity:"secondary",onClick:()=>j(e=>!e)}),_jsx(Button,{icon:"report"===c?"pi pi-spin pi-spinner":"pi pi-file",label:"Download Report",size:"small",outlined:!0,disabled:!a||null!==c,onClick:()=>I("report")}),_jsx(Button,{icon:"archive"===c?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!a||null!==c,onClick:()=>I("archive")}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:T,disabled:r})]})]}),_jsx(SampleSummaryPanel,{projectId:a,methodId:i}),_jsxs(DataTable,{value:E,loading:r,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},rowClassName:e=>!0===e?.excluded?"tis-row-excluded":"",selectionMode:"single",onSelectionChange:e=>{const a=e.value;a?.run_id&&t.setSelection({projectId:a.project_id??null,methodId:a.method_id??null,runId:a.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:sampleIdOf,sortFunction:e=>{const t=[...e.data];return t.sort((t,a)=>sampleIdOf(t).localeCompare(sampleIdOf(a))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>formatDate(e.start_time),style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{header:"Status",field:"status",sortable:!0,body:e=>{const t=statusOf(e);return _jsx(Badge,{value:t.label,severity:t.severity})},style:{minWidth:"6rem"}}),_jsx(Column,{header:"Incl.",align:"center",alignHeader:"center",style:{width:"4.5rem"},body:e=>{const t=!0===e?.excluded,s=m===e.run_id;return _jsx(Button,{icon:s?"pi pi-spin pi-spinner":t?"pi pi-eye-slash":"pi pi-check",rounded:!0,text:!0,size:"small",severity:t?"warning":"success",disabled:null!==m,onClick:()=>(async e=>{const t=e?.run_id,s=e?.method_id??i;if(!t||!s||!a)return;const o=!0!==e?.excluded;u(t);try{const e=await D("tis.set_test_excluded",MessageType.Request,{project_id:a,method_id:s,run_id:t,excluded:o});if(!e?.success)return void alert(`Failed to ${o?"exclude":"include"} ${t}`+(e?.error_message?`: ${e.error_message}`:""));await T()}catch(e){alert(`Exclude toggle failed: ${e instanceof Error?e.message:String(e)}`)}finally{u(null)}})(e),"aria-label":t?"Excluded — click to include":"Included — click to exclude"})}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=n?.runId===e.run_id&&"data"===n?.kind,a=n?.runId===e.run_id&&"report"===n?.kind,i=null!==n;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Raw",size:"small",outlined:!0,disabled:i,onClick:()=>S(e,"data")}),_jsx(Button,{icon:a?"pi pi-spin pi-spinner":"pi pi-file",label:"Results",size:"small",outlined:!0,disabled:i,onClick:()=>S(e,"report")})]})}}),_jsx(Column,{header:"",style:{width:"3rem"},body:e=>_jsx(Button,{icon:y===e.run_id?"pi pi-spin pi-spinner":"pi pi-ellipsis-v",text:!0,rounded:!0,size:"small",disabled:null!==y,"aria-label":"Test actions",onClick:t=>{h.current+=1,b({row:e,anchor:t.currentTarget,seq:h.current})}})})]}),_jsx(Menu,{model:_?(e=>{const t=e?.status,a=!0===e?.finalized,i="in_progress"===t||"paused"===t,s="completed"===t&&!0===e?.auto_closed&&!a,o=[{label:"Details",icon:"pi pi-info-circle",command:()=>w(e)},{separator:!0}];return i?(o.push({label:"Complete Test",icon:"pi pi-check-circle",command:()=>$(e,"complete")}),o.push({label:"Abandon Test",icon:"pi pi-ban",command:()=>confirmDialog({header:"Abandon Test",message:`Mark ${e?.sample_id||e?.run_id} as abandoned? Recorded data is kept, but the test can no longer be reopened.`,icon:"pi pi-exclamation-triangle",acceptClassName:"p-button-danger",acceptLabel:"Abandon",accept:()=>$(e,"abandon")})})):s?(o.push({label:"Close Test",icon:"pi pi-check-circle",command:()=>confirmDialog({header:"Close Interrupted Test",message:`Close ${e?.sample_id||e?.run_id}? It was closed automatically when the software restarted mid-test. Closing it marks it Completed and keeps the recorded end time. Recorded data is not changed, and the test can still be reopened later.`,icon:"pi pi-check-circle",acceptLabel:"Close Test",accept:()=>$(e,"complete")})}),o.push({label:"Reopen Test",icon:"pi pi-replay",command:()=>confirmDialog({header:"Reopen Test",message:`Reopen ${e?.sample_id||e?.run_id} so new cycles are added to it? Cycle numbering continues from where it stopped. This changes where data is recorded — it does not start the machine.`,icon:"pi pi-replay",acceptLabel:"Reopen",accept:()=>$(e,"reopen")})}),o.push({label:"Abandon Test",icon:"pi pi-ban",command:()=>confirmDialog({header:"Abandon Test",message:`Mark ${e?.sample_id||e?.run_id} as abandoned? Recorded data is kept, but the test can no longer be reopened.`,icon:"pi pi-exclamation-triangle",acceptClassName:"p-button-danger",acceptLabel:"Abandon",accept:()=>$(e,"abandon")})})):"completed"!==t||a?a&&o.push({label:"Abandoned — cannot reopen",icon:"pi pi-ban",disabled:!0}):o.push({label:"Reopen Test",icon:"pi pi-replay",command:()=>confirmDialog({header:"Reopen Test",message:`Reopen ${e?.sample_id||e?.run_id} so new cycles are added to it? Cycle numbering continues from where it stopped. This changes where data is recorded — it does not start the machine.`,icon:"pi pi-replay",acceptLabel:"Reopen",accept:()=>$(e,"reopen")})}),o})(_.row):[],popup:!0,ref:f}),_jsx(Toast,{ref:C,position:"top-right"}),_jsx(Dialog,{header:"Test Details",visible:null!==v,onHide:()=>w(null),style:{width:"min(44rem, 94vw)"},dismissableMask:!0,children:v&&_jsx(TestDetails,{row:v})})]})};
|
package/package.json
CHANGED
|
@@ -39,20 +39,15 @@ import { TabPanel, TabView } from 'primereact/tabview';
|
|
|
39
39
|
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
40
40
|
import { MessageType } from '../../hub/CommandMessage';
|
|
41
41
|
import { useAms, type AmsRole } from './AmsProvider';
|
|
42
|
+
import {
|
|
43
|
+
type SchemaField, SchemaFieldInput, coerceField, fieldLabel,
|
|
44
|
+
} from './schemaField';
|
|
42
45
|
|
|
43
46
|
// Sentinel: keeps the Role dropdown consistent with the Add dialog's
|
|
44
47
|
// "Other..." escape hatch so operators can move an asset to a custom
|
|
45
48
|
// role that isn't declared in project.json.
|
|
46
49
|
const ROLE_OTHER = '__other__';
|
|
47
50
|
|
|
48
|
-
interface SchemaField {
|
|
49
|
-
name: string;
|
|
50
|
-
type: string;
|
|
51
|
-
units?: string;
|
|
52
|
-
label?: string;
|
|
53
|
-
description?: string;
|
|
54
|
-
required?: boolean;
|
|
55
|
-
}
|
|
56
51
|
|
|
57
52
|
interface SubLocationsSchema {
|
|
58
53
|
label?: string;
|
|
@@ -104,24 +99,6 @@ function perAxisCalibrationFieldsFor(
|
|
|
104
99
|
return (sub?.calibration_fields ?? []) as SchemaField[];
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
/** Coerce a per-cell string back to the field's declared type.
|
|
108
|
-
* Mirrors the Add dialog's helper so empty strings drop out, numbers
|
|
109
|
-
* come back as JSON numbers, bools as bools. */
|
|
110
|
-
function coerceField(field: SchemaField, raw: string): any {
|
|
111
|
-
if (raw === undefined || raw === '') return undefined;
|
|
112
|
-
switch (field.type) {
|
|
113
|
-
case 'f32': case 'f64':
|
|
114
|
-
case 'i8': case 'i16': case 'i32': case 'i64':
|
|
115
|
-
case 'u8': case 'u16': case 'u32': case 'u64': {
|
|
116
|
-
const n = Number(raw);
|
|
117
|
-
return Number.isFinite(n) ? n : undefined;
|
|
118
|
-
}
|
|
119
|
-
case 'bool':
|
|
120
|
-
return raw === 'true' || raw === '1';
|
|
121
|
-
default:
|
|
122
|
-
return raw;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
102
|
|
|
126
103
|
/** Default a field's input from whatever's in the asset's custom blob.
|
|
127
104
|
* Numbers / bools serialize back to strings for the text inputs to
|
|
@@ -551,38 +528,18 @@ export const AssetEditDialog: React.FC<AssetEditDialogProps> = ({
|
|
|
551
528
|
|
|
552
529
|
{/* Schema-declared nameplate fields. */}
|
|
553
530
|
{fields.map(field => {
|
|
554
|
-
const label = field.label ?? field.name;
|
|
555
|
-
const fullLabel = field.units
|
|
556
|
-
? `${label} [${field.units}]${field.required ? ' *' : ''}`
|
|
557
|
-
: `${label}${field.required ? ' *' : ''}`;
|
|
558
|
-
const isNum = field.type !== 'string' && field.type !== 'bool';
|
|
559
531
|
return (
|
|
560
532
|
<React.Fragment key={field.name}>
|
|
561
533
|
<label title={field.description ?? undefined}>
|
|
562
|
-
{
|
|
534
|
+
{fieldLabel(field)}
|
|
563
535
|
</label>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
onChange={(e) => setCustomFields(s => ({
|
|
572
|
-
...s, [field.name]: e.value,
|
|
573
|
-
}))}
|
|
574
|
-
placeholder="—"
|
|
575
|
-
/>
|
|
576
|
-
) : (
|
|
577
|
-
<InputText
|
|
578
|
-
value={customFields[field.name] ?? ''}
|
|
579
|
-
keyfilter={isNum ? 'num' : undefined}
|
|
580
|
-
placeholder={field.description ?? undefined}
|
|
581
|
-
onChange={(e) => setCustomFields(s => ({
|
|
582
|
-
...s, [field.name]: e.target.value,
|
|
583
|
-
}))}
|
|
584
|
-
/>
|
|
585
|
-
)}
|
|
536
|
+
<SchemaFieldInput
|
|
537
|
+
field={field}
|
|
538
|
+
value={customFields[field.name] ?? ''}
|
|
539
|
+
onChange={(v) => setCustomFields(s => ({
|
|
540
|
+
...s, [field.name]: v,
|
|
541
|
+
}))}
|
|
542
|
+
/>
|
|
586
543
|
</React.Fragment>
|
|
587
544
|
);
|
|
588
545
|
})}
|
|
@@ -625,16 +582,13 @@ export const AssetEditDialog: React.FC<AssetEditDialogProps> = ({
|
|
|
625
582
|
{subLocationsSchema.fields.map(field => {
|
|
626
583
|
const cellValue =
|
|
627
584
|
subLocationFields[key]?.[field.name] ?? '';
|
|
628
|
-
const isNum =
|
|
629
|
-
field.type !== 'string' && field.type !== 'bool';
|
|
630
585
|
return (
|
|
631
586
|
<td key={field.name}
|
|
632
587
|
style={{ padding: '0.25rem 0.5rem' }}>
|
|
633
|
-
<
|
|
588
|
+
<SchemaFieldInput
|
|
589
|
+
field={field}
|
|
634
590
|
value={cellValue}
|
|
635
|
-
|
|
636
|
-
onChange={(e) => {
|
|
637
|
-
const v = e.target.value;
|
|
591
|
+
onChange={(v) => {
|
|
638
592
|
setSubLocationFields(s => ({
|
|
639
593
|
...s,
|
|
640
594
|
[key]: {
|
|
@@ -742,16 +696,13 @@ export const AssetEditDialog: React.FC<AssetEditDialogProps> = ({
|
|
|
742
696
|
{perAxisCalibrationFields.map(field => {
|
|
743
697
|
const cellValue =
|
|
744
698
|
calPerAxisValues[key]?.[field.name] ?? '';
|
|
745
|
-
const isNum =
|
|
746
|
-
field.type !== 'string' && field.type !== 'bool';
|
|
747
699
|
return (
|
|
748
700
|
<td key={field.name}
|
|
749
701
|
style={{ padding: '0.25rem 0.5rem' }}>
|
|
750
|
-
<
|
|
702
|
+
<SchemaFieldInput
|
|
703
|
+
field={field}
|
|
751
704
|
value={cellValue}
|
|
752
|
-
|
|
753
|
-
onChange={(e) => {
|
|
754
|
-
const v = e.target.value;
|
|
705
|
+
onChange={(v) => {
|
|
755
706
|
setCalPerAxisValues(s => ({
|
|
756
707
|
...s,
|
|
757
708
|
[key]: {
|
|
@@ -776,22 +727,16 @@ export const AssetEditDialog: React.FC<AssetEditDialogProps> = ({
|
|
|
776
727
|
gap: '0.5rem 1rem',
|
|
777
728
|
alignItems: 'center' }}>
|
|
778
729
|
{calibrationFields.map(field => {
|
|
779
|
-
const label = field.label ?? field.name;
|
|
780
|
-
const fullLabel = field.units
|
|
781
|
-
? `${label} [${field.units}]${field.required ? ' *' : ''}`
|
|
782
|
-
: `${label}${field.required ? ' *' : ''}`;
|
|
783
|
-
const isNum = field.type !== 'string'
|
|
784
|
-
&& field.type !== 'bool';
|
|
785
730
|
return (
|
|
786
731
|
<React.Fragment key={field.name}>
|
|
787
732
|
<label title={field.description ?? undefined}>
|
|
788
|
-
{
|
|
733
|
+
{fieldLabel(field)}
|
|
789
734
|
</label>
|
|
790
|
-
<
|
|
735
|
+
<SchemaFieldInput
|
|
736
|
+
field={field}
|
|
791
737
|
value={calValues[field.name] ?? ''}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
...s, [field.name]: e.target.value,
|
|
738
|
+
onChange={(v) => setCalValues(s => ({
|
|
739
|
+
...s, [field.name]: v,
|
|
795
740
|
}))}
|
|
796
741
|
/>
|
|
797
742
|
</React.Fragment>
|