diff --git a/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx b/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx index 2e1658b85..b53b73022 100644 --- a/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx +++ b/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { CaseInfo_api, EnsembleInfo_api, FieldInfo_api } from "@api"; +import { CaseInfo_api, EnsembleInfo_api } from "@api"; import { apiService } from "@framework/ApiService"; import { CheckIcon, PlusIcon, TrashIcon } from "@heroicons/react/20/solid"; import { ApiStateWrapper } from "@lib/components/ApiStateWrapper"; @@ -11,6 +11,7 @@ import { Dropdown } from "@lib/components/Dropdown"; import { IconButton } from "@lib/components/IconButton"; import { Label } from "@lib/components/Label"; import { Select } from "@lib/components/Select"; +import { useValidState } from "@lib/hooks/useValidState"; import { useQuery } from "@tanstack/react-query"; import { isEqual } from "lodash"; @@ -31,9 +32,6 @@ const CACHE_TIME = 5 * 60 * 1000; export const SelectEnsemblesDialog: React.FC = (props) => { const [confirmCancel, setConfirmCancel] = React.useState(false); - const [selectedField, setSelectedField] = React.useState(""); - const [selectedCaseId, setSelectedCaseId] = React.useState(""); - const [selectedEnsembleName, setSelectedEnsembleName] = React.useState(""); const [newlySelectedEnsembles, setNewlySelectedEnsembles] = React.useState([]); React.useLayoutEffect(() => { @@ -47,36 +45,50 @@ export const SelectEnsemblesDialog: React.FC = (prop }, }); - const computedFieldIdentifier = fixupFieldIdentifier(selectedField, fieldsQuery.data); + const [selectedField, setSelectedField] = useValidState( + "", + [fieldsQuery.data ?? [], (item) => item.field_identifier], + true + ); const casesQuery = useQuery({ - queryKey: ["getCases", computedFieldIdentifier], + queryKey: ["getCases", selectedField], queryFn: () => { - if (!computedFieldIdentifier) { + if (!selectedField) { return Promise.resolve([]); } - return apiService.explore.getCases(computedFieldIdentifier); + return apiService.explore.getCases(selectedField); }, enabled: fieldsQuery.isSuccess, cacheTime: CACHE_TIME, staleTime: STALE_TIME, }); - const computedCaseUuid = fixupCaseUuid(selectedCaseId, casesQuery.data); + const [selectedCaseId, setSelectedCaseId] = useValidState( + "", + [casesQuery.data ?? [], (item) => item.uuid], + true + ); const ensemblesQuery = useQuery({ - queryKey: ["getEnsembles", computedCaseUuid], + queryKey: ["getEnsembles", selectedCaseId], queryFn: () => { - if (!computedCaseUuid) { + if (!selectedCaseId) { return Promise.resolve([]); } - return apiService.explore.getEnsembles(computedCaseUuid); + return apiService.explore.getEnsembles(selectedCaseId); }, enabled: casesQuery.isSuccess, cacheTime: CACHE_TIME, staleTime: STALE_TIME, }); + const [selectedEnsembleName, setSelectedEnsembleName] = useValidState( + "", + [ensemblesQuery.data ?? [], (el) => el.name], + true + ); + function handleFieldChanged(fieldIdentifier: string) { setSelectedField(fieldIdentifier); } @@ -89,13 +101,11 @@ export const SelectEnsemblesDialog: React.FC = (prop setSelectedEnsembleName(ensembleNames[0]); } - const computedEnsembleName = fixupEnsembleName(selectedEnsembleName, ensemblesQuery.data); - function checkIfEnsembleAlreadySelected(): boolean { - if (computedCaseUuid && computedEnsembleName) { + if (selectedCaseId && selectedEnsembleName) { if ( newlySelectedEnsembles.some( - (e) => e.caseUuid === computedCaseUuid && e.ensembleName === computedEnsembleName + (e) => e.caseUuid === selectedCaseId && e.ensembleName === selectedEnsembleName ) ) { return true; @@ -106,8 +116,8 @@ export const SelectEnsemblesDialog: React.FC = (prop function handleAddEnsemble() { if (!checkIfEnsembleAlreadySelected()) { - const caseName = casesQuery.data?.find((c) => c.uuid === computedCaseUuid)?.name ?? "UNKNOWN"; - const ensArr = [{ caseUuid: computedCaseUuid, caseName: caseName, ensembleName: computedEnsembleName }]; + const caseName = casesQuery.data?.find((c) => c.uuid === selectedCaseId)?.name ?? "UNKNOWN"; + const ensArr = [{ caseUuid: selectedCaseId, caseName: caseName, ensembleName: selectedEnsembleName }]; setNewlySelectedEnsembles((prev) => [...prev, ...ensArr]); } } @@ -175,7 +185,7 @@ export const SelectEnsemblesDialog: React.FC = (prop > @@ -189,7 +199,7 @@ export const SelectEnsemblesDialog: React.FC = (prop > = (prop ); }; - -function fixupFieldIdentifier(currFieldIdentifier: string, fieldArr: FieldInfo_api[] | undefined): string { - const fieldIdentifiers = fieldArr ? fieldArr.map((item) => item.field_identifier) : []; - if (currFieldIdentifier && fieldIdentifiers.includes(currFieldIdentifier)) { - return currFieldIdentifier; - } - - if (fieldIdentifiers.length > 0) { - return fieldIdentifiers[0]; - } - - return ""; -} - -function fixupCaseUuid(currCaseUuid: string, caseArr: CaseInfo_api[] | undefined): string { - const caseIds = caseArr ? caseArr.map((item) => item.uuid) : []; - if (currCaseUuid && caseIds.includes(currCaseUuid)) { - return currCaseUuid; - } - - if (caseIds.length > 0) { - return caseIds[0]; - } - - return ""; -} - -function fixupEnsembleName(currEnsembleName: string, ensembleArr: EnsembleInfo_api[] | undefined): string { - const ensembleNames = ensembleArr ? ensembleArr.map((item) => item.name) : []; - if (currEnsembleName && ensembleNames.includes(currEnsembleName)) { - return currEnsembleName; - } - - if (ensembleNames.length > 0) { - return ensembleNames[0]; - } - - return ""; -} diff --git a/frontend/src/lib/hooks/useValidState.ts b/frontend/src/lib/hooks/useValidState.ts new file mode 100644 index 000000000..5e8998d17 --- /dev/null +++ b/frontend/src/lib/hooks/useValidState.ts @@ -0,0 +1,41 @@ +import React from "react"; + +export function useValidState( + initialState: T | (() => T), + validStates: readonly T[] | [readonly K[], (element: K) => T], + keepStateWhenInvalid = true +): [T, (newState: T | ((prevState: T) => T), acceptInvalidState?: boolean) => void] { + const [state, setState] = React.useState(initialState); + + let validState = state; + const computedInitialState = typeof initialState === "function" ? (initialState as () => T)() : initialState; + + let adjustedValidStates: T[] = []; + if (validStates.length === 2 && Array.isArray(validStates[0]) && typeof validStates[1] === "function") { + adjustedValidStates = validStates[0].map(validStates[1] as (element: K) => T); + } else { + adjustedValidStates = validStates as T[]; + } + + if (!adjustedValidStates.includes(state)) { + if (adjustedValidStates.length > 0) { + validState = adjustedValidStates[0]; + } else { + validState = computedInitialState; + } + if (!keepStateWhenInvalid) { + setState(validState); + } + } + + function setValidState(newState: T | ((prevState: T) => T), acceptInvalidState = true) { + const computedNewState = typeof newState === "function" ? (newState as (prevState: T) => T)(state) : newState; + if (!acceptInvalidState && !adjustedValidStates.includes(computedNewState)) { + return; + } + + setState(newState); + } + + return [validState, setValidState]; +}