diff --git a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.stories.tsx b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.stories.tsx index a4e169f..35296ed 100644 --- a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.stories.tsx +++ b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.stories.tsx @@ -24,6 +24,7 @@ const meta: Meta = { // More on argTypes: https://storybook.js.org/docs/react/api/argtypes argTypes: {}, args: { + loading: false, purposes: ["core"], }, }; @@ -186,6 +187,23 @@ Synthesis.args!.timeFeatureAtom = runRequiredAtom: synthesisRunRequiredAtom, }); +const loadingRunRequiredAtom = getInferFeatureAttributesRunRequiredFields(); +export const Loading: Story = { + args: { + loading: true, + activeFeatureAtom: getFeatureAttributesActiveFeatureAtom(), + optionsAtom: getFeatureAttributesOptionsAtom({}), + paramsAtom: getInferFeatureAttributesParamsAtom({ + features: sampleFeatureAttributesIndex, + }), + runRequiredAtom: loadingRunRequiredAtom, + }, +}; +Default.args!.timeFeatureAtom = getInferFeatureAttributesParamsTimeFeatureAtom({ + paramsAtom: Default.args!.paramsAtom!, + runRequiredAtom: loadingRunRequiredAtom, +}); + const noFeaturesRunRequiredAtom = getInferFeatureAttributesRunRequiredFields(); export const NoFeatures: Story = { args: { diff --git a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.test.tsx b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.test.tsx index a3c339f..0bdcfe5 100644 --- a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.test.tsx +++ b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.test.tsx @@ -76,7 +76,10 @@ describe("FeaturesAttributesRows", () => { ); const table = screen.getByRole("table"); - const featureRows = within(table).getAllByRole("row").slice(1); + const featureRows = within(table) + .getAllByRole("row") + // Remove the header row + .slice(1); expect(featureRows.length).toBe(features.length); features.forEach((feature) => { within(table).getByText(feature); diff --git a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.tsx b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.tsx index 593d3ec..52e0b6e 100644 --- a/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.tsx +++ b/src/components/FeatureAttributes/FeaturesAttributesRows/FeaturesAttributesRows.tsx @@ -1,9 +1,11 @@ import { FeatureAttributes } from "@howso/engine"; import { ErrorBoundary, + FieldSelectProps, FormModal, Radio, SecondaryButton, + Skeleton, ToggleInput, UpdateIcon, WarningIcon, @@ -55,9 +57,10 @@ import { FeaturesAttributesRowsI18nBundle as i18n } from "./FeaturesAttributesRo export type FeaturesAttributesRowsProps = IFeatureAttributePurposes & { activeFeatureAtom: FeatureAttributesActiveFeatureAtom; - runRequiredAtom: InferFeatureAttributesRunRequiredFieldsAtom; - paramsAtom: InferFeatureAttributesParamsAtom; + loading?: boolean; optionsAtom: FeatureAttributesOptionsAtom; + paramsAtom: InferFeatureAttributesParamsAtom; + runRequiredAtom: InferFeatureAttributesRunRequiredFieldsAtom; timeFeatureAtom: InferFeatureAttributesParamsTimeFeatureAtom; }; /** @@ -74,54 +77,37 @@ export const FeaturesAttributesRows: FC = ({ const { activeFeatureAtom, paramsAtom, optionsAtom, timeFeatureAtom } = props; const activeFeature = useAtomValue(activeFeatureAtom); const params = useAtomValue(paramsAtom); - const [options, setOptions] = useAtom(optionsAtom); - const setTimeFeature = useSetAtom(timeFeatureAtom); const features = Object.keys(params.features || {}); - // Toggle time series - const onChangeTimeSeries = (evt: ChangeEvent) => { - setOptions({ ...options, time_series: evt.currentTarget.checked }); - if (!evt.currentTarget.checked) setTimeFeature(null); - }; - return (
- - {t(i18n.strings.headings.feature)} - - - {t(i18n.strings.headings.sample)} - - - {t(featureAttributeTypeLabel)} - - -
- {options.time_series - ? t(i18n.strings.headings.timeFeature) - : t(i18n.strings.headings.timeSeries)} - -
-
- - {t(i18n.strings.headings.configuration)} - +
- {features.map((featureName) => ( - - ))} + {props.loading + ? new Array(10) + .fill(0) + .map((_, index) => ( + + )) + : features.map((featureName) => ( + + ))}
@@ -136,22 +122,71 @@ export const FeaturesAttributesRows: FC = ({ ); }; +type TableHeadCellsProps = { + optionsAtom: FeatureAttributesOptionsAtom; + timeFeatureAtom: InferFeatureAttributesParamsTimeFeatureAtom; +}; +const TableHeadCells: FC = ({ + optionsAtom, + timeFeatureAtom, +}) => { + const { t } = useTranslation(i18n.namespace); + const [options, setOptions] = useAtom(optionsAtom); + const setTimeFeature = useSetAtom(timeFeatureAtom); + + // Toggle time series + const onChangeTimeSeries = (evt: ChangeEvent) => { + setOptions({ ...options, time_series: evt.currentTarget.checked }); + if (!evt.currentTarget.checked) setTimeFeature(null); + }; + + return ( + <> + + {t(i18n.strings.headings.feature)} + + + {t(i18n.strings.headings.sample)} + + + {t(featureAttributeTypeLabel)} + + +
+ {options.time_series + ? t(i18n.strings.headings.timeFeature) + : t(i18n.strings.headings.timeSeries)} + +
+
+ + {t(i18n.strings.headings.configuration)} + + + ); +}; + type FeatureFieldsProps = { - feature: string; + feature: string | undefined; } & Pick< FeaturesAttributesRowsProps, | "activeFeatureAtom" - | "paramsAtom" + | "loading" | "optionsAtom" + | "paramsAtom" | "runRequiredAtom" | "timeFeatureAtom" >; const FeatureFields: FC = ({ activeFeatureAtom, - runRequiredAtom, - paramsAtom, feature, + loading, optionsAtom, + paramsAtom, + runRequiredAtom, timeFeatureAtom, }) => { const { t } = useTranslation(i18n.namespace); @@ -162,9 +197,13 @@ const FeatureFields: FC = ({ const [params, setParams] = useAtom(paramsAtom); const options = useAtomValue(optionsAtom); - const attributes = params.features?.[feature]; + const attributes = feature ? params.features?.[feature] : undefined; const setFeatureType = useCallback( (attributes: FeatureAttributes) => { + if (!feature) { + return; + } + const newParams = setInferFeatureAttributeParamsFeatureAttributes( params, feature, @@ -175,18 +214,26 @@ const FeatureFields: FC = ({ }, [setRunRequired, setParams, params, feature], ); - const issues = getFeatureAttributeConfigurationIssues(attributes, { - purposes, - }); + const issues = + attributes && + getFeatureAttributeConfigurationIssues(attributes, { + purposes, + }); return ( - {feature} + + {loading ? : feature} +
- + {loading ? ( + + ) : ( + + )}
@@ -194,6 +241,7 @@ const FeatureFields: FC = ({ attributes={attributes} feature={feature} setAttributes={setFeatureType} + disabled={loading} /> @@ -207,7 +255,10 @@ const FeatureFields: FC = ({
- setActiveFeature(feature)}> + setActiveFeature(feature || "")} + disabled={loading} + > {t(i18n.strings.actions.configure)} @@ -230,9 +281,12 @@ const FeatureFields: FC = ({ }; type InlineInputProps = { - feature: string; + feature?: string; }; -type AttributeProps = { +type AttributeProps = Omit< + FieldSelectProps, + "fieldType" | "label" | "helperText" | "onChange" +> & { attributes: FeatureAttributes | undefined; setAttributes: (attributes: FeatureAttributes) => void; }; @@ -242,6 +296,7 @@ type FeatureTypeControlProps = InlineInputProps & AttributeProps; const FeatureTypeControl: FC = ({ attributes, setAttributes, + ...props }) => { const form = useForm({ defaultValues: attributes }); @@ -269,6 +324,7 @@ const FeatureTypeControl: FC = ({ onChange={onChange} label={undefined} helperText={undefined} + {...props} /> ); @@ -277,10 +333,14 @@ const FeatureTypeControl: FC = ({ // Table row time feature radio type TimeFeatureControlProps = InlineInputProps & Pick & - Pick & { disabled?: boolean }; + Pick & { + loading?: boolean; + disabled?: boolean; + }; const TimeFeatureControl: FC = ({ attributes, disabled, + loading, feature, timeFeatureAtom, }) => { @@ -301,7 +361,7 @@ const TimeFeatureControl: FC = ({ setTimeFeature(feature); }} checked={attributes?.time_series?.time_feature ?? false} - disabled={disabled} + disabled={loading || disabled} /> ); }; @@ -415,25 +475,35 @@ const Form: FC void }> = ({ ); }; -type ControlsProps = Pick & { +type ControlsProps = Pick< + FeaturesAttributesRowsProps, + "loading" | "paramsAtom" +> & { containerProps: React.HTMLProps; }; -const Controls: FC = ({ paramsAtom, containerProps }) => { +const Controls: FC = ({ + loading, + paramsAtom, + containerProps, +}) => { return (
- +
); }; type MapDependenciesControlProps = Pick< FeaturesAttributesRowsProps, - "paramsAtom" + "loading" | "paramsAtom" >; -const MapDependenciesControl: FC = (props) => { +const MapDependenciesControl: FC = ({ + loading, + ...props +}) => { const { t } = useTranslation(i18n.namespace); const params = useAtomValue(props.paramsAtom); const featuresAttributes = params.features || {}; @@ -451,7 +521,7 @@ const MapDependenciesControl: FC = (props) => { return ( <> - + {label}