feature_validation #10
@ -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",
|
||||
@ -29,6 +30,8 @@
|
||||
"css-loader": "3.4.2",
|
||||
"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",
|
||||
@ -62,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",
|
||||
|
@ -2,7 +2,7 @@ import React, { HTMLProps } from "react";
|
||||
import { useHorizontalSpacing } from "@/styles";
|
||||
|
||||
export const Actions = (props: HTMLProps<HTMLDivElement>) => {
|
||||
const classes = useHorizontalSpacing(1);
|
||||
const classes = useHorizontalSpacing(2);
|
||||
|
||||
return <div className={ classes.root } { ...props }/>
|
||||
return <div className={ classes.root } { ...props } style={{ display: "flex", alignItems: "center" }}/>
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { Internship, internshipTypeLabels } from "@/data";
|
||||
import React from "react";
|
||||
import { Button, Paper, PaperProps, Typography, TypographyProps } from "@material-ui/core";
|
||||
import { Paper, PaperProps, Typography, TypographyProps } from "@material-ui/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createStyles, makeStyles } from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { route } from "@/routing";
|
||||
import { Actions } from "@/components/actions";
|
||||
import { useVerticalSpacing } from "@/styles";
|
||||
import moment from "moment";
|
||||
|
||||
@ -36,7 +33,6 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const classes = useVerticalSpacing(3);
|
||||
|
||||
const duration = moment.duration(proposal.endDate.diff(proposal.startDate));
|
||||
|
||||
return <div className={ classNames("proposal", classes.root) }>
|
||||
@ -74,9 +70,9 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
|
||||
{ t('internship.date-range', { start: proposal.startDate, end: proposal.endDate }) }
|
||||
</Typography>
|
||||
<Typography className="proposal__secondary">
|
||||
{ t('internship.duration', { duration }) }
|
||||
{ t('internship.duration', { duration, count: Math.floor(duration.asWeeks()) }) }
|
||||
{ ", " }
|
||||
{ t('internship.hours', { hours: proposal.hours }) }
|
||||
{ t('internship.hours', { hours: proposal.hours, count: proposal.hours }) }
|
||||
</Typography>
|
||||
</Section>
|
||||
|
||||
@ -85,11 +81,5 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
|
||||
<Typography className="proposal__primary">{ proposal.mentor.name } { proposal.mentor.surname }</Typography>
|
||||
<Typography className="proposal__secondary">{ proposal.mentor.email }, { proposal.mentor.phone }</Typography>
|
||||
</Section>
|
||||
|
||||
<Actions>
|
||||
<Button component={ RouterLink } to={ route("home") } variant="contained" color="primary">
|
||||
{ t('go-back') }
|
||||
</Button>
|
||||
</Actions>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export type Identifier = string;
|
||||
|
||||
export interface Identifiable {
|
||||
id?: string
|
||||
id?: Identifier
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ export interface Company extends Identifiable {
|
||||
name: string;
|
||||
url?: string;
|
||||
nip: string;
|
||||
offices: BranchOffice[];
|
||||
offices: Office[];
|
||||
}
|
||||
|
||||
export interface BranchOffice extends Identifiable {
|
||||
export interface Office extends Identifiable {
|
||||
address: Address;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export const emptyAddress: Address = {
|
||||
building: ""
|
||||
}
|
||||
|
||||
export const emptyBranchOffice: BranchOffice = {
|
||||
export const emptyBranchOffice: Office = {
|
||||
address: emptyAddress,
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ export type Edition = {
|
||||
startDate: Moment;
|
||||
endDate: Moment;
|
||||
proposalDeadline: Moment;
|
||||
minimumInternshipHours: number;
|
||||
maximumInternshipHours?: number;
|
||||
}
|
||||
|
||||
export type Deadlines = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Moment } from "moment";
|
||||
import { Identifiable } from "./common";
|
||||
import { Student } from "@/data/student";
|
||||
import { BranchOffice, Company } from "@/data/company";
|
||||
import { Company, Office } from "@/data/company";
|
||||
|
||||
export enum InternshipType {
|
||||
FreeInternship = "FreeInternship",
|
||||
@ -64,7 +64,7 @@ export interface Internship extends Identifiable {
|
||||
hours: number;
|
||||
mentor: Mentor;
|
||||
company: Company;
|
||||
office: BranchOffice;
|
||||
office: Office;
|
||||
}
|
||||
|
||||
export interface Plan extends Identifiable {
|
||||
|
@ -1,19 +1,12 @@
|
||||
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 { Autocomplete } from "@material-ui/lab";
|
||||
import { Grid, TextField, Typography } from "@material-ui/core";
|
||||
import { BoundProperty, formFieldProps } from "./helpers";
|
||||
import { InternshipFormSectionProps } from "@/forms/internship";
|
||||
import { emptyMentor } from "@/provider/dummy/internship";
|
||||
import { useProxyState } from "@/hooks";
|
||||
|
||||
export type CompanyFormProps = {} & InternshipFormSectionProps;
|
||||
|
||||
export type BranchOfficeProps = {
|
||||
disabled?: boolean;
|
||||
offices?: BranchOffice[];
|
||||
} & BoundProperty<BranchOffice, "onChange", "value">
|
||||
import { InternshipFormValues } from "@/forms/internship";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import { TextField as TextFieldFormik } from "formik-material-ui"
|
||||
|
||||
export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps<any>) => (
|
||||
<div className="company-item" { ...props }>
|
||||
@ -22,42 +15,65 @@ export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLPr
|
||||
</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>{ office.address.city }</div>
|
||||
<Typography variant="caption">{ formatAddress(office.address) }</Typography>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChange: setOffice, offices = [], disabled = false }) => {
|
||||
const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
|
||||
const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
|
||||
export const BranchForm: React.FC = () => {
|
||||
const { values, errors, setValues, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
|
||||
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") {
|
||||
setOffice({
|
||||
...emptyBranchOffice,
|
||||
address: {
|
||||
...emptyAddress,
|
||||
city: value,
|
||||
}
|
||||
});
|
||||
setValues({
|
||||
...values,
|
||||
office: null,
|
||||
city: value,
|
||||
}, true);
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
setOffice(value);
|
||||
} else {
|
||||
setOffice(emptyBranchOffice);
|
||||
const office = value as Office;
|
||||
|
||||
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 base = office.id ? emptyBranchOffice : office;
|
||||
setOffice({
|
||||
...base,
|
||||
address: {
|
||||
...base.address,
|
||||
city: value,
|
||||
}
|
||||
})
|
||||
setValues( {
|
||||
...values,
|
||||
office: null,
|
||||
...(values.office ? {
|
||||
country: "",
|
||||
street: "",
|
||||
building: "",
|
||||
postalCode: "",
|
||||
} : { }),
|
||||
city: value,
|
||||
}, true);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -68,73 +84,89 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChang
|
||||
disabled={ disabled }
|
||||
getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
|
||||
renderOption={ office => <OfficeItem office={ office }/> }
|
||||
renderInput={ props => <TextField { ...props } label={ "Miasto" } fullWidth/> }
|
||||
renderInput={
|
||||
props =>
|
||||
<TextField { ...props }
|
||||
label={ t("forms.internship.fields.city") }
|
||||
fullWidth
|
||||
error={ touched.city && !!errors.city }
|
||||
helperText={ touched.city && errors.city }
|
||||
/>
|
||||
}
|
||||
onChange={ handleCityChange }
|
||||
onInputChange={ handleCityInput }
|
||||
inputValue={ office.address.city }
|
||||
value={ office.id ? office : null }
|
||||
onBlur={ ev => setFieldTouched("city", true) }
|
||||
inputValue={ values.city }
|
||||
value={ values.office ? values.office : null }
|
||||
freeSolo
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 2 }>
|
||||
<TextField label={ "Kod pocztowy" } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
|
||||
<Field label={ t("forms.internship.fields.postal-code") } name="postalCode" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
|
||||
</Grid>
|
||||
<Grid item md={ 3 }>
|
||||
<TextField label={ "Kraj" } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
|
||||
<Field label={ t("forms.internship.fields.country") } name="country" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
|
||||
</Grid>
|
||||
<Grid item md={ 10 }>
|
||||
<TextField label={ "Ulica" } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
|
||||
<Field label={ t("forms.internship.fields.street") } name="street" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
|
||||
</Grid>
|
||||
<Grid item md={ 2 }>
|
||||
<TextField label={ "Nr Budynku" } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
|
||||
<Field label={ t("forms.internship.fields.building") } name="building" fullWidth disabled={ !canEdit } component={ TextFieldFormik }/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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="Imię" fullWidth { ...fieldProps("name") }/>
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<TextField label="Nazwisko" value={ mentor.surname } fullWidth { ...fieldProps("surname") }/>
|
||||
</Grid>
|
||||
<Grid item md={8}>
|
||||
<TextField label="E-mail" value={ mentor.email } fullWidth { ...fieldProps("email") }/>
|
||||
</Grid>
|
||||
<Grid item md={4}>
|
||||
<TextField label="Nr telefonu" 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>
|
||||
);
|
||||
}
|
||||
|
||||
export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ internship, onChange }) => {
|
||||
const [company, setCompany] = useProxyState<Company>(internship.company || emptyCompany, company => onChange({ ...internship, company }));
|
||||
const [mentor, setMentor] = useProxyState<Mentor>(internship.mentor || emptyMentor, mentor => onChange({ ...internship, mentor }));
|
||||
const [office, setOffice] = useProxyState<BranchOffice>(internship.office || emptyBranchOffice, office => onChange({ ...internship, office }));
|
||||
export const CompanyForm: React.FunctionComponent = () => {
|
||||
const { values, setValues, errors, touched, setFieldTouched } = useFormikContext<InternshipFormValues>();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const canEdit = useMemo(() => !company.id, [company.id]);
|
||||
|
||||
const fieldProps = formFieldProps(company, setCompany)
|
||||
const canEdit = useMemo(() => !values.company, [values.company]);
|
||||
|
||||
const handleCompanyChange = (event: any, value: Company | string | null) => {
|
||||
setFieldTouched("companyName", true);
|
||||
|
||||
if (typeof value === "string") {
|
||||
setCompany({
|
||||
...emptyCompany,
|
||||
name: value,
|
||||
});
|
||||
setValues({
|
||||
...values,
|
||||
company: null,
|
||||
companyName: value
|
||||
}, true)
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
setCompany(value);
|
||||
setValues({
|
||||
...values,
|
||||
company: value as Company,
|
||||
companyName: value.name,
|
||||
companyNip: value.nip,
|
||||
}, true)
|
||||
} else {
|
||||
setCompany(emptyCompany);
|
||||
setValues({
|
||||
...values,
|
||||
company: null,
|
||||
companyName: "",
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,24 +175,22 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<Autocomplete options={ sampleCompanies }
|
||||
getOptionLabel={ option => option.name }
|
||||
getOptionLabel={ option => typeof option === "string" ? option : option.name }
|
||||
renderOption={ company => <CompanyItem company={ company }/> }
|
||||
renderInput={ props => <TextField { ...props } label={ "Nazwa firmy" } fullWidth/> }
|
||||
onChange={ handleCompanyChange } value={ company }
|
||||
renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.company-name") } fullWidth
|
||||
error={ touched.companyName && !!errors.companyName } helperText={ touched.companyName && errors.companyName }/> }
|
||||
onChange={ handleCompanyChange } value={ values.company || values.companyName }
|
||||
freeSolo
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 4 }>
|
||||
<TextField label={ "NIP" } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
|
||||
<Field label={ t("forms.internship.fields.nip") } fullWidth name="companyNip" disabled={ !canEdit } component={ TextFieldFormik }/>
|
||||
</Grid>
|
||||
{/*<Grid item md={ 8 }>*/}
|
||||
{/* <TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>*/}
|
||||
{/*</Grid>*/}
|
||||
</Grid>
|
||||
<Typography variant="subtitle1" className="subsection-header">Zakładowy opiekun praktyki</Typography>
|
||||
<MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
|
||||
<Typography variant="subtitle1" className="subsection-header">Oddział</Typography>
|
||||
<BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
|
||||
<Typography variant="subtitle1" className="subsection-header">{ t("internship.mentor") }</Typography>
|
||||
<MentorForm/>
|
||||
<Typography variant="subtitle1" className="subsection-header">{ t("internship.office") }</Typography>
|
||||
<BranchForm/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,28 +1,14 @@
|
||||
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
Input,
|
||||
InputLabel,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import React, { HTMLProps, useMemo, useState } from "react";
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core";
|
||||
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 { Course, Internship, InternshipType, internshipTypeLabels } from "@/data";
|
||||
import { Company, Internship, InternshipType, internshipTypeLabels, Office, Student } from "@/data";
|
||||
import { Nullable } from "@/helpers";
|
||||
import moment, { Moment } from "moment";
|
||||
import { computeWorkingHours } from "@/utils/date";
|
||||
import { Autocomplete } from "@material-ui/lab";
|
||||
import { formFieldProps } from "@/forms/helpers";
|
||||
import { emptyInternship } from "@/provider/dummy/internship";
|
||||
import { InternshipProposalActions, useDispatch } from "@/state/actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -30,15 +16,61 @@ import { useSelector } from "react-redux";
|
||||
import { AppState } from "@/state/reducer";
|
||||
import { Link as RouterLink, useHistory } from "react-router-dom";
|
||||
import { route } from "@/routing";
|
||||
import { useProxyState } from "@/hooks";
|
||||
import { getInternshipProposal } from "@/state/reducer/proposal";
|
||||
import { Actions } from "@/components";
|
||||
import { Field, Form, Formik, useFormikContext } from "formik";
|
||||
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 = {
|
||||
startDate: Moment | null;
|
||||
endDate: Moment | null;
|
||||
hours: number | "";
|
||||
workingHours: number;
|
||||
companyName: string;
|
||||
companyNip: string;
|
||||
city: string;
|
||||
postalCode: string;
|
||||
country: string;
|
||||
street: string;
|
||||
building: string;
|
||||
mentorFirstName: string;
|
||||
mentorLastName: string;
|
||||
mentorEmail: string;
|
||||
mentorPhone: string;
|
||||
kindOther: string | null;
|
||||
|
||||
export type InternshipFormSectionProps = {
|
||||
internship: Nullable<Internship>,
|
||||
onChange: (internship: Nullable<Internship>) => void,
|
||||
// relations
|
||||
kind: InternshipType | null;
|
||||
company: Company | null;
|
||||
office: Office | null;
|
||||
student: Student;
|
||||
}
|
||||
|
||||
const emptyInternshipValues: InternshipFormValues = {
|
||||
building: "",
|
||||
city: "",
|
||||
company: null,
|
||||
companyName: "",
|
||||
companyNip: "",
|
||||
country: "",
|
||||
street: "",
|
||||
endDate: null,
|
||||
hours: "",
|
||||
kind: null,
|
||||
kindOther: "",
|
||||
mentorEmail: "",
|
||||
mentorFirstName: "",
|
||||
mentorLastName: "",
|
||||
mentorPhone: "",
|
||||
office: null,
|
||||
postalCode: "",
|
||||
startDate: null,
|
||||
student: sampleStudent,
|
||||
workingHours: 40,
|
||||
}
|
||||
|
||||
export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
|
||||
@ -52,144 +84,250 @@ export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType }
|
||||
)
|
||||
}
|
||||
|
||||
const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionProps) => {
|
||||
const fieldProps = formFieldProps(internship, onChange);
|
||||
|
||||
const course = internship.intern?.course as Course;
|
||||
const InternshipProgramForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item md={ 4 }>
|
||||
<Autocomplete renderInput={ props => <TextField { ...props } label="Rodzaj praktyki/umowy" 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 }
|
||||
renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option }/> }
|
||||
options={ Object.values(InternshipType) as InternshipType[] }
|
||||
disableClearable
|
||||
{ ...fieldProps("type", (event, value) => value) as any }
|
||||
value={ values.kind || undefined }
|
||||
onChange={ (_, value) => setFieldValue("kind", value) }
|
||||
onBlur={ handleBlur }
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 8 }>
|
||||
{ internship.type === InternshipType.Other && <TextField label={ "Inny - Wprowadź" } fullWidth/> }
|
||||
{
|
||||
values.kind === InternshipType.Other &&
|
||||
<Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />
|
||||
}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionProps) => {
|
||||
const [startDate, setStartDate] = useProxyState<Moment | null>(internship.startDate, value => onChange({ ...internship, startDate: value }));
|
||||
const [endDate, setEndDate] = useProxyState<Moment | null>(internship.endDate, value => onChange({ ...internship, endDate: value }));
|
||||
const InternshipDurationForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
values: { startDate, endDate, workingHours },
|
||||
errors,
|
||||
touched,
|
||||
setFieldTouched,
|
||||
setFieldValue
|
||||
} = useFormikContext<InternshipFormValues>();
|
||||
|
||||
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 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 (
|
||||
<Grid container>
|
||||
<Grid item md={ 6 }>
|
||||
<DatePicker value={ startDate } onChange={ setStartDate }
|
||||
<DatePicker value={ startDate } onChange={ value => setFieldValue("startDate", value) }
|
||||
format="DD MMMM yyyy"
|
||||
clearable disableToolbar fullWidth
|
||||
variant="inline" label={ "Data rozpoczęcia praktyki" }
|
||||
disableToolbar fullWidth
|
||||
variant="inline" label={ t("forms.internship.fields.start-date") }
|
||||
minDate={ moment() }
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 6 }>
|
||||
<DatePicker value={ endDate } onChange={ setEndDate }
|
||||
<DatePicker value={ endDate } onChange={ value => setFieldValue("endDate", value) }
|
||||
format="DD MMMM yyyy"
|
||||
clearable disableToolbar fullWidth
|
||||
variant="inline" label={ "Data zakończenia praktyki" }
|
||||
disableToolbar fullWidth
|
||||
variant="inline" label={ t("forms.internship.fields.end-date") }
|
||||
minDate={ startDate || moment() }
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 4 }>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Wymiar etatu</InputLabel>
|
||||
<Input value={ workingHours }
|
||||
onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
|
||||
fullWidth
|
||||
/>
|
||||
<FormHelperText>Liczba godzin w tygodniu roboczym</FormHelperText>
|
||||
</FormControl>
|
||||
<Field component={ TextFieldFormik }
|
||||
name="workingHours"
|
||||
label={ t("forms.internship.fields.working-hours") }
|
||||
helperText={ t("forms.internship.help.working-hours") }
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 4 }>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Łączna liczba godzin</InputLabel>
|
||||
<Input value={ hours || "" }
|
||||
<TextField fullWidth
|
||||
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) }
|
||||
fullWidth
|
||||
/>
|
||||
</FormControl>
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={ 4 }>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Liczba tygodni</InputLabel>
|
||||
<Input value={ weeks || "" }
|
||||
<TextField fullWidth label={ t("forms.internship.fields.weeks") }
|
||||
value={ weeks || "" }
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
<FormHelperText>Wyliczona automatycznie</FormHelperText>
|
||||
</FormControl>
|
||||
helperText={ t("forms.internship.help.weeks") }
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
|
||||
const initialInternshipState = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || {
|
||||
type InternshipConverterContext = {
|
||||
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,
|
||||
office: null,
|
||||
company: null,
|
||||
mentor: null,
|
||||
intern: sampleStudent
|
||||
});
|
||||
|
||||
const [internship, setInternship] = useState<Nullable<Internship>>(initialInternshipState)
|
||||
const edition = useSelector<AppState, Edition>(state => state.edition as Edition);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const validationSchema = Yup.object<Partial<InternshipFormValues>>({
|
||||
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(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"))
|
||||
})
|
||||
})
|
||||
|
||||
const values = converter.transform(initialInternship);
|
||||
|
||||
const handleSubmit = (values: InternshipFormValues) => {
|
||||
setConfirmDialogOpen(false);
|
||||
|
||||
dispatch({ type: InternshipProposalActions.Send, internship: internship as Internship });
|
||||
dispatch({
|
||||
type: InternshipProposalActions.Send,
|
||||
internship: converter.reverseTransform(values, {
|
||||
internship: initialInternship as Internship,
|
||||
}) as Internship
|
||||
});
|
||||
|
||||
history.push(route("home"))
|
||||
}
|
||||
|
||||
const handleSubmitConfirmation = () => {
|
||||
setConfirmDialogOpen(true);
|
||||
}
|
||||
const InnerForm = () => {
|
||||
const { submitForm, validateForm } = useFormikContext();
|
||||
|
||||
const handleCancel = () => {
|
||||
setConfirmDialogOpen(false);
|
||||
}
|
||||
const handleSubmitConfirmation = async () => {
|
||||
const errors = await validateForm();
|
||||
|
||||
return (
|
||||
<div className="internship-form">
|
||||
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>
|
||||
<StudentForm student={ sampleStudent }/>
|
||||
<StudentForm />
|
||||
<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>
|
||||
<InternshipDurationForm internship={ internship } onChange={ setInternship }/>
|
||||
<InternshipDurationForm />
|
||||
<Typography variant="h3" className="section-header">{ t('internship.sections.place') }</Typography>
|
||||
<CompanyForm internship={ internship } onChange={ setInternship }/>
|
||||
<CompanyForm />
|
||||
<Actions>
|
||||
<Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button>
|
||||
|
||||
@ -204,10 +342,21 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={ handleCancel }>{ t('cancel') }</Button>
|
||||
<Button color="primary" autoFocus onClick={ handleSubmit }>{ t('confirm') }</Button>
|
||||
<Button color="primary" autoFocus onClick={ submitForm }>{ t('confirm') }</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik initialValues={ values }
|
||||
onSubmit={ handleSubmit }
|
||||
validationSchema={ validationSchema }
|
||||
validateOnChange={ false }
|
||||
validateOnBlur={ true }
|
||||
>
|
||||
<InnerForm />
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,44 +1,44 @@
|
||||
import { Course, Student } from "@/data";
|
||||
import { Course } from "@/data";
|
||||
import { Button, Grid, TextField } from "@material-ui/core";
|
||||
import { Alert, Autocomplete } from "@material-ui/lab";
|
||||
import React from "react";
|
||||
import { sampleCourse } from "@/provider/dummy/student";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormikContext } from "formik";
|
||||
import { InternshipFormValues } from "@/forms/internship";
|
||||
|
||||
type StudentFormProps = {
|
||||
student: Student
|
||||
}
|
||||
export const StudentForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const { values: { student } } = useFormikContext<InternshipFormValues>();
|
||||
|
||||
export const StudentForm = ({ student }: StudentFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Grid container>
|
||||
<Grid item md={4}>
|
||||
<TextField label="Imię" value={ student.name } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item md={4}>
|
||||
<TextField label="Nazwisko" value={ student.surname } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item md={4}>
|
||||
<TextField label="Nr Indeksu" value={ student.albumNumber } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item md={9}>
|
||||
<Autocomplete
|
||||
getOptionLabel={ (course: Course) => course.name }
|
||||
renderInput={ props => <TextField { ...props } label={ "Kierunek" } fullWidth/> }
|
||||
options={[ sampleCourse ]}
|
||||
value={ student.course }
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={3}>
|
||||
<TextField label="Semestr" value={ student.semester } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Alert severity="warning" action={ <Button color="inherit" size="small">skontaktuj się z opiekunem</Button> }>
|
||||
Powyższe dane nie są poprawne?
|
||||
</Alert>
|
||||
</Grid>
|
||||
return <>
|
||||
<Grid container>
|
||||
<Grid item md={4}>
|
||||
<TextField label={ t("forms.internship.fields.first-name") } value={ student.name } disabled fullWidth/>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
<Grid item md={4}>
|
||||
<TextField label={ t("forms.internship.fields.last-name") } value={ student.surname } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item md={4}>
|
||||
<TextField label={ t("forms.internship.fields.album") } value={ student.albumNumber } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item md={9}>
|
||||
<Autocomplete
|
||||
getOptionLabel={ (course: Course) => course.name }
|
||||
renderInput={ props => <TextField { ...props } label={ t("forms.internship.fields.course") } fullWidth/> }
|
||||
options={[ sampleCourse ]}
|
||||
value={ student.course }
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item md={3}>
|
||||
<TextField label={ t("forms.internship.fields.semester") } value={ student.semester } disabled fullWidth/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Alert severity="warning" action={ <Button color="inherit" size="small">skontaktuj się z opiekunem</Button> }>
|
||||
Powyższe dane nie są poprawne?
|
||||
</Alert>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>;
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
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,7 +4,7 @@ import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import "moment/locale/pl"
|
||||
import "moment/locale/en-gb"
|
||||
import moment, { isDuration, isMoment } from "moment";
|
||||
import moment, { isDuration, isMoment, unitOfTime } from "moment";
|
||||
import { convertToRoman } from "@/utils/numbers";
|
||||
|
||||
const resources = {
|
||||
@ -22,6 +22,7 @@ i18n
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "pl",
|
||||
compatibilityJSON: "v3",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
format: (value, format, lng) => {
|
||||
@ -34,7 +35,11 @@ i18n
|
||||
}
|
||||
|
||||
if (isDuration(value)) {
|
||||
return value.locale(lng || "pl").humanize();
|
||||
if (format === "humanize") {
|
||||
return value.locale(lng || "pl").humanize();
|
||||
} else {
|
||||
return Math.floor(value.locale(lng || "pl").as(format as unitOfTime.Base));
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
|
@ -12,7 +12,7 @@ export const SubmitPlanPage = () => {
|
||||
return <Page title={ t("steps.plan.submit") }>
|
||||
<Page.Header maxWidth="md">
|
||||
<Page.Breadcrumbs>
|
||||
<Link component={ RouterLink } to={ route("home") }>{ t('sections.my-internship.header') }</Link>
|
||||
<Link component={ RouterLink } to={ route("home") }>{ t('pages.my-internship.header') }</Link>
|
||||
<Typography color="textPrimary">{ t("steps.plan.submit") }</Typography>
|
||||
</Page.Breadcrumbs>
|
||||
<Page.Title>{ t("steps.plan.submit") }</Page.Title>
|
||||
|
@ -1,9 +1,22 @@
|
||||
import { Page } from "@/pages/base";
|
||||
import { Container, Link, Typography } from "@material-ui/core";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Container,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Link,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import { Link as RouterLink, useHistory } from "react-router-dom";
|
||||
import { route } from "@/routing";
|
||||
import { InternshipForm } from "@/forms/internship";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { ProposalComment } from "@/pages/steps/proposal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ProposalPreview } from "@/components/proposalPreview";
|
||||
@ -11,15 +24,21 @@ import { useSelector } from "react-redux";
|
||||
import { Internship } from "@/data";
|
||||
import { AppState } from "@/state/reducer";
|
||||
import { internshipSerializationTransformer } from "@/serialization";
|
||||
import { Actions } from "@/components";
|
||||
import { InternshipProposalActions, useDispatch } from "@/state/actions";
|
||||
import { MenuDown, StickerCheckOutline, StickerRemoveOutline } from "mdi-material-ui/index";
|
||||
import { useVerticalSpacing } from "@/styles";
|
||||
|
||||
export const InternshipProposalFormPage = () => {
|
||||
return <Page title="Zgłoszenie praktyki">
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <Page title={ t("pages.proposal-form.header") }>
|
||||
<Page.Header maxWidth="md">
|
||||
<Page.Breadcrumbs>
|
||||
<Link component={ RouterLink } to={ route("home") }>Moja praktyka</Link>
|
||||
<Typography color="textPrimary">Zgłoszenie praktyki</Typography>
|
||||
<Link component={ RouterLink } to={ route("home") }>{ t("pages.my-internship.header") }</Link>
|
||||
<Typography color="textPrimary">{ t("pages.proposal-form.header") }</Typography>
|
||||
</Page.Breadcrumbs>
|
||||
<Page.Title>Zgłoszenie praktyki</Page.Title>
|
||||
<Page.Title>{ t("pages.proposal-form.header") }</Page.Title>
|
||||
</Page.Header>
|
||||
<Container maxWidth={ "md" }>
|
||||
<ProposalComment />
|
||||
@ -32,6 +51,57 @@ export const InternshipProposalPreviewPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const proposal = useSelector<AppState, Internship | null>(state => state.proposal.proposal && internshipSerializationTransformer.reverseTransform(state.proposal.proposal));
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
const [isDiscardModalOpen, setDiscardModelOpen] = useState<boolean>(false);
|
||||
const [isAcceptModalOpen, setAcceptModelOpen] = useState<boolean>(false);
|
||||
|
||||
const [comment, setComment] = useState<string>("");
|
||||
const [menuAnchor, setMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleAccept = () => {
|
||||
dispatch({ type: InternshipProposalActions.Approve, comment });
|
||||
history.push(route("home"));
|
||||
}
|
||||
|
||||
const handleDiscard = () => {
|
||||
dispatch({ type: InternshipProposalActions.Decline, comment });
|
||||
history.push(route("home"));
|
||||
}
|
||||
|
||||
const handleAcceptModalClose = () => {
|
||||
setAcceptModelOpen(false);
|
||||
}
|
||||
|
||||
const handleDiscardModalClose = () => {
|
||||
setDiscardModelOpen(false);
|
||||
}
|
||||
|
||||
const handleDiscardAction = () => {
|
||||
setDiscardModelOpen(true);
|
||||
}
|
||||
|
||||
const handleAcceptMenuOpen = (ev: React.MouseEvent<HTMLElement>) => {
|
||||
setMenuAnchor(ev.currentTarget);
|
||||
}
|
||||
|
||||
const handleAcceptMenuClose = () => {
|
||||
setMenuAnchor(null);
|
||||
}
|
||||
|
||||
const handleAcceptWithComment = () => {
|
||||
setAcceptModelOpen(true);
|
||||
setMenuAnchor(null);
|
||||
}
|
||||
|
||||
const handleAcceptWithoutComment = () => {
|
||||
dispatch({ type: InternshipProposalActions.Approve, comment: null });
|
||||
history.push(route("home"));
|
||||
}
|
||||
|
||||
const classes = useVerticalSpacing(3);
|
||||
|
||||
return <Page title={ t("") }>
|
||||
<Page.Header maxWidth="md">
|
||||
<Page.Breadcrumbs>
|
||||
@ -40,10 +110,64 @@ export const InternshipProposalPreviewPage = () => {
|
||||
</Page.Breadcrumbs>
|
||||
<Page.Title>Moje zgłoszenie</Page.Title>
|
||||
</Page.Header>
|
||||
<Container maxWidth={ "md" }>
|
||||
<Container maxWidth={ "md" } className={ classes.root }>
|
||||
<ProposalComment />
|
||||
{ proposal && <ProposalPreview proposal={ proposal } /> }
|
||||
|
||||
<Actions>
|
||||
<Button component={ RouterLink } to={ route("home") } variant="contained" color="primary">
|
||||
{ t('go-back') }
|
||||
</Button>
|
||||
|
||||
<ButtonGroup color="primary" variant="contained">
|
||||
<Button onClick={ handleAcceptWithoutComment } startIcon={ <StickerCheckOutline /> }>
|
||||
{ t('accept-without-comments') }
|
||||
</Button>
|
||||
<Button size="small" onClick={ handleAcceptMenuOpen }><MenuDown /></Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Menu open={ !!menuAnchor } anchorEl={ menuAnchor } onClose={ handleAcceptMenuClose }>
|
||||
<MenuItem onClick={ handleAcceptWithoutComment }>{ t("accept-without-comments") }</MenuItem>
|
||||
<MenuItem onClick={ handleAcceptWithComment }>{ t("accept-with-comments") }</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Button onClick={ handleDiscardAction } color="secondary" startIcon={ <StickerRemoveOutline /> }>
|
||||
{ t('discard') }
|
||||
</Button>
|
||||
</Actions>
|
||||
</Container>
|
||||
<Dialog open={ isDiscardModalOpen } onClose={ handleDiscardModalClose } maxWidth="md">
|
||||
<DialogTitle>{ t("internship.discard.title") }</DialogTitle>
|
||||
<DialogContent className={ classes.root }>
|
||||
<Typography variant="body1">{ t("internship.discard.info") }</Typography>
|
||||
<TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") } rows={3}/>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={ handleDiscardModalClose }>
|
||||
{ t('cancel') }
|
||||
</Button>
|
||||
<Button onClick={ handleDiscard } color="primary" variant="contained">
|
||||
{ t('confirm') }
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog open={ isAcceptModalOpen } onClose={ handleAcceptModalClose } maxWidth="md">
|
||||
<DialogTitle>{ t("internship.accept.title") }</DialogTitle>
|
||||
<DialogContent className={ classes.root }>
|
||||
<Typography variant="body1">{ t("internship.accept.info") }</Typography>
|
||||
<TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") }/>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={ handleAcceptModalClose }>
|
||||
{ t('cancel') }
|
||||
</Button>
|
||||
<Button onClick={ handleAccept } color="primary" variant="contained">
|
||||
{ t('confirm') }
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Page>
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ export const MainPage = () => {
|
||||
|
||||
return <Page my={ 6 }>
|
||||
<Container>
|
||||
<Typography variant="h2">{ t("sections.my-internship.header") }</Typography>
|
||||
<Typography variant="h2">{ t("pages.my-internship.header") }</Typography>
|
||||
<Stepper orientation="vertical" nonLinear>
|
||||
<Step label={ t('steps.personal-data.header') } completed={ missingStudentData.length === 0 } until={ deadlines.personalData }>
|
||||
{ missingStudentData.length > 0 && <>
|
||||
|
@ -4,5 +4,6 @@ import moment from "moment";
|
||||
export const sampleEdition: Edition = {
|
||||
startDate: moment("2020-07-01"),
|
||||
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;
|
||||
|
||||
export type Serializable<T> = { [K in keyof T]: Simplify<T[K]> }
|
||||
export type Transformer<TFrom, TResult> = {
|
||||
transform(subject: TFrom): TResult;
|
||||
reverseTransform(subject: TResult): TFrom;
|
||||
export type Transformer<TFrom, TResult, TContext = never> = {
|
||||
transform(subject: TFrom, context?: TContext): TResult;
|
||||
reverseTransform(subject: TResult, context?: TContext): TFrom;
|
||||
}
|
||||
|
||||
export type SerializationTransformer<T, TSerialized = Serializable<T>> = Transformer<T, TSerialized>
|
||||
|
@ -8,6 +8,9 @@ export const studentTheme = responsiveFontSizes(createMuiTheme({
|
||||
},
|
||||
MuiContainer: {
|
||||
maxWidth: "md"
|
||||
},
|
||||
MuiTextField: {
|
||||
variant: "outlined",
|
||||
}
|
||||
},
|
||||
palette: {
|
||||
|
@ -11,6 +11,41 @@ left: '{{ left, humanize }} left'
|
||||
|
||||
dropzone: "Drag and drop a file here or click to choose"
|
||||
|
||||
forms:
|
||||
internship:
|
||||
fields:
|
||||
start-date: Internship start date
|
||||
end-date: Internship end date
|
||||
working-hours: Working time
|
||||
total-hours: Total hours
|
||||
weeks: Total weeks
|
||||
first-name: First name
|
||||
last-name: Last name
|
||||
album: Album number
|
||||
course: Course
|
||||
semester: Semester
|
||||
kind: Contract type
|
||||
kind-other: Other - please fill
|
||||
company-name: Company name
|
||||
nip: NIP
|
||||
e-mail: e-mail address
|
||||
phone: Phone number
|
||||
city: City
|
||||
postal-code: Postal code
|
||||
country: Country
|
||||
street: Street
|
||||
building: Building
|
||||
help:
|
||||
weeks: Calculated automatically
|
||||
working-hours: Total working hours in working week
|
||||
send-confirmation: >
|
||||
Po wysłaniu zgłoszenia nie będzie możliwości jego zmiany do czasu zweryfikowania go przez pełnomocnika ds. Twojego
|
||||
kierunku. Czy na pewno chcesz wysłać zgłoszenie praktyki w tej formie?
|
||||
plan:
|
||||
instructions: >
|
||||
Wypełnij i zeskanuj Indywidualny program Praktyk a następnie wyślij go z pomocą tego formularza. <więcej informacji>
|
||||
dropzone-help: Skan dokumentu w formacie PDF
|
||||
|
||||
student:
|
||||
name: first name
|
||||
surname: last name
|
||||
@ -19,7 +54,41 @@ student:
|
||||
email: e-mail
|
||||
albumNumber: album number
|
||||
|
||||
sections:
|
||||
internship:
|
||||
intern:
|
||||
semester: semesetr {{ semester, roman }}
|
||||
album: "album number {{ album }}"
|
||||
date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}"
|
||||
duration: "{{ duration, weeks }} week"
|
||||
duration_plural: "{{ duration, weeks }} weeks"
|
||||
hours: "{{ hours }} hour"
|
||||
hours_plural: "{{ hours }} hours"
|
||||
office: "Office / Address"
|
||||
mentor: "Internship mentor"
|
||||
address:
|
||||
city: "{{ city }}, {{ country }}"
|
||||
street: "{{ postalCode }}, {{ street }} {{ building }}"
|
||||
sections:
|
||||
intern-info: "Intern personal data"
|
||||
duration: "Internship duration"
|
||||
place: "Internship place"
|
||||
kind: "Contract and programme"
|
||||
mentor: "Internship mentor"
|
||||
discard:
|
||||
title: "Discard internship proposal"
|
||||
info: "This comments will be presented to student in order to fix errors."
|
||||
accept:
|
||||
title: "Accept internship proposal"
|
||||
info: "This comments will be presented to student."
|
||||
|
||||
submission:
|
||||
status:
|
||||
awaiting: "sent, awaiting verification"
|
||||
accepted: "accepted"
|
||||
declined: "needs correction"
|
||||
draft: "draft"
|
||||
|
||||
pages:
|
||||
my-internship:
|
||||
header: "My internship"
|
||||
|
||||
@ -29,10 +98,22 @@ steps:
|
||||
info: >
|
||||
Your profile is incomplete. In order to continue your internship you have to supply information given below. In
|
||||
case of problem with providing those information - please contact with your internship coordinator of your course.
|
||||
form: "Add missing data"
|
||||
internship-proposal:
|
||||
header: "Internship proposal"
|
||||
form: "Internship proposal form"
|
||||
info: ""
|
||||
info:
|
||||
draft: >
|
||||
Przed podjęciem praktyki należy ją zgłosić. (TODO)
|
||||
awaiting: >
|
||||
Twoje zgłoszenie musi zostać zweryfikowane i zatwierdzone. Po weryfikacji zostaniesz poinformowany o
|
||||
akceptacji bądź konieczności wprowadzenia zmian.
|
||||
accepted: >
|
||||
Twoje zgłoszenie zostało zweryfikowane i zaakceptowane.
|
||||
declined: >
|
||||
Twoje zgłoszenie zostało zweryfikowane i odrzucone. Popraw zgłoszone uwagi i wyślij zgłoszenie ponownie. W razie
|
||||
pytań możesz również skontaktować się z pełnomocnikiem ds. praktyk Twojego kierunku.
|
||||
action: "Send internship proposal"
|
||||
plan:
|
||||
header: "Individual Internship Plan"
|
||||
info: ""
|
||||
|
@ -20,15 +20,46 @@ comments: Zgłoszone uwagi
|
||||
send-again: wyślij ponownie
|
||||
cancel: anuluj
|
||||
|
||||
accept: zaakceptuj
|
||||
accept-with-comments: zaakceptuj z uwagami
|
||||
accept-without-comments: zaakceptuj bez uwag
|
||||
discard: zgłoś uwagi
|
||||
|
||||
dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
|
||||
|
||||
sections:
|
||||
pages:
|
||||
my-internship:
|
||||
header: "Moja praktyka"
|
||||
proposal-form:
|
||||
header: "Propose internship"
|
||||
|
||||
forms:
|
||||
internship:
|
||||
fields:
|
||||
start-date: Data rozpoczęcia praktyki
|
||||
end-date: Data zakończenia praktyki
|
||||
working-hours: Wymiar etatu
|
||||
total-hours: Łączna liczba godzin
|
||||
weeks: Liczba tygodni
|
||||
first-name: Imię
|
||||
last-name: Nazwisko
|
||||
album: Numer albumu
|
||||
course: Kierunek
|
||||
semester: Semestr
|
||||
kind: Rodzaj praktyki/umowy
|
||||
kind-other: Inny - wprowadź
|
||||
company-name: Nazwa firmy
|
||||
nip: NIP
|
||||
e-mail: Kontaktowy adres e-mail
|
||||
phone: Numer telefonu
|
||||
city: Miasto
|
||||
postal-code: Kod pocztowy
|
||||
country: Kraj
|
||||
street: Ulica
|
||||
building: Nr budynku
|
||||
help:
|
||||
weeks: Wartość wyliczana automatycznie
|
||||
working-hours: Liczba godzin w tygodniu roboczym
|
||||
send-confirmation: >
|
||||
Po wysłaniu zgłoszenia nie będzie możliwości jego zmiany do czasu zweryfikowania go przez pełnomocnika ds. Twojego
|
||||
kierunku. Czy na pewno chcesz wysłać zgłoszenie praktyki w tej formie?
|
||||
@ -57,9 +88,14 @@ internship:
|
||||
semester: semestr {{ semester, roman }}
|
||||
album: "numer albumu {{ album }}"
|
||||
date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}"
|
||||
duration: "{{ duration, humanize }}"
|
||||
hours: "{{ hours }} godzin"
|
||||
duration_2: "{{ duration, weeks }} tygodni"
|
||||
duration_0: "{{ duration, weeks }} tydzień"
|
||||
duration_1: "{{ duration, weeks }} tygodnie"
|
||||
hours_2: "{{ hours }} godzin"
|
||||
hours_0: "{{ hours }} godzina"
|
||||
hours_1: "{{ hours }} godziny"
|
||||
office: "Oddział / adres"
|
||||
mentor: "Zakładowy opiekun praktyki"
|
||||
address:
|
||||
city: "{{ city }}, {{ country }}"
|
||||
street: "{{ postalCode }}, {{ street }} {{ building }}"
|
||||
@ -69,7 +105,12 @@ internship:
|
||||
place: "Miejsce odbywania praktyki"
|
||||
kind: "Rodzaj i program praktyki"
|
||||
mentor: "Zakładowy opiekun praktyki"
|
||||
|
||||
discard:
|
||||
title: "Odrzuć zgłoszenie praktyki"
|
||||
info: "Poniższa informacja zostanie przekazana praktykantowi w celu poprawy zgłoszenia."
|
||||
accept:
|
||||
title: "Zaakceptuj zgłoszenie praktyki"
|
||||
info: "Poniższa informacja zostanie przekazana praktykantowi."
|
||||
|
||||
steps:
|
||||
personal-data:
|
||||
@ -118,4 +159,11 @@ steps:
|
||||
instructions: >
|
||||
papierki do podpisania...
|
||||
|
||||
validation:
|
||||
required: "To pole jest wymagane"
|
||||
email: "Wprowadź poprawny adres e-mail"
|
||||
phone: "Wprowadź poprawny numer telefonu"
|
||||
internship:
|
||||
minimum-hours: "Minimalna liczba godzin wynosi {{ hours }}"
|
||||
|
||||
contact-coordinator: "Skontaktuj się z koordynatorem"
|
||||
|
87
yarn.lock
87
yarn.lock
@ -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"
|
||||
@ -3170,6 +3182,11 @@ deep-equal@^1.0.1:
|
||||
object-keys "^1.1.1"
|
||||
regexp.prototype.flags "^1.2.0"
|
||||
|
||||
deepmerge@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
||||
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
||||
|
||||
default-gateway@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
||||
@ -3914,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"
|
||||
@ -3954,6 +3976,25 @@ 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"
|
||||
integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ==
|
||||
dependencies:
|
||||
deepmerge "^2.1.1"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
lodash "^4.17.14"
|
||||
lodash-es "^4.17.14"
|
||||
react-fast-compare "^2.0.1"
|
||||
scheduler "^0.18.0"
|
||||
tiny-warning "^1.0.2"
|
||||
tslib "^1.10.0"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
@ -5364,6 +5405,11 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
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==
|
||||
|
||||
lodash._reinterpolate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
@ -7192,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"
|
||||
@ -7400,6 +7451,11 @@ react-error-overlay@^6.0.7:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||
|
||||
react-fast-compare@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-i18next@^11.7.0:
|
||||
version "11.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.7.0.tgz#f27c4c237a274e007a48ac1210db83e33719908b"
|
||||
@ -7888,6 +7944,14 @@ sax@~1.2.4:
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
scheduler@^0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
|
||||
integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
scheduler@^0.19.1:
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
|
||||
@ -8554,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"
|
||||
@ -8696,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"
|
||||
@ -9469,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"
|
||||
|
Loading…
Reference in New Issue
Block a user