Add mentor validation

This commit is contained in:
Kacper Donat 2020-08-14 15:51:27 +02:00
parent c6c6649245
commit 99e12f7681
6 changed files with 168 additions and 47 deletions

View File

@ -18,6 +18,7 @@
"@types/react-router-dom": "^5.1.5",
"@types/redux": "^3.6.0",
"@types/redux-persist": "^4.3.1",
"@types/yup": "^0.29.4",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"babel-core": "^6.26.3",
@ -30,6 +31,7 @@
"date-holidays": "^1.5.3",
"file-loader": "4.3.0",
"formik": "^2.1.5",
"formik-material-ui": "^3.0.0-alpha.0",
"html-webpack-plugin": "4.0.0-beta.11",
"i18next": "^19.6.0",
"i18next-browser-languagedetector": "^5.0.0",
@ -63,7 +65,8 @@
"webpack-cli": "^3.3.11",
"webpack-dev-server": "3.10.3",
"workbox-webpack-plugin": "4.3.1",
"yaml-loader": "^0.6.0"
"yaml-loader": "^0.6.0",
"yup": "^0.29.3"
},
"scripts": {
"serve": "webpack-dev-server --mode development",

View File

@ -1,3 +1,5 @@
export type Identifier = string;
export interface Identifiable {
id?: string
id?: Identifier
}

View File

@ -8,6 +8,8 @@ import { InternshipFormSectionProps } from "@/forms/internship";
import { emptyMentor } from "@/provider/dummy/internship";
import { useProxyState } from "@/hooks";
import { useTranslation } from "react-i18next";
import { Field } from "formik";
import { TextField as TextFieldFormik } from "formik-material-ui"
export type CompanyFormProps = {} & InternshipFormSectionProps;
@ -95,27 +97,24 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
)
}
export const MentorForm = ({ mentor, onMentorChange }: BoundProperty<Mentor, 'onMentorChange', 'mentor'>) => {
const fieldProps = formFieldProps(mentor, onMentorChange)
export const MentorForm = () => {
const { t } = useTranslation();
return (
<>
<Grid container>
<Grid item md={6}>
<TextField label={ t("forms.internship.fields.first-name") } fullWidth { ...fieldProps("name") }/>
</Grid>
<Grid item md={6}>
<TextField label={ t("forms.internship.fields.last-name") } value={ mentor.surname } fullWidth { ...fieldProps("surname") }/>
</Grid>
<Grid item md={8}>
<TextField label={ t("forms.internship.fields.e-mail") } value={ mentor.email } fullWidth { ...fieldProps("email") }/>
</Grid>
<Grid item md={4}>
<TextField label={ t("forms.internship.fields.phone" )} value={ mentor.phone } fullWidth { ...fieldProps("phone") }/>
</Grid>
<Grid container>
<Grid item md={6}>
<Field name="mentorFirstName" label={ t("forms.internship.fields.first-name") } fullWidth component={ TextFieldFormik } />
</Grid>
</>
<Grid item md={6}>
<Field name="mentorLastName" label={ t("forms.internship.fields.last-name") } fullWidth component={ TextFieldFormik } />
</Grid>
<Grid item md={8}>
<Field name="mentorEmail" label={ t("forms.internship.fields.e-mail") } fullWidth component={ TextFieldFormik } />
</Grid>
<Grid item md={4}>
<Field name="mentorPhone" label={ t("forms.internship.fields.phone") } fullWidth component={ TextFieldFormik } />
</Grid>
</Grid>
);
}
@ -162,7 +161,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
{/*</Grid>*/}
</Grid>
<Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
<MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
<MentorForm />
<Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
<BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
</>

View File

@ -4,7 +4,7 @@ import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
import { CompanyForm } from "@/forms/company";
import { StudentForm } from "@/forms/student";
import { sampleStudent } from "@/provider/dummy/student";
import { Internship, InternshipType, internshipTypeLabels } from "@/data";
import { BranchOffice, Company, Internship, InternshipType, internshipTypeLabels, Student } from "@/data";
import { Nullable } from "@/helpers";
import moment, { Moment } from "moment";
import { computeWorkingHours } from "@/utils/date";
@ -20,6 +20,8 @@ import { route } from "@/routing";
import { useProxyState } from "@/hooks";
import { getInternshipProposal } from "@/state/reducer/proposal";
import { Actions } from "@/components";
import { Form, Formik } from "formik";
import * as Yup from "yup";
export type InternshipFormProps = {}
@ -28,6 +30,50 @@ export type InternshipFormSectionProps = {
onChange: (internship: Nullable<Internship>) => void,
}
export type InternshipFormState = {
startDate: Moment | null;
endDate: Moment | null;
hours: number | null;
companyName: string;
companyNip: string;
city: string;
postalCode: string;
country: string;
building: string;
mentorFirstName: string;
mentorLastName: string;
mentorEmail: string;
mentorPhone: string;
kindOther: string | null;
// relations
kind: InternshipType | null;
company: Company | null;
office: BranchOffice | null;
student: Student | null;
}
const emptyInternshipValues: InternshipFormState = {
building: "",
city: "",
company: null,
companyName: "",
companyNip: "",
country: "",
endDate: null,
hours: null,
kind: null,
kindOther: "",
mentorEmail: "",
mentorFirstName: "",
mentorLastName: "",
mentorPhone: "",
office: null,
postalCode: "",
startDate: null,
student: null
}
export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
const info = internshipTypeLabels[type];
@ -158,34 +204,50 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
setConfirmDialogOpen(false);
}
const validationSchema = Yup.object<Partial<InternshipFormState>>({
mentorFirstName: Yup.string().required(t("validation.required")),
mentorLastName: Yup.string().required(t("validation.required")),
mentorEmail: Yup.string()
.required(t("validation.required"))
.email(t("validation.email")),
mentorPhone: Yup.string()
.required(t("validation.required"))
.matches(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/, t("validation.phone")),
hours: Yup.number()
.min(40, t("validation.internship.minimum-hours")) // todo: take it from edition
})
return (
<div className="internship-form">
<Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
<StudentForm student={ sampleStudent }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
<InternshipProgramForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
<InternshipDurationForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
<CompanyForm internship={ internship } onChange={ setInternship }/>
<Actions>
<Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button>
<Formik initialValues={ emptyInternshipValues } onSubmit={ values => console.log(values) }
validationSchema={ validationSchema } validateOnChange={ false } validateOnBlur={ true }>
{ formik => <Form>
<Typography variant="h3" className="section-header">{ t('internship.sections.intern-info') }</Typography>
<StudentForm student={ sampleStudent }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.kind' )}</Typography>
<InternshipProgramForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.duration') }</Typography>
<InternshipDurationForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
<CompanyForm internship={ internship } onChange={ setInternship }/>
<Actions>
<Button variant="contained" color="primary" onClick={ () => formik.validateForm(formik.values) }>{ t("confirm") }</Button>
<Button component={ RouterLink } to={ route("home") }>
{ t('go-back') }
</Button>
</Actions>
<Button component={ RouterLink } to={ route("home") }>
{ t('go-back') }
</Button>
</Actions>
<Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
<DialogContent>
<DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={ handleCancel }>{ t('cancel') }</Button>
<Button color="primary" autoFocus onClick={ handleSubmit }>{ t('confirm') }</Button>
</DialogActions>
</Dialog>
</div>
<Dialog open={ confirmDialogOpen } onClose={ handleCancel }>
<DialogContent>
<DialogContentText>{ t('forms.internship.send-confirmation') }</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={ handleCancel }>{ t('cancel') }</Button>
<Button color="primary" autoFocus onClick={ formik.submitForm }>{ t('confirm') }</Button>
</DialogActions>
</Dialog>
</Form> }
</Formik>
)
}

View File

@ -159,4 +159,9 @@ steps:
instructions: >
papierki do podpisania...
validation:
required: "To pole jest wymagane"
email: "Wprowadź poprawny adres e-mail"
phone: "Wprowadź poprawny numer telefonu"
contact-coordinator: "Skontaktuj się z koordynatorem"

View File

@ -933,6 +933,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.5":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1", "@babel/template@^7.8.6":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -1311,6 +1318,11 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/yup@^0.29.4":
version "0.29.4"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.4.tgz#f7b1f9978180d5155663c1cd0ecdc41a72c23d81"
integrity sha512-OQ7gZRQb7eSbGu5h57tbK67sgX8UH5wbuqPORTFBG7qiBtOkEf1dXAr0QULyHIeRwaGLPYxPXiQru+40ClR6ng==
"@typescript-eslint/eslint-plugin@^2.10.0":
version "2.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
@ -3919,6 +3931,11 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
fn-name@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
follow-redirects@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
@ -3959,6 +3976,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formik-material-ui@^3.0.0-alpha.0:
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/formik-material-ui/-/formik-material-ui-3.0.0-alpha.0.tgz#4020b5cbd9e431406fb275a317cdce95ad398545"
integrity sha512-N9JcSngi4nWaKN67sN1M3ILXgz0fLCdoMhHHecrZC3NeR+C5lWWAUuAcjGZWNj+z87Qt7NW8VXlxSnGxGus8Uw==
formik@^2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8"
@ -5383,7 +5405,7 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.14:
lodash-es@^4.17.11, lodash-es@^4.17.14:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
@ -7216,6 +7238,11 @@ prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
property-expr@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330"
integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g==
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -8591,6 +8618,11 @@ symbol-observable@^1.2.0:
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
synchronous-promise@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
@ -8733,6 +8765,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@ -9506,3 +9543,16 @@ yargs@^13.3.2:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.2"
yup@^0.29.3:
version "0.29.3"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
dependencies:
"@babel/runtime" "^7.10.5"
fn-name "~3.0.0"
lodash "^4.17.15"
lodash-es "^4.17.11"
property-expr "^2.0.2"
synchronous-promise "^2.0.13"
toposort "^2.0.2"