Ivan Galakhov 4 лет назад
Родитель
Сommit
b4634749da

+ 5 - 1
dashboard/src/components/form-refactor/PorterForm.tsx

@@ -20,7 +20,9 @@ interface Props {
 }
 
 const PorterForm: React.FC<Props> = (props) => {
-  const { formData, isReadOnly } = useContext(PorterFormContext);
+  const { formData, isReadOnly, validationInfo } = useContext(
+    PorterFormContext
+  );
 
   const [currentTab, setCurrentTab] = useState(
     formData.tabs.length > 0 ? formData.tabs[0].name : ""
@@ -102,6 +104,8 @@ const PorterForm: React.FC<Props> = (props) => {
         text={props.saveButtonText || "Deploy"}
         onClick={() => {}}
         makeFlush
+        status={validationInfo.validated ? "" : validationInfo.error}
+        disabled={isReadOnly || !validationInfo.validated}
       />
       <Spacer />
     </>

+ 39 - 5
dashboard/src/components/form-refactor/PorterFormContextProvider.tsx

@@ -4,7 +4,7 @@ import {
   PorterFormState,
   PorterFormAction,
   PorterFormVariableList,
-  GenericInputField,
+  PorterFormValidationInfo,
 } from "./types";
 import { ShowIf, ShowIfAnd, ShowIfNot, ShowIfOr } from "../../shared/types";
 
@@ -19,6 +19,7 @@ interface ContextProps {
   formData: PorterFormData;
   formState: PorterFormState;
   dispatchAction: (event: PorterFormAction) => void;
+  validationInfo: PorterFormValidationInfo;
   isReadOnly?: boolean;
 }
 
@@ -66,6 +67,19 @@ export const PorterFormContextProvider: React.FC<Props> = (props) => {
             },
           },
         };
+      case "update-validation":
+        return {
+          ...state,
+          components: {
+            ...state.components,
+            [action.id]: {
+              ...state.components[action.id],
+              validation: action.updateFunc(
+                state.components[action.id].validation
+              ),
+            },
+          },
+        };
       case "mutate-vars":
         return {
           ...state,
@@ -169,19 +183,35 @@ export const PorterFormContextProvider: React.FC<Props> = (props) => {
           if (field.type == "heading" || field.type == "subtitle") return;
           if (field.required) {
             requiredIds.push(field.id);
-            if (!mapping[field.variable]) {
-              mapping[field.variable] = [];
-            }
-            mapping[field.variable].push(field.id);
           }
+          if (!mapping[field.variable]) {
+            mapping[field.variable] = [];
+          }
+          mapping[field.variable].push(field.id);
         })
       )
     );
     return [requiredIds, mapping];
   };
 
+  /*
+    Validate the form based
+    Will get more complicated over time
+   */
+  const doValidation = (requiredIds: string[]) =>
+    requiredIds
+      .map((id) => state.components[id]?.validation.validated)
+      .every((x) => x);
+
   const formData = computeFormStructure(props.rawFormData, state.variables);
   const [requiredIds, varMapping] = computeRequiredVariables(formData);
+  const isValidated = doValidation(requiredIds);
+
+  console.group("Validation Info:");
+  console.log(requiredIds);
+  console.log(varMapping);
+  console.log(isValidated);
+  console.groupEnd();
 
   return (
     <Provider
@@ -190,6 +220,10 @@ export const PorterFormContextProvider: React.FC<Props> = (props) => {
         formState: state,
         dispatchAction: dispatch,
         isReadOnly: props.isReadOnly,
+        validationInfo: {
+          validated: isValidated,
+          error: isValidated ? null : "Missing required fields",
+        },
       }}
     >
       {props.children}

+ 23 - 10
dashboard/src/components/form-refactor/field-components/StringInput.tsx

@@ -21,15 +21,17 @@ const StringInput: React.FC<Props> = ({
   settings,
   isReadOnly,
 }) => {
-  const { state, variables, mutateVars } = useFormField<StringInputFieldState>(
-    id,
-    {
-      initValue: {},
-      initValidation: {
-        validated: !required,
-      },
-    }
-  );
+  const {
+    state,
+    variables,
+    mutateVars,
+    updateValidation,
+  } = useFormField<StringInputFieldState>(id, {
+    initValue: {},
+    initValidation: {
+      validated: false,
+    },
+  });
 
   // TODO: needs a loading wrapper
   if (state == undefined) {
@@ -38,7 +40,9 @@ const StringInput: React.FC<Props> = ({
 
   const curValue =
     settings?.type == "number"
-      ? parseFloat(variables[variable]) || ""
+      ? !isNaN(parseFloat(variables[variable]))
+        ? parseFloat(variables[variable])
+        : ""
       : variables[variable] || "";
 
   return (
@@ -54,6 +58,15 @@ const StringInput: React.FC<Props> = ({
             [variable]: x,
           };
         });
+        updateValidation((prev) => {
+          return {
+            ...prev,
+            validated:
+              settings?.type == "number"
+                ? !isNaN(x as number)
+                : !!(x as string).trim(),
+          };
+        });
       }}
       label={label}
       isRequired={required}

+ 18 - 0
dashboard/src/components/form-refactor/hooks/useFormField.tsx

@@ -13,6 +13,11 @@ interface FormFieldData<T> {
   mutateVars: (
     mutateFunc: (vars: PorterFormVariableList) => PorterFormVariableList
   ) => void;
+  updateValidation: (
+    updateFunc: (
+      state: PorterFormFieldValidationState
+    ) => PorterFormFieldValidationState
+  ) => void;
 }
 
 interface Options<T> {
@@ -52,11 +57,24 @@ const useFormField = <T extends PorterFormFieldFieldState>(
     });
   };
 
+  const updateValidation = (
+    updateFunc: (
+      state: PorterFormFieldValidationState
+    ) => PorterFormFieldValidationState
+  ) => {
+    dispatchAction({
+      id: fieldId,
+      type: "update-validation",
+      updateFunc,
+    });
+  };
+
   return {
     state: formState.components[fieldId]?.state as T,
     variables: formState.variables,
     updateState,
     mutateVars,
+    updateValidation,
   };
 };
 

+ 12 - 1
dashboard/src/components/form-refactor/types.ts

@@ -78,6 +78,11 @@ export interface PorterFormData {
   tabs: Tab[];
 }
 
+export interface PorterFormValidationInfo {
+  validated: boolean;
+  error?: string;
+}
+
 // internal field state interfaces
 
 export interface StringInputFieldState {}
@@ -121,9 +126,15 @@ export interface PorterFormUpdateFieldAction {
   updateFunc: (prev: PorterFormFieldFieldState) => PorterFormFieldFieldState;
 }
 
+export interface PorterFormUpdateValidationAction {
+  type: "update-validation",
+  id: string;
+  updateFunc: (prev: PorterFormFieldValidationState) => PorterFormFieldValidationState;
+}
+
 export interface PorterFormMutateVariablesAction {
   type: "mutate-vars",
   mutateFunc: (prev: PorterFormVariableList) => PorterFormVariableList;
 }
 
-export type PorterFormAction = PorterFormInitFieldAction|PorterFormUpdateFieldAction|PorterFormMutateVariablesAction;
+export type PorterFormAction = PorterFormInitFieldAction|PorterFormUpdateFieldAction|PorterFormMutateVariablesAction|PorterFormUpdateValidationAction;

+ 6 - 2
dashboard/src/components/values-form/FormDebugger.tsx

@@ -322,11 +322,11 @@ tabs:
         type: number
         unit: km
     - type: checkbox
-      required: true
+      required: false
       label: Checkbox A alternative
       variable: checkbox_a
     - type: subtitle
-      label: "Note: Hidden required fields aren't supported yet (global only)"
+      label: "Note: Hidden required fields are definitely supported"
   - name: controlled-by-external
     show_if:
       or:
@@ -337,6 +337,10 @@ tabs:
       label: Conditional Display (A)
     - type: subtitle
       label: This section can be externally controlled by the value of checkbox_a
+    - type: string-input
+      label: Required Number Input D that could be hidden
+      required: true
+      variable: field_d
     - type: string-input
       variable: input_a
       placeholder: "Override w/ input_a"