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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}