diff --git a/public/locales/cs/translation.json b/public/locales/cs/translation.json index afebf339..f74030ea 100644 --- a/public/locales/cs/translation.json +++ b/public/locales/cs/translation.json @@ -91,10 +91,14 @@ "fhaBasedFailureRate": "Intenzita poruch založená na FHA", "predictedFailureRate": "Predikovaná intenzita poruch", "ataSystem": "ATA systém", - "partNumber": "Číslo součásti", - "stock": "Zásoba", - "quantity": "Množství", - "schematicDesignation": "Schématické označení" + "partNumber": "Číslo dílu", + "stock": "Skladem", + "quantity": "Počet", + "schematicDesignation": "Schematické označení", + "requiredFailureRate": "Požadovaná intenzita poruch", + "calculatedFailureRate": "Vypočtená intenzita poruch", + "operationalFailureRate": "Provozní intenzita poruch", + "manuallyDefinedFailureRate": "Manuálně definovaná intenzita poruch" }, "appBar": { "selectSystemPlaceholder": "Vyberte systém" diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b635286c..662add64 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -94,7 +94,11 @@ "partNumber": "Part number", "stock": "Stock", "quantity": "Quantity", - "schematicDesignation": "Schematic designation" + "schematicDesignation": "Schematic designation", + "requiredFailureRate": "Required failure rate", + "calculatedFailureRate": "Calculated failure rate", + "operationalFailureRate": "Operational failure rate", + "manuallyDefinedFailureRate": "Manually defined failure rate" }, "appBar": { "selectSystemPlaceholder": "Select system" diff --git a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx index c55d5293..08d8ceb2 100644 --- a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx +++ b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx @@ -4,17 +4,39 @@ import { makeStyles } from "tss-react/mui"; const useStyles = makeStyles()((theme: Theme) => ({ label: { fontWeight: "500", - color: "#00000080", marginRight: 8, + fontSize: 16, + color: "black", }, labelRow: { + color: "grey", display: "flex", flexDirection: "row", + alignItems: "center", + }, + selectableLabel: { + color: "black", + fontSize: 16, + }, + black: { + color: "black", + "&.Mui-checked": { + color: "black", + }, + }, + grey: { + color: "grey", }, divider: { marginTop: 8, marginBottom: 8, }, + numberInput: { + "& .MuiInputBase-input": { + color: "black", + padding: "8px 12px", + }, + }, })); export default useStyles; diff --git a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx index 9efde798..8d658a78 100644 --- a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx +++ b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next"; import { asArray } from "@utils/utils"; import { ReusableFaultEventsProvider } from "@hooks/useReusableFaultEvents"; import { useSelectedSystem } from "@hooks/useSelectedSystem"; +import { Radio, RadioGroup, FormControlLabel, FormControl, TextField } from "@mui/material"; interface Props { shapeToolData?: FaultEvent; @@ -21,7 +22,19 @@ interface Props { rootIri?: string; } +enum RadioButtonType { + Predicted = "Predicted", + Manual = "Manual", + Operational = "Operational", +} + +enum ManualFailureRateType { + Sns = "Sns", + External = "External", +} + const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: Props) => { + if (shapeToolData) console.log("shapeToolData", shapeToolData); const { t } = useTranslation(); const { classes } = useStyles(); const [failureModeDialogOpen, setFailureModeDialogOpen] = useState(false); @@ -38,12 +51,69 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: const [schematicDesignation, setSchematicDesignation] = useState(undefined); const [selectedSystem] = useSelectedSystem(); + const [snsOperationalFailureRate, setSnsOperationalFailureRate] = useState(undefined); + const [snsPredictedFailureRate, setSnsPredictedFailureRate] = useState(undefined); + const [snsManuallyDefinedFailureRate, setSnsManuallyDefinedFailureRate] = useState(undefined); + const [externalManuallyDefinedFailureRate, setExternalManuallyDefinedFailureRate] = useState( + undefined, + ); + const [selectedRadioButton, setSelectedRadioButton] = useState(RadioButtonType.Predicted); + + const [snsOperationalIri, setSnsOperationalIri] = useState(undefined); + const [snsPredictedIri, setSnsPredictedIri] = useState(undefined); + + const handleManuallyDefinedFailureRateChange = (event, type: ManualFailureRateType) => { + const inputValue = event.target.value; + const regex = /^[0-9]*\.?[0-9]*$/; + if (regex.test(inputValue)) { + if (type === ManualFailureRateType.Sns) { + setSnsManuallyDefinedFailureRate(inputValue); + } + if (type === ManualFailureRateType.External) { + setExternalManuallyDefinedFailureRate(inputValue); + } + } + }; + + const handleSnsBasicSelectedFailureRateChange = (event: React.ChangeEvent) => { + // TODO: Add handler for update with error + setSelectedRadioButton(event.target.value as RadioButtonType); + if (event.target.value === RadioButtonType.Predicted) { + // Updated when we switch to Pred. rate: + onEventUpdated({ + ...shapeToolData, + selectedEstimate: { iri: snsPredictedIri, value: snsPredictedFailureRate }, + probability: snsPredictedFailureRate, + }); + } + if (event.target.value === RadioButtonType.Operational) { + // Updated when we switch to Oper. rate: + onEventUpdated({ + ...shapeToolData, + selectedEstimate: { iri: snsOperationalIri, value: snsOperationalFailureRate }, + probability: snsOperationalFailureRate, + }); + } + }; + + const handleManualFailureRateUpdate = () => { + onEventUpdated({ ...shapeToolData, probability: snsManuallyDefinedFailureRate }); + }; + const handleFailureModeClicked = (failureMode: FailureMode) => { setFailureModeOverview(failureMode); setFailureModeOverviewDialogOpen(true); }; useEffect(() => { + // Clear values, after node was changed + setSnsPredictedFailureRate(undefined); + setSnsOperationalFailureRate(undefined); + setSnsOperationalIri(undefined); + setSnsPredictedIri(undefined); + setExternalManuallyDefinedFailureRate(undefined); + setSelectedRadioButton(RadioButtonType.Predicted); + if (shapeToolData?.supertypes?.criticality) { setCriticality(shapeToolData.supertypes.criticality); } else { @@ -99,11 +169,66 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: } else { setSchematicDesignation(undefined); } + + let supertypes: any = shapeToolData?.supertypes?.supertypes; + + if (shapeToolData?.selectedEstimate) { + // SELECTED ESTIMATE => PREDICTED OR OPERATIONAL IS SELECTED + const iriOfSelectedValue = shapeToolData.selectedEstimate.iri; + const { predictionIri, operationalIri } = supertypes.reduce( + (acc, item) => { + if (item?.hasFailureRate?.prediction?.iri) acc.predictionIri = item.hasFailureRate.prediction.iri; + if (item?.hasFailureRate?.estimate?.iri) acc.operationalIri = item.hasFailureRate.estimate.iri; + return acc; + }, + { predictionIri: "", operationalIri: "" }, + ); + + if (iriOfSelectedValue === predictionIri) { + setSelectedRadioButton(RadioButtonType.Predicted); + } else if (iriOfSelectedValue === operationalIri) { + setSelectedRadioButton(RadioButtonType.Operational); + } + setSnsManuallyDefinedFailureRate(shapeToolData?.probability); + } else { + // NO SELECTED ESTIMATE => MANUAL IS SELECTED + setSelectedRadioButton(RadioButtonType.Manual); + if (shapeToolData?.probability) { + setSnsManuallyDefinedFailureRate(shapeToolData.probability); + setExternalManuallyDefinedFailureRate(shapeToolData.probability); + } + } + + if (supertypes) { + for (let i = 0; i < supertypes.length; i++) { + const item = supertypes[i]; + if (item?.hasFailureRate?.estimate?.value) { + setSnsOperationalFailureRate(item?.hasFailureRate?.estimate?.value); + setSnsOperationalIri(item?.hasFailureRate?.estimate?.iri); + } + if (item?.hasFailureRate?.prediction?.value) { + setSnsPredictedFailureRate(item?.hasFailureRate?.prediction?.value); + setSnsPredictedIri(item?.hasFailureRate?.prediction?.iri); + } + } + } }, [shapeToolData]); const basedFailureRate = shapeToolData?.supertypes?.supertypes?.hasFailureRate?.estimate?.value; const requiredFailureRate = shapeToolData?.supertypes?.hasFailureRate?.requirement?.upperBound; + const FailureRateBox = ({ value, label, rate, selected, classes }) => ( + + } + label={`${label}:`} + className={selected ? classes.black : classes.grey} + /> + {rate} + + ); + return ( @@ -118,7 +243,7 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {basedFailureRate && ( - FHA based failure rate: + {t("faultEventMenu.fhaBasedFailureRate")}: {basedFailureRate.toExponential(2)} @@ -126,7 +251,7 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {shapeToolData?.probabilityRequirement && ( - Required failure rate: + {t("faultEventMenu.requiredFailureRate")}: {shapeToolData?.probabilityRequirement.toExponential(2)} @@ -141,7 +266,7 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {shapeToolData?.probability && ( - Calculated failure rate + {t("faultEventMenu.calculatedFailureRate")}: {shapeToolData?.probability.toExponential(2)} @@ -150,7 +275,7 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {basedFailureRate && ( - Based failure rate + {t("faultEventMenu.fhaBasedFailureRate")}: {shapeToolData?.supertypes?.supertypes?.hasFailureRate?.estimate?.value.toExponential(2)} @@ -158,7 +283,7 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {requiredFailureRate && ( - Required failure rate + {t("faultEventMenu.requiredFailureRate")}: {shapeToolData?.supertypes?.hasFailureRate?.requirement?.upperBound.toExponential(2)} @@ -169,18 +294,78 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {/* INTERMEDIATE NODE */} {shapeToolData && shapeToolData.eventType === EventType.INTERMEDIATE && ( - + <> + {shapeToolData?.probability && ( + + + {t("faultEventMenu.calculatedFailureRate")}: + {shapeToolData?.probability.toExponential(2)} + + + )} + + )} + {/* BASIC EVENT */} + {shapeToolData && shapeToolData.eventType === EventType.BASIC && ( + <> + + + + {snsPredictedFailureRate && ( + + )} + {snsOperationalFailureRate && ( + + )} + + } + label={`${t("faultEventMenu.manuallyDefinedFailureRate")}:`} + className={selectedRadioButton === RadioButtonType.Manual ? classes.black : classes.grey} + /> + handleManuallyDefinedFailureRateChange(event, ManualFailureRateType.Sns)} + inputProps={{ inputMode: "decimal" }} + disabled={selectedRadioButton !== RadioButtonType.Manual} + onBlur={handleManualFailureRateUpdate} + /> + + + + + )} {/* EXTERNAL NODE */} {shapeToolData && shapeToolData.eventType === EventType.EXTERNAL && !shapeToolData.isReference && ( - {shapeToolData?.probability && ( - - Manually defined failure rate - {shapeToolData?.probability.toExponential(2)} - - )} + {`${t("faultEventMenu.manuallyDefinedFailureRate")}:`} + handleManuallyDefinedFailureRateChange(event, ManualFailureRateType.External)} + inputProps={{ inputMode: "decimal" }} + onBlur={handleManualFailureRateUpdate} + /> )} @@ -190,45 +375,38 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {criticality && ( - {t("faultEventMenu.criticality")}: {criticality} + {t("faultEventMenu.criticality")}: + {criticality} )} - - {predictedFailureRate && ( - - {shapeToolData.eventType === EventType.INTERMEDIATE ? ( - {t("faultEventMenu.fhaBasedFailureRate")}: - ) : ( - {t("faultEventMenu.predictedFailureRate")}: - )} - {` ${predictedFailureRate.toExponential(2)}`} - - )} - {ataSystem && ( - {t("faultEventMenu.ataSystem")}: {ataSystem} + {t("faultEventMenu.ataSystem")}: + {ataSystem} )} {partNumber && ( - {t("faultEventMenu.partNumber")}: {partNumber} + {t("faultEventMenu.partNumber")}: + {partNumber} )} {stock && ( - {t("faultEventMenu.stock")}: {stock} + {t("faultEventMenu.stock")}: + {stock} )} {quantity && ( - {t("faultEventMenu.quantity")}: {quantity} + {t("faultEventMenu.quantity")}: + {quantity} )} {schematicDesignation && ( {t("faultEventMenu.schematicDesignation")}: - {schematicDesignation} + {schematicDesignation} )} diff --git a/src/components/editor/faultTree/shapes/RenderTree.tsx b/src/components/editor/faultTree/shapes/RenderTree.tsx index 52d99eea..cd6e27ed 100644 --- a/src/components/editor/faultTree/shapes/RenderTree.tsx +++ b/src/components/editor/faultTree/shapes/RenderTree.tsx @@ -54,6 +54,16 @@ const renderTree = async (container, node, parentShape = null, pathsToHighlight) if (width > DEFAULT_NODE_SHAPE_SIZE) nodeShape.prop("size", { width: width }); nodeShape.attr(["label", "text"], node.name); + + // For now it seems impossible to detect when operational failure rate was selected. + // "has" function from lodash can only check property existence with direct path. So we can't show "(o)" in front of "probability" + + // if (has(node, "probability")) { + // nodeShape.attr( + // ["probabilityLabel", "text"], + // `${has(node, "selectedEstimate") ? "(p)" : "(m)"}${node.probability.toExponential(2)}`, + // ); + // } if (has(node, "probability")) { nodeShape.attr(["probabilityLabel", "text"], node.probability.toExponential(2)); } diff --git a/src/models/eventModel.tsx b/src/models/eventModel.tsx index 65ff2e54..769a9e53 100644 --- a/src/models/eventModel.tsx +++ b/src/models/eventModel.tsx @@ -56,6 +56,7 @@ const ctx = { username: VocabularyUtils.PREFIX + "username", estimate: VocabularyUtils.PREFIX + "has-estimate", schematicDesignation: VocabularyUtils.PREFIX + "schematic-designation", + selectedEstimate: VocabularyUtils.PREFIX + "has-selected-estimation", }; export const CONTEXT = Object.assign({}, ctx, ABSTRACT_CONTEXT, FAILURE_MODE_CONTEXT, RECTANGLE_CONTEXT); @@ -85,6 +86,11 @@ export interface FaultEvent extends AbstractModel { references?: { isPartOf?: string; }; + selectedEstimate?: { + iri?: string; + types?: string[]; + value?: number; + }; supertypes?: { criticality?: number; supertypes?: {