diff --git a/app/api/entities/reaction_variation_entity.rb b/app/api/entities/reaction_variation_entity.rb index 6909de9c66..cee722d5ec 100644 --- a/app/api/entities/reaction_variation_entity.rb +++ b/app/api/entities/reaction_variation_entity.rb @@ -4,9 +4,8 @@ module Entities class ReactionVariationEntity < ApplicationEntity expose( :id, - :notes, :properties, - :analyses, + :metadata, :reactants, :products, :solvents, @@ -14,12 +13,15 @@ class ReactionVariationEntity < ApplicationEntity expose :starting_materials, as: :startingMaterials def properties - {}.tap do |properties| - properties[:temperature] = ReactionVariationPropertyEntity.represent(object[:properties][:temperature]) - properties[:duration] = ReactionVariationPropertyEntity.represent(object[:properties][:duration]) + object[:properties].slice(:duration, :temperature).transform_values do |value| + ReactionVariationPropertyEntity.represent(value) end end + def metadata + object[:metadata].slice(:notes, :analyses) + end + def materials(material_type, entity) {}.tap do |materials| object[material_type]&.each do |k, v| diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js index 1293603f9e..4b3da7d69c 100644 --- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js +++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js @@ -11,104 +11,42 @@ import { isEqual } from 'lodash'; import PropTypes from 'prop-types'; import Reaction from 'src/models/Reaction'; import { - createVariationsRow, copyVariationsRow, updateVariationsRow, getCellDataType, getStandardUnits, materialTypes + createVariationsRow, copyVariationsRow, updateVariationsRow, getVariationsColumns, materialTypes, + addMissingColumnsToVariations, removeObsoleteColumnsFromVariations, getColumnDefinitions } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils'; import { - AnalysesCellRenderer, AnalysesCellEditor, getReactionAnalyses, updateAnalyses, getAnalysesOverlay, AnalysisOverlay + getReactionAnalyses, updateAnalyses } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses'; import { - getMaterialColumnGroupChild, updateVariationsGasTypes, + updateVariationsGasTypes, getReactionMaterials, getReactionMaterialsIDs, getReactionMaterialsGasTypes, - removeObsoleteMaterialsFromVariations, addMissingMaterialsToVariations + removeObsoleteMaterialColumns } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials'; import { PropertyFormatter, PropertyParser, MaterialFormatter, MaterialParser, EquivalentParser, GasParser, FeedstockParser, - NoteCellRenderer, NoteCellEditor, - RowToolsCellRenderer, MenuHeader + ColumnSelection } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents'; -import { - columnDefinitionsReducer -} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers'; +import columnDefinitionsReducer + from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers'; import GasPhaseReactionStore from 'src/stores/alt/stores/GasPhaseReactionStore'; export default function ReactionVariations({ reaction, onReactionChange, isActive }) { const gridRef = useRef(null); const reactionVariations = reaction.variations; const reactionHasPolymers = reaction.hasPolymers(); + const { dispValue: durationValue = null, dispUnit: durationUnit = 'None' } = reaction.durationDisplay ?? {}; + const { userText: temperatureValue = null, valueUnit: temperatureUnit = 'None' } = reaction.temperature ?? {}; + const vesselVolume = GasPhaseReactionStore.getState().reactionVesselSizeValue; const [gasMode, setGasMode] = useState(reaction.gaseous); const [allReactionAnalyses, setAllReactionAnalyses] = useState(getReactionAnalyses(reaction)); const [reactionMaterials, setReactionMaterials] = useState(getReactionMaterials(reaction)); - const [columnDefinitions, setColumnDefinitions] = useReducer(columnDefinitionsReducer, [ - { - headerName: 'Tools', - cellRenderer: RowToolsCellRenderer, - lockPosition: 'left', - sortable: false, - maxWidth: 100, - cellDataType: false, - }, - { - headerName: 'Notes', - field: 'notes', - cellRenderer: NoteCellRenderer, - sortable: false, - cellDataType: 'text', - cellEditor: NoteCellEditor, - }, - { - headerName: 'Analyses', - field: 'analyses', - tooltipValueGetter: getAnalysesOverlay, - tooltipComponent: AnalysisOverlay, - cellRenderer: AnalysesCellRenderer, - cellEditor: AnalysesCellEditor, - cellDataType: false, - sortable: false, - }, - { - headerName: 'Properties', - groupId: 'properties', - marryChildren: true, - children: [ - { - field: 'properties.temperature', - cellDataType: getCellDataType('temperature'), - entryDefs: { - currentEntry: 'temperature', - displayUnit: getStandardUnits('temperature')[0], - availableEntries: ['temperature'] - }, - headerComponent: MenuHeader, - headerComponentParams: { - names: ['T'], - } - }, - { - field: 'properties.duration', - cellDataType: getCellDataType('duration'), - editable: !gasMode, - entryDefs: { - currentEntry: 'duration', - displayUnit: getStandardUnits('duration')[0], - availableEntries: ['duration'] - }, - headerComponent: MenuHeader, - headerComponentParams: { - names: ['t'], - } - }, - ] - }, - ].concat( - Object.entries(reactionMaterials).map(([materialType, materials]) => ({ - headerName: materialTypes[materialType].label, - groupId: materialType, - marryChildren: true, - children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, MenuHeader, gasMode)) - })) - )); + const [selectedColumns, setSelectedColumns] = useState(getVariationsColumns(reactionVariations)); + const [columnDefinitions, setColumnDefinitions] = useReducer( + columnDefinitionsReducer, + getColumnDefinitions(selectedColumns, reactionMaterials, gasMode) + ); const dataTypeDefinitions = { property: { @@ -180,7 +118,7 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv /* Keep set of materials up-to-date. Materials could have been added or removed in the "Scheme" tab. - These changes need to be reflected in the variations. + We need to only *remove* obsolete materials, not *add* missing ones, since users add materials manually. */ if ( !isEqual( @@ -188,21 +126,25 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv getReactionMaterialsIDs(updatedReactionMaterials) ) ) { - let updatedReactionVariations = removeObsoleteMaterialsFromVariations(reactionVariations, updatedReactionMaterials); - updatedReactionVariations = addMissingMaterialsToVariations( - updatedReactionVariations, + const updatedSelectedColumns = removeObsoleteMaterialColumns( updatedReactionMaterials, - updatedGasMode + selectedColumns ); + setSelectedColumns(updatedSelectedColumns); + const updatedReactionVariations = removeObsoleteColumnsFromVariations( + reactionVariations, + updatedSelectedColumns + ); setReactionVariations(updatedReactionVariations); + setColumnDefinitions( { - type: 'update_material_set', - gasMode: updatedGasMode, - reactionMaterials: updatedReactionMaterials + type: 'remove_obsolete_materials', + selectedColumns: updatedSelectedColumns } ); + setReactionMaterials(updatedReactionMaterials); } @@ -210,11 +152,17 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv Update gas mode according to "Scheme" tab. */ if (gasMode !== updatedGasMode) { + const updatedSelectedColumns = getVariationsColumns([]); + setSelectedColumns(updatedSelectedColumns); setColumnDefinitions( { type: 'toggle_gas_mode', - gasMode: updatedGasMode, - reactionMaterials: updatedReactionMaterials + materials: Object.keys(materialTypes).reduce((materials, materialType) => { + materials[materialType] = []; + return materials; + }, {}), + selectedColumns: updatedSelectedColumns, + gasMode: updatedGasMode } ); setGasMode(updatedGasMode); @@ -240,8 +188,9 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv setColumnDefinitions( { type: 'update_gas_type', + selectedColumns, + materials: updatedReactionMaterials, gasMode: updatedGasMode, - reactionMaterials: updatedReactionMaterials } ); @@ -294,12 +243,27 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv setAllReactionAnalyses(updatedAllReactionAnalyses); } - const addRow = useCallback(() => { - const vesselVolume = GasPhaseReactionStore.getState().reactionVesselSizeValue; + const addRow = () => { setReactionVariations( - [...reactionVariations, createVariationsRow(reaction, reactionVariations, gasMode, vesselVolume)] + [ + ...reactionVariations, + createVariationsRow( + { + materials: reactionMaterials, + selectedColumns, + variations: reactionVariations, + reactionHasPolymers, + durationValue, + durationUnit, + temperatureValue, + temperatureUnit, + gasMode, + vesselVolume + } + ) + ] ); - }, [reaction, reactionVariations, gasMode]); + }; const copyRow = useCallback((data) => { const copiedRow = copyVariationsRow(data, reactionVariations); @@ -320,9 +284,35 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv ); }, [reactionVariations, reactionHasPolymers]); - const fitColumnToContent = (event) => { - const { column } = event; - gridRef.current.api.autoSizeColumns([column], false); + const applyColumnSelection = (columns) => { + let updatedReactionVariations = addMissingColumnsToVariations({ + materials: reactionMaterials, + selectedColumns: columns, + variations: reactionVariations, + reactionHasPolymers, + durationValue, + durationUnit, + temperatureValue, + temperatureUnit, + gasMode, + vesselVolume + }); + updatedReactionVariations = removeObsoleteColumnsFromVariations( + updatedReactionVariations, + columns + ); + setReactionVariations(updatedReactionVariations); + + setColumnDefinitions( + { + type: 'apply_column_selection', + materials: reactionMaterials, + selectedColumns: columns, + gasMode + } + ); + + setSelectedColumns(columns); }; const removeAllVariations = () => { @@ -392,11 +382,25 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv ); } + const fitColumnToContent = (event) => { + const { column } = event; + gridRef.current.api.autoSizeColumns([column], false); + }; + return (
{addVariation()} {removeAllVariations()} + {ColumnSelection( + selectedColumns, + { + ...getReactionMaterialsIDs(reactionMaterials), + properties: ['duration', 'temperature'], + metadata: ['notes', 'analyses'] + }, + applyColumnSelection, + )}
{ // eslint-disable-next-line no-param-reassign - row.analyses = row.analyses.filter((id) => analysesIDs.includes(id)); + row.metadata.analyses = row.metadata.analyses.filter((id) => analysesIDs.includes(id)); }); return updatedVariations; } -function getAnalysesOverlay({ data: variationsRow, context }) { - const { analyses: analysesIDs } = variationsRow; +function getAnalysesOverlay({ data: row, context }) { + const { analyses: analysesIDs } = row; const { allReactionAnalyses } = context; return allReactionAnalyses.filter((analysis) => analysesIDs.includes(analysis.id)); @@ -61,7 +61,7 @@ AnalysisOverlay.propTypes = { function AnalysisVariationLink({ reaction, analysisID }) { const { variations } = cloneDeep(reaction); - const linkedVariations = variations.filter((row) => row.analyses.includes(analysisID)) ?? []; + const linkedVariations = variations.filter((row) => row.metadata.analyses.includes(analysisID)) ?? []; if (linkedVariations.length === 0) { return null; @@ -94,7 +94,7 @@ AnalysesCellRenderer.propTypes = { }; function AnalysesCellEditor({ - data: variationsRow, + data: row, value: analysesIDs, onValueChange, stopEditing, @@ -150,7 +150,7 @@ function AnalysesCellEditor({ const cellContent = ( - {`Link analyses to ${getVariationsRowName(reactionShortLabel, variationsRow.id)}`} + {`Link analyses to ${getVariationsRowName(reactionShortLabel, row.id)}`} {analysesSelection} diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js index 6b8002ec07..303289eeda 100644 --- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js +++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js @@ -1,5 +1,6 @@ /* eslint-disable react/display-name */ import React, { useState, useEffect } from 'react'; +import Select from 'react-select'; import { AgGridReact } from 'ag-grid-react'; import { Button, ButtonGroup, Modal, Form, OverlayTrigger, Tooltip @@ -18,7 +19,7 @@ import { } from 'src/utilities/UnitsConversion'; function RowToolsCellRenderer({ - data: variationsRow, context + data: row, context }) { const { reactionShortLabel, copyRow, removeRow } = context; return ( @@ -26,14 +27,14 @@ function RowToolsCellRenderer({ {getVariationsRowName(reactionShortLabel, variationsRow.id)}} + overlay={{getVariationsRowName(reactionShortLabel, row.id)}} > - + - - @@ -52,13 +53,13 @@ RowToolsCellRenderer.propTypes = { }).isRequired, }; -function EquivalentParser({ data: variationsRow, oldValue: cellData, newValue }) { +function EquivalentParser({ data: row, oldValue: cellData, newValue }) { let equivalent = parseNumericString(newValue); if (equivalent < 0) { equivalent = 0; } // Adapt mass to updated equivalent. - const referenceMaterial = getReferenceMaterial(variationsRow); + const referenceMaterial = getReferenceMaterial(row); const referenceMol = getMolFromGram(referenceMaterial.mass.value, referenceMaterial); const mass = getGramFromMol(referenceMol * equivalent, cellData); @@ -106,7 +107,7 @@ function MaterialFormatter({ value: cellData, colDef }) { } function MaterialParser({ - data: variationsRow, oldValue: cellData, newValue, colDef, context + data: row, oldValue: cellData, newValue, colDef, context }) { const { currentEntry, displayUnit } = colDef.entryDefs; let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit); @@ -129,7 +130,7 @@ function MaterialParser({ return updatedCellData; } - const referenceMaterial = getReferenceMaterial(variationsRow); + const referenceMaterial = getReferenceMaterial(row); // Adapt equivalent to updated mass. if ('equivalent' in updatedCellData) { @@ -147,7 +148,7 @@ function MaterialParser({ } function GasParser({ - data: variationsRow, oldValue: cellData, newValue, colDef + data: row, oldValue: cellData, newValue, colDef }) { const { currentEntry, displayUnit } = colDef.entryDefs; let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit); @@ -170,11 +171,11 @@ function GasParser({ const amount = calculateGasMoles(vesselVolume, concentration, temperatureInKelvin); const mass = getGramFromMol(amount, updatedCellData); - const catalyst = getCatalystMaterial(variationsRow); + const catalyst = getCatalystMaterial(row); const catalystAmount = catalyst?.amount.value ?? 0; const turnoverNumber = calculateTON(amount, catalystAmount); - const percentYield = computePercentYieldGas(amount, getFeedstockMaterial(variationsRow), vesselVolume); + const percentYield = computePercentYieldGas(amount, getFeedstockMaterial(row), vesselVolume); updatedCellData = { ...updatedCellData, @@ -204,7 +205,7 @@ function GasParser({ } function FeedstockParser({ - data: variationsRow, oldValue: cellData, newValue, colDef + data: row, oldValue: cellData, newValue, colDef }) { const { currentEntry, displayUnit } = colDef.entryDefs; let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit); @@ -253,7 +254,7 @@ function FeedstockParser({ return updatedCellData; } - const referenceMaterial = getReferenceMaterial(variationsRow); + const referenceMaterial = getReferenceMaterial(row); const equivalent = computeEquivalent(updatedCellData, referenceMaterial); return { ...updatedCellData, equivalent: { ...updatedCellData.equivalent, value: equivalent } }; @@ -275,7 +276,7 @@ function NoteCellRenderer(props) { } function NoteCellEditor({ - data: variationsRow, + data: row, value, onValueChange, stopEditing, @@ -296,7 +297,7 @@ function NoteCellEditor({ const cellContent = ( - {`Edit note for ${getVariationsRowName(reactionShortLabel, variationsRow.id)}`} + {`Edit note for ${getVariationsRowName(reactionShortLabel, row.id)}`} { + onApply(currentColumns); + setShowModal(false); + }; + + const handleSelectChange = (key) => (selectedOptions) => { + const updatedCurrentColumns = { ...currentColumns }; + updatedCurrentColumns[key] = selectedOptions ? selectedOptions.map((option) => option.value) : []; + setCurrentColumns(updatedCurrentColumns); + }; + + const splitCamelCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1 $2'); + const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1); + + return ( + <> + + + setShowModal(false)}> + + Column Selection + + + {Object.entries(availableColumns).map(([key, values]) => ( +
+
{toUpperCase(splitCamelCase(key))}
+