import React, { useCallback } from "react"; import { Edition } from "@/data/edition"; import { Nullable } from "@/helpers"; import { useTranslation } from "react-i18next"; import { FieldProps, Field, FieldArrayRenderProps, FieldArray, getIn } from "formik"; import { identityTransformer, Transformer } from "@/serialization"; import { Button, Card, CardContent, CardHeader, Checkbox, Grid, IconButton, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, Paper, TextField, Tooltip, Typography } from "@material-ui/core"; import { useSpacing } from "@/styles"; import { Moment } from "moment-timezone"; import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers"; import { TextField as TextFieldFormik } from "formik-material-ui"; import { Course, Identifiable, InternshipProgramEntry, InternshipType } from "@/data"; import { Autocomplete } from "@material-ui/lab"; import { useAsync } from "@/hooks"; import api from "@/management/api"; import { Async } from "@/components/async"; import { AccountCheck, ArrowDown, ArrowUp, ShieldCheck, TrashCan } from "mdi-material-ui"; import { Actions } from "@/components"; import { Add } from "@material-ui/icons"; export type EditionFormValues = Nullable<Edition>; export const initialEditionFormValues: EditionFormValues = { course: null, endDate: null, minimumInternshipHours: 80, proposalDeadline: null, reportingEnd: null, reportingStart: null, startDate: null, types: [], program: [], } export const editionFormValuesTransformer: Transformer<Edition, EditionFormValues> = identityTransformer; function toggleValueInArray<T extends Identifiable>(array: T[], value: T, comparator: (a: T, b: T) => boolean = (a, b) => a == b): T[] { return array.findIndex(other => comparator(other, value)) === -1 ? [ ...array, value ] : array.filter(other => !comparator(other, value)); } export const ProgramField = ({ remove, swap, push, form, name, ...props }: FieldArrayRenderProps) => { const value = getIn(form.values, name) as InternshipProgramEntry[]; const { t } = useTranslation("management"); return <> { value.map((entry, index) => <Card> <CardHeader subheader={ t('edition.program.entry', { index: index + 1 }) } action={ <> { index < value.length - 1 && <IconButton onClick={ () => swap(index, index + 1) }><ArrowDown /></IconButton> } { index > 0 && <IconButton onClick={ () => swap(index, index - 1) }><ArrowUp /></IconButton> } <IconButton onClick={ () => remove(index) }><TrashCan /></IconButton> </> } /> <CardContent> <Field component={ TextFieldFormik } label={ t('edition.program.field.description') } name={`${name}[${index}].description`} fullWidth /> </CardContent> </Card>) } <Actions> <Button variant="contained" color="primary" startIcon={ <Add /> } onClick={ () => push({ description: "" }) }>{ t("actions.add") }</Button> </Actions> </> } export const TypesField = ({ field, form, meta, ...props }: FieldProps<InternshipType[]>) => { const { name, value = [] } = field; const types = useAsync(useCallback(() => api.type.all(), [])); const { t } = useTranslation("management"); const toggle = (type: InternshipType) => () => form.setFieldValue(name, toggleValueInArray(value, type, (a, b) => a.id == b.id)); const isChecked = (type: InternshipType) => value.findIndex(v => v.id == type.id) !== -1; return <Async async={ types }> { types => <List>{ types.map(type => <ListItem dense button onClick={ toggle(type) }> <ListItemIcon> <Checkbox edge="start" onChange={ toggle(type) } checked={ isChecked(type) }/> </ListItemIcon> <ListItemText> <div>{ type.label.pl }</div> <Typography variant="caption">{ type.description?.pl }</Typography> </ListItemText> <ListItemSecondaryAction> <div style={{ display: "flex", flexDirection: "column" }}> { type.requiresDeanApproval && <Tooltip title={ t("type.flag.dean-approval") as string }><AccountCheck/></Tooltip> } { type.requiresInsurance && <Tooltip title={ t("type.flag.insurance") as string }><ShieldCheck/></Tooltip> } </div> </ListItemSecondaryAction> </ListItem>) }</List> } </Async> } export const CoursePickerField = ({ field, form, meta, ...props }: FieldProps<Course>) => { const courses = useAsync(useCallback(() => api.course.all(), [])); const { t } = useTranslation("management"); return <Autocomplete options={ courses.isLoading ? [] : courses.value as Course[] } renderInput={ props => <TextField { ...props } label={ t("edition.field.course") } fullWidth/> } getOptionLabel={ course => course.name } value={ field.value } onChange={ field.onChange } onBlur={ field.onBlur } /> } export const DatePickerField = ({ field, form, meta, ...props }: FieldProps<Moment>) => { const { value, onChange, onBlur } = field; return <DatePicker value={ value } onChange={ onChange } onBlur={ onBlur } { ...props } format="DD.MM.yyyy" disableToolbar fullWidth variant="inline" /> } export const EditionForm = () => { const { t } = useTranslation("management"); const spacing = useSpacing(2); return <div className={ spacing.vertical }> <Typography variant="h5">{ t("edition.fields.basic") }</Typography> <Grid container> <Grid item xs={ 12 } md={ 6 }> <Field name="startDate" component={ DatePickerField } label={ t("edition.field.start") } /> </Grid> <Grid item xs={ 12 } md={ 6 }> <Field name="endDate" component={ DatePickerField } label={ t("edition.field.end") } /> </Grid> <Grid item xs={ 12 } md={ 6 }> <Field name="course" component={ CoursePickerField } label={ t("edition.field.course") } /> </Grid> <Grid item xs={ 12 } md={ 6 }> </Grid> <Grid item xs={ 12 } md={ 6 }> <Field name="minimumInternshipHours" component={ TextFieldFormik } label={ t("edition.field.minimumInternshipHours") } /> </Grid> </Grid> <Typography variant="h5">{ t("edition.fields.deadlines") }</Typography> <Grid container> <Grid item xs={ 12 } md={ 6 }> <Field name="proposalDeadline" component={ DatePickerField } label={ t("edition.field.proposalDeadline") } /> </Grid> <Grid item xs={ 12 } md={ 6 }> </Grid> <Grid item xs={ 12 } md={ 6 }> <Field name="reportingStart" component={ DatePickerField } label={ t("edition.field.reportingStart") } /> </Grid> <Grid item xs={ 12 } md={ 6 }> <Field name="reportingEnd" component={ DatePickerField } label={ t("edition.field.reportingEnd") } /> </Grid> </Grid> <Typography variant="h5">{ t("edition.fields.program") }</Typography> <FieldArray name="program" component={ ProgramField as any } /> <Typography variant="h5">{ t("edition.fields.types") }</Typography> <Paper elevation={ 2 }> <Field name="types" component={ TypesField } /> </Paper> </div> }