formik#FieldArray JavaScript Examples

The following examples show how to use formik#FieldArray. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ArrayField.js    From react-invenio-forms with MIT License 6 votes vote down vote up
render() {
    const { fieldPath } = this.props;
    return (
      <FieldArray
        className="invenio-array-field"
        name={fieldPath}
        component={this.renderFormField}
      />
    );
  }
Example #2
Source File: LicenseField.js    From react-invenio-deposit with MIT License 6 votes vote down vote up
render() {
    return (
      <FieldArray
        name={this.props.fieldPath}
        component={(formikProps) => (
          <LicenseFieldForm {...formikProps} {...this.props} />
        )}
      />
    );
  }
Example #3
Source File: FundingField.js    From react-invenio-deposit with MIT License 6 votes vote down vote up
export function FundingField(props) {
  return (
    <FieldArray
      name={props.fieldPath}
      component={(formikProps) => (
        <FundingFieldForm {...formikProps} {...props} />
      )}
    />
  );
}
Example #4
Source File: CreatibutorsField.js    From react-invenio-deposit with MIT License 6 votes vote down vote up
render() {
    return (
      <FieldArray
        name={this.props.fieldPath}
        component={(formikProps) => (
          <CreatibutorsFieldForm {...formikProps} {...this.props} />
        )}
      />
    );
  }
Example #5
Source File: AddChildren.js    From neighborhood-chef-fe with MIT License 5 votes vote down vote up
AddChildren = (props) => {
  const classes = buttonStyles();

  return (
    <FieldArray name="children">
      {({ push, remove }) => (
        <div
          className="restriction"
          style={{
            marginTop: "10px",
            display: "none",
            flexDirection: "column",
          }}
        >
          <Typography>Children</Typography>
          {props.values.children.map((child, index) => {
            return (
              <div
                key={index}
                style={{ display: "flex", alignItems: "center" }}
              >
                <Field
                  component={TextField}
                  className={classes.field}
                  margin="normal"
                  variant="outlined"
                  label="Child"
                  name={`children[${index}]`}
                  value={child}
                  onChange={props.handleChange}
                  onBlur={props.handleBlur}
                />
                <Button
                  className={classes.button}
                  margin="none"
                  type="button"
                  color="secondary"
                  variant="outlined"
                  onClick={() => remove(index)}
                >
                  x
                </Button>
              </div>
            );
          })}
          <Button
            className={classes.button}
            type="button"
            variant="outlined"
            onClick={() => push("")}
          >
            Add Child
          </Button>
        </div>
      )}
    </FieldArray>
  );
}
Example #6
Source File: AddPets.js    From neighborhood-chef-fe with MIT License 5 votes vote down vote up
AddPets = (props) => {
  const classes = buttonStyles();

  return (
    <FieldArray name="pets">
      {({ push, remove }) => (
        <div
          className="restriction"
          style={{
            marginTop: "10px",
            display: "none",
            flexDirection: "column",
          }}
        >
          <Typography>Pets</Typography>
          {props.values.pets.map((pet, index) => {
            return (
              <div
                key={index}
                style={{ display: "flex", alignItems: "center" }}
              >
                <Field
                  component={TextField}
                  className={classes.field}
                  margin="normal"
                  variant="outlined"
                  label="Pet"
                  name={`pets[${index}]`}
                  value={pet}
                  onChange={props.handleChange}
                  onBlur={props.handleBlur}
                />
                <Button
                  className={classes.button}
                  margin="none"
                  type="button"
                  color="secondary"
                  variant="outlined"
                  onClick={() => remove(index)}
                >
                  x
                </Button>
              </div>
            );
          })}
          <Button
            className={classes.button}
            type="button"
            variant="outlined"
            onClick={() => push("")}
          >
            Add Pet
          </Button>
        </div>
      )}
    </FieldArray>
  );
}
Example #7
Source File: AddDietaryPreferences.js    From neighborhood-chef-fe with MIT License 5 votes vote down vote up
AddDietaryPreferences = (props) => {
  const classes = buttonStyles();

  return (
    <FieldArray name="dietaryPreferences">
      {({ push, remove }) => (
        <div
          className="restriction"
          style={{
            marginTop: "10px",
            display: "none",
            flexDirection: "column",
          }}
        >
          <Typography>Dietary Preferences</Typography>
          {props.values.dietaryPreferences.map((dietaryPreference, index) => {
            return (
              <div
                key={index}
                style={{ display: "flex", alignItems: "center" }}
              >
                <Field
                  component={TextField}
                  className={classes.field}
                  margin="normal"
                  variant="outlined"
                  label="Dietary Preference"
                  name={`dietaryPreferences[${index}]`}
                  value={dietaryPreference}
                  onChange={props.handleChange}
                  onBlur={props.handleBlur}
                />
                <Button
                  className={classes.button}
                  margin="none"
                  type="button"
                  color="secondary"
                  variant="outlined"
                  onClick={() => remove(index)}
                >
                  x
                </Button>
              </div>
            );
          })}
          <Button
            className={classes.button}
            type="button"
            variant="outlined"
            onClick={() => push("")}
          >
            Add Dietary Preference
          </Button>
        </div>
      )}
    </FieldArray>
  );
}
Example #8
Source File: AddAllergens.js    From neighborhood-chef-fe with MIT License 5 votes vote down vote up
AddAllergens = (props) => {
  const classes = buttonStyles();

  return (
    <FieldArray name="allergens">
      {({ push, remove }) => (
        <div
          className="restriction"
          style={{
            marginTop: "10px",
            display: "none",
            flexDirection: "column",
          }}
        >
          <Typography>Allergens</Typography>
          {props.values.allergens.map((allergen, index) => {
            return (
              <div
                key={index}
                style={{ display: "flex", alignItems: "center" }}
              >
                <Field
                  component={TextField}
                  margin="normal"
                  variant="outlined"
                  label="Allergen"
                  name={`allergens[${index}]`}
                  value={allergen}
                />
                <Button
                  className={classes.button}
                  margin="none"
                  type="button"
                  color="secondary"
                  variant="outlined"
                  onClick={() => remove(index)}
                >
                  x
                </Button>
              </div>
            );
          })}
          <Button
            className={classes.button}
            type="button"
            variant="outlined"
            onClick={() => push("")}
          >
            Add Allergen
          </Button>
        </div>
      )}
    </FieldArray>
  );
}
Example #9
Source File: index.js    From whaticket with MIT License 4 votes vote down vote up
ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
	const classes = useStyles();
	const isMounted = useRef(true);

	const initialState = {
		name: "",
		number: "",
		email: "",
	};

	const [contact, setContact] = useState(initialState);

	useEffect(() => {
		return () => {
			isMounted.current = false;
		};
	}, []);

	useEffect(() => {
		const fetchContact = async () => {
			if (initialValues) {
				setContact(prevState => {
					return { ...prevState, ...initialValues };
				});
			}

			if (!contactId) return;

			try {
				const { data } = await api.get(`/contacts/${contactId}`);
				if (isMounted.current) {
					setContact(data);
				}
			} catch (err) {
				toastError(err);
			}
		};

		fetchContact();
	}, [contactId, open, initialValues]);

	const handleClose = () => {
		onClose();
		setContact(initialState);
	};

	const handleSaveContact = async values => {
		try {
			if (contactId) {
				await api.put(`/contacts/${contactId}`, values);
				handleClose();
			} else {
				const { data } = await api.post("/contacts", values);
				if (onSave) {
					onSave(data);
				}
				handleClose();
			}
			toast.success(i18n.t("contactModal.success"));
		} catch (err) {
			toastError(err);
		}
	};

	return (
		<div className={classes.root}>
			<Dialog open={open} onClose={handleClose} maxWidth="lg" scroll="paper">
				<DialogTitle id="form-dialog-title">
					{contactId
						? `${i18n.t("contactModal.title.edit")}`
						: `${i18n.t("contactModal.title.add")}`}
				</DialogTitle>
				<Formik
					initialValues={contact}
					enableReinitialize={true}
					validationSchema={ContactSchema}
					onSubmit={(values, actions) => {
						setTimeout(() => {
							handleSaveContact(values);
							actions.setSubmitting(false);
						}, 400);
					}}
				>
					{({ values, errors, touched, isSubmitting }) => (
						<Form>
							<DialogContent dividers>
								<Typography variant="subtitle1" gutterBottom>
									{i18n.t("contactModal.form.mainInfo")}
								</Typography>
								<Field
									as={TextField}
									label={i18n.t("contactModal.form.name")}
									name="name"
									autoFocus
									error={touched.name && Boolean(errors.name)}
									helperText={touched.name && errors.name}
									variant="outlined"
									margin="dense"
									className={classes.textField}
								/>
								<Field
									as={TextField}
									label={i18n.t("contactModal.form.number")}
									name="number"
									error={touched.number && Boolean(errors.number)}
									helperText={touched.number && errors.number}
									placeholder="5513912344321"
									variant="outlined"
									margin="dense"
								/>
								<div>
									<Field
										as={TextField}
										label={i18n.t("contactModal.form.email")}
										name="email"
										error={touched.email && Boolean(errors.email)}
										helperText={touched.email && errors.email}
										placeholder="Email address"
										fullWidth
										margin="dense"
										variant="outlined"
									/>
								</div>
								<Typography
									style={{ marginBottom: 8, marginTop: 12 }}
									variant="subtitle1"
								>
									{i18n.t("contactModal.form.extraInfo")}
								</Typography>

								<FieldArray name="extraInfo">
									{({ push, remove }) => (
										<>
											{values.extraInfo &&
												values.extraInfo.length > 0 &&
												values.extraInfo.map((info, index) => (
													<div
														className={classes.extraAttr}
														key={`${index}-info`}
													>
														<Field
															as={TextField}
															label={i18n.t("contactModal.form.extraName")}
															name={`extraInfo[${index}].name`}
															variant="outlined"
															margin="dense"
															className={classes.textField}
														/>
														<Field
															as={TextField}
															label={i18n.t("contactModal.form.extraValue")}
															name={`extraInfo[${index}].value`}
															variant="outlined"
															margin="dense"
															className={classes.textField}
														/>
														<IconButton
															size="small"
															onClick={() => remove(index)}
														>
															<DeleteOutlineIcon />
														</IconButton>
													</div>
												))}
											<div className={classes.extraAttr}>
												<Button
													style={{ flex: 1, marginTop: 8 }}
													variant="outlined"
													color="primary"
													onClick={() => push({ name: "", value: "" })}
												>
													{`+ ${i18n.t("contactModal.buttons.addExtraInfo")}`}
												</Button>
											</div>
										</>
									)}
								</FieldArray>
							</DialogContent>
							<DialogActions>
								<Button
									onClick={handleClose}
									color="secondary"
									disabled={isSubmitting}
									variant="outlined"
								>
									{i18n.t("contactModal.buttons.cancel")}
								</Button>
								<Button
									type="submit"
									color="primary"
									disabled={isSubmitting}
									variant="contained"
									className={classes.btnWrapper}
								>
									{contactId
										? `${i18n.t("contactModal.buttons.okEdit")}`
										: `${i18n.t("contactModal.buttons.okAdd")}`}
									{isSubmitting && (
										<CircularProgress
											size={24}
											className={classes.buttonProgress}
										/>
									)}
								</Button>
							</DialogActions>
						</Form>
					)}
				</Formik>
			</Dialog>
		</div>
	);
}
Example #10
Source File: DrillEditorPage.js    From UltimateApp with MIT License 4 votes vote down vote up
DrillEditorPage = (props) => {
  const [currentDrill, setCurrentDrill] = useState(props.route.params?.currentDrill || newDrill);

  const validationSchema = Yup.object({
    author: Yup.string().trim(),
    title: Yup.string()
      .trim()
      .required(I18n.t('drillEditorPage.errors.title.empty'))
      .notOneOf(
        props.customDrills.filter((existingDrill) => existingDrill.id !== currentDrill.id).map((drill) => drill.title),
        I18n.t('drillEditorPage.errors.title.alreadyExists'),
      ),
    image: Yup.string().trim().min(1),
    description: Yup.string().trim(),
    minimalPlayersNumber: Yup.number().positive(),
    inGame: Yup.string().trim(),
    equipment: Yup.string().trim(),
    durationInMinutes: Yup.number().positive(),
    intensity: Yup.string().trim().oneOf(Object.values(Intensities)),
    goals: Yup.array(Yup.string().oneOf(Object.values(FrisbeeGoals))).min(
      1,
      I18n.t('drillEditorPage.errors.goals.empty'),
    ),
    level: Yup.string().trim().oneOf(Object.values(Levels)),
    steps: Yup.array(
      Yup.object({
        id: Yup.number(),
        title: Yup.string().trim(),
        animation: Yup.object(),
        vimeoId: Yup.string().trim().min(1),
        youtube: Yup.string().trim().min(1),
        instruction: Yup.string().trim(),
      }),
    )
      .required()
      .min(1, I18n.t('drillEditorPage.errors.steps.empty')),
  });

  const behavior = Platform.select({
    ios: 'padding',
    android: 'height',
  });

  return (
    <KeyboardAvoidingView style={{ flex: 1 }} behavior={behavior}>
      <ScrollView style={styles.drillEditorPage}>
        <Formik
          initialValues={currentDrill}
          validationSchema={validationSchema}
          onSubmit={(values) => {
            props.saveDrill(values);
            showSuccess(I18n.t('drillEditorPage.saveSuccess', { title: values.title }));
            props.navigation.navigate('DrillPage', { id: values.id });
          }}
        >
          {({ handleSubmit, handleChange, errors, values, touched, isValid }) => (
            <View>
              <Input fieldName="author" label={I18n.t('drillEditorPage.labels.author')} />
              <Input fieldName="title" label={I18n.t('drillEditorPage.labels.title')} required />
              <Input fieldName="image" label={I18n.t('drillEditorPage.labels.image')} />
              <Input fieldName="description" label={I18n.t('drillEditorPage.labels.description')} multiline />
              <Input
                fieldName="minimalPlayersNumber"
                keyboardType="number-pad"
                label={I18n.t('drillEditorPage.labels.minimalPlayersNumber')}
              />
              <Input fieldName="inGame" label={I18n.t('drillEditorPage.labels.inGame')} multiline />
              <Input fieldName="equipment" label={I18n.t('drillEditorPage.labels.equipment')} />
              <Input
                fieldName="durationInMinutes"
                keyboardType="number-pad"
                label={I18n.t('drillEditorPage.labels.durationInMinutes')}
              />
              <RadioButton
                fieldName="intensity"
                label={I18n.t('drillEditorPage.labels.intensity')}
                values={Object.values(Intensities)}
                labels={Object.values(Intensities).map((intensity) => I18n.t(`data.intensities.${intensity}`))}
              />
              <Checkbox
                fieldName="goals"
                label={I18n.t('drillEditorPage.labels.goals')}
                values={Object.values(FrisbeeGoals)}
                labels={Object.values(FrisbeeGoals).map((goal) => I18n.t(`data.frisbeeGoals.${goal}`))}
              />
              <RadioButton
                fieldName="level"
                label={I18n.t('drillEditorPage.labels.level')}
                values={Object.values(Levels)}
                labels={Object.values(Levels).map((level) => I18n.t(`data.levels.${level}`))}
              />
              <FieldArray
                name="steps"
                render={({ push, remove }) => (
                  <View>
                    <View style={styles.stepHeader}>
                      <Text style={styles.stepHeadertext}>{I18n.t('drillEditorPage.labels.stepsHeader')}</Text>
                      <Button
                        onPress={() => push({ ...newStep, id: values.steps.length })}
                        text="+"
                        small
                        light
                        testID="addStep"
                      />
                    </View>
                    <Text style={styles.error}>{errors.steps}</Text>
                    {values.steps.map((step, index) => (
                      <View key={index} style={styles.step}>
                        <View style={styles.stepHeader}>
                          <Button
                            onPress={() => remove(index)}
                            text="-"
                            small
                            light
                            style={styles.button}
                            testID="removeStep"
                          />
                          <Text style={styles.stepHeadertext}>
                            {I18n.t('drillEditorPage.labels.steps.header', { count: index + 1 })}
                          </Text>
                        </View>
                        <Input
                          fieldName={`steps[${index}].title`}
                          label={I18n.t('drillEditorPage.labels.steps.title')}
                        />
                        <Input
                          fieldName={`steps[${index}].instruction`}
                          label={I18n.t('drillEditorPage.labels.steps.instruction')}
                          multiline
                        />
                        <AnimationInput
                          fieldName={`steps[${index}].animation`}
                          label={I18n.t('drillEditorPage.labels.steps.animation')}
                        />
                        <Input
                          fieldName={`steps[${index}].vimeoId`}
                          label={I18n.t('drillEditorPage.labels.steps.vimeoId')}
                        />
                        <Input
                          fieldName={`steps[${index}].youtube`}
                          label={I18n.t('drillEditorPage.labels.steps.youtube')}
                        />
                      </View>
                    ))}
                  </View>
                )}
              />
              <Button
                onPress={handleSubmit}
                text={I18n.t('drillEditorPage.cta')}
                style={styles.cta}
                testID="submitButton"
              />
            </View>
          )}
        </Formik>
      </ScrollView>
    </KeyboardAvoidingView>
  );
}
Example #11
Source File: WorkingGroupsWrapper.js    From react-eclipsefdn-members with Eclipse Public License 2.0 4 votes vote down vote up
WorkingGroupsWrapper = ({
  formField,
  workingGroupsData,
  ...otherProps
}) => {
  const { currentFormId } = useContext(MembershipContext);
  const { setFieldValue } = otherProps.parentState.formik;
  const [loading, setLoading] = useState(false);

  // Fetch existing form data

  const fetchWorkingGroupsData = useCallback(() => {
    // All pre-process: if running without server,
    // use fake json data; if running with API, use API
    let url_prefix_local;
    let url_suffix_local = '';
    if (getCurrentMode() === MODE_REACT_ONLY) {
      url_prefix_local = 'membership_data';
      url_suffix_local = '.json';
    }

    if (getCurrentMode() === MODE_REACT_API) {
      url_prefix_local = api_prefix_form;
    }

    // If the current form exsits, and it is not creating a new form
    if (currentFormId && currentFormId !== newForm_tempId) {
      fetch(
        url_prefix_local +
          `/${currentFormId}/` +
          end_point.working_groups +
          url_suffix_local,
        { headers: FETCH_HEADER }
      )
        .then((resp) => resp.json())
        .then((data) => {
          if (data.length) {
            // matchWorkingGroupFields(): Call the the function to map
            // the retrived working groups backend data to fit frontend, and
            // setFieldValue(): Prefill Data --> Call the setFieldValue
            // of Formik, to set workingGroups field with the mapped data
            setFieldValue(
              workingGroups,
              matchWorkingGroupFields(data, workingGroupsData)
            );
          }
          setLoading(false);
        });
    } else {
      setLoading(false);
    }
  }, [setFieldValue, currentFormId, workingGroupsData]);

  // Fetch data only once and prefill data, as long as
  // fetchWorkingGroupsData Function does not change,
  // will not cause re-render again
  useEffect(() => {
    // Fetch existing form data
    fetchWorkingGroupsData();
  }, [fetchWorkingGroupsData]);

  if (loading) {
    return <Loading />;
  }

  return (
    <>
      <h1 className="fw-600 h2">Working Group</h1>
      <p>Please complete the following details for joining a Working Group</p>
      <div
        id="working-groups-page"
        className="align-center margin-top-50 margin-bottom-30">
        <FieldArray
          name={workingGroups}
          render={(arrayHelpers) => {
            return (
              <WorkingGroup
                formField={formField}
                arrayHelpers={arrayHelpers}
                workingGroupsData={workingGroupsData}
                formikProps={otherProps.parentState.formik}
              />
            );
          }}></FieldArray>
      </div>
    </>
  );
}
Example #12
Source File: AddDietaryRestrictions.js    From neighborhood-chef-fe with MIT License 4 votes vote down vote up
AddDietaryRestrictions = (props) => {
  const classes = buttonStyles();

  return (
    <FieldArray name="dietaryRestrictions">
      {({ push, remove }) => (
        <div
          className="restriction"
          style={{
            marginTop: "10px",
            display: "none",
            flexDirection: "column",
          }}
        >
          <Typography>Dietary Restrictions</Typography>
          {props.values.dietaryRestrictions.map((dietaryRestriction, index) => {
            return (
              <div
                key={index}
                style={{ display: "flex", alignItems: "center" }}
              >
                <Field
                  component={TextField}
                  className={classes.field}
                  margin="normal"
                  variant="outlined"
                  label="Dietary Restriction"
                  name={`dietaryRestrictions[${index}]`}
                  value={dietaryRestriction}
                  onChange={props.handleChange}
                  onBlur={props.handleBlur}
                />
                <Button
                  className={classes.button}
                  margin="none"
                  type="button"
                  color="secondary"
                  variant="outlined"
                  onClick={() => remove(index)}
                >
                  x
                </Button>
              </div>
            );
          })}
          <Button
            className={classes.button}
            type="button"
            variant="outlined"
            onClick={() => push("")}
          >
            Add Dietary Restriction
          </Button>
        </div>
      )}
    </FieldArray>
  );
}
Example #13
Source File: AddTelephone.js    From AED-Map with MIT License 4 votes vote down vote up
AddTelephone = ({
  className,
  formik: {
    values: { phone },
    errors,
    touched,
    handleBlur,
    setFieldValue
  }
}) => {
  const classes = useStyles();
  return (
    <div>
      <p className={classes.title}>Ваш номер телефону</p>

      <FieldArray
        name="phone"
        render={arrayHelpers => (
          <div className={classes.numbersGroup}>
            {phone &&
              phone.length > 0 &&
              phone.map((phone, index) => {
                const errorMessage = getIn(
                  errors,
                  `phone[${index}]`
                );
                const isTouched = getIn(
                  touched,
                  `phone[${index}]`
                );
                return (
                  // eslint-disable-next-line react/no-array-index-key
                  <div key={index}>
                    <MuiPhoneNumber
                      name={`phone[${index}]`}
                      className={className}
                      value={phone}
                      onChange={value =>
                        setFieldValue(
                          `phone[${index}]`,
                          value
                        )
                      }
                      onBlur={handleBlur}
                      defaultCountry="ua"
                      regions="europe"
                      helperText={
                        errorMessage &&
                        isTouched &&
                        errorMessage
                      }
                      error={errorMessage && isTouched}
                      InputProps={{
                        endAdornment: (
                          <InputAdornment
                            position="end"
                            onClick={() =>
                              arrayHelpers.remove(index)
                            }
                          >
                            <IconButton
                              color="secondary"
                              aria-label="delete phone"
                            >
                              <DeleteIcon fontSize="small" />
                            </IconButton>
                          </InputAdornment>
                        )
                      }}
                    />
                  </div>
                );
              })}
            <div className={classes.centered}>
              <IconButton
                color="primary"
                aria-label="add phone"
                onClick={() =>
                  arrayHelpers.insert(phone.length, '')
                }
              >
                <AddCircleIcon fontSize="large" />
              </IconButton>
            </div>
          </div>
        )}
      />
    </div>
  );
}
Example #14
Source File: add-lesson.js    From what-front with MIT License 4 votes vote down vote up
AddLesson = () => {
  const history = useHistory();

  const [markError, setMarkError] = React.useState(false);
  const [mentorError, setMentorError] = React.useState(false);
  const [groupError, setGroupError] = React.useState(false);
  const [studentsGroup, setStudentsGroup] = React.useState(null);
  const [mentorInput, setMentorInput] = React.useState('');
  const [btnSave, setBtnSave] = React.useState(false);
  const [classRegister, setClassRegister] = React.useState(false);
  const [formData, setFormData] = React.useState([]);

  const {
    data: mentors,
    isLoading: mentorsIsLoading,
    isLoaded: mentorsIsLoaded,
    error: mentorsError,
  } = useSelector(mentorsActiveSelector, shallowEqual);

  const {
    data: groups,
    isLoading: groupsIsLoading,
    isLoaded: groupsIsLoaded,
    error: groupsError,
  } = useSelector(loadStudentGroupsSelector, shallowEqual);

  const {
    data: students,
    isLoading: studentsIsLoading,
    isLoaded: studentsIsLoaded,
    error: studentsError,
  } = useSelector(studentsSelector, shallowEqual);

  const {
    isLoaded: addIsLoaded,
    isLoading: lessonIsLoading,
    error: addError,
  } = useSelector(addLessonSelector, shallowEqual);

  const [
    getMentors,
    getGroups,
    getStudents,
    createLesson,
    dispatchAddAlert,
  ] = useActions([fetchActiveMentors, globalLoadStudentGroups, loadStudents, addLesson, addAlert]);

  useEffect(() => {
      getMentors();
      getGroups();
      getStudents();
  }, []);

  useEffect(() => {
    if (!addError && addIsLoaded) {
      history.push(paths.LESSONS);
      dispatchAddAlert('The lesson has been added successfully!', 'success');
    }
    if (addError && !addIsLoaded) {
      dispatchAddAlert(addError);
    }
  }, [addError, addIsLoaded, dispatchAddAlert, history]);

  const openStudentDetails = useCallback((id) => {
    history.push(`${paths.STUDENTS_DETAILS}/${id}`);
  }, [history]);

  const handleCancel = useCallback(() => {
    history.push(paths.LESSONS);
  }, [history]);

  const onSubmit = (values) => {
      const { lessonDate, themeName } = values;
      const lessonVisits = formData.map((lessonVisit) => {
        const {
          presence, studentId, studentMark,
        } = lessonVisit;
        return (
          {
            comment: '',
            presence,
            studentId,
            studentMark: studentMark || null,
          }
        );
      });

      const mentorData = mentors.find((mentor) => mentor.email === mentorInput);

      const theme = commonHelpers.capitalizeTheme(themeName);
      const formalizedDate = commonHelpers.transformDateTime({ isRequest: true, dateTime: lessonDate }).formDateTimeForRequest;

      const lessonObject = {
        lessonDate: formalizedDate,
        themeName: theme,
        lessonVisits,
        studentGroupId: studentsGroup.id,
        mentorId: mentorData.id,
      };
      if (!mentorsError && lessonObject) {
        createLesson(lessonObject);
      }
  };

  const getFormData = (studentsGroup, students) => {
    const uniqueIds = [...new Set(studentsGroup.studentIds)];

    const studentD = uniqueIds.map(
      (id) => students.find((student) => student.id === id),
    );

    const activeStudents = studentD.filter((student) => student !== undefined);

    const studentsData = activeStudents.map((student) => (
      {
        studentId: student.id,
        studentName: `${student.firstName} ${student.lastName}`,
      }
    ));

    return studentsData.map((student) => ({
      ...student,
      studentMark: 0,
      presence: false,
      comment: '',
    }));
  };

  const openClassRegister = useCallback(() => {
    if (studentsGroup) {
      setFormData(getFormData(studentsGroup, students));
      setBtnSave(true);
      setClassRegister(true);
      setGroupError(false);
    }
    !studentsGroup && setCorrectError('#inputGroupName', setGroupError, 'group name');
    !mentorInput && setCorrectError('#mentorEmail', setMentorError, 'mentor email');
  }, [studentsGroup, students, mentorInput, setGroupError, setMentorError, setBtnSave, setClassRegister, setGroupError]);

  const setCorrectError = (inputSelector, setError, fieldName) => {
    const {value} = document.querySelector(inputSelector);
    value ? setError(`Invalid ${fieldName}`) : setError('This field is required');
  };

  const handleMentorChange = (ev) => {
    setMentorInput(ev.target.value);
    const mentorData = mentors.find((mentor) => mentor.email === ev.target.value);

    mentorData ? setMentorError(false)
      : setCorrectError('#mentorEmail', setMentorError, 'mentor email');
  };

  const handleGroupChange = (ev) => {
    const resultGroup = groups.find((group) => group.name.toUpperCase() === ev.target.value.toUpperCase());
    if (resultGroup) {
      setStudentsGroup(resultGroup);
      setGroupError(false);
      setBtnSave(false);
      setClassRegister(false);
    } else {
      setCorrectError('#inputGroupName', setGroupError, 'group name');
    }
  };

  const handlePresenceChange = (ev) => {
    const arrIndex = ev.target.dataset.id;
    const newFormData = cloneDeep(formData);
    newFormData[arrIndex].presence = !newFormData[arrIndex].presence;
    newFormData[arrIndex].studentMark = 0;
    setFormData(newFormData);
  };

  const handleMarkChange = (ev) => {
    const arrIndex = ev.target.dataset.id;
    const mark = Number(ev.target.value);
    if (mark > 0 && mark < 13) {
      const newFormData = cloneDeep(formData);
      newFormData[arrIndex].studentMark = mark;
      setFormData(newFormData);
      setMarkError(false);
    } else {
      setMarkError(true);
      ev.target.value = '';
    }
  };

  return (
    <div className="container">
      <div className={classNames(styles.page, 'mx-auto', `${classRegister ? 'col-12' : 'col-8'}`)}>
        <div className="d-flex flex-row">
          {groupsError && mentorsError && studentsError && (
            <div className="col-12 alert-danger">
              Server Problems
            </div>
          )}
          <div className='col-12'>
            <h3>Add a Lesson</h3>
            <hr />
            <WithLoading
              isLoading={
                mentorsIsLoading
                || studentsIsLoading
                || groupsIsLoading
                || lessonIsLoading
              }
              className={classNames(styles['loader-centered'])}
            >
              <Formik
                initialValues={{
                  themeName: '',
                  groupName: '',
                  lessonDate: '',
                  mentorEmail: '',
                  formData,
                }}
                onSubmit={onSubmit}
                validationSchema={addLessonValidation}
              >
                {({ errors, touched, setFieldTouched }) => (
                  <Form id="form" className={classNames(styles.size)}>
                    <div className='d-flex flex-sm-column flex-lg-row' data-testid='addForm'>
                      <div className={classRegister ? 'col-lg-6' : 'col-lg-12'}>
                        <div className="mt-3 form-group row">
                          <label htmlFor="inputLessonTheme" className="col-md-4 col-form-label">Lesson Theme:</label>
                          <div className="col-md-8">
                            <Field
                              type="text"
                              className={classNames('form-control',
                                { 'border-danger': !!(errors.themeName && touched.themeName) })}
                              name="themeName"
                              id="inputLessonTheme"
                              placeholder="Lesson Theme"
                              required
                            />
                            {
                              errors.themeName
                              && <div className={styles.error}>{errors.themeName}</div>
                            }
                          </div>
                        </div>
                        <div className="form-group row">
                          <label htmlFor="inputGroupName" className="col-md-4 col-form-label">Group Name:</label>
                          <div className="col-md-8 input-group">
                            <input
                              name="groupName"
                              id="inputGroupName"
                              type="text"
                              className={classNames('form-control group-input', { 'border-danger': !!groupError })}
                              placeholder="Group Name"
                              onChange={handleGroupChange}
                              list="group-list"
                              disabled={groupsIsLoading}
                              required
                            />
                            <datalist id="group-list">
                              {groups.map(({ id, name }) => (
                                <option key={id}>{name}</option>
                              ))}
                            </datalist>
                          </div>
                          {
                            groupError
                              ? <div id='group-error' className={classNames('col-8 offset-4', styles.error)}>{groupError}</div>
                              : null
                          }
                        </div>
                        <div className="form-group row">
                          <label className="col-md-4 col-form-label" htmlFor="choose-date/time">Lesson Date/Time:</label>
                          <div className="col-md-8">
                            <Field
                              className="form-control"
                              type="datetime-local"
                              name="lessonDate"
                              id="choose-date-time"
                              max={ commonHelpers.transformDateTime({}).formInitialValue }
                              required
                            />
                          </div>
                        </div>
                        <div className="form-group row">
                          <label className="col-md-4 col-form-label" htmlFor="mentorEmail">Mentor Email:</label>
                          <div className="col-md-8 input-group">
                            <input
                              className={classNames('form-control group-input', { 'border-danger': !!mentorError })}
                              type="text"
                              name="mentorEmail"
                              id="mentorEmail"
                              list="mentor-list"
                              placeholder="Mentor Email"
                              onChange={handleMentorChange}
                              disabled={mentorsIsLoading}
                              required
                            />
                            <datalist id="mentor-list">
                              {mentors.map(({ id, firstName, lastName, email }) => (
                                <option key={id} value={email}>
                                  {`${firstName} ${lastName}`}
                                </option>
                              ))}
                            </datalist>
                          </div>
                          {
                            mentorError
                              ? <div id='mentor-error' className={classNames('col-8 offset-4', styles.error)}>{mentorError}</div>
                              : null
                          }
                        </div>
                      </div>
                      {classRegister && formData && (
                        <div className={classRegister ? 'col-lg-6' : 'col-lg-12'}>
                          <FieldArray name="formData">
                            {() => (
                              <div className={classNames(styles.list, 'col-lg-12 pt-2')}>
                                <table className="table table-bordered table-hover" data-testid='students-form'>
                                  <thead>
                                    <tr>
                                      <th scope="col" aria-label="first_col" />
                                      <th scope="col">Full Student`s Name</th>
                                      <th scope="col" className="text-center">Mark</th>
                                      <th scope="col" className="text-center">Presence</th>
                                    </tr>
                                  </thead>
                                  <tbody data-testid='students-formData-table'>
                                    {formData && formData.length > 0 && (
                                      formData.map((lessonVisit, index) => (
                                        <tr key={lessonVisit.studentId}>
                                          <th scope="row">{ index + 1 }</th>
                                          <td>
                                            <p
                                              data-testid={`openStudentDetails-${lessonVisit.studentId}`}
                                              className={classNames(styles.link)}
                                              onClick={() => openStudentDetails(lessonVisit.studentId)}
                                            >
                                              { lessonVisit.studentName }
                                            </p>
                                          </td>
                                          <td>
                                            <Field
                                              data-testid={`formData[${index}].studentMark`}
                                              name={`formData[${index}].studentMark`}
                                              className={classNames(
                                                'form-control',
                                                { 'border-danger': markError },
                                                styles.mode,
                                              )}
                                              type="number"
                                              max="12"
                                              min="0"
                                              placeholder=""
                                              onChange={handleMarkChange}
                                              data-id={index}
                                              disabled={!formData[index].presence}
                                            />
                                          </td>
                                          <td>
                                            <Field
                                              data-testid={`formData[${index}].presence`}
                                              name={`formData[${index}].presence`}
                                              className={styles.mode}
                                              type="checkbox"
                                              onClick={handlePresenceChange}
                                              data-id={index}
                                              checked={formData[index].presence}
                                            />
                                          </td>
                                        </tr>
                                      ))
                                    )}
                                  </tbody>
                                </table>
                              </div>
                            )}
                          </FieldArray>
                        </div>
                      )}
                    </div>
                    <div className='col-12 d-flex justify-content-between'>
                      <button form="form" data-testid='cancelBtn' type="button" className="btn btn-secondary btn-lg" onClick={handleCancel}>Cancel</button>
                      {btnSave
                        ? <button id='submit' form="form" type="submit" className="btn btn-success btn-lg">Save</button>
                        : (
                          <Button
                            id='class-register-btn'
                            className="btn btn-success btn-lg"
                            onClick={(event) => {
                              event.preventDefault();
                              setFieldTouched();
                              openClassRegister();
                            }}
                          >
                            Class Register
                          </Button>
                        )}
                    </div>
                  </Form>
                )}
              </Formik>
            </WithLoading>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #15
Source File: edit-lesson.js    From what-front with MIT License 4 votes vote down vote up
EditLesson = () => {
  const history = useHistory();
  const { id } = useParams();
  const [lessonOnEdit, setLessonOnEdit] = useState({});

  const [
    loadLessons, // not getById, cos of mistake in fetch response (lesson.theme === null)
    getGroup,
    getStudents,
    updateLesson,
    dispatchAddAlert,
  ] = useActions([fetchLessons, loadStudentGroupById, loadStudents, editLesson, addAlert]);

  const {
    data: lessons,
    isLoading: lessonsIsLoading,
    isLoaded: lessonsIsLoaded,
    error: lessonsError,
  } = useSelector(lessonsSelector, shallowEqual);

  const {
    data: students,
    isLoading: studentsIsLoading,
    isLoaded: studentsIsLoaded,
    error: studentsError,
  } = useSelector(studentsSelector, shallowEqual);

  const {
    data: group,
    isLoading: groupIsLoading,
    isLoaded: groupIsLoaded,
    error: groupError,
  } = useSelector(loadStudentGroupByIdSelector, shallowEqual);

  const {
    isLoaded: editIsLoaded,
    error: editError,
  } = useSelector(editLessonSelector, shallowEqual);

  useEffect(() => {
      getStudents();
      loadLessons();
  }, []);

  useEffect(() => {
    if (lessonsIsLoaded && studentsIsLoaded) {
      const lesson = lessons.find((lesson) => lesson.id === Number(id));
      if (lesson !== undefined) {
        const lessonToEdit = cloneDeep(lesson);
        getGroup(lesson.studentGroupId);
        setLessonOnEdit(lessonToEdit);
      } else {
        history.push(paths.NOT_FOUND);
      }
    }
  }, [lessonsIsLoaded, studentsIsLoaded]);

  useEffect(() => {
    if (groupIsLoaded) {
      const studentsData = group.studentIds.reduce((acc, student) => {
        const studentObj = students.find((stud) => stud.id === student);
        if (studentObj.comment === undefined) {
          studentObj.comment = '';
        }
        if (studentObj.studentMark === undefined) {
          studentObj.studentMark = '';
        }
        studentObj.studentName = `${studentObj.firstName} ${studentObj.lastName}`;

        acc.push(studentObj);
        return acc;
      }, []);

      studentsData.sort((a, b) => a.studentName.localeCompare(b.studentName));

      const newlessonOnEdit = { ...lessonOnEdit, lessonVisits: studentsData };
      setLessonOnEdit(newlessonOnEdit);
    }
  }, [groupIsLoaded, students]);

  useEffect(() => {
    if (!editError && editIsLoaded) {
      history.push(paths.LESSONS);
      dispatchAddAlert('The lesson has been edited successfully', 'success');
    }
    if (editError && !editIsLoaded) {
      dispatchAddAlert(editError);
    }
  }, [dispatchAddAlert, editError, editIsLoaded, history]);

  const openStudentDetails = useCallback((studentId) => {
    history.push(`${paths.STUDENTS_DETAILS}/${studentId}`);
  }, [history]);

  const handleCancel = useCallback(() => {
    history.push(paths.LESSONS);
  }, [history]);

  const onSubmit = (values) => {
      const { lessonDate, themeName } = values;
      let lessonVisits = group.studentIds.map((item, ind) => {

        const studentMark = values?.formData[ind]?.studentMark ? values.formData[ind].studentMark : null;
        const presence = values?.formData[ind]?.presence ? values.formData[ind].presence : 'false';

        return (
          {
            comment: '',
            presence,
            studentId: item,
            studentMark,
          }
        );
      }).sort((a, b) => a.studentId - b.studentId);
      const theme = commonHelpers.capitalizeTheme(!themeName ? 'text' : themeName);
      const formalizedDate = commonHelpers.transformDateTime({ isRequest:true, dateTime: lessonDate }).formDateTimeForRequest;
      const lessonObject = {
        themeName: theme,
        lessonDate: formalizedDate,
        lessonVisits,
      };
      if (lessonObject) {
        updateLesson(lessonObject, id);
      }
  };

  const handlePresenceChange = (ev) => {
    const arrIndex = ev.target.dataset.id;
    const lessonOnEditChange = {...lessonOnEdit};
    lessonOnEditChange.lessonVisits[arrIndex].presence = !lessonOnEdit.lessonVisits[arrIndex].presence;
    lessonOnEditChange.lessonVisits[arrIndex].studentMark = '';
    setLessonOnEdit(lessonOnEditChange);
  };

  const handleMarkChange = (ev) => {
    const arrIndex = ev.target.dataset.id;
    let mark = Number(ev.target.value);
    if (mark < 0 || mark > 12) {
      mark = 0;
    }
    const lessonOnEditChange = {...lessonOnEdit};
    lessonOnEditChange.lessonVisits[arrIndex].studentMark = mark;
    setLessonOnEdit(lessonOnEditChange);
  };

  return (
    <div className="container" data-testid='editLessonRenderForm'>
      <div className={classNames(styles.page, 'mx-auto', 'col-12')}>
        <div className="d-flex flex-row">
          {groupError && lessonsError && editError && studentsError && (
            <div className="col-12 alert-danger">
              Server Problems
            </div>
          )}
          <div className="col-12">
            <h3>Edit a Lesson</h3>
            <hr />
            <WithLoading
              isLoading={
                lessonsIsLoading
                || studentsIsLoading
                || groupIsLoading
                || !lessons
              }
              className={classNames(styles['loader-centered'])}
            >
              <Formik
                data-testid='formik'
                initialValues={{
                  themeName: lessonOnEdit.themeName,
                  groupName: group.name,
                  lessonDate: commonHelpers.transformDateTime({ dateTime: lessonOnEdit.lessonDate }).formInitialValue,
                  formData: lessonOnEdit.lessonVisits,
                }}
                onSubmit={onSubmit}
                validationSchema={editLessonValidation}
              >
                {({ errors, isSubmitting }) => (
                  <Form id="form" className={classNames(styles.size)} data-testid='editForm'>
                    <div className="d-flex flex-sm-column flex-lg-row">
                      <div className="col-lg-6">
                        <div className="mt-3 form-group row">
                          <label
                            htmlFor="inputLessonTheme"
                            className="col-sm-4 col-form-label"
                          >Lesson Theme:
                          </label>
                          <div className="col-sm-8">
                            <Field
                              data-testid='themeName'
                              type="text"
                              className={classNames('form-control', { 'border-danger': errors.themeName })}
                              name="themeName"
                              id="inputLessonTheme"
                            />
                            { errors.themeName ? <div className={styles.error}>{errors.themeName}</div> : null }
                          </div>
                        </div>
                        <div className="form-group row">
                          <label htmlFor="inputGroupName" className="col-md-4 col-form-label">Group
                            Name:
                          </label>
                          <div className="col-sm-8 input-group">
                            <Field
                              data-testid='groupName'
                              name="groupName"
                              id="inputGroupName"
                              type="text"
                              className="form-control group-input"
                              value={group.name}
                              disabled
                            />
                          </div>
                        </div>
                        <div className="form-group row">
                          <label
                            className="col-md-4 col-form-label"
                            htmlFor="choose-date/time"
                          >Lesson Date/Time:
                          </label>
                          <div className="col-md-8">
                            <Field
                              data-testid='lessonDate'
                              className="form-control"
                              type="datetime-local"
                              name="lessonDate"
                              id="choose-date/time"
                              max={commonHelpers.transformDateTime({}).formInitialValue }
                              required
                            />
                          </div>
                        </div>
                      </div>
                      <div className="col-lg-6 mt-2" >
                        <FieldArray name="formData">
                          {() => (
                            <div className={classNames(styles.list, 'col-lg-12')}>
                              <table className="table table-bordered table-hover">
                                <thead>
                                  <tr>
                                    <th scope="col" aria-label="first_col" />
                                    <th scope="col">Full Student`s Name</th>
                                    <th scope="col" className="text-center">Mark</th>
                                    <th scope="col" className="text-center">Presence
                                    </th>
                                  </tr>
                                </thead>
                                <tbody data-testid='formData'>
                                  {lessonOnEdit.lessonVisits && lessonOnEdit.lessonVisits.length > 0 && (
                                      lessonOnEdit.lessonVisits.map((lessonVisit, index) => (
                                      <tr key={lessonVisit.studentId}>
                                        <th scope="row">{index + 1}</th>
                                        <td>
                                          <p
                                            data-testid={lessonVisit.studentId}
                                            className={classNames(styles.link)}
                                            onClick={() => openStudentDetails(lessonVisit.studentId)}
                                          >
                                            {lessonVisit.studentName}
                                          </p>
                                        </td>
                                        <td>
                                          <Field
                                            key = {`mark-${index}`}
                                            data-testid={`formData[${index}].studentMark`}
                                            name={`formData[${index}].studentMark`}
                                            type="number"
                                            className={classNames(
                                              'form-control',
                                              styles.mode,
                                            )}
                                            max="12"
                                            min="1"
                                            data-id={index}
                                            disabled={!lessonOnEdit.lessonVisits[index].presence}
                                            onBlur={handleMarkChange}
                                          />
                                        </td>
                                        <td>
                                          <Field
                                            key = {`presence-${index}`}
                                            data-testid={`formData[${index}].presence`}
                                            name={`formData[${index}].presence`}
                                            className={styles.mode}
                                            type="checkbox"
                                            onClick={handlePresenceChange}
                                            data-id={index}
                                            checked = {lessonOnEdit.lessonVisits[index].presence}
                                          />
                                        </td>
                                      </tr>
                                    ))
                                  )}
                                </tbody>
                              </table>
                            </div>
                          )}
                        </FieldArray>
                      </div>
                    </div>
                    <div className={classNames(styles.placement, 'col-12 ')}>
                      <button
                        data-testid='cancelBtn'
                        form="form"
                        type="button"
                        className="btn btn-secondary btn-lg"
                        onClick={handleCancel}
                      >Cancel
                      </button>
                      <button
                          data-testid='submitBtn'
                          form="form"
                          type="submit"
                          className="btn btn-success btn-lg"
                          disabled={isSubmitting}
                      >Save
                      </button>
                    </div>
                  </Form>
                )}
              </Formik>
            </WithLoading>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #16
Source File: ConfigurationForm.js    From kuwala with Apache License 2.0 4 votes vote down vote up
ConfigurationForm = ({ elements, selectedElement, setFieldValue, values }) => {
    const renderNullOption = (optionText) => {
        return <option className={Classes.DropdownItem} value={null}>
            {optionText}
        </option>
    }

    const renderEmptyField = (placeHolder) => {
        return <Field
            type={'text'}
            className={Classes.DisabledTextField}
            placeholder={placeHolder}
            disabled={true}
            name={'empty'}
        />
    }

    const renderFormByType = ({ index, parameter, setFieldValue, values}) => {
        let formBody;
        let options;
        let type = parameter.type;
        if (parameter.options) type = 'options';
        if (parameter.id === 'column') type = parameter.id;
        if (parameter.id === 'columns') type = parameter.id;
        if (parameter.id === 'group_by_columns') type = parameter.id;
        if (parameter.id === 'left_block') type = parameter.id;
        if (parameter.id === 'right_block') type = parameter.id;
        if (parameter.id === 'column_left') type = parameter.id;
        if (parameter.id === 'column_right') type = parameter.id;
        if (parameter.id === 'dividend_column') type = parameter.id;
        if (parameter.id === 'divisor_column') type = parameter.id;
        if (parameter.id === 'aggregated_columns') type = parameter.id;

        const getColumnOptions = (id) => {
            const precedingBlock = getElementById(elements, id);

            if (!precedingBlock) {
                return null;
            }

            let columnOptions = [];

            if (precedingBlock.type === DATA_BLOCK) {
                columnOptions = precedingBlock.data.dataBlock.columns
            } else if (precedingBlock.type === TRANSFORMATION_BLOCK) {
                columnOptions = precedingBlock.data.transformationBlock.columns
            }

            return columnOptions
        }

        switch (type) {
            case 'column':
            case 'dividend_column':
            case 'divisor_column':
                options = getColumnOptions(selectedElement.data.transformationBlock.connectedSourceNodeIds[0])
                formBody = (
                    <Field
                        as={'select'}
                        component={'select'}
                        key={parameter.id}
                        name={`parameters[${index}].value`}
                        className={Classes.FieldContainer}
                    >
                        {renderNullOption('Select a column')}

                        {options.map(el =>
                            <option
                                className={Classes.DropdownItem}
                                value={el}>{el}
                            </option>
                        )}
                    </Field>
                )
                break;
            case 'columns':
            case 'group_by_columns':
                options = getColumnOptions(selectedElement.data.transformationBlock.connectedSourceNodeIds[0]).map(el => ({
                    value: el,
                    label: el,
                }));

                const customStyles = {
                    control: styles => ({
                        ...styles,
                        backgroundColor: `#FFF`,
                        width: '18rem',
                        borderRadius: '0.5rem',
                        borderColor: KUWALA_PURPLE,
                        boxShadow: 'none',
                        "&:hover": {
                            borderColor: KUWALA_PURPLE,
                        },
                    }),
                    option: (styles) => {
                        return {
                            ...styles,
                            backgroundColor: `#FFF`,
                            "&:hover": {
                                backgroundColor: KUWALA_LIGHT_PURPLE,
                            },
                            color: KUWALA_PURPLE,
                        };
                    },
                };

                formBody = (
                    <Select
                        key={parameter.id}
                        options={options}
                        value={options.filter(el => values.parameters[index].value.includes(el.value))}
                        name={`parameters[${index}].value`}
                        onChange={(e) => {
                            setFieldValue(`parameters[${index}].value`, e.map(el => el.value))
                        }}
                        isMulti
                        styles={customStyles}
                    />
                )
                break;
            case 'left_block':
            case 'right_block':
                const listOfBlocks = selectedElement.data.transformationBlock.connectedSourceNodeIds.map(el => getElementById(elements, el));

                formBody = <Field
                    as={'select'}
                    component={'select'}
                    key={parameter.id}
                    name={`parameters[${index}].value`}
                    className={Classes.FieldContainer}
                >
                    {renderNullOption('Select a block')}
                    {listOfBlocks.map(el => {
                        const blockId = el.type === DATA_BLOCK ? el.data.dataBlock.dataBlockEntityId : el.data.transformationBlock.transformationBlockEntityId
                        const blockName = el.type === DATA_BLOCK ? el.data.dataBlock.name : el.data.transformationBlock.name

                        return (
                            <option
                                className={Classes.DropdownItem}
                                value={blockId}
                            >
                                {blockName}
                            </option>
                        )
                    })}
                </Field>

                break;
            case 'column_left':
            case 'column_right':
                let entityId;

                if (type === "column_left") {
                    entityId = values.parameters.find((el) => el.id === 'left_block')
                } else {
                    entityId = values.parameters.find((el) => el.id === 'right_block')
                }

                if (!entityId.value || !entityId.value.length) {
                    formBody = renderEmptyField('Please select the block first');
                    break;
                }

                const block = getBlockByEntityId(elements, entityId.value);

                if (!block) {
                    formBody = renderEmptyField('Failed to get blocks');
                    break;
                }

                formBody = <Field
                    as={'select'}
                    component={'select'}
                    key={parameter.id}
                    name={`parameters[${index}].value`}
                    className={Classes.FieldContainer}
                >
                    {renderNullOption('Select a column')}
                    {getColumnOptions(block.id).map(el =>
                        <option
                            className={Classes.DropdownItem}
                            value={el}
                        >
                            {el}
                        </option>
                    )}
                </Field>

                break;
            case 'text':
                formBody = (
                    <Field
                        name={`parameters[${index}].value`}
                        type={'text'}
                        className={Classes.TextField}
                        key={parameter.id}
                        placeholder={`Enter ${parameter.name}`}
                    />
                )
                break;
            case 'list[text]':
                formBody = (
                    <FieldArray name={`parameters[${index}].value`}>
                        {({ remove, push }) => (
                            <div>
                                {values.parameters[index].value.length > 0 &&
                                    values.parameters[index].value.map((value, i) => (
                                        <div key={i}>
                                            <div>
                                                <Field
                                                    name={`parameters[${index}].value.${i}`}
                                                    placeholder={`Enter ${parameter.name}`}
                                                    type="text"
                                                    className={`${Classes.TextField} mb-2`}
                                                />

                                                <CloseButton onClick={() => remove(i)} />
                                            </div>
                                        </div>
                                    ))
                                }

                                <Button onClick={() => push('')} text={'Add value'} color="kuwalaPurple" />
                            </div>
                        )}
                    </FieldArray>
                )
                break;
            case 'aggregated_columns':
                const columnOptions = getColumnOptions(selectedElement.data.transformationBlock.connectedSourceNodeIds[0])
                formBody = (
                    <FieldArray name={`parameters[${index}].value`}>
                        {({ remove, push }) => (
                            <div>
                                {values.parameters[index].value.length > 0 &&
                                    values.parameters[index].value.map((value, i) => (
                                        <div key={i}>
                                            <div className={Classes.ColumnAggregationContainer}>
                                                <div>
                                                    <Field
                                                        as={'select'}
                                                        component={'select'}
                                                        key={parameter.id}
                                                        name={`parameters[${index}].value.${i}.column`}
                                                        className={`${Classes.FieldContainer} mb-2`}
                                                    >
                                                        {renderNullOption('Select a column')}

                                                        {columnOptions.map(el =>
                                                            <option
                                                                className={Classes.DropdownItem}
                                                                value={el}
                                                            >
                                                                {el}
                                                            </option>
                                                        )}
                                                    </Field>

                                                    <Field
                                                        as={'select'}
                                                        component={'select'}
                                                        key={parameter.id}
                                                        name={`parameters[${index}].value.${i}.aggregation`}
                                                        className={Classes.FieldContainer}
                                                    >
                                                        {renderNullOption('Select an aggregation')}

                                                        {parameter.options.map(el =>
                                                            <option
                                                                className={Classes.DropdownItem}
                                                                value={el.id}
                                                            >
                                                                {el.name}
                                                            </option>
                                                        )}
                                                    </Field>
                                                </div>

                                                <CloseButton onClick={() => remove(i)} />
                                            </div>
                                        </div>
                                    ))
                                }

                                <Button onClick={() => push('')} text={'Add column'} color="kuwalaPurple" />
                            </div>
                        )}
                    </FieldArray>
                )
                break;
            case 'options':
                formBody = (
                    <div className={'dropdown relative'}>
                        <Field
                            as={'select'}
                            component={'select'}
                            key={parameter.id}
                            name={`parameters[${index}].value`}
                            className={Classes.FieldContainer}
                        >
                            {renderNullOption('Select an Option')}
                            {parameter.options.map(el =>
                                <option
                                    className={Classes.DropdownItem}
                                    value={el.id}
                                >
                                    {el.name}
                                </option>
                            )}
                        </Field>
                    </div>
                )
                break;
            case 'date':
                formBody = (
                    <div>
                        <DatePicker
                            dateFormat="yyyy-MM-dd"
                            name={`parameters[${index}].value`}
                            values={values.parameters[index].value}
                            selected={values.parameters[index].value}
                            onChange={(val) => {
                                setFieldValue(`parameters[${index}].value`, val);
                            }}
                            className={Classes.FieldContainer}
                            style={{
                                focusVisible: 'none',
                            }}
                        />
                    </div>
                )
                break;
            default:
                formBody = (
                    <Field
                        name={`parameters[${index}].value`}
                        type={'text'}
                        className={Classes.TextField}
                        placeholder={`Enter ${parameter.name}`}
                    />
                )
                break;
        }

        return (
            <div className={'flex flex-row h-full w-full space-x-4'} key={index}>
                <span className={'w-24 mr-6'}>
                    {parameter.name}
                </span>

                {formBody}
            </div>
        );
    };

    const renderFormBodyContainer = (values, setFieldValue) => {
        return (
            <div className={'flex flex-col bg-white rounded-lg h-full space-y-8'}>
                {values.parameters.map((parameter,index) => renderFormByType({parameter, index, values, setFieldValue}))}
            </div>
        );
    }

    return (
        <Form>
            <FieldArray
                name={'parameters'}
                render={() => renderFormBodyContainer(values, setFieldValue)}
            />
        </Form>
    )
}
Example #17
Source File: BlackoutDatesField.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
BlackoutDatesField = ({ intl }) => {
  const {
    values: appConfig,
    setFieldValue,
    errors,
    validateForm,
  } = useFormikContext();
  const { blackoutDates } = appConfig;

  const handleOnClose = useCallback((index) => {
    const updatedBlackoutDates = [...blackoutDates];
    updatedBlackoutDates[index] = {
      ...updatedBlackoutDates[index],
      status: checkStatus(denormalizeBlackoutDate(updatedBlackoutDates[index])),
    };
    setFieldValue('blackoutDates', updatedBlackoutDates);
  }, [blackoutDates]);

  const newBlackoutDateItem = {
    id: uuid(),
    startDate: '',
    startTime: '',
    endDate: '',
    endTime: '',
    status: STATUS.UPCOMING,
  };

  const onAddNewItem = async (push) => {
    await push(newBlackoutDateItem);
    validateForm();
  };
  return (
    <>
      <h5 className="text-gray-500 mt-4 mb-2">
        {intl.formatMessage(messages.blackoutDatesLabel)}
      </h5>
      <label className="text-primary-500 mb-1 h4">
        {intl.formatMessage(messages.blackoutDates)}
      </label>
      <div className="small mb-4 text-muted">
        {intl.formatMessage(messages.blackoutDatesHelp)}
      </div>
      <div>
        <FieldArray
          name="blackoutDates"
          render={({ push, remove }) => (
            <div>
              {blackoutDates.map((blackoutDate, index) => (
                <BlackoutDatesItem
                  fieldNameCommonBase={`blackoutDates.${index}`}
                  blackoutDate={blackoutDate}
                  key={`date-${blackoutDate.id}`}
                  id={blackoutDate.id}
                  onDelete={() => remove(index)}
                  onClose={() => handleOnClose(index)}
                  hasError={Boolean(errors?.blackoutDates?.[index])}
                />
              ))}
              <div className="mb-4">
                <Button
                  onClick={() => onAddNewItem(push)}
                  variant="link"
                  iconBefore={Add}
                  className="text-primary-500 p-0"
                >
                  {intl.formatMessage(messages.addBlackoutDatesButton)}
                </Button>
              </div>
            </div>
          )}
        />
      </div>
    </>
  );
}
Example #18
Source File: DivisionByGroupFields.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
DivisionByGroupFields = ({ intl }) => {
  const { validDiscussionTopics } = useContext(OpenedXConfigFormContext);
  const {
    handleChange,
    handleBlur,
    values: appConfig,
    setFieldValue,
  } = useFormikContext();
  const {
    divideDiscussionIds,
    discussionTopics,
    divideByCohorts,
    divideCourseTopicsByCohorts,
  } = appConfig;

  useEffect(() => {
    if (divideByCohorts) {
      if (!divideCourseTopicsByCohorts && _.size(discussionTopics) !== _.size(divideDiscussionIds)) {
        setFieldValue('divideDiscussionIds', discussionTopics.map(topic => topic.id));
      }
    } else {
      setFieldValue('divideDiscussionIds', []);
      setFieldValue('divideCourseTopicsByCohorts', false);
    }
  }, [
    divideByCohorts,
    divideCourseTopicsByCohorts,
  ]);

  const handleCheckBoxToggle = (event, push, remove) => {
    const { checked, value } = event.target;
    if (checked) {
      push(value);
    } else {
      remove(divideDiscussionIds.indexOf(value));
    }
  };

  const handleDivideCourseTopicsByCohortsToggle = (event) => {
    const { checked } = event.target;
    if (!checked) {
      setFieldValue('divideDiscussionIds', []);
    }
    handleChange(event);
  };

  return (
    <>
      <h5 className="text-gray-500 mb-2 mt-4">
        {intl.formatMessage(messages.divisionByGroup)}
      </h5>
      <FormSwitchGroup
        onChange={handleChange}
        className="mt-2"
        onBlur={handleBlur}
        id="divideByCohorts"
        checked={divideByCohorts}
        label={intl.formatMessage(messages.divideByCohortsLabel)}
        helpText={intl.formatMessage(messages.divideByCohortsHelp)}
      />
      <TransitionReplace>
        {divideByCohorts ? (
          <React.Fragment key="open">
            <AppConfigFormDivider />
            <FormSwitchGroup
              onChange={(event) => handleDivideCourseTopicsByCohortsToggle(event)}
              onBlur={handleBlur}
              className="ml-4 mt-3"
              id="divideCourseTopicsByCohorts"
              checked={divideCourseTopicsByCohorts}
              label={intl.formatMessage(messages.divideCourseTopicsByCohortsLabel)}
              helpText={intl.formatMessage(messages.divideCourseTopicsByCohortsHelp)}
            />
            <TransitionReplace>
              {divideCourseTopicsByCohorts ? (
                <React.Fragment key="open">
                  <FieldArray
                    name="divideDiscussionIds"
                    render={({ push, remove }) => (
                      <Form.Group className="ml-4">
                        <Form.CheckboxSet
                          name="dividedTopics"
                          onChange={(event) => handleCheckBoxToggle(event, push, remove)}
                          onBlur={handleBlur}
                          defaultValue={divideDiscussionIds}
                        >
                          {validDiscussionTopics.map((topic) => (
                            topic.name ? (
                              <Form.Checkbox
                                key={`checkbox-${topic.id}`}
                                id={`checkbox-${topic.id}`}
                                value={topic.id}
                              >
                                {topic.name}
                              </Form.Checkbox>
                            ) : null
                          ))}
                        </Form.CheckboxSet>
                      </Form.Group>
                    )}
                  />
                </React.Fragment>
              ) : (
                <React.Fragment key="closed" />
              )}
            </TransitionReplace>
          </React.Fragment>
        ) : (
          <React.Fragment key="closed" />
        )}
      </TransitionReplace>
    </>
  );
}
Example #19
Source File: DiscussionTopics.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
DiscussionTopics = ({ intl }) => {
  const {
    values: appConfig,
    validateForm,
    setFieldValue,
  } = useFormikContext();
  const { discussionTopics, divideDiscussionIds } = appConfig;
  const {
    discussionTopicErrors,
    validDiscussionTopics,
    setValidDiscussionTopics,
  } = useContext(OpenedXConfigFormContext);

  const handleTopicDelete = async (topicIndex, topicId, remove) => {
    await remove(topicIndex);
    validateForm();
    setValidDiscussionTopics(filterItemFromObject(validDiscussionTopics, 'id', topicId));
  };

  const handleOnFocus = useCallback((id, hasError) => {
    if (hasError) {
      setValidDiscussionTopics(currentValidTopics => filterItemFromObject(currentValidTopics, 'id', id));
      setFieldValue('divideDiscussionIds', filterItemFromObject(divideDiscussionIds, 'id', id));
    } else {
      setValidDiscussionTopics(currentValidTopics => {
        const allDiscussionTopics = [...currentValidTopics, ...discussionTopics.filter(topic => topic.id === id)];
        const allValidTopics = _.remove(allDiscussionTopics, topic => topic.name !== '');
        return _.uniqBy(allValidTopics, 'id');
      });
      setFieldValue('divideDiscussionIds', _.uniq([...divideDiscussionIds, id]));
    }
  }, [divideDiscussionIds, discussionTopics]);

  const addNewTopic = (push) => {
    const payload = { name: '', id: uuid() };
    push(payload);
  };

  return (
    <>
      <h5 className="text-gray-500 mt-4 mb-2">
        {intl.formatMessage(messages.discussionTopics)}
      </h5>
      <label className="text-primary-500 mb-1 h4">
        {intl.formatMessage(messages.discussionTopicsLabel)}
      </label>
      <div className="small mb-4 text-muted">
        {intl.formatMessage(messages.discussionTopicsHelp)}
      </div>
      <div>
        <FieldArray
          name="discussionTopics"
          render={({ push, remove }) => (
            <div>
              {discussionTopics.map((topic, index) => (
                <TopicItem
                  {...topic}
                  key={`topic-${topic.id}`}
                  index={index}
                  onDelete={() => handleTopicDelete(index, topic.id, remove)}
                  onFocus={(hasError) => handleOnFocus(topic.id, hasError)}
                  hasError={discussionTopicErrors[index]}
                />
              ))}
              <div className="mb-4">
                <Button
                  onClick={() => addNewTopic(push)}
                  variant="link"
                  iconBefore={Add}
                  className="text-primary-500 p-0"
                >
                  {intl.formatMessage(messages.addTopicButton)}
                </Button>
              </div>
            </div>
          )}
        />
      </div>
    </>
  );
}
Example #20
Source File: Settings.jsx    From frontend-app-course-authoring with GNU Affero General Public License v3.0 4 votes vote down vote up
function TeamSettings({
  intl,
  onClose,
}) {
  const [teamsConfiguration, saveSettings] = useAppSetting('teamsConfiguration');
  const blankNewGroup = {
    name: '',
    description: '',
    type: GroupTypes.OPEN,
    maxTeamSize: null,
    id: null,
    key: uuid(),
  };

  const handleSettingsSave = async (values) => {
    // For newly-added teams, fill in an id.
    const groups = values.groups?.map(group => ({
      id: group.id || uuid(),
      name: group.name,
      type: group.type,
      description: group.description,
      max_team_size: group.maxTeamSize,
    }));
    return saveSettings({
      team_sets: groups,
      max_team_size: values.maxTeamSize,
      enabled: values.enabled,
    });
  };
  const enableAppError = {
    title: intl.formatMessage(messages.noGroupsErrorTitle),
    message: intl.formatMessage(messages.noGroupsErrorMessage),
  };

  return (
    <AppSettingsModal
      appId="teams"
      title={intl.formatMessage(messages.heading)}
      enableAppHelp={intl.formatMessage(messages.enableTeamsHelp)}
      enableAppLabel={intl.formatMessage(messages.enableTeamsLabel)}
      learnMoreText={intl.formatMessage(messages.enableTeamsLink)}
      onClose={onClose}
      bodyClassName="bg-light-200"
      // Topic is supported for backwards compatibility, the new field is team_sets:
      // ref: https://github.com/edx/edx-platform/blob/15461d3b6e6c0a724a7b8ed09241d970f201e5e7/openedx/core/lib/teams_config.py#L104-L108
      initialValues={{
        maxTeamSize: teamsConfiguration?.maxTeamSize,
        groups: teamsConfiguration?.teamSets || teamsConfiguration?.topics,
      }}
      validationSchema={{
        enabled: Yup.boolean()
          .test(
            'has-groups',
            enableAppError,
            (value, context) => (!value || context.parent.groups.length > 0),
          ),
        maxTeamSize: Yup.number()
          .required(intl.formatMessage(messages.maxTeamSizeEmpty))
          .min(TeamSizes.MIN, intl.formatMessage(messages.maxTeamSizeInvalid))
          .max(
            TeamSizes.MAX,
            intl.formatMessage(messages.maxTeamSizeTooHigh, {
              max: TeamSizes.MAX,
            }),
          ),
        groups: Yup.array().of(
          Yup.object({
            id: Yup.string().nullable(),
            name: Yup.string()
              .required(intl.formatMessage(messages.groupFormNameEmpty))
              .trim(),
            type: Yup.string().oneOf(Object.values(GroupTypes)),
            description: Yup.string()
              .required(intl.formatMessage(messages.groupFormDescriptionError))
              .trim(),
            maxTeamSize: Yup.number()
              .nullable()
              .min(TeamSizes.MIN, intl.formatMessage(messages.maxTeamSizeInvalid))
              .max(
                TeamSizes.MAX,
                intl.formatMessage(messages.maxTeamSizeTooHigh, {
                  max: TeamSizes.MAX,
                }),
              )
              .default(null),
          }),
        )
          .when('enabled', {
            is: true,
            then: Yup.array().min(1),
          })
          .default([])
          .uniqueProperty('name', intl.formatMessage(messages.groupFormNameExists)),
      }}
      onSettingsSave={handleSettingsSave}
      configureBeforeEnable
    >
      {
        ({
          handleChange, handleBlur, values, errors,
        }) => (
          <>
            <h4 className="my-3 pb-2">{intl.formatMessage(messages.teamSize)}</h4>
            <FormikControl
              name="maxTeamSize"
              value={values.maxTeamSize}
              floatingLabel={intl.formatMessage(messages.maxTeamSize)}
              help={intl.formatMessage(messages.maxTeamSizeHelp)}
              className="pb-1"
              type="number"
            />
            <div className="bg-light-200 d-flex flex-column mx-n4 px-4 py-4 border border-top mb-n3.5">
              <h4>{intl.formatMessage(messages.groups)}</h4>
              <Form.Text className="mb-3">{intl.formatMessage(messages.groupsHelp)}</Form.Text>
              <FieldArray name="groups">
                {({ push, remove }) => (
                  <>
                    {values.groups?.map((group, index) => (
                      <GroupEditor
                        key={group.id || group.key}
                        group={group}
                        errors={errors.groups?.[index]}
                        fieldNameCommonBase={`groups.${index}`}
                        onDelete={() => remove(index)}
                        onChange={handleChange}
                        onBlur={handleBlur}
                      />
                    ))}
                    <Button
                      variant="plain"
                      className="p-0 align-self-start mt-3"
                      iconBefore={Add}
                      onClick={() => push(blankNewGroup)}
                    >
                      {intl.formatMessage(messages.addGroup)}
                    </Button>
                  </>
                )}
              </FieldArray>
            </div>
          </>
        )
      }
    </AppSettingsModal>
  );
}