feature_validation #10
@ -12,10 +12,10 @@ export interface Company extends Identifiable {
 | 
				
			|||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    url?: string;
 | 
					    url?: string;
 | 
				
			||||||
    nip: string;
 | 
					    nip: string;
 | 
				
			||||||
    offices: BranchOffice[];
 | 
					    offices: Office[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BranchOffice extends Identifiable {
 | 
					export interface Office extends Identifiable {
 | 
				
			||||||
    address: Address;
 | 
					    address: Address;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,7 +34,7 @@ export const emptyAddress: Address = {
 | 
				
			|||||||
    building: ""
 | 
					    building: ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const emptyBranchOffice: BranchOffice = {
 | 
					export const emptyBranchOffice: Office = {
 | 
				
			||||||
    address: emptyAddress,
 | 
					    address: emptyAddress,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@ export type Edition = {
 | 
				
			|||||||
    startDate: Moment;
 | 
					    startDate: Moment;
 | 
				
			||||||
    endDate: Moment;
 | 
					    endDate: Moment;
 | 
				
			||||||
    proposalDeadline: Moment;
 | 
					    proposalDeadline: Moment;
 | 
				
			||||||
 | 
					    minimumInternshipHours: number;
 | 
				
			||||||
 | 
					    maximumInternshipHours?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Deadlines = {
 | 
					export type Deadlines = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Moment } from "moment";
 | 
					import { Moment } from "moment";
 | 
				
			||||||
import { Identifiable } from "./common";
 | 
					import { Identifiable } from "./common";
 | 
				
			||||||
import { Student } from "@/data/student";
 | 
					import { Student } from "@/data/student";
 | 
				
			||||||
import { BranchOffice, Company } from "@/data/company";
 | 
					import { Company, Office } from "@/data/company";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum InternshipType {
 | 
					export enum InternshipType {
 | 
				
			||||||
    FreeInternship = "FreeInternship",
 | 
					    FreeInternship = "FreeInternship",
 | 
				
			||||||
@ -64,7 +64,7 @@ export interface Internship extends Identifiable {
 | 
				
			|||||||
    hours: number;
 | 
					    hours: number;
 | 
				
			||||||
    mentor: Mentor;
 | 
					    mentor: Mentor;
 | 
				
			||||||
    company: Company;
 | 
					    company: Company;
 | 
				
			||||||
    office: BranchOffice;
 | 
					    office: Office;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Plan extends Identifiable {
 | 
					export interface Plan extends Identifiable {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +1,13 @@
 | 
				
			|||||||
import React, { HTMLProps, useMemo } from "react";
 | 
					import React, { HTMLProps, useMemo } from "react";
 | 
				
			||||||
import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress, Mentor } from "@/data";
 | 
					import { Company, formatAddress, Office } from "@/data";
 | 
				
			||||||
import { sampleCompanies } from "@/provider/dummy";
 | 
					import { sampleCompanies } from "@/provider/dummy";
 | 
				
			||||||
import { Autocomplete } from "@material-ui/lab";
 | 
					import { Autocomplete } from "@material-ui/lab";
 | 
				
			||||||
import { Grid, TextField, Typography } from "@material-ui/core";
 | 
					import { Grid, TextField, Typography } from "@material-ui/core";
 | 
				
			||||||
import { BoundProperty, formFieldProps } from "./helpers";
 | 
					import { InternshipFormValues } from "@/forms/internship";
 | 
				
			||||||
import { InternshipFormSectionProps } from "@/forms/internship";
 | 
					 | 
				
			||||||
import { emptyMentor } from "@/provider/dummy/internship";
 | 
					 | 
				
			||||||
import { useProxyState } from "@/hooks";
 | 
					 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import { Field } from "formik";
 | 
					import { Field, useFormikContext } from "formik";
 | 
				
			||||||
import { TextField as TextFieldFormik } from "formik-material-ui"
 | 
					import { TextField as TextFieldFormik } from "formik-material-ui"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type CompanyFormProps = {} & InternshipFormSectionProps;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type BranchOfficeProps = {
 | 
					 | 
				
			||||||
    disabled?: boolean;
 | 
					 | 
				
			||||||
    offices?: BranchOffice[];
 | 
					 | 
				
			||||||
} & BoundProperty<BranchOffice, "onChange", "value">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
 | 
					export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
 | 
				
			||||||
    <div className="company-item" { ...props }>
 | 
					    <div className="company-item" { ...props }>
 | 
				
			||||||
        <div>{ company.name }</div>
 | 
					        <div>{ company.name }</div>
 | 
				
			||||||
@ -25,43 +15,65 @@ export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLPr
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const OfficeItem = ({ office, ...props }: { office: BranchOffice } & HTMLProps<any>) => (
 | 
					export const OfficeItem = ({ office, ...props }: { office: Office } & HTMLProps<any>) => (
 | 
				
			||||||
    <div className="office-item" { ...props }>
 | 
					    <div className="office-item" { ...props }>
 | 
				
			||||||
        <div>{ office.address.city }</div>
 | 
					        <div>{ office.address.city }</div>
 | 
				
			||||||
        <Typography variant="caption">{ formatAddress(office.address) }</Typography>
 | 
					        <Typography variant="caption">{ formatAddress(office.address) }</Typography>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChange: setOffice, offices = [], disabled = false }) => {
 | 
					export const BranchForm: React.FC = () => {
 | 
				
			||||||
    const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
 | 
					    const { values, errors, setValues, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
 | 
				
			||||||
    const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
 | 
					 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleCityChange = (event: any, value: BranchOffice | string | null) => {
 | 
					    const disabled = useMemo(() => !values.companyName, [values.companyName]);
 | 
				
			||||||
 | 
					    const offices = useMemo(() => values.company?.offices || [], [values.company]);
 | 
				
			||||||
 | 
					    const canEdit = useMemo(() => !values.office && !disabled, [values.office, disabled]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleCityChange = (event: any, value: Office | string | null) => {
 | 
				
			||||||
        if (typeof value === "string") {
 | 
					        if (typeof value === "string") {
 | 
				
			||||||
            setOffice({
 | 
					            setValues({
 | 
				
			||||||
                ...emptyBranchOffice,
 | 
					                ...values,
 | 
				
			||||||
                address: {
 | 
					                office: null,
 | 
				
			||||||
                    ...emptyAddress,
 | 
					 | 
				
			||||||
                city: value,
 | 
					                city: value,
 | 
				
			||||||
                }
 | 
					            }, true);
 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        } else if (typeof value === "object" && value !== null) {
 | 
					        } else if (typeof value === "object" && value !== null) {
 | 
				
			||||||
            setOffice(value);
 | 
					            const office = value as Office;
 | 
				
			||||||
        } else {
 | 
					
 | 
				
			||||||
            setOffice(emptyBranchOffice);
 | 
					            setValues({
 | 
				
			||||||
 | 
					                ...values,
 | 
				
			||||||
 | 
					                office,
 | 
				
			||||||
 | 
					                city: office.address.city,
 | 
				
			||||||
 | 
					                country: office.address.country,
 | 
				
			||||||
 | 
					                street: office.address.street,
 | 
				
			||||||
 | 
					                building: office.address.building,
 | 
				
			||||||
 | 
					                postalCode: office.address.postalCode,
 | 
				
			||||||
 | 
					            }, true)
 | 
				
			||||||
 | 
					        } else { // null
 | 
				
			||||||
 | 
					            setValues({
 | 
				
			||||||
 | 
					                ...values,
 | 
				
			||||||
 | 
					                office: null,
 | 
				
			||||||
 | 
					                city: "",
 | 
				
			||||||
 | 
					                country: "",
 | 
				
			||||||
 | 
					                street: "",
 | 
				
			||||||
 | 
					                building: "",
 | 
				
			||||||
 | 
					                postalCode: "",
 | 
				
			||||||
 | 
					            }, true)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleCityInput = (event: any, value: string) => {
 | 
					    const handleCityInput = (event: any, value: string) => {
 | 
				
			||||||
        const base = office.id ? emptyBranchOffice : office;
 | 
					        setValues( {
 | 
				
			||||||
        setOffice({
 | 
					            ...values,
 | 
				
			||||||
            ...base,
 | 
					            office: null,
 | 
				
			||||||
            address: {
 | 
					            ...(values.office ? {
 | 
				
			||||||
                ...base.address,
 | 
					                country: "",
 | 
				
			||||||
 | 
					                street: "",
 | 
				
			||||||
 | 
					                building: "",
 | 
				
			||||||
 | 
					                postalCode: "",
 | 
				
			||||||
 | 
					            } : { }),
 | 
				
			||||||
            city: value,
 | 
					            city: value,
 | 
				
			||||||
            }
 | 
					        }, true);
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
@ -72,25 +84,34 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
 | 
				
			|||||||
                                  disabled={ disabled }
 | 
					                                  disabled={ disabled }
 | 
				
			||||||
                                  getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
 | 
					                                  getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
 | 
				
			||||||
                                  renderOption={ office => <OfficeItem office={ office }/> }
 | 
					                                  renderOption={ office => <OfficeItem office={ office }/> }
 | 
				
			||||||
                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.city") } fullWidth/> }
 | 
					                                  renderInput={
 | 
				
			||||||
 | 
					                                      props =>
 | 
				
			||||||
 | 
					                                          <TextField { ...props }
 | 
				
			||||||
 | 
					                                                     label={ t("forms.internship.fields.city") }
 | 
				
			||||||
 | 
					                                                     fullWidth
 | 
				
			||||||
 | 
					                                                     error={ touched.city && !!errors.city }
 | 
				
			||||||
 | 
					                                                     helperText={ touched.city && errors.city }
 | 
				
			||||||
 | 
					                                          />
 | 
				
			||||||
 | 
					                                  }
 | 
				
			||||||
                                  onChange={ handleCityChange }
 | 
					                                  onChange={ handleCityChange }
 | 
				
			||||||
                                  onInputChange={ handleCityInput }
 | 
					                                  onInputChange={ handleCityInput }
 | 
				
			||||||
                                  inputValue={ office.address.city }
 | 
					                                  onBlur={ ev => setFieldTouched("city", true) }
 | 
				
			||||||
                                  value={ office.id ? office : null }
 | 
					                                  inputValue={ values.city }
 | 
				
			||||||
 | 
					                                  value={ values.office ? values.office : null }
 | 
				
			||||||
                                  freeSolo
 | 
					                                  freeSolo
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                <Grid item md={ 2 }>
 | 
					                <Grid item md={ 2 }>
 | 
				
			||||||
                    <TextField label={ t("forms.internship.fields.postal-code") } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
 | 
					                    <Field label={ t("forms.internship.fields.postal-code") } name="postalCode" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                <Grid item md={ 3 }>
 | 
					                <Grid item md={ 3 }>
 | 
				
			||||||
                    <TextField label={ t("forms.internship.fields.country") } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
 | 
					                    <Field label={ t("forms.internship.fields.country") } name="country" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                <Grid item md={ 10 }>
 | 
					                <Grid item md={ 10 }>
 | 
				
			||||||
                    <TextField label={ t("forms.internship.fields.street") } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
 | 
					                    <Field label={ t("forms.internship.fields.street") } name="street" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                <Grid item md={ 2 }>
 | 
					                <Grid item md={ 2 }>
 | 
				
			||||||
                    <TextField label={ t("forms.internship.fields.building") } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
 | 
					                    <Field label={ t("forms.internship.fields.building") } name="building" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@ -102,42 +123,50 @@ export const MentorForm = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Grid container>
 | 
					        <Grid container>
 | 
				
			||||||
            <Grid item md={6}>
 | 
					            <Grid item md={ 6 }>
 | 
				
			||||||
                <Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik } />
 | 
					                <Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik }/>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={6}>
 | 
					            <Grid item md={ 6 }>
 | 
				
			||||||
                <Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik } />
 | 
					                <Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik }/>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={8}>
 | 
					            <Grid item md={ 8 }>
 | 
				
			||||||
                <Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik } />
 | 
					                <Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik }/>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={4}>
 | 
					            <Grid item md={ 4 }>
 | 
				
			||||||
                <Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik } />
 | 
					                <Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik }/>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
        </Grid>
 | 
					        </Grid>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ internship, onChange }) => {
 | 
					export const CompanyForm: React.FunctionComponent = () => {
 | 
				
			||||||
    const [company, setCompany] = useProxyState<Company>(internship.company || emptyCompany, company => onChange({ ...internship, company }));
 | 
					    const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
 | 
				
			||||||
    const [mentor, setMentor] = useProxyState<Mentor>(internship.mentor || emptyMentor, mentor => onChange({ ...internship, mentor }));
 | 
					 | 
				
			||||||
    const [office, setOffice] = useProxyState<BranchOffice>(internship.office || emptyBranchOffice, office => onChange({ ...internship, office }));
 | 
					 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const canEdit = useMemo(() => !company.id, [company.id]);
 | 
					    const canEdit = useMemo(() => !values.company, [values.company]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const fieldProps = formFieldProps(company, setCompany)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleCompanyChange = (event: any, value: Company | string | null) => {
 | 
					    const handleCompanyChange = (event: any, value: Company | string | null) => {
 | 
				
			||||||
 | 
					        setFieldTouched("companyName", true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (typeof value === "string") {
 | 
					        if (typeof value === "string") {
 | 
				
			||||||
            setCompany({
 | 
					            setValues({
 | 
				
			||||||
                ...emptyCompany,
 | 
					                ...values,
 | 
				
			||||||
                name: value,
 | 
					                company: null,
 | 
				
			||||||
            });
 | 
					                companyName: value
 | 
				
			||||||
 | 
					            }, true)
 | 
				
			||||||
        } else if (typeof value === "object" && value !== null) {
 | 
					        } else if (typeof value === "object" && value !== null) {
 | 
				
			||||||
            setCompany(value);
 | 
					            setValues({
 | 
				
			||||||
 | 
					                ...values,
 | 
				
			||||||
 | 
					                company: value as Company,
 | 
				
			||||||
 | 
					                companyName: value.name,
 | 
				
			||||||
 | 
					                companyNip: value.nip,
 | 
				
			||||||
 | 
					            }, true)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            setCompany(emptyCompany);
 | 
					            setValues({
 | 
				
			||||||
 | 
					                ...values,
 | 
				
			||||||
 | 
					                company: null,
 | 
				
			||||||
 | 
					                companyName: "",
 | 
				
			||||||
 | 
					            }, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -146,24 +175,22 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
 | 
				
			|||||||
            <Grid container>
 | 
					            <Grid container>
 | 
				
			||||||
                <Grid item>
 | 
					                <Grid item>
 | 
				
			||||||
                    <Autocomplete options={ sampleCompanies }
 | 
					                    <Autocomplete options={ sampleCompanies }
 | 
				
			||||||
                                  getOptionLabel={ option => option.name }
 | 
					                                  getOptionLabel={ option => typeof option === "string" ? option : option.name }
 | 
				
			||||||
                                  renderOption={ company => <CompanyItem company={ company }/> }
 | 
					                                  renderOption={ company => <CompanyItem company={ company }/> }
 | 
				
			||||||
                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth/> }
 | 
					                                  renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth
 | 
				
			||||||
                                  onChange={ handleCompanyChange } value={ company }
 | 
					                                                                    error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> }
 | 
				
			||||||
 | 
					                                  onChange={ handleCompanyChange } value={ values.company || values.companyName }
 | 
				
			||||||
                                  freeSolo
 | 
					                                  freeSolo
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                <Grid item md={ 4 }>
 | 
					                <Grid item md={ 4 }>
 | 
				
			||||||
                    <TextField label={ t("forms.internship.fields.nip") } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
 | 
					                    <Field label={ t("forms.internship.fields.nip") } fullWidth name="companyNip" disabled={ !canEdit } component={ TextFieldFormik }/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
                {/*<Grid item md={ 8 }>*/}
 | 
					 | 
				
			||||||
                {/*    <TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>*/}
 | 
					 | 
				
			||||||
                {/*</Grid>*/}
 | 
					 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
 | 
					            <Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
 | 
				
			||||||
            <MentorForm />
 | 
					            <MentorForm/>
 | 
				
			||||||
            <Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
 | 
					            <Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
 | 
				
			||||||
            <BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
 | 
					            <BranchForm/>
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,14 @@
 | 
				
			|||||||
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 | 
					import React, { HTMLProps, useMemo, useState } from "react";
 | 
				
			||||||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core";
 | 
					import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core";
 | 
				
			||||||
import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
 | 
					import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
 | 
				
			||||||
import { CompanyForm } from "@/forms/company";
 | 
					import { CompanyForm } from "@/forms/company";
 | 
				
			||||||
import { StudentForm } from "@/forms/student";
 | 
					import { StudentForm } from "@/forms/student";
 | 
				
			||||||
import { sampleStudent } from "@/provider/dummy/student";
 | 
					import { sampleStudent } from "@/provider/dummy/student";
 | 
				
			||||||
import { BranchOffice, Company, Internship, InternshipType, internshipTypeLabels, Student } from "@/data";
 | 
					import { Company, Internship, InternshipType, internshipTypeLabels, Office, Student } from "@/data";
 | 
				
			||||||
import { Nullable } from "@/helpers";
 | 
					import { Nullable } from "@/helpers";
 | 
				
			||||||
import moment, { Moment } from "moment";
 | 
					import moment, { Moment } from "moment";
 | 
				
			||||||
import { computeWorkingHours } from "@/utils/date";
 | 
					import { computeWorkingHours } from "@/utils/date";
 | 
				
			||||||
import { Autocomplete } from "@material-ui/lab";
 | 
					import { Autocomplete } from "@material-ui/lab";
 | 
				
			||||||
import { formFieldProps } from "@/forms/helpers";
 | 
					 | 
				
			||||||
import { emptyInternship } from "@/provider/dummy/internship";
 | 
					import { emptyInternship } from "@/provider/dummy/internship";
 | 
				
			||||||
import { InternshipProposalActions, useDispatch } from "@/state/actions";
 | 
					import { InternshipProposalActions, useDispatch } from "@/state/actions";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
@ -17,28 +16,26 @@ import { useSelector } from "react-redux";
 | 
				
			|||||||
import { AppState } from "@/state/reducer";
 | 
					import { AppState } from "@/state/reducer";
 | 
				
			||||||
import { Link as RouterLink, useHistory } from "react-router-dom";
 | 
					import { Link as RouterLink, useHistory } from "react-router-dom";
 | 
				
			||||||
import { route } from "@/routing";
 | 
					import { route } from "@/routing";
 | 
				
			||||||
import { useProxyState } from "@/hooks";
 | 
					 | 
				
			||||||
import { getInternshipProposal } from "@/state/reducer/proposal";
 | 
					import { getInternshipProposal } from "@/state/reducer/proposal";
 | 
				
			||||||
import { Actions } from "@/components";
 | 
					import { Actions } from "@/components";
 | 
				
			||||||
import { Form, Formik } from "formik";
 | 
					import { Field, Form, Formik, useFormikContext } from "formik";
 | 
				
			||||||
import * as Yup from "yup";
 | 
					import * as Yup from "yup";
 | 
				
			||||||
 | 
					import { Transformer } from "@/serialization";
 | 
				
			||||||
 | 
					import { TextField as TextFieldFormik } from "formik-material-ui"
 | 
				
			||||||
 | 
					import { Edition } from "@/data/edition";
 | 
				
			||||||
 | 
					import { useUpdateEffect } from "@/hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InternshipFormProps = {}
 | 
					export type InternshipFormValues = {
 | 
				
			||||||
 | 
					 | 
				
			||||||
export type InternshipFormSectionProps = {
 | 
					 | 
				
			||||||
    internship: Nullable<Internship>,
 | 
					 | 
				
			||||||
    onChange: (internship: Nullable<Internship>) => void,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type InternshipFormState = {
 | 
					 | 
				
			||||||
    startDate: Moment | null;
 | 
					    startDate: Moment | null;
 | 
				
			||||||
    endDate: Moment | null;
 | 
					    endDate: Moment | null;
 | 
				
			||||||
    hours: number | null;
 | 
					    hours: number | "";
 | 
				
			||||||
 | 
					    workingHours: number;
 | 
				
			||||||
    companyName: string;
 | 
					    companyName: string;
 | 
				
			||||||
    companyNip: string;
 | 
					    companyNip: string;
 | 
				
			||||||
    city: string;
 | 
					    city: string;
 | 
				
			||||||
    postalCode: string;
 | 
					    postalCode: string;
 | 
				
			||||||
    country: string;
 | 
					    country: string;
 | 
				
			||||||
 | 
					    street: string;
 | 
				
			||||||
    building: string;
 | 
					    building: string;
 | 
				
			||||||
    mentorFirstName: string;
 | 
					    mentorFirstName: string;
 | 
				
			||||||
    mentorLastName: string;
 | 
					    mentorLastName: string;
 | 
				
			||||||
@ -49,19 +46,20 @@ export type InternshipFormState = {
 | 
				
			|||||||
    // relations
 | 
					    // relations
 | 
				
			||||||
    kind: InternshipType | null;
 | 
					    kind: InternshipType | null;
 | 
				
			||||||
    company: Company | null;
 | 
					    company: Company | null;
 | 
				
			||||||
    office: BranchOffice | null;
 | 
					    office: Office | null;
 | 
				
			||||||
    student: Student | null;
 | 
					    student: Student;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emptyInternshipValues: InternshipFormState = {
 | 
					const emptyInternshipValues: InternshipFormValues = {
 | 
				
			||||||
    building: "",
 | 
					    building: "",
 | 
				
			||||||
    city: "",
 | 
					    city: "",
 | 
				
			||||||
    company: null,
 | 
					    company: null,
 | 
				
			||||||
    companyName: "",
 | 
					    companyName: "",
 | 
				
			||||||
    companyNip: "",
 | 
					    companyNip: "",
 | 
				
			||||||
    country: "",
 | 
					    country: "",
 | 
				
			||||||
 | 
					    street: "",
 | 
				
			||||||
    endDate: null,
 | 
					    endDate: null,
 | 
				
			||||||
    hours: null,
 | 
					    hours: "",
 | 
				
			||||||
    kind: null,
 | 
					    kind: null,
 | 
				
			||||||
    kindOther: "",
 | 
					    kindOther: "",
 | 
				
			||||||
    mentorEmail: "",
 | 
					    mentorEmail: "",
 | 
				
			||||||
@ -71,7 +69,8 @@ const emptyInternshipValues: InternshipFormState = {
 | 
				
			|||||||
    office: null,
 | 
					    office: null,
 | 
				
			||||||
    postalCode: "",
 | 
					    postalCode: "",
 | 
				
			||||||
    startDate: null,
 | 
					    startDate: null,
 | 
				
			||||||
    student: null
 | 
					    student: sampleStudent,
 | 
				
			||||||
 | 
					    workingHours: 40,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
 | 
					export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
 | 
				
			||||||
@ -85,89 +84,94 @@ export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType }
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionProps) => {
 | 
					const InternshipProgramForm = () => {
 | 
				
			||||||
    const fieldProps = formFieldProps(internship, onChange);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					    const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Grid container>
 | 
					        <Grid container>
 | 
				
			||||||
            <Grid item md={ 4 }>
 | 
					            <Grid item md={ 4 }>
 | 
				
			||||||
                <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth/> }
 | 
					                <Autocomplete renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.kind") } fullWidth error={ !!errors.kind } helperText={ errors.kind }/> }
 | 
				
			||||||
                              getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label }
 | 
					                              getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label }
 | 
				
			||||||
                              renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> }
 | 
					                              renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> }
 | 
				
			||||||
                              options={ Object.values(InternshipType) as InternshipType[] }
 | 
					                              options={ Object.values(InternshipType) as InternshipType[] }
 | 
				
			||||||
                              disableClearable
 | 
					                              disableClearable
 | 
				
			||||||
                              { ...fieldProps("type", (event, value) => value) as any }
 | 
					                              value={ values.kind || undefined }
 | 
				
			||||||
 | 
					                              onChange={ (_, value) => setFieldValue("kind", value) }
 | 
				
			||||||
 | 
					                              onBlur={ handleBlur }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={ 8 }>
 | 
					            <Grid item md={ 8 }>
 | 
				
			||||||
                { internship.type === InternshipType.Other && <TextField label={ t("forms.internship.fields.kind") } fullWidth/> }
 | 
					                {
 | 
				
			||||||
 | 
					                    values.kind === InternshipType.Other &&
 | 
				
			||||||
 | 
					                    <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            {/*<Grid item>*/ }
 | 
					 | 
				
			||||||
            {/*    <FormGroup>*/ }
 | 
					 | 
				
			||||||
            {/*        <FormLabel component="legend" className="subsection-header">Realizowane punkty programu praktyk (minimum 3)</FormLabel>*/ }
 | 
					 | 
				
			||||||
            {/*        { course.possibleProgramEntries.map(entry => {*/ }
 | 
					 | 
				
			||||||
            {/*            return (*/ }
 | 
					 | 
				
			||||||
            {/*                <FormControlLabel label={ entry.description } key={ entry.id }*/ }
 | 
					 | 
				
			||||||
            {/*                                  control={ <Checkbox /> }*/ }
 | 
					 | 
				
			||||||
            {/*                />*/ }
 | 
					 | 
				
			||||||
            {/*            )*/ }
 | 
					 | 
				
			||||||
            {/*        }) }*/ }
 | 
					 | 
				
			||||||
            {/*    </FormGroup>*/ }
 | 
					 | 
				
			||||||
            {/*</Grid>*/ }
 | 
					 | 
				
			||||||
        </Grid>
 | 
					        </Grid>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionProps) => {
 | 
					const InternshipDurationForm = () => {
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
    const [startDate, setStartDate] = useProxyState<Moment | null>(internship.startDate, value => onChange({ ...internship, startDate: value }));
 | 
					        values: { startDate, endDate, workingHours },
 | 
				
			||||||
    const [endDate, setEndDate] = useProxyState<Moment | null>(internship.endDate, value => onChange({ ...internship, endDate: value }));
 | 
					        errors,
 | 
				
			||||||
 | 
					        touched,
 | 
				
			||||||
 | 
					        setFieldTouched,
 | 
				
			||||||
 | 
					        setFieldValue
 | 
				
			||||||
 | 
					    } = useFormikContext<InternshipFormValues>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [overrideHours, setHoursOverride] = useState<number | null>(null)
 | 
					    const [overrideHours, setHoursOverride] = useState<number | null>(null)
 | 
				
			||||||
    const [workingHours, setWorkingHours] = useState<number>(40)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const computedHours = useMemo(() => startDate && endDate && computeWorkingHours(startDate, endDate, workingHours / 5), [startDate, endDate, workingHours]);
 | 
					    const computedHours = useMemo(() => startDate && endDate && computeWorkingHours(startDate, endDate, workingHours / 5), [startDate, endDate, workingHours]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const hours = useMemo(() => overrideHours !== null ? overrideHours : computedHours || null, [overrideHours, computedHours]);
 | 
					    const hours = useMemo(() => overrideHours !== null ? overrideHours : computedHours || null, [overrideHours, computedHours]);
 | 
				
			||||||
    const weeks = useMemo(() => hours !== null ? Math.floor(hours / 40) : null, [hours]);
 | 
					    const weeks = useMemo(() => hours !== null ? Math.floor(hours / workingHours) : null, [ hours ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => onChange({ ...internship, hours }), [hours])
 | 
					    useUpdateEffect(() => {
 | 
				
			||||||
 | 
					        setFieldTouched("hours", true);
 | 
				
			||||||
 | 
					        setFieldValue("hours", hours, true);
 | 
				
			||||||
 | 
					    }, [ hours ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Grid container>
 | 
					        <Grid container>
 | 
				
			||||||
            <Grid item md={ 6 }>
 | 
					            <Grid item md={ 6 }>
 | 
				
			||||||
                <DatePicker value={ startDate } onChange={ setStartDate }
 | 
					                <DatePicker value={ startDate } onChange={ value => setFieldValue("startDate", value) }
 | 
				
			||||||
                            format="DD MMMM yyyy"
 | 
					                            format="DD MMMM yyyy"
 | 
				
			||||||
                            clearable disableToolbar fullWidth
 | 
					                            disableToolbar fullWidth
 | 
				
			||||||
                            variant="inline" label={ t("forms.internship.fields.start-date") }
 | 
					                            variant="inline" label={ t("forms.internship.fields.start-date") }
 | 
				
			||||||
                            minDate={ moment() }
 | 
					                            minDate={ moment() }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={ 6 }>
 | 
					            <Grid item md={ 6 }>
 | 
				
			||||||
                <DatePicker value={ endDate } onChange={ setEndDate }
 | 
					                <DatePicker value={ endDate } onChange={ value => setFieldValue("endDate", value) }
 | 
				
			||||||
                            format="DD MMMM yyyy"
 | 
					                            format="DD MMMM yyyy"
 | 
				
			||||||
                            clearable disableToolbar fullWidth
 | 
					                            disableToolbar fullWidth
 | 
				
			||||||
                            variant="inline" label={ t("forms.internship.fields.end-date") }
 | 
					                            variant="inline" label={ t("forms.internship.fields.end-date") }
 | 
				
			||||||
                            minDate={ startDate || moment() }
 | 
					                            minDate={ startDate || moment() }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={ 4 }>
 | 
					            <Grid item md={ 4 }>
 | 
				
			||||||
                <TextField fullWidth label={ t("forms.internship.fields.working-hours") }
 | 
					                <Field component={ TextFieldFormik }
 | 
				
			||||||
                           value={ workingHours } onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
 | 
					                       name="workingHours"
 | 
				
			||||||
 | 
					                       label={ t("forms.internship.fields.working-hours") }
 | 
				
			||||||
                       helperText={ t("forms.internship.help.working-hours") }
 | 
					                       helperText={ t("forms.internship.help.working-hours") }
 | 
				
			||||||
 | 
					                       fullWidth
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={ 4 }>
 | 
					            <Grid item md={ 4 }>
 | 
				
			||||||
                <TextField fullWidth label={ t("forms.internship.fields.total-hours") }
 | 
					                <TextField fullWidth
 | 
				
			||||||
                           value={ hours } onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
 | 
					                           label={ t("forms.internship.fields.total-hours") }
 | 
				
			||||||
 | 
					                           error={ !!errors.hours && touched.hours }
 | 
				
			||||||
 | 
					                           helperText={ touched.hours && errors.hours }
 | 
				
			||||||
 | 
					                           value={ hours || "" }
 | 
				
			||||||
 | 
					                           onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item md={ 4 }>
 | 
					            <Grid item md={ 4 }>
 | 
				
			||||||
                <TextField fullWidth label={ t("forms.internship.fields.weeks") }
 | 
					                <TextField fullWidth label={ t("forms.internship.fields.weeks") }
 | 
				
			||||||
                           value={ weeks } disabled
 | 
					                           value={ weeks || "" }
 | 
				
			||||||
 | 
					                           disabled
 | 
				
			||||||
                           helperText={ t("forms.internship.help.weeks") }
 | 
					                           helperText={ t("forms.internship.help.weeks") }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
@ -175,36 +179,86 @@ const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionP
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
 | 
					type InternshipConverterContext = {
 | 
				
			||||||
    const initialInternshipState = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
 | 
					    internship: Internship,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const converter: Transformer<Nullable<Internship>, InternshipFormValues, InternshipConverterContext> = {
 | 
				
			||||||
 | 
					    transform(internship: Nullable<Internship>): InternshipFormValues {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            student: internship.intern as Student,
 | 
				
			||||||
 | 
					            kind: internship.type,
 | 
				
			||||||
 | 
					            kindOther: "",
 | 
				
			||||||
 | 
					            startDate: internship.startDate,
 | 
				
			||||||
 | 
					            endDate: internship.endDate,
 | 
				
			||||||
 | 
					            hours: internship.hours || "",
 | 
				
			||||||
 | 
					            building: internship.office?.address?.building || "",
 | 
				
			||||||
 | 
					            office: internship.office,
 | 
				
			||||||
 | 
					            city: internship.office?.address?.city || "",
 | 
				
			||||||
 | 
					            postalCode: internship.office?.address?.postalCode || "",
 | 
				
			||||||
 | 
					            street: internship.office?.address?.street || "",
 | 
				
			||||||
 | 
					            country: internship.office?.address?.country || "",
 | 
				
			||||||
 | 
					            company: internship.company,
 | 
				
			||||||
 | 
					            companyName: internship.company?.name || "",
 | 
				
			||||||
 | 
					            companyNip: internship.company?.nip || "",
 | 
				
			||||||
 | 
					            mentorEmail: internship.mentor?.email || "",
 | 
				
			||||||
 | 
					            mentorFirstName: internship.mentor?.name || "",
 | 
				
			||||||
 | 
					            mentorLastName: internship.mentor?.surname || "",
 | 
				
			||||||
 | 
					            mentorPhone: internship.mentor?.phone || "",
 | 
				
			||||||
 | 
					            workingHours: 40,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    reverseTransform(form: InternshipFormValues, context: InternshipConverterContext): Nullable<Internship> {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            ...context.internship,
 | 
				
			||||||
 | 
					            startDate: form.startDate as Moment,
 | 
				
			||||||
 | 
					            endDate: form.endDate as Moment,
 | 
				
			||||||
 | 
					            office: form.office || {
 | 
				
			||||||
 | 
					                address: {
 | 
				
			||||||
 | 
					                    street: form.street,
 | 
				
			||||||
 | 
					                    postalCode: form.postalCode,
 | 
				
			||||||
 | 
					                    country: form.country,
 | 
				
			||||||
 | 
					                    city: form.city,
 | 
				
			||||||
 | 
					                    building: form.building,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            mentor: {
 | 
				
			||||||
 | 
					                surname: form.mentorLastName,
 | 
				
			||||||
 | 
					                name: form.mentorFirstName,
 | 
				
			||||||
 | 
					                email: form.mentorEmail,
 | 
				
			||||||
 | 
					                phone: form.mentorPhone,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            company: form.company || {
 | 
				
			||||||
 | 
					                name: form.companyName,
 | 
				
			||||||
 | 
					                nip: form.companyNip,
 | 
				
			||||||
 | 
					                offices: [],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            hours: form.hours as number,
 | 
				
			||||||
 | 
					            type: form.kind as InternshipType,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const InternshipForm: React.FunctionComponent = () => {
 | 
				
			||||||
 | 
					    const initialInternship = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
 | 
				
			||||||
        ...emptyInternship,
 | 
					        ...emptyInternship,
 | 
				
			||||||
 | 
					        office: null,
 | 
				
			||||||
 | 
					        company: null,
 | 
				
			||||||
 | 
					        mentor: null,
 | 
				
			||||||
        intern: sampleStudent
 | 
					        intern: sampleStudent
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [internship, setInternship] = useState<Nullable<Internship>>(initialInternshipState)
 | 
					    const edition = useSelector<AppState, Edition>(state => state.edition as Edition);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = useDispatch();
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
    const history = useHistory();
 | 
					    const history = useHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
 | 
					    const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleSubmit = () => {
 | 
					    const validationSchema = Yup.object<Partial<InternshipFormValues>>({
 | 
				
			||||||
        setConfirmDialogOpen(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dispatch({ type: InternshipProposalActions.Send, internship: internship as Internship });
 | 
					 | 
				
			||||||
        history.push(route("home"))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleSubmitConfirmation = () => {
 | 
					 | 
				
			||||||
        setConfirmDialogOpen(true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleCancel = () => {
 | 
					 | 
				
			||||||
        setConfirmDialogOpen(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const validationSchema = Yup.object<Partial<InternshipFormState>>({
 | 
					 | 
				
			||||||
        mentorFirstName: Yup.string().required(t("validation.required")),
 | 
					        mentorFirstName: Yup.string().required(t("validation.required")),
 | 
				
			||||||
        mentorLastName: Yup.string().required(t("validation.required")),
 | 
					        mentorLastName: Yup.string().required(t("validation.required")),
 | 
				
			||||||
        mentorEmail: Yup.string()
 | 
					        mentorEmail: Yup.string()
 | 
				
			||||||
@ -214,23 +268,68 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
 | 
				
			|||||||
            .required(t("validation.required"))
 | 
					            .required(t("validation.required"))
 | 
				
			||||||
            .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
 | 
					            .matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
 | 
				
			||||||
        hours: Yup.number()
 | 
					        hours: Yup.number()
 | 
				
			||||||
            .min(40, t("validation.internship.minimum-hours")) // todo: take it from edition
 | 
					            .min(edition.minimumInternshipHours, t("validation.internship.minimum-hours", { hours: edition.minimumInternshipHours })),
 | 
				
			||||||
 | 
					        companyName: Yup.string().when("company", {
 | 
				
			||||||
 | 
					            is: null,
 | 
				
			||||||
 | 
					            then: Yup.string().required(t("validation.required"))
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        companyNip: Yup.string().when("company", {
 | 
				
			||||||
 | 
					            is: null,
 | 
				
			||||||
 | 
					            then: Yup.string()
 | 
				
			||||||
 | 
					                .required(t("validation.required"))
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        street: Yup.string().required(t("validation.required")),
 | 
				
			||||||
 | 
					        country: Yup.string().required(t("validation.required")),
 | 
				
			||||||
 | 
					        city: Yup.string().required(t("validation.required")),
 | 
				
			||||||
 | 
					        postalCode: Yup.string().required(t("validation.required")),
 | 
				
			||||||
 | 
					        building: Yup.string().required(t("validation.required")),
 | 
				
			||||||
 | 
					        kindOther: Yup.string().when("kind", {
 | 
				
			||||||
 | 
					            is: (values: InternshipFormValues) => values?.kind !== InternshipType.Other,
 | 
				
			||||||
 | 
					            then: Yup.string().required(t("validation.required"))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    const values = converter.transform(initialInternship);
 | 
				
			||||||
        <Formik initialValues={ emptyInternshipValues } onSubmit={ values => console.log(values) }
 | 
					
 | 
				
			||||||
                validationSchema={ validationSchema } validateOnChange={ false } validateOnBlur={ true }>
 | 
					    const handleSubmit = (values: InternshipFormValues) => {
 | 
				
			||||||
            { formik => <Form>
 | 
					        setConfirmDialogOpen(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dispatch({
 | 
				
			||||||
 | 
					            type: InternshipProposalActions.Send,
 | 
				
			||||||
 | 
					            internship: converter.reverseTransform(values, {
 | 
				
			||||||
 | 
					                internship: initialInternship as Internship,
 | 
				
			||||||
 | 
					            }) as Internship
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        history.push(route("home"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const InnerForm = () => {
 | 
				
			||||||
 | 
					        const { submitForm, validateForm } = useFormikContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const handleSubmitConfirmation = async () => {
 | 
				
			||||||
 | 
					            const errors = await validateForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Object.keys(errors).length == 0) {
 | 
				
			||||||
 | 
					                setConfirmDialogOpen(true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const handleCancel = () => {
 | 
				
			||||||
 | 
					            setConfirmDialogOpen(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return <Form>
 | 
				
			||||||
            <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
 | 
					            <Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
 | 
				
			||||||
                <StudentForm student={ sampleStudent }/>
 | 
					            <StudentForm />
 | 
				
			||||||
            <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
 | 
					            <Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
 | 
				
			||||||
                <InternshipProgramForm internship={ internship } onChange={ setInternship }/>
 | 
					            <InternshipProgramForm />
 | 
				
			||||||
            <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
 | 
					            <Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
 | 
				
			||||||
                <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
 | 
					            <InternshipDurationForm />
 | 
				
			||||||
            <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
 | 
					            <Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
 | 
				
			||||||
                <CompanyForm internship={ internship } onChange={ setInternship }/>
 | 
					            <CompanyForm />
 | 
				
			||||||
            <Actions>
 | 
					            <Actions>
 | 
				
			||||||
                    <Button variant="contained" color="primary" onClick={ () => formik.validateForm(formik.values) }>{ t("confirm") }</Button>
 | 
					                <Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <Button component={ RouterLink } to={ route("home") }>
 | 
					                <Button component={ RouterLink } to={ route("home") }>
 | 
				
			||||||
                    { t('go-back') }
 | 
					                    { t('go-back') }
 | 
				
			||||||
@ -243,10 +342,20 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
 | 
				
			|||||||
                </DialogContent>
 | 
					                </DialogContent>
 | 
				
			||||||
                <DialogActions>
 | 
					                <DialogActions>
 | 
				
			||||||
                    <Button onClick={ handleCancel }>{ t('cancel') }</Button>
 | 
					                    <Button onClick={ handleCancel }>{ t('cancel') }</Button>
 | 
				
			||||||
                        <Button color="primary" autoFocus onClick={ formik.submitForm }>{ t('confirm') }</Button>
 | 
					                    <Button color="primary" autoFocus onClick={ submitForm }>{ t('confirm') }</Button>
 | 
				
			||||||
                </DialogActions>
 | 
					                </DialogActions>
 | 
				
			||||||
            </Dialog>
 | 
					            </Dialog>
 | 
				
			||||||
            </Form> }
 | 
					        </Form>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Formik initialValues={ values }
 | 
				
			||||||
 | 
					                onSubmit={ handleSubmit }
 | 
				
			||||||
 | 
					                validationSchema={ validationSchema }
 | 
				
			||||||
 | 
					                validateOnChange={ false }
 | 
				
			||||||
 | 
					                validateOnBlur={ true }
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <InnerForm />
 | 
				
			||||||
        </Formik>
 | 
					        </Formik>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,15 @@
 | 
				
			|||||||
import { Course, Student } from "@/data";
 | 
					import { Course } from "@/data";
 | 
				
			||||||
import { Button, Grid, TextField } from "@material-ui/core";
 | 
					import { Button, Grid, TextField } from "@material-ui/core";
 | 
				
			||||||
import { Alert, Autocomplete } from "@material-ui/lab";
 | 
					import { Alert, Autocomplete } from "@material-ui/lab";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { sampleCourse } from "@/provider/dummy/student";
 | 
					import { sampleCourse } from "@/provider/dummy/student";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import { useFormikContext } from "formik";
 | 
				
			||||||
 | 
					import { InternshipFormValues } from "@/forms/internship";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type StudentFormProps = {
 | 
					export const StudentForm = () => {
 | 
				
			||||||
    student: Student
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const StudentForm = ({ student }: StudentFormProps) => {
 | 
					 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					    const { values: { student } } = useFormikContext<InternshipFormValues>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <>
 | 
					    return <>
 | 
				
			||||||
        <Grid container>
 | 
					        <Grid container>
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1,2 @@
 | 
				
			|||||||
export * from "./useProxyState"
 | 
					export * from "./useProxyState"
 | 
				
			||||||
 | 
					export * from "./useUpdateEffect"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								src/hooks/useUpdateEffect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/hooks/useUpdateEffect.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { DependencyList, EffectCallback, useEffect, useRef } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useUpdateEffect = (effect: EffectCallback, dependencies: DependencyList) => {
 | 
				
			||||||
 | 
					    const flag = useRef<boolean>(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (flag.current) {
 | 
				
			||||||
 | 
					            effect();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            flag.current = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, dependencies)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useUpdateEffect;
 | 
				
			||||||
@ -4,5 +4,6 @@ import moment from "moment";
 | 
				
			|||||||
export const sampleEdition: Edition = {
 | 
					export const sampleEdition: Edition = {
 | 
				
			||||||
    startDate: moment("2020-07-01"),
 | 
					    startDate: moment("2020-07-01"),
 | 
				
			||||||
    endDate: moment("2020-09-30"),
 | 
					    endDate: moment("2020-09-30"),
 | 
				
			||||||
    proposalDeadline: moment("2020-07-31")
 | 
					    proposalDeadline: moment("2020-07-31"),
 | 
				
			||||||
 | 
					    minimumInternshipHours: 40,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,9 +10,9 @@ type Simplify<T> = string |
 | 
				
			|||||||
    T extends Object ? Serializable<T> : any;
 | 
					    T extends Object ? Serializable<T> : any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Serializable<T> = { [K in keyof T]: Simplify<T[K]> }
 | 
					export type Serializable<T> = { [K in keyof T]: Simplify<T[K]> }
 | 
				
			||||||
export type Transformer<TFrom, TResult> = {
 | 
					export type Transformer<TFrom, TResult, TContext = never> = {
 | 
				
			||||||
    transform(subject: TFrom): TResult;
 | 
					    transform(subject: TFrom, context?: TContext): TResult;
 | 
				
			||||||
    reverseTransform(subject: TResult): TFrom;
 | 
					    reverseTransform(subject: TResult, context?: TContext): TFrom;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SerializationTransformer<T, TSerialized = Serializable<T>> = Transformer<T, TSerialized>
 | 
					export type SerializationTransformer<T, TSerialized = Serializable<T>> = Transformer<T, TSerialized>
 | 
				
			||||||
 | 
				
			|||||||
@ -163,5 +163,7 @@ validation:
 | 
				
			|||||||
  required: "To pole jest wymagane"
 | 
					  required: "To pole jest wymagane"
 | 
				
			||||||
  email: "Wprowadź poprawny adres e-mail"
 | 
					  email: "Wprowadź poprawny adres e-mail"
 | 
				
			||||||
  phone: "Wprowadź poprawny numer telefonu"
 | 
					  phone: "Wprowadź poprawny numer telefonu"
 | 
				
			||||||
 | 
					  internship:
 | 
				
			||||||
 | 
					    minimum-hours: "Minimalna liczba godzin wynosi {{ hours }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
contact-coordinator: "Skontaktuj się z koordynatorem"
 | 
					contact-coordinator: "Skontaktuj się z koordynatorem"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user