master #7
@ -33,6 +33,7 @@
 | 
				
			|||||||
    "i18next": "^19.6.0",
 | 
					    "i18next": "^19.6.0",
 | 
				
			||||||
    "i18next-browser-languagedetector": "^5.0.0",
 | 
					    "i18next-browser-languagedetector": "^5.0.0",
 | 
				
			||||||
    "material-ui-dropzone": "^3.3.0",
 | 
					    "material-ui-dropzone": "^3.3.0",
 | 
				
			||||||
 | 
					    "mdi-material-ui": "^6.17.0",
 | 
				
			||||||
    "moment": "^2.26.0",
 | 
					    "moment": "^2.26.0",
 | 
				
			||||||
    "node-sass": "^4.14.1",
 | 
					    "node-sass": "^4.14.1",
 | 
				
			||||||
    "optimize-css-assets-webpack-plugin": "5.0.3",
 | 
					    "optimize-css-assets-webpack-plugin": "5.0.3",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/components/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/components/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					export * from "./actions"
 | 
				
			||||||
 | 
					export * from "./step"
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/components/step.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/components/step.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import moment, { Moment } from "moment";
 | 
				
			||||||
 | 
					import { Box, Step as StepperStep, StepContent, StepLabel, StepProps as StepperStepProps, Typography } from "@material-ui/core";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import React, { ReactChild, useMemo } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StepProps = StepperStepProps & {
 | 
				
			||||||
 | 
					    until?: Moment;
 | 
				
			||||||
 | 
					    completedOn?: Moment;
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					    state?: ReactChild | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** this roughly translates into completed */
 | 
				
			||||||
 | 
					    accepted?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** this roughly translates into error */
 | 
				
			||||||
 | 
					    declined?: boolean;
 | 
				
			||||||
 | 
					    sent?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = moment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Step = ({ until, label, completedOn, children, accepted = false, declined = false, completed = false, state = null, ...props }: StepProps) => {
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isLate = useMemo(() => until?.isBefore(completedOn || now), [completedOn, until]);
 | 
				
			||||||
 | 
					    const left = useMemo(() => moment.duration(now.diff(until)), [until]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <StepperStep { ...props } completed={ completed }>
 | 
				
			||||||
 | 
					        <StepLabel error={ declined }>
 | 
				
			||||||
 | 
					            { label }
 | 
				
			||||||
 | 
					            { until && <Box>
 | 
				
			||||||
 | 
					                { state && <>
 | 
				
			||||||
 | 
					                    <Typography variant="subtitle2" display="inline">{ state }</Typography>
 | 
				
			||||||
 | 
					                    <Typography variant="subtitle2" display="inline" color="textSecondary"> • </Typography>
 | 
				
			||||||
 | 
					                </> }
 | 
				
			||||||
 | 
					                <Typography variant="subtitle2" color="textSecondary" display="inline">
 | 
				
			||||||
 | 
					                    { t('until', { date: until }) }
 | 
				
			||||||
 | 
					                    { isLate && <Typography color="error" display="inline"
 | 
				
			||||||
 | 
					                                            variant="body2"> - { t('late', { by: moment.duration(now.diff(until)) }) }</Typography> }
 | 
				
			||||||
 | 
					                    { !isLate && !completed && <Typography display="inline" variant="body2"> - { t('left', { left: left }) }</Typography> }
 | 
				
			||||||
 | 
					                </Typography>
 | 
				
			||||||
 | 
					            </Box> }
 | 
				
			||||||
 | 
					        </StepLabel>
 | 
				
			||||||
 | 
					        { children && <StepContent>{ children }</StepContent> }
 | 
				
			||||||
 | 
					    </StepperStep>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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 { Company } from "@/data/company";
 | 
					import { BranchOffice, Company } from "@/data/company";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum InternshipType {
 | 
					export enum InternshipType {
 | 
				
			||||||
    FreeInternship = "FreeInternship",
 | 
					    FreeInternship = "FreeInternship",
 | 
				
			||||||
@ -61,8 +61,10 @@ export interface Internship extends Identifiable {
 | 
				
			|||||||
    endDate: Moment;
 | 
					    endDate: Moment;
 | 
				
			||||||
    isAccepted: boolean;
 | 
					    isAccepted: boolean;
 | 
				
			||||||
    lengthInWeeks: number;
 | 
					    lengthInWeeks: number;
 | 
				
			||||||
 | 
					    hours: number;
 | 
				
			||||||
    mentor: Mentor;
 | 
					    mentor: Mentor;
 | 
				
			||||||
    company: Company;
 | 
					    company: Company;
 | 
				
			||||||
 | 
					    office: BranchOffice;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Mentor {
 | 
					export interface Mentor {
 | 
				
			||||||
@ -71,3 +73,4 @@ export interface Mentor {
 | 
				
			|||||||
    email: string;
 | 
					    email: string;
 | 
				
			||||||
    phone: string | null;
 | 
					    phone: string | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +1,19 @@
 | 
				
			|||||||
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 | 
					import React, { HTMLProps, useMemo } from "react";
 | 
				
			||||||
import { BranchOffice, Company, Course, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress, Mentor } from "@/data";
 | 
					import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress, Mentor } from "@/data";
 | 
				
			||||||
import { sampleCompanies } from "@/provider/dummy";
 | 
					import { sampleCompanies } from "@/provider/dummy";
 | 
				
			||||||
import { Alert, Autocomplete } from "@material-ui/lab";
 | 
					import { Autocomplete } from "@material-ui/lab";
 | 
				
			||||||
import { Button, Grid, TextField, Typography } from "@material-ui/core";
 | 
					import { Grid, TextField, Typography } from "@material-ui/core";
 | 
				
			||||||
import { BoundProperty, formFieldProps } from "./helpers";
 | 
					import { BoundProperty, formFieldProps } from "./helpers";
 | 
				
			||||||
import { InternshipFormSectionProps } from "@/forms/Internship";
 | 
					import { InternshipFormSectionProps } from "@/forms/internship";
 | 
				
			||||||
import { sampleCourse } from "@/provider/dummy/student";
 | 
					 | 
				
			||||||
import { emptyMentor } from "@/provider/dummy/internship";
 | 
					import { emptyMentor } from "@/provider/dummy/internship";
 | 
				
			||||||
 | 
					import { useProxyState } from "@/hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type CompanyFormProps = {} & InternshipFormSectionProps;
 | 
					export type CompanyFormProps = {} & InternshipFormSectionProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type BranchOfficeProps = {
 | 
					export type BranchOfficeProps = {
 | 
				
			||||||
    company: Company,
 | 
					    disabled?: boolean;
 | 
				
			||||||
    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 }>
 | 
				
			||||||
@ -29,11 +29,8 @@ export const OfficeItem = ({ office, ...props }: { office: BranchOffice } & HTML
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BranchForm: React.FC<BranchOfficeProps> = ({ company, disabled = false }) => {
 | 
					export const BranchForm: React.FC<BranchOfficeProps> = ({ value: office, onChange: setOffice, offices = [], disabled = false }) => {
 | 
				
			||||||
    const [office, setOffice] = useState<BranchOffice>(emptyBranchOffice)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
 | 
					    const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
 | 
					    const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleCityChange = (event: any, value: BranchOffice | string | null) => {
 | 
					    const handleCityChange = (event: any, value: BranchOffice | string | null) => {
 | 
				
			||||||
@ -63,13 +60,11 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ company, disabled = fa
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => void (office.id && setOffice(emptyBranchOffice)), [company])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
            <Grid container>
 | 
					            <Grid container>
 | 
				
			||||||
                <Grid item md={ 7 }>
 | 
					                <Grid item md={ 7 }>
 | 
				
			||||||
                    <Autocomplete options={ company?.offices || [] }
 | 
					                    <Autocomplete options={ offices || [] }
 | 
				
			||||||
                                  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 }/> }
 | 
				
			||||||
@ -122,14 +117,12 @@ export const MentorForm = ({ mentor, onMentorChange }: BoundProperty<Mentor, 'on
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ internship, onChange }) => {
 | 
					export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ internship, onChange }) => {
 | 
				
			||||||
    const [company, setCompany] = useState<Company>(emptyCompany);
 | 
					    const [company, setCompany] = useProxyState<Company>(internship.company || emptyCompany, company => onChange({ ...internship, company }));
 | 
				
			||||||
    const [mentor, setMentor] = useState<Mentor>(emptyMentor);
 | 
					    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 canEdit = useMemo(() => !company.id, [company.id]);
 | 
					    const canEdit = useMemo(() => !company.id, [company.id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => onChange({ ...internship, mentor }), [ mentor ]);
 | 
					 | 
				
			||||||
    useEffect(() => onChange({ ...internship, company }), [ company ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const fieldProps = formFieldProps(company, setCompany)
 | 
					    const fieldProps = formFieldProps(company, setCompany)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleCompanyChange = (event: any, value: Company | string | null) => {
 | 
					    const handleCompanyChange = (event: any, value: Company | string | null) => {
 | 
				
			||||||
@ -153,7 +146,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
 | 
				
			|||||||
                                  getOptionLabel={ option => option.name }
 | 
					                                  getOptionLabel={ option => option.name }
 | 
				
			||||||
                                  renderOption={ company => <CompanyItem company={ company }/> }
 | 
					                                  renderOption={ company => <CompanyItem company={ company }/> }
 | 
				
			||||||
                                  renderInput={ props => <TextField { ...props } label={ "Nazwa firmy" } fullWidth/> }
 | 
					                                  renderInput={ props => <TextField { ...props } label={ "Nazwa firmy" } fullWidth/> }
 | 
				
			||||||
                                  onChange={ handleCompanyChange }
 | 
					                                  onChange={ handleCompanyChange } value={ company }
 | 
				
			||||||
                                  freeSolo
 | 
					                                  freeSolo
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
@ -167,7 +160,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = ({ interns
 | 
				
			|||||||
            <Typography variant="subtitle1" className="subsection-header">Zakładowy opiekun praktyki</Typography>
 | 
					            <Typography variant="subtitle1" className="subsection-header">Zakładowy opiekun praktyki</Typography>
 | 
				
			||||||
            <MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
 | 
					            <MentorForm mentor={ mentor } onMentorChange={ setMentor }/>
 | 
				
			||||||
            <Typography variant="subtitle1" className="subsection-header">Oddział</Typography>
 | 
					            <Typography variant="subtitle1" className="subsection-header">Oddział</Typography>
 | 
				
			||||||
            <BranchForm company={ company }/>
 | 
					            <BranchForm value={ office } onChange={ setOffice } offices={ company.offices } />
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ export function formFieldProps<T>(subject: T, update: (value: T) => void, option
 | 
				
			|||||||
    return <P extends keyof T, TArgs extends any[]>(
 | 
					    return <P extends keyof T, TArgs extends any[]>(
 | 
				
			||||||
        field: P,
 | 
					        field: P,
 | 
				
			||||||
        extractor: (...args: TArgs) => T[P] = ((event: DOMEvent<HTMLInputElement>) => event.target.value as unknown as T[P]) as any
 | 
					        extractor: (...args: TArgs) => T[P] = ((event: DOMEvent<HTMLInputElement>) => event.target.value as unknown as T[P]) as any
 | 
				
			||||||
    ) => ({
 | 
					    ): any => ({
 | 
				
			||||||
        [property]: subject[field],
 | 
					        [property]: subject[field],
 | 
				
			||||||
        [event]: (...args: TArgs) => update({
 | 
					        [event]: (...args: TArgs) => update({
 | 
				
			||||||
            ...subject,
 | 
					            ...subject,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,5 @@
 | 
				
			|||||||
import React, { HTMLProps, useMemo, useState } from "react";
 | 
					import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 | 
				
			||||||
import {
 | 
					import { Button, FormControl, FormHelperText, Grid, Input, InputLabel, TextField, Typography } from "@material-ui/core";
 | 
				
			||||||
    FormControl,
 | 
					 | 
				
			||||||
    Grid,
 | 
					 | 
				
			||||||
    Input,
 | 
					 | 
				
			||||||
    InputLabel,
 | 
					 | 
				
			||||||
    Typography,
 | 
					 | 
				
			||||||
    FormHelperText,
 | 
					 | 
				
			||||||
    TextField,
 | 
					 | 
				
			||||||
    FormGroup,
 | 
					 | 
				
			||||||
    FormControlLabel,
 | 
					 | 
				
			||||||
    Checkbox,
 | 
					 | 
				
			||||||
    FormLabel,
 | 
					 | 
				
			||||||
    Button
 | 
					 | 
				
			||||||
} 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";
 | 
				
			||||||
@ -24,6 +11,14 @@ import { computeWorkingHours } from "@/utils/date";
 | 
				
			|||||||
import { Autocomplete } from "@material-ui/lab";
 | 
					import { Autocomplete } from "@material-ui/lab";
 | 
				
			||||||
import { formFieldProps } from "@/forms/helpers";
 | 
					import { formFieldProps } from "@/forms/helpers";
 | 
				
			||||||
import { emptyInternship } from "@/provider/dummy/internship";
 | 
					import { emptyInternship } from "@/provider/dummy/internship";
 | 
				
			||||||
 | 
					import { InternshipProposalActions, useDispatch } from "@/state/actions";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import { AppState } from "@/state/reducer";
 | 
				
			||||||
 | 
					import { useHistory } from "react-router-dom";
 | 
				
			||||||
 | 
					import { route } from "@/routing";
 | 
				
			||||||
 | 
					import { useProxyState } from "@/hooks";
 | 
				
			||||||
 | 
					import { getInternshipProposal } from "@/state/reducer/proposal";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InternshipFormProps = {}
 | 
					export type InternshipFormProps = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -62,25 +57,26 @@ const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionPr
 | 
				
			|||||||
            <Grid item md={8}>
 | 
					            <Grid item md={8}>
 | 
				
			||||||
                { internship.type === InternshipType.Other && <TextField label={"Inny - Wprowadź"} fullWidth/> }
 | 
					                { internship.type === InternshipType.Other && <TextField label={"Inny - Wprowadź"} fullWidth/> }
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
            <Grid item>
 | 
					            {/*<Grid item>*/}
 | 
				
			||||||
                <FormGroup>
 | 
					            {/*    <FormGroup>*/}
 | 
				
			||||||
                    <FormLabel component="legend" className="subsection-header">Realizowane punkty programu praktyk (minimum 3)</FormLabel>
 | 
					            {/*        <FormLabel component="legend" className="subsection-header">Realizowane punkty programu praktyk (minimum 3)</FormLabel>*/}
 | 
				
			||||||
                    { course.possibleProgramEntries.map(entry => {
 | 
					            {/*        { course.possibleProgramEntries.map(entry => {*/}
 | 
				
			||||||
                        return (
 | 
					            {/*            return (*/}
 | 
				
			||||||
                            <FormControlLabel label={ entry.description } key={ entry.id }
 | 
					            {/*                <FormControlLabel label={ entry.description } key={ entry.id }*/}
 | 
				
			||||||
                                              control={ <Checkbox /> }
 | 
					            {/*                                  control={ <Checkbox /> }*/}
 | 
				
			||||||
                            />
 | 
					            {/*                />*/}
 | 
				
			||||||
                        )
 | 
					            {/*            )*/}
 | 
				
			||||||
                    }) }
 | 
					            {/*        }) }*/}
 | 
				
			||||||
                </FormGroup>
 | 
					            {/*    </FormGroup>*/}
 | 
				
			||||||
            </Grid>
 | 
					            {/*</Grid>*/}
 | 
				
			||||||
        </Grid>
 | 
					        </Grid>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const InternshipDurationForm = ({ internship }: InternshipFormSectionProps) => {
 | 
					const InternshipDurationForm = ({ internship, onChange }: InternshipFormSectionProps) => {
 | 
				
			||||||
    const [startDate, setStartDate] = useState<Moment | null>(internship.startDate);
 | 
					    const [startDate, setStartDate] = useProxyState<Moment | null>(internship.startDate, value => onChange({ ...internship, startDate: value }));
 | 
				
			||||||
    const [endDate, setEndDate] = useState<Moment | null>(internship.endDate);
 | 
					    const [endDate, setEndDate] = useProxyState<Moment | null>(internship.endDate, value => onChange({ ...internship, endDate: value }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [overrideHours, setHoursOverride] = useState<number | null>(null)
 | 
					    const [overrideHours, setHoursOverride] = useState<number | null>(null)
 | 
				
			||||||
    const [workingHours, setWorkingHours] = useState<number>(40)
 | 
					    const [workingHours, setWorkingHours] = useState<number>(40)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -89,6 +85,8 @@ const InternshipDurationForm = ({ internship }: InternshipFormSectionProps) => {
 | 
				
			|||||||
    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 / 40) : null, [ hours ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => onChange({ ...internship, hours }), [hours])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Grid container>
 | 
					        <Grid container>
 | 
				
			||||||
            <Grid item md={ 6 }>
 | 
					            <Grid item md={ 6 }>
 | 
				
			||||||
@ -140,7 +138,18 @@ const InternshipDurationForm = ({ internship }: InternshipFormSectionProps) => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
 | 
					export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
 | 
				
			||||||
    const [internship, setInternship] = useState<Nullable<Internship>>({ ...emptyInternship, intern: sampleStudent })
 | 
					    const initialInternshipState = useSelector<AppState, Nullable<Internship>>(state => getInternshipProposal(state.proposal) || { ...emptyInternship, intern: sampleStudent });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [internship, setInternship] = useState<Nullable<Internship>>(initialInternshipState)
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
 | 
					    const history = useHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleSubmit = () => {
 | 
				
			||||||
 | 
					        dispatch({ type: InternshipProposalActions.Send, internship: internship as Internship });
 | 
				
			||||||
 | 
					        history.push(route("home"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="internship-form">
 | 
					        <div className="internship-form">
 | 
				
			||||||
@ -152,7 +161,7 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
 | 
				
			|||||||
            <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
 | 
					            <InternshipDurationForm internship={ internship } onChange={ setInternship }/>
 | 
				
			||||||
            <Typography variant="h3" className="section-header">Miejsce odbywania praktyki</Typography>
 | 
					            <Typography variant="h3" className="section-header">Miejsce odbywania praktyki</Typography>
 | 
				
			||||||
            <CompanyForm internship={ internship } onChange={ setInternship }/>
 | 
					            <CompanyForm internship={ internship } onChange={ setInternship }/>
 | 
				
			||||||
            <Button variant="contained" color="primary">Wyślij</Button>
 | 
					            <Button variant="contained" color="primary" onClick={ handleSubmit }>{ t("confirm") }</Button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
export type Nullable<T> = { [P in keyof T]: T[P] | null }
 | 
					export type Nullable<T> = { [P in keyof T]: T[P] | null }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Partial<T> = { [K in keyof T]?: T[K] }
 | 
				
			||||||
 | 
					export type Dictionary<T> = { [key: string]: T };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Index = string | symbol | number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DOMEvent<TTarget extends EventTarget> extends Event {
 | 
					export interface DOMEvent<TTarget extends EventTarget> extends Event {
 | 
				
			||||||
    target: TTarget;
 | 
					    target: TTarget;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from "./useProxyState"
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/hooks/useProxyState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/hooks/useProxyState.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Dispatch, SetStateAction, useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useProxyState<T>(initial: T, setter: (value: T) => void): [T, Dispatch<SetStateAction<T>>] {
 | 
				
			||||||
 | 
					    const [value, proxy] = useState<T>(initial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => setter(value), [ value ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [value, proxy];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
export * from "./internship/proposal";
 | 
					export * from "./internship/proposal";
 | 
				
			||||||
export * from "./errors/not-found"
 | 
					export * from "./errors/not-found"
 | 
				
			||||||
export * from "./main"
 | 
					export * from "./main"
 | 
				
			||||||
export { Actions } from "@/components/actions";
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { Page } from "@/pages/base";
 | 
				
			|||||||
import { Container, Link, Typography } from "@material-ui/core";
 | 
					import { Container, Link, Typography } from "@material-ui/core";
 | 
				
			||||||
import { Link as RouterLink } from "react-router-dom";
 | 
					import { Link as RouterLink } from "react-router-dom";
 | 
				
			||||||
import { route } from "@/routing";
 | 
					import { route } from "@/routing";
 | 
				
			||||||
import { InternshipForm } from "@/forms/Internship";
 | 
					import { InternshipForm } from "@/forms/internship";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const InternshipProposalPage = () => {
 | 
					export const InternshipProposalPage = () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,45 +1,69 @@
 | 
				
			|||||||
import React, { useMemo } from "react";
 | 
					import React, { useMemo } from "react";
 | 
				
			||||||
import { Page } from "@/pages/base";
 | 
					import { Page } from "@/pages/base";
 | 
				
			||||||
import { Box, Button, Container, Step as StepperStep, StepContent, StepLabel, Stepper, StepProps as StepperStepProps, Typography } from "@material-ui/core";
 | 
					import { Button, Container, Stepper, StepProps, Theme, Typography } from "@material-ui/core";
 | 
				
			||||||
import { Link as RouterLink } from "react-router-dom";
 | 
					import { Link as RouterLink } from "react-router-dom";
 | 
				
			||||||
import { route } from "@/routing";
 | 
					import { route } from "@/routing";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import moment, { Moment } from "moment";
 | 
					 | 
				
			||||||
import { useSelector } from "react-redux";
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
import { AppState } from "@/state/reducer";
 | 
					import { AppState } from "@/state/reducer";
 | 
				
			||||||
import { getMissingStudentData, Student } from "@/data";
 | 
					import { getMissingStudentData, Student } from "@/data";
 | 
				
			||||||
import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 | 
					import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 | 
				
			||||||
import { Description as DescriptionIcon } from "@material-ui/icons"
 | 
					import { Description as DescriptionIcon } from "@material-ui/icons"
 | 
				
			||||||
import { Actions } from "@/components/actions";
 | 
					import { Actions, Step } from "@/components";
 | 
				
			||||||
 | 
					import { getInternshipProposalStatus, InternshipProposalState, InternshipProposalStatus } from "@/state/reducer/proposal";
 | 
				
			||||||
 | 
					import { createStyles, makeStyles } from "@material-ui/core/styles";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type StepProps = StepperStepProps & {
 | 
					
 | 
				
			||||||
    until?: Moment;
 | 
					const getColorByStatus = (status: InternshipProposalStatus, theme: Theme) => {
 | 
				
			||||||
    completedOn?: Moment;
 | 
					    switch (status) {
 | 
				
			||||||
    label: string;
 | 
					        case "awaiting":
 | 
				
			||||||
 | 
					            return theme.palette.info.dark;
 | 
				
			||||||
 | 
					        case "accepted":
 | 
				
			||||||
 | 
					            return theme.palette.success.dark;
 | 
				
			||||||
 | 
					        case "declined":
 | 
				
			||||||
 | 
					            return theme.palette.error.dark;
 | 
				
			||||||
 | 
					        case "draft":
 | 
				
			||||||
 | 
					            return theme.palette.grey["800"];
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return "textPrimary";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const now = moment();
 | 
					const useStatusStyles = makeStyles((theme: Theme) => {
 | 
				
			||||||
 | 
					    const colorByStatusGetter = ({ status }: any) => getColorByStatus(status, theme);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return createStyles({
 | 
				
			||||||
 | 
					        foreground: {
 | 
				
			||||||
 | 
					            color: colorByStatusGetter
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        background: {
 | 
				
			||||||
 | 
					            backgroundColor: colorByStatusGetter
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ProposalStatus = () => {
 | 
				
			||||||
 | 
					    const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(state.proposal))
 | 
				
			||||||
 | 
					    const classes = useStatusStyles({ status });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Step = ({ until, label, completedOn, children, completed, ...props }: StepProps) => {
 | 
					 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isLate = useMemo(() => until?.isBefore(completedOn || now), [completedOn, until]);
 | 
					    return <span className={ classes.foreground }>{ t(`proposal.status.${status}`) }</span>;
 | 
				
			||||||
    const left = useMemo(() => moment.duration(now.diff(until)), [until]);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <StepperStep { ...props } completed={ completed || !!completedOn }>
 | 
					const ProposalStep = (props: StepProps) => {
 | 
				
			||||||
        <StepLabel>
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
            { label }
 | 
					
 | 
				
			||||||
            { until && <Box>
 | 
					    const { sent } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
 | 
				
			||||||
                <Typography variant="subtitle2" color="textSecondary">
 | 
					    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
 | 
				
			||||||
                    { t('until', { date: until }) }
 | 
					
 | 
				
			||||||
                    { isLate && <Typography color="error" display="inline"
 | 
					    return <Step { ...props } label={ t('steps.internship-proposal.header') } active={ true } completed={ sent } until={ deadlines.proposal } state={ <ProposalStatus /> }>
 | 
				
			||||||
                                            variant="body2"> - { t('late', { by: moment.duration(now.diff(until)) }) }</Typography> }
 | 
					        <p>{ t('steps.internship-proposal.info') }</p>
 | 
				
			||||||
                    { !isLate && !completed && <Typography display="inline" variant="body2"> - { t('left', { left: left }) }</Typography> }
 | 
					
 | 
				
			||||||
                </Typography>
 | 
					        <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink }>
 | 
				
			||||||
            </Box> }
 | 
					            { t('steps.internship-proposal.form') }
 | 
				
			||||||
        </StepLabel>
 | 
					        </Button>
 | 
				
			||||||
        { children && <StepContent>{ children }</StepContent> }
 | 
					    </Step>;
 | 
				
			||||||
    </StepperStep>
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const MainPage = () => {
 | 
					export const MainPage = () => {
 | 
				
			||||||
@ -67,13 +91,7 @@ export const MainPage = () => {
 | 
				
			|||||||
                        </Button>
 | 
					                        </Button>
 | 
				
			||||||
                    </> }
 | 
					                    </> }
 | 
				
			||||||
                </Step>
 | 
					                </Step>
 | 
				
			||||||
                <Step label={ t('steps.internship-proposal.header') } active={ missingStudentData.length === 0 } until={ deadlines.proposal }>
 | 
					                <ProposalStep />
 | 
				
			||||||
                    <p>{ t('steps.internship-proposal.info') }</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink }>
 | 
					 | 
				
			||||||
                        { t('steps.internship-proposal.form') }
 | 
					 | 
				
			||||||
                    </Button>
 | 
					 | 
				
			||||||
                </Step>
 | 
					 | 
				
			||||||
                <Step label={ t('steps.plan.header') } active={ missingStudentData.length === 0 } until={ deadlines.proposal }>
 | 
					                <Step label={ t('steps.plan.header') } active={ missingStudentData.length === 0 } until={ deadlines.proposal }>
 | 
				
			||||||
                    <p>{ t('steps.plan.info') }</p>
 | 
					                    <p>{ t('steps.plan.info') }</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,7 +99,7 @@ export const MainPage = () => {
 | 
				
			|||||||
                        <Button to={ route("internship_plan") } variant="contained" color="primary" component={ RouterLink }>
 | 
					                        <Button to={ route("internship_plan") } variant="contained" color="primary" component={ RouterLink }>
 | 
				
			||||||
                            { t('steps.plan.submit') }
 | 
					                            { t('steps.plan.submit') }
 | 
				
			||||||
                        </Button>
 | 
					                        </Button>
 | 
				
			||||||
                        <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon /> }>
 | 
					                        <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon/> }>
 | 
				
			||||||
                            { t('steps.plan.template') }
 | 
					                            { t('steps.plan.template') }
 | 
				
			||||||
                        </Button>
 | 
					                        </Button>
 | 
				
			||||||
                    </Actions>
 | 
					                    </Actions>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/serialization/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/serialization/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export * from "./internship"
 | 
				
			||||||
 | 
					export * from "./moment"
 | 
				
			||||||
 | 
					export * from "./types"
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/serialization/internship.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/serialization/internship.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { Internship, InternshipType } from "@/data";
 | 
				
			||||||
 | 
					import { Serializable, SerializationTransformer } from "@/serialization/types";
 | 
				
			||||||
 | 
					import { momentSerializationTransformer } from "@/serialization/moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const internshipSerializationTransformer: SerializationTransformer<Internship> = {
 | 
				
			||||||
 | 
					    transform: (internship: Internship): Serializable<Internship> => ({
 | 
				
			||||||
 | 
					        ...internship,
 | 
				
			||||||
 | 
					        startDate: momentSerializationTransformer.transform(internship.startDate),
 | 
				
			||||||
 | 
					        endDate: momentSerializationTransformer.transform(internship.endDate),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    reverseTransform: (serialized: Serializable<Internship>): Internship => ({
 | 
				
			||||||
 | 
					        ...serialized,
 | 
				
			||||||
 | 
					        startDate: momentSerializationTransformer.reverseTransform(serialized.startDate),
 | 
				
			||||||
 | 
					        endDate: momentSerializationTransformer.reverseTransform(serialized.endDate),
 | 
				
			||||||
 | 
					        type: serialized.type as InternshipType,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/serialization/moment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/serialization/moment.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import { SerializationTransformer } from "@/serialization/types";
 | 
				
			||||||
 | 
					import moment, { Moment } from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const momentSerializationTransformer: SerializationTransformer<Moment, string> = {
 | 
				
			||||||
 | 
					    transform: (subject: Moment) => subject.toISOString(),
 | 
				
			||||||
 | 
					    reverseTransform: (subject: string) => moment(subject),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/serialization/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/serialization/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { Moment } from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Simplify<T> = string |
 | 
				
			||||||
 | 
					    T extends string ? string :
 | 
				
			||||||
 | 
					    T extends number ? number :
 | 
				
			||||||
 | 
					    T extends boolean ? boolean :
 | 
				
			||||||
 | 
					    T extends Moment ? string :
 | 
				
			||||||
 | 
					    T extends Array<infer K> ? Array<Simplify<K>> :
 | 
				
			||||||
 | 
					    T extends (infer K)[] ? Simplify<K>[] :
 | 
				
			||||||
 | 
					    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 SerializationTransformer<T, TSerialized = Serializable<T>> = Transformer<T, TSerialized>
 | 
				
			||||||
@ -18,7 +18,7 @@ export interface ReceiveProposalApproveAction extends Action<InternshipProposalA
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ReceiveProposalDeclineAction extends Action<InternshipProposalActions.Decline> {
 | 
					export interface ReceiveProposalDeclineAction extends Action<InternshipProposalActions.Decline> {
 | 
				
			||||||
 | 
					    comment: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ReceiveProposalUpdateAction extends Action<InternshipProposalActions.Receive> {
 | 
					export interface ReceiveProposalUpdateAction extends Action<InternshipProposalActions.Receive> {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,28 +1,33 @@
 | 
				
			|||||||
import { DeanApproval } from "@/data/deanApproval";
 | 
					import { DeanApproval } from "@/data/deanApproval";
 | 
				
			||||||
import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
 | 
					import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
 | 
				
			||||||
import { Internship } from "@/data";
 | 
					import { Internship } from "@/data";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					import { Serializable } from "@/serialization/types";
 | 
				
			||||||
 | 
					import { internshipSerializationTransformer, momentSerializationTransformer } from "@/serialization";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ProposalStatus = "draft" | "awaiting" | "accepted" | "declined";
 | 
					export type InternshipProposalStatus = "draft" | "awaiting" | "accepted" | "declined";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InternshipProposalState = {
 | 
					export type InternshipProposalState = {
 | 
				
			||||||
    accepted: boolean;
 | 
					    accepted: boolean;
 | 
				
			||||||
    sent: boolean;
 | 
					    sent: boolean;
 | 
				
			||||||
 | 
					    sentOn: string | null;
 | 
				
			||||||
    declined: boolean;
 | 
					    declined: boolean;
 | 
				
			||||||
    requiredDeanApprovals: DeanApproval[];
 | 
					    requiredDeanApprovals: DeanApproval[];
 | 
				
			||||||
    proposal: Internship | null;
 | 
					    proposal: Serializable<Internship> | null;
 | 
				
			||||||
    comment: string | null;
 | 
					    comment: string | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultInternshipProposalState: InternshipProposalState = {
 | 
					const defaultInternshipProposalState: InternshipProposalState = {
 | 
				
			||||||
    accepted: false,
 | 
					    accepted: false,
 | 
				
			||||||
    declined: false,
 | 
					    declined: false,
 | 
				
			||||||
 | 
					    sentOn: null,
 | 
				
			||||||
    proposal: null,
 | 
					    proposal: null,
 | 
				
			||||||
    requiredDeanApprovals: [],
 | 
					    requiredDeanApprovals: [],
 | 
				
			||||||
    sent: false,
 | 
					    sent: false,
 | 
				
			||||||
    comment: null
 | 
					    comment: null
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getInternshipProposalStatus = ({ accepted, declined, sent }: InternshipProposalState): ProposalStatus => {
 | 
					export const getInternshipProposalStatus = ({ accepted, declined, sent }: InternshipProposalState): InternshipProposalStatus => {
 | 
				
			||||||
    switch (true) {
 | 
					    switch (true) {
 | 
				
			||||||
        case !sent:
 | 
					        case !sent:
 | 
				
			||||||
            return "draft";
 | 
					            return "draft";
 | 
				
			||||||
@ -37,13 +42,38 @@ const getInternshipProposalStatus = ({ accepted, declined, sent }: InternshipPro
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getInternshipProposal = ({ proposal }: InternshipProposalState): Internship | null =>
 | 
				
			||||||
 | 
					    proposal && internshipSerializationTransformer.reverseTransform(proposal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const internshipProposalReducer = (state: InternshipProposalState = defaultInternshipProposalState, action: InternshipProposalAction): InternshipProposalState => {
 | 
					const internshipProposalReducer = (state: InternshipProposalState = defaultInternshipProposalState, action: InternshipProposalAction): InternshipProposalState => {
 | 
				
			||||||
    switch (action.type) {
 | 
					    switch (action.type) {
 | 
				
			||||||
 | 
					        case InternshipProposalActions.Approve:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                accepted: true,
 | 
				
			||||||
 | 
					                declined: false,
 | 
				
			||||||
 | 
					                comment: ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        case InternshipProposalActions.Decline:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                accepted: false,
 | 
				
			||||||
 | 
					                declined: true,
 | 
				
			||||||
 | 
					                comment: action.comment
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        case InternshipProposalActions.Save:
 | 
					        case InternshipProposalActions.Save:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                proposal: internshipSerializationTransformer.transform(action.internship),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        case InternshipProposalActions.Send:
 | 
					        case InternshipProposalActions.Send:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                ...state,
 | 
					                ...state,
 | 
				
			||||||
                proposal: action.internship
 | 
					                proposal: internshipSerializationTransformer.transform(action.internship),
 | 
				
			||||||
 | 
					                sent: true,
 | 
				
			||||||
 | 
					                sentOn: momentSerializationTransformer.transform(moment()),
 | 
				
			||||||
 | 
					                accepted: false,
 | 
				
			||||||
 | 
					                declined: false,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            return state;
 | 
					            return state;
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,8 @@ const store = createStore(
 | 
				
			|||||||
    devToolsEnhancer({})
 | 
					    devToolsEnhancer({})
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const persistor = persistStore(store)
 | 
					export const persistor = persistStore(store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(window as any)._store = store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default store;
 | 
					export default store;
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,13 @@ student:
 | 
				
			|||||||
  email: adres e-mail
 | 
					  email: adres e-mail
 | 
				
			||||||
  albumNumber: numer albumu
 | 
					  albumNumber: numer albumu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					proposal:
 | 
				
			||||||
 | 
					  status:
 | 
				
			||||||
 | 
					    awaiting: "wysłano, oczekuje na weryfikacje"
 | 
				
			||||||
 | 
					    accepted: "zaakceptowano"
 | 
				
			||||||
 | 
					    declined: "do poprawy"
 | 
				
			||||||
 | 
					    draft: "wersja robocza"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  personal-data:
 | 
					  personal-data:
 | 
				
			||||||
    header: "Uzupełnienie informacji"
 | 
					    header: "Uzupełnienie informacji"
 | 
				
			||||||
 | 
				
			|||||||
@ -53,6 +53,7 @@ const config = {
 | 
				
			|||||||
        host: 'system-praktyk-front.localhost',
 | 
					        host: 'system-praktyk-front.localhost',
 | 
				
			||||||
        disableHostCheck: true,
 | 
					        disableHostCheck: true,
 | 
				
			||||||
        historyApiFallback: true,
 | 
					        historyApiFallback: true,
 | 
				
			||||||
 | 
					        overlay: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    optimization: {
 | 
					    optimization: {
 | 
				
			||||||
        usedExports: true
 | 
					        usedExports: true
 | 
				
			||||||
 | 
				
			|||||||
@ -5521,6 +5521,11 @@ md5.js@^1.3.4:
 | 
				
			|||||||
    inherits "^2.0.1"
 | 
					    inherits "^2.0.1"
 | 
				
			||||||
    safe-buffer "^5.1.2"
 | 
					    safe-buffer "^5.1.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mdi-material-ui@^6.17.0:
 | 
				
			||||||
 | 
					  version "6.17.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/mdi-material-ui/-/mdi-material-ui-6.17.0.tgz#da69f0b7d7c6fc2255e6007ed8b8ca858c1aede7"
 | 
				
			||||||
 | 
					  integrity sha512-eOprRu31lklPIS1WGe3cM0G/8glKl1WKRvewxjDrgXH2Ryxxg7uQ+uwDUwUEONtLku0p2ZOLzgXUIy2uRy5rLg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mdn-data@2.0.4:
 | 
					mdn-data@2.0.4:
 | 
				
			||||||
  version "2.0.4"
 | 
					  version "2.0.4"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
 | 
					  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user