master #7
@ -17,6 +17,15 @@ const plugins = [
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    'core'
 | 
					    'core'
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    'babel-plugin-import',
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      'libraryName': 'mdi-material-ui',
 | 
				
			||||||
 | 
					      'libraryDirectory': '.',
 | 
				
			||||||
 | 
					      'camel2DashComponentName': false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    'mdi-material-ui'
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  [
 | 
					  [
 | 
				
			||||||
    'babel-plugin-import',
 | 
					    'babel-plugin-import',
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
@ -46,6 +47,7 @@
 | 
				
			|||||||
    "react-dev-utils": "^10.2.1",
 | 
					    "react-dev-utils": "^10.2.1",
 | 
				
			||||||
    "react-dom": "^16.13.1",
 | 
					    "react-dom": "^16.13.1",
 | 
				
			||||||
    "react-i18next": "^11.7.0",
 | 
					    "react-i18next": "^11.7.0",
 | 
				
			||||||
 | 
					    "react-moment": "^0.9.7",
 | 
				
			||||||
    "react-redux": "^7.2.0",
 | 
					    "react-redux": "^7.2.0",
 | 
				
			||||||
    "react-router-dom": "^5.2.0",
 | 
					    "react-router-dom": "^5.2.0",
 | 
				
			||||||
    "redux": "^4.0.5",
 | 
					    "redux": "^4.0.5",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										51
									
								
								src/app.tsx
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								src/app.tsx
									
									
									
									
									
								
							@ -1,23 +1,29 @@
 | 
				
			|||||||
import React, { Dispatch, HTMLProps, useEffect } from 'react';
 | 
					import React, { HTMLProps, useEffect } from 'react';
 | 
				
			||||||
import { Link, Route, Switch } from "react-router-dom"
 | 
					import { Link, Route, Switch } from "react-router-dom"
 | 
				
			||||||
import moment from "moment";
 | 
					 | 
				
			||||||
import { route, routes } from "@/routing";
 | 
					import { route, routes } from "@/routing";
 | 
				
			||||||
import { useDispatch, useSelector } from "react-redux";
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
import { AppState } from "@/state/reducer";
 | 
					import { AppState, isReady } from "@/state/reducer";
 | 
				
			||||||
import { StudentAction, StudentActions } from "@/state/actions/student";
 | 
					import { StudentActions } from "@/state/actions/student";
 | 
				
			||||||
import { sampleStudent } from "@/provider/dummy/student";
 | 
					import { sampleStudent } from "@/provider/dummy/student";
 | 
				
			||||||
import { Trans, useTranslation } from "react-i18next";
 | 
					import { Trans, useTranslation } from "react-i18next";
 | 
				
			||||||
import { Student } from "@/data";
 | 
					import { Student } from "@/data";
 | 
				
			||||||
import '@/styles/overrides.scss'
 | 
					import '@/styles/overrides.scss'
 | 
				
			||||||
import '@/styles/header.scss'
 | 
					import '@/styles/header.scss'
 | 
				
			||||||
 | 
					import '@/styles/footer.scss'
 | 
				
			||||||
import classNames from "classnames";
 | 
					import classNames from "classnames";
 | 
				
			||||||
import { EditionAction, EditionActions } from "@/state/actions/edition";
 | 
					import { EditionActions } from "@/state/actions/edition";
 | 
				
			||||||
import { sampleEdition } from "@/provider/dummy/edition";
 | 
					import { sampleEdition } from "@/provider/dummy/edition";
 | 
				
			||||||
import { Edition } from "@/data/edition";
 | 
					import { Edition } from "@/data/edition";
 | 
				
			||||||
 | 
					import { SettingActions } from "@/state/actions/settings";
 | 
				
			||||||
 | 
					import { useDispatch } from "@/state/actions";
 | 
				
			||||||
 | 
					import { getLocale, Locale } from "@/state/reducer/settings";
 | 
				
			||||||
 | 
					import i18n from "@/i18n";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					import { Container } from "@material-ui/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
 | 
					const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
 | 
				
			||||||
    const student = useSelector<AppState, Student>(state => state.student as Student);
 | 
					    const student = useSelector<AppState, Student>(state => state.student as Student);
 | 
				
			||||||
    const dispatch = useDispatch<Dispatch<StudentAction>>();
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleUserLogin = () => {
 | 
					    const handleUserLogin = () => {
 | 
				
			||||||
@ -47,17 +53,17 @@ const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
 | 
				
			|||||||
const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>) => {
 | 
					const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>) => {
 | 
				
			||||||
    const { i18n } = useTranslation();
 | 
					    const { i18n } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleLanguageChange = (language: string) => () => {
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
        i18n.changeLanguage(language);
 | 
					
 | 
				
			||||||
        document.documentElement.lang = language;
 | 
					    const handleLanguageChange = (language: Locale) => () => {
 | 
				
			||||||
        moment.locale(language)
 | 
					        dispatch({ type: SettingActions.SetLocale, locale: language })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isActive = (language: string) => language.toLowerCase() === i18n.language.toLowerCase();
 | 
					    const isActive = (language: string) => language.toLowerCase() === i18n.language.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <ul className={ classNames(className, "language-switcher") } { ...props }>
 | 
					    return <ul className={ classNames(className, "language-switcher") } { ...props }>
 | 
				
			||||||
        { ['pl', 'en'].map(language => <li key={ language }>
 | 
					        { ['pl', 'en'].map(language => <li key={ language }>
 | 
				
			||||||
            <Link to="#" onClick={ handleLanguageChange(language) }
 | 
					            <Link to="#" onClick={ handleLanguageChange(language as Locale) }
 | 
				
			||||||
                  className={ classNames("language-switcher__language", isActive(language) && "language-switcher__language--active") }>
 | 
					                  className={ classNames("language-switcher__language", isActive(language) && "language-switcher__language--active") }>
 | 
				
			||||||
                { language }
 | 
					                { language }
 | 
				
			||||||
            </Link>
 | 
					            </Link>
 | 
				
			||||||
@ -66,8 +72,10 @@ const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() {
 | 
					function App() {
 | 
				
			||||||
    const dispatch = useDispatch<Dispatch<EditionAction>>();
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
    const edition = useSelector<AppState, Edition | null>(state => state.edition);
 | 
					    const edition = useSelector<AppState, Edition | null>(state => state.edition);
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					    const locale = useSelector<AppState, Locale>(state => getLocale(state.settings));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (!edition) {
 | 
					        if (!edition) {
 | 
				
			||||||
@ -75,7 +83,13 @@ function App() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isReady = !!edition;
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        i18n.changeLanguage(locale);
 | 
				
			||||||
 | 
					        document.documentElement.lang = locale;
 | 
				
			||||||
 | 
					        moment.locale(locale)
 | 
				
			||||||
 | 
					    }, [ locale ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ready = useSelector(isReady);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <>
 | 
					    return <>
 | 
				
			||||||
        <header className="header">
 | 
					        <header className="header">
 | 
				
			||||||
@ -96,7 +110,14 @@ function App() {
 | 
				
			|||||||
                </nav>
 | 
					                </nav>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </header>
 | 
					        </header>
 | 
				
			||||||
        { isReady && <Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch> }
 | 
					        <main id="content">
 | 
				
			||||||
 | 
					            { ready && <Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch> }
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					        <footer className="footer">
 | 
				
			||||||
 | 
					            <Container>
 | 
				
			||||||
 | 
					                <div className="footer__copyright">{ t('copyright', { date: moment() }) }</div>
 | 
				
			||||||
 | 
					            </Container>
 | 
				
			||||||
 | 
					        </footer>
 | 
				
			||||||
    </>;
 | 
					    </>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/components/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/components/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					export * from "./actions"
 | 
				
			||||||
 | 
					export * from "./step"
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/components/step.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/components/step.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					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";
 | 
				
			||||||
 | 
					import { StepIcon } from "@/components/stepIcon";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StepProps = StepperStepProps & {
 | 
				
			||||||
 | 
					    until?: Moment;
 | 
				
			||||||
 | 
					    completedOn?: Moment;
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					    state?: ReactChild | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    waiting?: boolean;
 | 
				
			||||||
 | 
					    declined?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = moment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Step = (props: StepProps) => {
 | 
				
			||||||
 | 
					    const { until, label, completedOn, children, completed = false, declined = false, waiting = false, state = null, ...rest } = props;
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isLate = useMemo(() => until?.isBefore(completedOn || now), [completedOn, until]);
 | 
				
			||||||
 | 
					    const left = useMemo(() => moment.duration(now.diff(until)), [until]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <StepperStep { ...rest } completed={ completed }>
 | 
				
			||||||
 | 
					        <StepLabel error={ declined } StepIconComponent={ StepIcon } StepIconProps={{ ...props, waiting } as any}>
 | 
				
			||||||
 | 
					            { 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>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/components/stepIcon.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/stepIcon.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { StepIcon as MuiStepIcon, StepIconProps as MuiStepIconProps, Theme } from "@material-ui/core";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { TimerSand } from "mdi-material-ui";
 | 
				
			||||||
 | 
					import { createStyles, makeStyles } from "@material-ui/core/styles";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StepIconProps = MuiStepIconProps & {
 | 
				
			||||||
 | 
					    waiting: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useStyles = makeStyles((theme: Theme) => createStyles({
 | 
				
			||||||
 | 
					    root: {
 | 
				
			||||||
 | 
					        color: theme.palette.primary.main
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StepIcon = ({ waiting, ...props }: StepIconProps) => {
 | 
				
			||||||
 | 
					    const classes = useStyles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return waiting
 | 
				
			||||||
 | 
					        ? <TimerSand className={ classes.root }/>
 | 
				
			||||||
 | 
					        : <MuiStepIcon { ...props } />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/data/deanApproval.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/data/deanApproval.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export type DeanApproval = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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,14 @@ 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 Plan extends Identifiable {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Mentor {
 | 
					export interface Mentor {
 | 
				
			||||||
@ -71,3 +77,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,15 @@ 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 { 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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InternshipFormProps = {}
 | 
					export type InternshipFormProps = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -62,25 +58,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 +86,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 +139,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 +162,13 @@ 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>
 | 
					            <Actions>
 | 
				
			||||||
 | 
					                <Button variant="contained" color="primary" onClick={ handleSubmit }>{ t("confirm") }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Button component={ RouterLink } to={ route("home") }>
 | 
				
			||||||
 | 
					                    { t('go-back') }
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/forms/plan.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/forms/plan.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import { Button, FormHelperText, Grid, Typography } from "@material-ui/core";
 | 
				
			||||||
 | 
					import { Description as DescriptionIcon } from "@material-ui/icons";
 | 
				
			||||||
 | 
					import { DropzoneArea } from "material-ui-dropzone";
 | 
				
			||||||
 | 
					import { Actions } from "@/components";
 | 
				
			||||||
 | 
					import { Link as RouterLink, useHistory } from "react-router-dom";
 | 
				
			||||||
 | 
					import { route } from "@/routing";
 | 
				
			||||||
 | 
					import React, { useState } from "react";
 | 
				
			||||||
 | 
					import { Plan } from "@/data";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import { InternshipPlanActions, useDispatch } from "@/state/actions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PlanForm = () => {
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [plan, setPlan] = useState<Plan>({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
 | 
					    const history = useHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleSubmit = () => {
 | 
				
			||||||
 | 
					        dispatch({ type: InternshipPlanActions.Send, plan });
 | 
				
			||||||
 | 
					        history.push(route("home"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <Grid container>
 | 
				
			||||||
 | 
					        <Grid item>
 | 
				
			||||||
 | 
					            <Typography variant="body1" component="p">{ t('forms.plan.instructions') }</Typography>
 | 
				
			||||||
 | 
					        </Grid>
 | 
				
			||||||
 | 
					        <Grid item>
 | 
				
			||||||
 | 
					            <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon /> }>
 | 
				
			||||||
 | 
					                { t('steps.plan.template') }
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					        </Grid>
 | 
				
			||||||
 | 
					        <Grid item>
 | 
				
			||||||
 | 
					            <DropzoneArea acceptedFiles={["image/*", "application/x-pdf"]} filesLimit={ 1 } dropzoneText={ t("dropzone") }/>
 | 
				
			||||||
 | 
					            <FormHelperText>{ t('forms.plan.dropzone-help') }</FormHelperText>
 | 
				
			||||||
 | 
					        </Grid>
 | 
				
			||||||
 | 
					        <Grid item>
 | 
				
			||||||
 | 
					            <Actions>
 | 
				
			||||||
 | 
					                <Button variant="contained" color="primary" onClick={ handleSubmit }>
 | 
				
			||||||
 | 
					                    { t('confirm') }
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Button component={ RouterLink } to={ route("home") }>
 | 
				
			||||||
 | 
					                    { t('go-back') }
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        </Grid>
 | 
				
			||||||
 | 
					    </Grid>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/i18n.ts
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/i18n.ts
									
									
									
									
									
								
							@ -2,10 +2,9 @@ import i18n from "i18next";
 | 
				
			|||||||
import { initReactI18next } from "react-i18next";
 | 
					import { initReactI18next } from "react-i18next";
 | 
				
			||||||
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
 | 
					import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import moment from "moment";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "moment/locale/pl"
 | 
					import "moment/locale/pl"
 | 
				
			||||||
import "moment/locale/en-gb"
 | 
					import "moment/locale/en-gb"
 | 
				
			||||||
 | 
					import moment, { isDuration, isMoment } from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const resources = {
 | 
					const resources = {
 | 
				
			||||||
    en: {
 | 
					    en: {
 | 
				
			||||||
@ -21,9 +20,20 @@ i18n
 | 
				
			|||||||
    .use(initReactI18next)
 | 
					    .use(initReactI18next)
 | 
				
			||||||
    .init({
 | 
					    .init({
 | 
				
			||||||
        resources,
 | 
					        resources,
 | 
				
			||||||
        fallbackLng: "en",
 | 
					        fallbackLng: "pl",
 | 
				
			||||||
        interpolation: {
 | 
					        interpolation: {
 | 
				
			||||||
            escapeValue: false
 | 
					            escapeValue: false,
 | 
				
			||||||
 | 
					            format: (value, format, lng) => {
 | 
				
			||||||
 | 
					                if (isMoment(value)) {
 | 
				
			||||||
 | 
					                    return value.locale(lng || "pl").format(format || "DD MMM YYYY");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (isDuration(value)) {
 | 
				
			||||||
 | 
					                    return value.locale(lng || "pl").humanize();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,7 @@
 | 
				
			|||||||
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";
 | 
					export { ProposalStep } from "@/pages/steps/proposal";
 | 
				
			||||||
 | 
					export { ProposalComment } from "@/pages/steps/proposal";
 | 
				
			||||||
 | 
					export { ProposalActions } from "@/pages/steps/proposal";
 | 
				
			||||||
 | 
					export { ProposalStatus } from "@/pages/steps/proposal";
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,10 @@
 | 
				
			|||||||
import { Page } from "@/pages/base";
 | 
					import { Page } from "@/pages/base";
 | 
				
			||||||
import { Button, Container, FormHelperText, Grid, 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 React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import { DropzoneArea } from "material-ui-dropzone";
 | 
					import { PlanForm } from "@/forms/plan";
 | 
				
			||||||
import { Description as DescriptionIcon } from "@material-ui/icons";
 | 
					 | 
				
			||||||
import { Actions } from "@/components/actions";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SubmitPlanPage = () => {
 | 
					export const SubmitPlanPage = () => {
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
@ -20,31 +18,7 @@ export const SubmitPlanPage = () => {
 | 
				
			|||||||
            <Page.Title>{ t("steps.plan.submit") }</Page.Title>
 | 
					            <Page.Title>{ t("steps.plan.submit") }</Page.Title>
 | 
				
			||||||
        </Page.Header>
 | 
					        </Page.Header>
 | 
				
			||||||
        <Container maxWidth={ "md" }>
 | 
					        <Container maxWidth={ "md" }>
 | 
				
			||||||
            <Grid container>
 | 
					            <PlanForm />
 | 
				
			||||||
                <Grid item>
 | 
					 | 
				
			||||||
                    <Typography variant="body1" component="p">{ t('forms.plan.instructions') }</Typography>
 | 
					 | 
				
			||||||
                </Grid>
 | 
					 | 
				
			||||||
                <Grid item>
 | 
					 | 
				
			||||||
                    <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon /> }>
 | 
					 | 
				
			||||||
                        { t('steps.plan.template') }
 | 
					 | 
				
			||||||
                    </Button>
 | 
					 | 
				
			||||||
                </Grid>
 | 
					 | 
				
			||||||
                <Grid item>
 | 
					 | 
				
			||||||
                    <DropzoneArea acceptedFiles={["image/*", "application/x-pdf"]} filesLimit={ 1 } dropzoneText={ t("dropzone") }/>
 | 
					 | 
				
			||||||
                    <FormHelperText>{ t('forms.plan.dropzone-help') }</FormHelperText>
 | 
					 | 
				
			||||||
                </Grid>
 | 
					 | 
				
			||||||
                <Grid item>
 | 
					 | 
				
			||||||
                    <Actions>
 | 
					 | 
				
			||||||
                        <Button variant="contained" color="primary">
 | 
					 | 
				
			||||||
                            { t('confirm') }
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <Button component={ RouterLink } to={ route("home") }>
 | 
					 | 
				
			||||||
                            { t('go-back') }
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
                    </Actions>
 | 
					 | 
				
			||||||
                </Grid>
 | 
					 | 
				
			||||||
            </Grid>
 | 
					 | 
				
			||||||
        </Container>
 | 
					        </Container>
 | 
				
			||||||
    </Page>
 | 
					    </Page>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,9 @@ 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";
 | 
				
			||||||
 | 
					import { ProposalComment } from "@/pages";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const InternshipProposalPage = () => {
 | 
					export const InternshipProposalPage = () => {
 | 
				
			||||||
    return <Page title="Zgłoszenie praktyki">
 | 
					    return <Page title="Zgłoszenie praktyki">
 | 
				
			||||||
@ -15,6 +16,7 @@ export const InternshipProposalPage = () => {
 | 
				
			|||||||
            <Page.Title>Zgłoszenie praktyki</Page.Title>
 | 
					            <Page.Title>Zgłoszenie praktyki</Page.Title>
 | 
				
			||||||
        </Page.Header>
 | 
					        </Page.Header>
 | 
				
			||||||
        <Container maxWidth={ "md" }>
 | 
					        <Container maxWidth={ "md" }>
 | 
				
			||||||
 | 
					            <ProposalComment />
 | 
				
			||||||
            <InternshipForm/>
 | 
					            <InternshipForm/>
 | 
				
			||||||
        </Container>
 | 
					        </Container>
 | 
				
			||||||
    </Page>
 | 
					    </Page>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,46 +1,16 @@
 | 
				
			|||||||
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, 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 { Step } from "@/components";
 | 
				
			||||||
import { Actions } from "@/components/actions";
 | 
					import { ProposalStep } from "@/pages/steps/proposal";
 | 
				
			||||||
 | 
					import { PlanStep } from "@/pages/steps/plan";
 | 
				
			||||||
type StepProps = StepperStepProps & {
 | 
					 | 
				
			||||||
    until?: Moment;
 | 
					 | 
				
			||||||
    completedOn?: Moment;
 | 
					 | 
				
			||||||
    label: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const now = moment();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Step = ({ until, label, completedOn, children, completed, ...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 || !!completedOn }>
 | 
					 | 
				
			||||||
        <StepLabel>
 | 
					 | 
				
			||||||
            { label }
 | 
					 | 
				
			||||||
            { until && <Box>
 | 
					 | 
				
			||||||
                <Typography variant="subtitle2" color="textSecondary">
 | 
					 | 
				
			||||||
                    { t('until', { date: until.format("DD MMMM YYYY") }) }
 | 
					 | 
				
			||||||
                    { isLate && <Typography color="error" display="inline"
 | 
					 | 
				
			||||||
                                            variant="body2"> - { t('late', { by: moment.duration(now.diff(until)).humanize() }) }</Typography> }
 | 
					 | 
				
			||||||
                    { !isLate && !completed && <Typography display="inline" variant="body2"> - { t('left', { left: left.humanize() }) }</Typography> }
 | 
					 | 
				
			||||||
                </Typography>
 | 
					 | 
				
			||||||
            </Box> }
 | 
					 | 
				
			||||||
        </StepLabel>
 | 
					 | 
				
			||||||
        { children && <StepContent>{ children }</StepContent> }
 | 
					 | 
				
			||||||
    </StepperStep>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const MainPage = () => {
 | 
					export const MainPage = () => {
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
@ -67,25 +37,8 @@ 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>
 | 
					                <PlanStep />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <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 }>
 | 
					 | 
				
			||||||
                    <p>{ t('steps.plan.info') }</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Actions>
 | 
					 | 
				
			||||||
                        <Button to={ route("internship_plan") } variant="contained" color="primary" component={ RouterLink }>
 | 
					 | 
				
			||||||
                            { t('steps.plan.submit') }
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
                        <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon /> }>
 | 
					 | 
				
			||||||
                            { t('steps.plan.template') }
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
                    </Actions>
 | 
					 | 
				
			||||||
                </Step>
 | 
					 | 
				
			||||||
                <Step label={ t('steps.insurance.header') }/>
 | 
					                <Step label={ t('steps.insurance.header') }/>
 | 
				
			||||||
                <Step label={ t('steps.report.header') } until={ deadlines.report }/>
 | 
					                <Step label={ t('steps.report.header') } until={ deadlines.report }/>
 | 
				
			||||||
                <Step label={ t('steps.grade.header') }/>
 | 
					                <Step label={ t('steps.grade.header') }/>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								src/pages/steps/common.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/pages/steps/common.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import { getSubmissionStatus, SubmissionState, SubmissionStatus } from "@/state/reducer/submission";
 | 
				
			||||||
 | 
					import { Theme } from "@material-ui/core";
 | 
				
			||||||
 | 
					import { createStyles, makeStyles } from "@material-ui/core/styles";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getColorByStatus = (status: SubmissionStatus, theme: Theme) => {
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					        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";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useStatusStyles = makeStyles((theme: Theme) => {
 | 
				
			||||||
 | 
					    const colorByStatusGetter = ({ status }: any) => getColorByStatus(status, theme);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return createStyles({
 | 
				
			||||||
 | 
					        foreground: {
 | 
				
			||||||
 | 
					            color: colorByStatusGetter
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        background: {
 | 
				
			||||||
 | 
					            backgroundColor: colorByStatusGetter
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SubmissionStatusProps = {
 | 
				
			||||||
 | 
					    submission: SubmissionState,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Status = ({ submission } : SubmissionStatusProps) => {
 | 
				
			||||||
 | 
					    const status = getSubmissionStatus(submission);
 | 
				
			||||||
 | 
					    const classes = useStatusStyles({ status });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <span className={ classes.foreground }>{ t(`submission.status.${ status }`) }</span>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								src/pages/steps/plan.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/pages/steps/plan.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import { AppState } from "@/state/reducer";
 | 
				
			||||||
 | 
					import { getSubmissionStatus, SubmissionState, SubmissionStatus } from "@/state/reducer/submission";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import { Box, Button, ButtonProps, StepProps } from "@material-ui/core";
 | 
				
			||||||
 | 
					import { CommentQuestion, FileFind } from "mdi-material-ui/index";
 | 
				
			||||||
 | 
					import { route } from "@/routing";
 | 
				
			||||||
 | 
					import { Link as RouterLink } from "react-router-dom";
 | 
				
			||||||
 | 
					import { Actions, Step } from "@/components";
 | 
				
			||||||
 | 
					import React, { HTMLProps } from "react";
 | 
				
			||||||
 | 
					import { Alert, AlertTitle } from "@material-ui/lab";
 | 
				
			||||||
 | 
					import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 | 
				
			||||||
 | 
					import { Status } from "@/pages/steps/common";
 | 
				
			||||||
 | 
					import { Description as DescriptionIcon } from "@material-ui/icons";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PlanActions = () => {
 | 
				
			||||||
 | 
					    const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.plan));
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ReviewAction = (props: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button startIcon={ <FileFind/> } { ...props }>{ t('review') }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const FormAction = ({ children = t('steps.plan.form'), ...props }: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button to={ route("plan") } variant="contained" color="primary" component={ RouterLink } { ...props as any }>
 | 
				
			||||||
 | 
					            { children }
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ContactAction = (props: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button startIcon={ <CommentQuestion/> } variant="outlined" color="primary" { ...props }>{ t('contact') }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					        case "awaiting":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <ReviewAction/>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "accepted":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <ReviewAction/>
 | 
				
			||||||
 | 
					                <FormAction variant="outlined" color="secondary">{ t('send-again') }</FormAction>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "declined":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <FormAction>{ t('fix-errors') }</FormAction>
 | 
				
			||||||
 | 
					                <ContactAction/>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "draft":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <Button to={ route("internship_plan") } variant="contained" color="primary" component={ RouterLink }>
 | 
				
			||||||
 | 
					                    { t('steps.plan.submit') }
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					                <Button href="https://eti.pg.edu.pl/documents/611675/100028367/indywidualny%20program%20praktyk" startIcon={ <DescriptionIcon/> }>
 | 
				
			||||||
 | 
					                    { t('steps.plan.template') }
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return <Actions/>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PlanComment = (props: HTMLProps<HTMLDivElement>) => {
 | 
				
			||||||
 | 
					    const { comment, declined } = useSelector<AppState, SubmissionState>(state => state.plan);
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return comment ? <Alert severity={ declined ? "error" : "warning" } { ...props as any }>
 | 
				
			||||||
 | 
					        <AlertTitle>{ t('comments') }</AlertTitle>
 | 
				
			||||||
 | 
					        { comment }
 | 
				
			||||||
 | 
					    </Alert> : null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PlanStep = (props: StepProps) => {
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const submission = useSelector<AppState, SubmissionState>(state => state.plan);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const status = getSubmissionStatus(submission);
 | 
				
			||||||
 | 
					    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { sent, declined, comment } = submission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <Step { ...props }
 | 
				
			||||||
 | 
					                 label={ t('steps.plan.header') }
 | 
				
			||||||
 | 
					                 active={ true } completed={ sent } declined={ declined } waiting={ status == "awaiting" }
 | 
				
			||||||
 | 
					                 until={ deadlines.proposal }
 | 
				
			||||||
 | 
					                 state={ <Status submission={ submission } /> }>
 | 
				
			||||||
 | 
					        <p>{ t(`steps.plan.info.${ status }`) }</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        { comment && <Box pb={ 2 }><PlanComment/></Box> }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <PlanActions/>
 | 
				
			||||||
 | 
					    </Step>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/pages/steps/proposal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/pages/steps/proposal.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import { AppState } from "@/state/reducer";
 | 
				
			||||||
 | 
					import { getSubmissionStatus, SubmissionState, SubmissionStatus } from "@/state/reducer/submission";
 | 
				
			||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import React, { HTMLProps } from "react";
 | 
				
			||||||
 | 
					import { InternshipProposalState } from "@/state/reducer/proposal";
 | 
				
			||||||
 | 
					import { Alert, AlertTitle } from "@material-ui/lab";
 | 
				
			||||||
 | 
					import { Box, Button, ButtonProps, StepProps } from "@material-ui/core";
 | 
				
			||||||
 | 
					import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
 | 
				
			||||||
 | 
					import { Actions, Step } from "@/components";
 | 
				
			||||||
 | 
					import { route } from "@/routing";
 | 
				
			||||||
 | 
					import { Link as RouterLink } from "react-router-dom";
 | 
				
			||||||
 | 
					import { ClipboardEditOutline, CommentQuestion, FileFind } from "mdi-material-ui/index";
 | 
				
			||||||
 | 
					import { Status } from "@/pages/steps/common";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ProposalActions = () => {
 | 
				
			||||||
 | 
					    const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ReviewAction = (props: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button startIcon={ <FileFind/> } { ...props }>{ t('review') }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const FormAction = ({ children = t('steps.internship-proposal.form'), ...props }: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink }
 | 
				
			||||||
 | 
					                startIcon={ <ClipboardEditOutline/> } { ...props as any }>
 | 
				
			||||||
 | 
					            { children }
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ContactAction = (props: ButtonProps) =>
 | 
				
			||||||
 | 
					        <Button startIcon={ <CommentQuestion/> } variant="outlined" color="primary" { ...props }>{ t('contact') }</Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					        case "awaiting":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <ReviewAction/>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "accepted":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <ReviewAction/>
 | 
				
			||||||
 | 
					                <FormAction variant="outlined" color="secondary">{ t('make-changes') }</FormAction>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "declined":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <FormAction>{ t('fix-errors') }</FormAction>
 | 
				
			||||||
 | 
					                <ContactAction/>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        case "draft":
 | 
				
			||||||
 | 
					            return <Actions>
 | 
				
			||||||
 | 
					                <FormAction color="primary">{ t('steps.internship-proposal.action') }</FormAction>
 | 
				
			||||||
 | 
					            </Actions>
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return <Actions/>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProposalComment = (props: HTMLProps<HTMLDivElement>) => {
 | 
				
			||||||
 | 
					    const { comment, declined } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return comment ? <Alert severity={ declined ? "error" : "warning" } { ...props as any }>
 | 
				
			||||||
 | 
					        <AlertTitle>{ t('comments') }</AlertTitle>
 | 
				
			||||||
 | 
					        { comment }
 | 
				
			||||||
 | 
					    </Alert> : null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProposalStep = (props: StepProps) => {
 | 
				
			||||||
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const submission = useSelector<AppState, SubmissionState>(state => state.proposal);
 | 
				
			||||||
 | 
					    const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
 | 
				
			||||||
 | 
					    const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { sent, declined, comment } = submission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <Step { ...props }
 | 
				
			||||||
 | 
					                 label={ t('steps.internship-proposal.header') }
 | 
				
			||||||
 | 
					                 active={ true } completed={ sent } declined={ declined } waiting={ status == "awaiting" }
 | 
				
			||||||
 | 
					                 until={ deadlines.proposal }
 | 
				
			||||||
 | 
					                 state={ <Status submission={ submission } /> }>
 | 
				
			||||||
 | 
					        <p>{ t(`steps.internship-proposal.info.${ status }`) }</p>
 | 
				
			||||||
 | 
					        { comment && <Box pb={ 2 }><ProposalComment/></Box> }
 | 
				
			||||||
 | 
					        <ProposalActions/>
 | 
				
			||||||
 | 
					    </Step>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1 +1,5 @@
 | 
				
			|||||||
export * from './company'
 | 
					export * from './company'
 | 
				
			||||||
 | 
					export * from './edition'
 | 
				
			||||||
 | 
					export * from './student'
 | 
				
			||||||
 | 
					export * from './internship'
 | 
				
			||||||
 | 
					export * from './helpers'
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { Nullable } from "@/helpers";
 | 
					import { Nullable } from "@/helpers";
 | 
				
			||||||
import { emptyCompany, Internship, Mentor } from "@/data";
 | 
					import { emptyBranchOffice, emptyCompany, Internship, Mentor } from "@/data";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const emptyMentor: Mentor = {
 | 
					export const emptyMentor: Mentor = {
 | 
				
			||||||
    phone: null,
 | 
					    phone: null,
 | 
				
			||||||
@ -18,4 +18,6 @@ export const emptyInternship: Nullable<Internship> = {
 | 
				
			|||||||
    lengthInWeeks: 0,
 | 
					    lengthInWeeks: 0,
 | 
				
			||||||
    mentor: emptyMentor,
 | 
					    mentor: emptyMentor,
 | 
				
			||||||
    company: emptyCompany,
 | 
					    company: emptyCompany,
 | 
				
			||||||
 | 
					    hours: 0,
 | 
				
			||||||
 | 
					    office: emptyBranchOffice,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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 | null, string> = {
 | 
				
			||||||
 | 
					    transform: (subject: Moment) => subject && subject.toISOString(),
 | 
				
			||||||
 | 
					    reverseTransform: (subject: string) => subject ? moment(subject) : null,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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>
 | 
				
			||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { StudentAction, StudentActions } from "@/state/actions/student";
 | 
				
			||||||
 | 
					import { EditionAction, EditionActions } from "@/state/actions/edition";
 | 
				
			||||||
 | 
					import { SettingActions, SettingsAction } from "@/state/actions/settings";
 | 
				
			||||||
 | 
					import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions/proposal";
 | 
				
			||||||
 | 
					import { Dispatch } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useDispatch as useReduxDispatch } from "react-redux";
 | 
				
			||||||
 | 
					import { InternshipPlanAction, InternshipPlanActions } from "@/state/actions/plan";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export * from "./base"
 | 
				
			||||||
 | 
					export * from "./edition"
 | 
				
			||||||
 | 
					export * from "./settings"
 | 
				
			||||||
 | 
					export * from "./student"
 | 
				
			||||||
 | 
					export * from "./proposal"
 | 
				
			||||||
 | 
					export * from "./plan"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Action = StudentAction | EditionAction | SettingsAction | InternshipProposalAction | InternshipPlanAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Actions = { ...StudentActions, ...EditionActions, ...SettingActions, ...InternshipProposalActions, ...InternshipPlanActions }
 | 
				
			||||||
 | 
					export type Actions = typeof Actions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useDispatch = () => useReduxDispatch<Dispatch<Action>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Actions;
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/state/actions/plan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/state/actions/plan.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import { Plan } from "@/data";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    ReceiveSubmissionApproveAction,
 | 
				
			||||||
 | 
					    ReceiveSubmissionDeclineAction,
 | 
				
			||||||
 | 
					    ReceiveSubmissionUpdateAction,
 | 
				
			||||||
 | 
					    SaveSubmissionAction,
 | 
				
			||||||
 | 
					    SendSubmissionAction
 | 
				
			||||||
 | 
					} from "@/state/actions/submission";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum InternshipPlanActions {
 | 
				
			||||||
 | 
					    Send = "SEND_PLAN",
 | 
				
			||||||
 | 
					    Save = "SAVE_PLAN",
 | 
				
			||||||
 | 
					    Approve = "RECEIVE_PLAN_APPROVE",
 | 
				
			||||||
 | 
					    Decline = "RECEIVE_PLAN_DECLINE",
 | 
				
			||||||
 | 
					    Receive = "RECEIVE_PLAN_STATE",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SendPlanAction extends SendSubmissionAction<InternshipPlanActions.Send> {
 | 
				
			||||||
 | 
					    plan: Plan;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceivePlanApproveAction extends ReceiveSubmissionApproveAction<InternshipPlanActions.Approve> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceivePlanDeclineAction extends ReceiveSubmissionDeclineAction<InternshipPlanActions.Decline> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceivePlanUpdateAction extends ReceiveSubmissionUpdateAction<InternshipPlanActions.Receive> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SavePlanAction extends SaveSubmissionAction<InternshipPlanActions.Save> {
 | 
				
			||||||
 | 
					    plan: Plan;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type InternshipPlanAction
 | 
				
			||||||
 | 
					    = SendPlanAction
 | 
				
			||||||
 | 
					    | SavePlanAction
 | 
				
			||||||
 | 
					    | ReceivePlanApproveAction
 | 
				
			||||||
 | 
					    | ReceivePlanDeclineAction
 | 
				
			||||||
 | 
					    | ReceivePlanUpdateAction;
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/state/actions/proposal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/state/actions/proposal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import { Internship } from "@/data";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    ReceiveSubmissionApproveAction,
 | 
				
			||||||
 | 
					    ReceiveSubmissionDeclineAction,
 | 
				
			||||||
 | 
					    ReceiveSubmissionUpdateAction,
 | 
				
			||||||
 | 
					    SaveSubmissionAction,
 | 
				
			||||||
 | 
					    SendSubmissionAction
 | 
				
			||||||
 | 
					} from "@/state/actions/submission";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum InternshipProposalActions {
 | 
				
			||||||
 | 
					    Send = "SEND_PROPOSAL",
 | 
				
			||||||
 | 
					    Save = "SAVE_PROPOSAL",
 | 
				
			||||||
 | 
					    Approve = "RECEIVE_PROPOSAL_APPROVE",
 | 
				
			||||||
 | 
					    Decline = "RECEIVE_PROPOSAL_DECLINE",
 | 
				
			||||||
 | 
					    Receive = "RECEIVE_PROPOSAL_STATE",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SendProposalAction extends SendSubmissionAction<InternshipProposalActions.Send> {
 | 
				
			||||||
 | 
					    internship: Internship;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveProposalApproveAction extends ReceiveSubmissionApproveAction<InternshipProposalActions.Approve> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAction<InternshipProposalActions.Decline> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
 | 
				
			||||||
 | 
					    internship: Internship;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type InternshipProposalAction
 | 
				
			||||||
 | 
					    = SendProposalAction
 | 
				
			||||||
 | 
					    | SaveProposalAction
 | 
				
			||||||
 | 
					    | ReceiveProposalApproveAction
 | 
				
			||||||
 | 
					    | ReceiveProposalDeclineAction
 | 
				
			||||||
 | 
					    | ReceiveProposalUpdateAction;
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/state/actions/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/state/actions/settings.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import { Action } from "@/state/actions/base";
 | 
				
			||||||
 | 
					import { Locale } from "@/state/reducer/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum SettingActions {
 | 
				
			||||||
 | 
					    SetLocale = "SET_LOCALE",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SetLocaleAction extends Action<SettingActions.SetLocale> {
 | 
				
			||||||
 | 
					    locale: Locale
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SettingsAction = SetLocaleAction;
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/state/actions/submission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/state/actions/submission.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import { Action } from "@/state/actions/base";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum SubmissionAction {
 | 
				
			||||||
 | 
					    Send = "SEND",
 | 
				
			||||||
 | 
					    Save = "SAVE",
 | 
				
			||||||
 | 
					    Approve = "RECEIVE_APPROVE",
 | 
				
			||||||
 | 
					    Decline = "RECEIVE_DECLINE",
 | 
				
			||||||
 | 
					    Receive = "RECEIVE_STATE",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SendSubmissionAction<T extends string> extends Action<T> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveSubmissionApproveAction<T extends string> extends Action<T> {
 | 
				
			||||||
 | 
					    comment: string | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveSubmissionDeclineAction<T extends string> extends Action<T> {
 | 
				
			||||||
 | 
					    comment: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ReceiveSubmissionUpdateAction<T extends string> extends Action<T> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SaveSubmissionAction<T extends string> extends Action<T> {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,13 +1,21 @@
 | 
				
			|||||||
import { combineReducers } from "redux";
 | 
					import { combineReducers } from "redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import studentReducer from "./student"
 | 
					import studentReducer from "@/state/reducer/student"
 | 
				
			||||||
import editionReducer from "@/state/reducer/edition";
 | 
					import editionReducer from "@/state/reducer/edition";
 | 
				
			||||||
 | 
					import settingsReducer from "@/state/reducer/settings";
 | 
				
			||||||
 | 
					import internshipProposalReducer from "@/state/reducer/proposal";
 | 
				
			||||||
 | 
					import internshipPlanReducer from "@/state/reducer/plan";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rootReducer = combineReducers({
 | 
					const rootReducer = combineReducers({
 | 
				
			||||||
    student: studentReducer,
 | 
					    student: studentReducer,
 | 
				
			||||||
    edition: editionReducer,
 | 
					    edition: editionReducer,
 | 
				
			||||||
 | 
					    settings: settingsReducer,
 | 
				
			||||||
 | 
					    proposal: internshipProposalReducer,
 | 
				
			||||||
 | 
					    plan: internshipPlanReducer,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AppState = ReturnType<typeof rootReducer>;
 | 
					export type AppState = ReturnType<typeof rootReducer>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default rootReducer;
 | 
					export default rootReducer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isReady = (state: AppState) => !!state.edition;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/state/reducer/plan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/state/reducer/plan.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { InternshipPlanAction, InternshipPlanActions } from "@/state/actions";
 | 
				
			||||||
 | 
					import { Plan } from "@/data";
 | 
				
			||||||
 | 
					import { Serializable } from "@/serialization/types";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    createSubmissionReducer,
 | 
				
			||||||
 | 
					    defaultDeanApprovalsState,
 | 
				
			||||||
 | 
					    defaultSubmissionState,
 | 
				
			||||||
 | 
					    MayRequireDeanApproval,
 | 
				
			||||||
 | 
					    SubmissionState
 | 
				
			||||||
 | 
					} from "@/state/reducer/submission";
 | 
				
			||||||
 | 
					import { Reducer } from "react";
 | 
				
			||||||
 | 
					import { SubmissionAction } from "@/state/actions/submission";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type InternshipPlanState = SubmissionState & MayRequireDeanApproval & {
 | 
				
			||||||
 | 
					    plan: Serializable<Plan> | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultInternshipPlanState: InternshipPlanState = {
 | 
				
			||||||
 | 
					    ...defaultDeanApprovalsState,
 | 
				
			||||||
 | 
					    ...defaultSubmissionState,
 | 
				
			||||||
 | 
					    plan: null,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getInternshipPlan = ({ plan }: InternshipPlanState): Plan | null => plan;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const internshipPlanSubmissionReducer: Reducer<InternshipPlanState, InternshipPlanAction> = createSubmissionReducer({
 | 
				
			||||||
 | 
					    [InternshipPlanActions.Approve]: SubmissionAction.Approve,
 | 
				
			||||||
 | 
					    [InternshipPlanActions.Decline]: SubmissionAction.Decline,
 | 
				
			||||||
 | 
					    [InternshipPlanActions.Receive]: SubmissionAction.Receive,
 | 
				
			||||||
 | 
					    [InternshipPlanActions.Save]: SubmissionAction.Save,
 | 
				
			||||||
 | 
					    [InternshipPlanActions.Send]: SubmissionAction.Send,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const internshipPlanReducer = (state: InternshipPlanState = defaultInternshipPlanState, action: InternshipPlanAction): InternshipPlanState => {
 | 
				
			||||||
 | 
					    state = internshipPlanSubmissionReducer(state, action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (action.type) {
 | 
				
			||||||
 | 
					        case InternshipPlanActions.Save:
 | 
				
			||||||
 | 
					        case InternshipPlanActions.Send:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                plan: action.plan,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default internshipPlanReducer;
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/state/reducer/proposal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/state/reducer/proposal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
 | 
				
			||||||
 | 
					import { Internship } from "@/data";
 | 
				
			||||||
 | 
					import { Serializable } from "@/serialization/types";
 | 
				
			||||||
 | 
					import { internshipSerializationTransformer } from "@/serialization";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    createSubmissionReducer,
 | 
				
			||||||
 | 
					    defaultDeanApprovalsState,
 | 
				
			||||||
 | 
					    defaultSubmissionState,
 | 
				
			||||||
 | 
					    MayRequireDeanApproval,
 | 
				
			||||||
 | 
					    SubmissionState
 | 
				
			||||||
 | 
					} from "@/state/reducer/submission";
 | 
				
			||||||
 | 
					import { Reducer } from "react";
 | 
				
			||||||
 | 
					import { SubmissionAction } from "@/state/actions/submission";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type InternshipProposalState = SubmissionState & MayRequireDeanApproval & {
 | 
				
			||||||
 | 
					    proposal: Serializable<Internship> | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultInternshipProposalState: InternshipProposalState = {
 | 
				
			||||||
 | 
					    ...defaultDeanApprovalsState,
 | 
				
			||||||
 | 
					    ...defaultSubmissionState,
 | 
				
			||||||
 | 
					    proposal: null,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getInternshipProposal = ({ proposal }: InternshipProposalState): Internship | null =>
 | 
				
			||||||
 | 
					    proposal && internshipSerializationTransformer.reverseTransform(proposal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const internshipProposalSubmissionReducer: Reducer<InternshipProposalState, InternshipProposalAction> = createSubmissionReducer({
 | 
				
			||||||
 | 
					    [InternshipProposalActions.Approve]: SubmissionAction.Approve,
 | 
				
			||||||
 | 
					    [InternshipProposalActions.Decline]: SubmissionAction.Decline,
 | 
				
			||||||
 | 
					    [InternshipProposalActions.Receive]: SubmissionAction.Receive,
 | 
				
			||||||
 | 
					    [InternshipProposalActions.Save]: SubmissionAction.Save,
 | 
				
			||||||
 | 
					    [InternshipProposalActions.Send]: SubmissionAction.Send,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const internshipProposalReducer = (state: InternshipProposalState = defaultInternshipProposalState, action: InternshipProposalAction): InternshipProposalState => {
 | 
				
			||||||
 | 
					    state = internshipProposalSubmissionReducer(state, action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (action.type) {
 | 
				
			||||||
 | 
					        case InternshipProposalActions.Save:
 | 
				
			||||||
 | 
					        case InternshipProposalActions.Send:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                proposal: internshipSerializationTransformer.transform(action.internship),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default internshipProposalReducer;
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/state/reducer/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/state/reducer/settings.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { SettingActions, SettingsAction } from "@/state/actions/settings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Locale = "pl" | "en"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SettingsState = {
 | 
				
			||||||
 | 
					    locale: Locale
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultSettingsState: SettingsState = {
 | 
				
			||||||
 | 
					    locale: "pl",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const settingsReducer = (state: SettingsState = defaultSettingsState, action: SettingsAction): SettingsState => {
 | 
				
			||||||
 | 
					    switch (action.type) {
 | 
				
			||||||
 | 
					        case SettingActions.SetLocale:
 | 
				
			||||||
 | 
					            return { ...state, locale: action.locale }
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default settingsReducer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getLocale = (state: SettingsState): Locale => state.locale;
 | 
				
			||||||
							
								
								
									
										80
									
								
								src/state/reducer/submission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/state/reducer/submission.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					import { DeanApproval } from "@/data/deanApproval";
 | 
				
			||||||
 | 
					import { Action } from "@/state/actions";
 | 
				
			||||||
 | 
					import { momentSerializationTransformer } from "@/serialization";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					import { ReceiveSubmissionApproveAction, ReceiveSubmissionDeclineAction, SubmissionAction } from "@/state/actions/submission";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SubmissionStatus = "draft" | "awaiting" | "accepted" | "declined";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SubmissionState = {
 | 
				
			||||||
 | 
					    accepted: boolean;
 | 
				
			||||||
 | 
					    sent: boolean;
 | 
				
			||||||
 | 
					    sentOn: string | null;
 | 
				
			||||||
 | 
					    declined: boolean;
 | 
				
			||||||
 | 
					    comment: string | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MayRequireDeanApproval = {
 | 
				
			||||||
 | 
					    requiredDeanApprovals: DeanApproval[],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const defaultSubmissionState: SubmissionState = {
 | 
				
			||||||
 | 
					    accepted: false,
 | 
				
			||||||
 | 
					    sent: false,
 | 
				
			||||||
 | 
					    sentOn: null,
 | 
				
			||||||
 | 
					    declined: false,
 | 
				
			||||||
 | 
					    comment: null,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const defaultDeanApprovalsState: MayRequireDeanApproval = {
 | 
				
			||||||
 | 
					    requiredDeanApprovals: [],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getSubmissionStatus = ({ accepted, declined, sent }: SubmissionState): SubmissionStatus => {
 | 
				
			||||||
 | 
					    switch (true) {
 | 
				
			||||||
 | 
					        case !sent:
 | 
				
			||||||
 | 
					            return "draft";
 | 
				
			||||||
 | 
					        case sent && accepted:
 | 
				
			||||||
 | 
					            return "accepted"
 | 
				
			||||||
 | 
					        case sent && declined:
 | 
				
			||||||
 | 
					            return "declined"
 | 
				
			||||||
 | 
					        case sent && (!accepted && !declined):
 | 
				
			||||||
 | 
					            return "awaiting"
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            throw new Error("Invalid submission state " + JSON.stringify({ accepted, declined, sent }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createSubmissionReducer<TState, TActionType, TAction extends Action>(mapping: { [TAction in keyof TActionType]: SubmissionAction }) {
 | 
				
			||||||
 | 
					    return (state: TState, action: TAction) => {
 | 
				
			||||||
 | 
					        const mappedAction = mapping[action.type as keyof TActionType];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (mappedAction) {
 | 
				
			||||||
 | 
					            case SubmissionAction.Approve:
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    ...state,
 | 
				
			||||||
 | 
					                    accepted: true,
 | 
				
			||||||
 | 
					                    declined: false,
 | 
				
			||||||
 | 
					                    comment: (action as ReceiveSubmissionApproveAction<any>).comment,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            case SubmissionAction.Decline:
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    ...state,
 | 
				
			||||||
 | 
					                    accepted: false,
 | 
				
			||||||
 | 
					                    declined: true,
 | 
				
			||||||
 | 
					                    comment: (action as ReceiveSubmissionDeclineAction<any>).comment,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            case SubmissionAction.Send:
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    ...state,
 | 
				
			||||||
 | 
					                    sent: true,
 | 
				
			||||||
 | 
					                    sentOn: momentSerializationTransformer.transform(moment()),
 | 
				
			||||||
 | 
					                    accepted: false,
 | 
				
			||||||
 | 
					                    declined: false,
 | 
				
			||||||
 | 
					                    comment: null,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                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;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/styles/footer.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/styles/footer.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					@import "variables";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer {
 | 
				
			||||||
 | 
					  background: $main-dark;
 | 
				
			||||||
 | 
					  margin-top: 3rem;
 | 
				
			||||||
 | 
					  color: #e4f1fe;
 | 
				
			||||||
 | 
					  padding: 1rem 0;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  font-size: 0.8rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer__copyright {
 | 
				
			||||||
 | 
					  text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -14,6 +14,7 @@
 | 
				
			|||||||
html, body {
 | 
					html, body {
 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
  padding: 0;
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  min-height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  font-family: "Roboto", "Helvetica", "Arial", sans-serif;
 | 
					  font-family: "Roboto", "Helvetica", "Arial", sans-serif;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -21,3 +22,16 @@ html, body {
 | 
				
			|||||||
* {
 | 
					* {
 | 
				
			||||||
  box-sizing: border-box;
 | 
					  box-sizing: border-box;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#root {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#content {
 | 
				
			||||||
 | 
					  flex: 1 1 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body, #root {
 | 
				
			||||||
 | 
					  min-height: 100vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,13 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
 | 
					copyright: ETI © {{ date, YYYY }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
login: login
 | 
					login: login
 | 
				
			||||||
logout: logout
 | 
					logout: logout
 | 
				
			||||||
logged-in-as: logged in as <1>{{ name }}</1>
 | 
					logged-in-as: logged in as <1>{{ name }}</1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
until: until {{ date }}
 | 
					until: until {{ date, DD MMMM YYYY }}
 | 
				
			||||||
late: late by {{ by }}
 | 
					late: late by {{ by, humanize }}
 | 
				
			||||||
left: '{{ left }} left'
 | 
					left: '{{ left, humanize }} left'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dropzone: "Drag and drop a file here or click to choose"
 | 
					dropzone: "Drag and drop a file here or click to choose"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,24 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
 | 
					copyright: Wydział ETI Politechniki Gdańskiej © {{ date, YYYY }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
login: zaloguj się
 | 
					login: zaloguj się
 | 
				
			||||||
logout: wyloguj się
 | 
					logout: wyloguj się
 | 
				
			||||||
logged-in-as: zalogowany jako <1>{{ name }}</1>
 | 
					logged-in-as: zalogowany jako <1>{{ name }}</1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
until: do {{ date }}
 | 
					until: do {{ date, DD MMMM YYYY }}
 | 
				
			||||||
late: '{{ by }} spóźnienia'
 | 
					late: '{{ by, humanize }} spóźnienia'
 | 
				
			||||||
left: jeszcze {{ left }}
 | 
					left: jeszcze {{ left, humanize }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
confirm: zatwierdź
 | 
					confirm: zatwierdź
 | 
				
			||||||
go-back: wstecz
 | 
					go-back: wstecz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					make-changes: wprowadź zmiany
 | 
				
			||||||
 | 
					review: podgląd
 | 
				
			||||||
 | 
					fix-errors: popraw uwagi
 | 
				
			||||||
 | 
					contact: skontaktuj się z pełnomocnikiem
 | 
				
			||||||
 | 
					comments: Zgłoszone uwagi
 | 
				
			||||||
 | 
					send-again: wyślij ponownie
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
 | 
					dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sections:
 | 
					sections:
 | 
				
			||||||
@ -30,6 +39,13 @@ student:
 | 
				
			|||||||
  email: adres e-mail
 | 
					  email: adres e-mail
 | 
				
			||||||
  albumNumber: numer albumu
 | 
					  albumNumber: numer albumu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					submission:
 | 
				
			||||||
 | 
					  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"
 | 
				
			||||||
@ -39,12 +55,30 @@ steps:
 | 
				
			|||||||
    form: "Uzupełnij dane"
 | 
					    form: "Uzupełnij dane"
 | 
				
			||||||
  internship-proposal:
 | 
					  internship-proposal:
 | 
				
			||||||
    header: "Zgłoszenie praktyki"
 | 
					    header: "Zgłoszenie praktyki"
 | 
				
			||||||
    info: >
 | 
					    info:
 | 
				
			||||||
 | 
					      draft: >
 | 
				
			||||||
        Przed podjęciem praktyki należy ją zgłosić.
 | 
					        Przed podjęciem praktyki należy ją zgłosić.
 | 
				
			||||||
 | 
					      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.
 | 
				
			||||||
    form: "Formularz zgłaszania praktyki"
 | 
					    form: "Formularz zgłaszania praktyki"
 | 
				
			||||||
 | 
					    action: "zgłoś praktykę"
 | 
				
			||||||
  plan:
 | 
					  plan:
 | 
				
			||||||
    header: "Indywidualny Program Praktyki"
 | 
					    header: "Indywidualny Program Praktyki"
 | 
				
			||||||
    info: ""
 | 
					    info:
 | 
				
			||||||
 | 
					      draft: >
 | 
				
			||||||
 | 
					        TODO
 | 
				
			||||||
 | 
					      awaiting: >
 | 
				
			||||||
 | 
					        TODO
 | 
				
			||||||
 | 
					      accepted: >
 | 
				
			||||||
 | 
					        TODO
 | 
				
			||||||
 | 
					      declined: >
 | 
				
			||||||
 | 
					        TODO
 | 
				
			||||||
    template: "Pobierz szablon"
 | 
					    template: "Pobierz szablon"
 | 
				
			||||||
    submit: "Wyślij Indywidualny Plan Praktyki"
 | 
					    submit: "Wyślij Indywidualny Plan Praktyki"
 | 
				
			||||||
  report:
 | 
					  report:
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								yarn.lock
									
									
									
									
									
								
							@ -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"
 | 
				
			||||||
@ -7408,6 +7413,11 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
 | 
					  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
 | 
				
			||||||
  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 | 
					  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react-moment@^0.9.7:
 | 
				
			||||||
 | 
					  version "0.9.7"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.9.7.tgz#ca570466595b1aa4f7619e62da18b3bb2de8b6f3"
 | 
				
			||||||
 | 
					  integrity sha512-ifzUrUGF6KRsUN2pRG5k56kO0mJBr8kRkWb0wNvtFIsBIxOuPxhUpL1YlXwpbQCbHq23hUu6A0VEk64HsFxk9g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
react-redux@^7.2.0:
 | 
					react-redux@^7.2.0:
 | 
				
			||||||
  version "7.2.0"
 | 
					  version "7.2.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
 | 
					  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user