diff --git a/src/app.tsx b/src/app.tsx index a334733..0f42d36 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,13 +1,8 @@ -import React, { Dispatch, HTMLProps } from 'react'; -import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles"; -import { studentTheme } from "./ui/theme"; -import { MuiPickersUtilsProvider } from "@material-ui/pickers"; -import MomentUtils from "@date-io/moment"; -import { BrowserRouter, Link, Route, Switch } from "react-router-dom" -import moment, { Moment } from "moment"; +import React, { Dispatch, HTMLProps, useEffect } from 'react'; +import { Link, Route, Switch } from "react-router-dom" +import moment from "moment"; import { route, routes } from "@/routing"; -import { Provider, useDispatch, useSelector } from "react-redux"; -import store, { persistor } from "@/state/store"; +import { useDispatch, useSelector } from "react-redux"; import { AppState } from "@/state/reducer"; import { StudentAction, StudentActions } from "@/state/actions/student"; import { sampleStudent } from "@/provider/dummy/student"; @@ -16,13 +11,9 @@ import { Student } from "@/data"; import '@/styles/overrides.scss' import '@/styles/header.scss' import classNames from "classnames"; -import { PersistGate } from 'redux-persist/integration/react'; - -class LocalizedMomentUtils extends MomentUtils { - getDatePickerHeaderText(date: Moment): string { - return this.format(date, "d MMM yyyy"); - } -} +import { EditionAction, EditionActions } from "@/state/actions/edition"; +import { sampleEdition } from "@/provider/dummy/edition"; +import { Edition } from "@/data/edition"; const UserMenu = (props: HTMLProps) => { const student = useSelector(state => state.student as Student); @@ -40,14 +31,14 @@ const UserMenu = (props: HTMLProps) => { dispatch({ type: StudentActions.Logout }) } - return
    + return
      { student ? <> - logged in as {{ name: `${student.name} ${student.surname}` }} - {' '} - ({ t('logout') }) + logged in as { { name: `${ student.name } ${ student.surname }` } } + { ' ' } + ({ t('logout') }) : <> - { t('login') } + { t('login') } }
    ; @@ -75,39 +66,38 @@ const LanguageSwitcher = ({ className, ...props }: HTMLProps) } function App() { - return ( - - - - - - -
    - -
    - - -
    -
    - { routes.map(({ name, content, ...route }) => { content() }) } -
    -
    -
    -
    -
    -
    - ); + const dispatch = useDispatch>(); + const edition = useSelector(state => state.edition); + + useEffect(() => { + if (!edition) { + dispatch({ type: EditionActions.Set, edition: sampleEdition }); + } + }) + + const isReady = !!edition; + + return <> +
    + +
    + + +
    +
    + { isReady && { routes.map(({ name, content, ...route }) => { content() }) } } + ; } export default App; diff --git a/src/data/edition.ts b/src/data/edition.ts new file mode 100644 index 0000000..8a29c94 --- /dev/null +++ b/src/data/edition.ts @@ -0,0 +1,21 @@ +import { Moment } from "moment"; + +export type Edition = { + startDate: Moment; + endDate: Moment; + proposalDeadline: Moment; +} + +export type Deadlines = { + personalData?: Moment; + proposal?: Moment; + personalPlan?: Moment; + report?: Moment; +} + +export function getEditionDeadlines(edition: Edition): Deadlines { + return { + proposal: edition.proposalDeadline, + personalPlan: edition.proposalDeadline, + } +} diff --git a/src/data/student.ts b/src/data/student.ts index 5fb3221..b8eabfc 100644 --- a/src/data/student.ts +++ b/src/data/student.ts @@ -11,3 +11,18 @@ export interface Student extends Identifiable { semester: Semester; course: Course; } + +export function isStudentDataComplete(student: Student): boolean { + return getMissingStudentData(student).length === 0; +} + +export function getMissingStudentData(student: Student): (keyof Student)[] { + return [ + !!student.name || "name", + !!student.surname || "surname", + !!student.email || "email", + !!student.albumNumber || "albumNumber", + !!student.semester || "semester", + !!student.course || "course", + ].filter(x => x !== true) as (keyof Student)[]; +} diff --git a/src/index.tsx b/src/index.tsx index 4bbd47a..2c53768 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,10 +2,37 @@ import React from 'react'; import ReactDOM from 'react-dom'; import "./i18n" import App from './app'; +import { Provider } from "react-redux"; +import store, { persistor } from "@/state/store"; +import { PersistGate } from "redux-persist/integration/react"; +import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles"; +import { MuiPickersUtilsProvider } from "@material-ui/pickers"; +import moment, { Moment } from "moment"; +import { studentTheme } from "@/ui/theme"; +import { BrowserRouter } from "react-router-dom"; +import MomentUtils from "@date-io/moment"; + +class LocalizedMomentUtils extends MomentUtils { + getDatePickerHeaderText(date: Moment): string { + return this.format(date, "d MMM yyyy"); + } +} ReactDOM.render( - + + + + + + + + + + + + + , document.getElementById('root') ); diff --git a/src/pages/main.tsx b/src/pages/main.tsx index a2c371f..2e6bb4b 100644 --- a/src/pages/main.tsx +++ b/src/pages/main.tsx @@ -5,6 +5,10 @@ import { Link as RouterLink } from "react-router-dom"; import { route } from "@/routing"; import { useTranslation } from "react-i18next"; import moment, { Moment } from "moment"; +import { useSelector } from "react-redux"; +import { AppState } from "@/state/reducer"; +import { getMissingStudentData, Student } from "@/data"; +import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition"; type StepProps = StepperStepProps & { until?: Moment; @@ -18,7 +22,7 @@ const Step = ({ until, label, completedOn, children, completed, ...props }: Step const { t } = useTranslation(); const isLate = useMemo(() => until?.isBefore(completedOn || now), [completedOn, until]); - const left = useMemo(() => moment.duration(now.diff(until)), [until]); + const left = useMemo(() => moment.duration(now.diff(until)), [until]); return @@ -26,7 +30,8 @@ const Step = ({ until, label, completedOn, children, completed, ...props }: Step { until && { t('until', { date: until.format("DD MMMM YYYY") }) } - { isLate && - { t('late', { by: moment.duration(now.diff(until)).humanize() }) } } + { isLate && - { t('late', { by: moment.duration(now.diff(until)).humanize() }) } } { !isLate && !completed && - { t('left', { left: left.humanize() }) } } } @@ -38,19 +43,47 @@ const Step = ({ until, label, completedOn, children, completed, ...props }: Step export const MainPage = () => { const { t } = useTranslation(); + const student = useSelector(state => state.student); + const deadlines = useSelector(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point + + const missingStudentData = useMemo(() => student ? getMissingStudentData(student) : [], [student]); + return { t("sections.my-internship.header") } - - + + { missingStudentData.length > 0 && <> +

    { t('steps.personal-data.info') }

    + +
      + { missingStudentData.map(field =>
    • { t(`student.${field}`) }
    • ) } +
    + + + } +
    + +

    { t('steps.internship-proposal.info') }

    +
    - - - + +

    { t('steps.plan.info') }

    + + + +
    + +
    diff --git a/src/provider/dummy/edition.ts b/src/provider/dummy/edition.ts new file mode 100644 index 0000000..290a3ac --- /dev/null +++ b/src/provider/dummy/edition.ts @@ -0,0 +1,8 @@ +import { Edition } from "@/data/edition"; +import moment from "moment"; + +export const sampleEdition: Edition = { + startDate: moment("2020-07-01"), + endDate: moment("2020-09-30"), + proposalDeadline: moment("2020-07-31") +} diff --git a/src/state/actions/edition.ts b/src/state/actions/edition.ts new file mode 100644 index 0000000..142220d --- /dev/null +++ b/src/state/actions/edition.ts @@ -0,0 +1,13 @@ +import { Action } from "@/state/actions/base"; +import { Edition } from "@/data/edition"; + +export enum EditionActions { + Set = 'SET', +} + +export interface SetAction extends Action { + edition: Edition, +} + +export type EditionAction = SetAction; + diff --git a/src/state/reducer/edition.ts b/src/state/reducer/edition.ts new file mode 100644 index 0000000..ef696f9 --- /dev/null +++ b/src/state/reducer/edition.ts @@ -0,0 +1,17 @@ +import { Edition } from "@/data/edition"; +import { EditionAction, EditionActions } from "@/state/actions/edition"; + +export type EditionState = Edition | null; + +const initialEditionState: EditionState = null; + +const editionReducer = (state: EditionState = initialEditionState, action: EditionAction): EditionState => { + switch (action.type) { + case EditionActions.Set: + return action.edition; + } + + return state; +} + +export default editionReducer; diff --git a/src/state/reducer/index.ts b/src/state/reducer/index.ts index 0bd70e0..c839818 100644 --- a/src/state/reducer/index.ts +++ b/src/state/reducer/index.ts @@ -1,9 +1,11 @@ import { combineReducers } from "redux"; import studentReducer from "./student" +import editionReducer from "@/state/reducer/edition"; const rootReducer = combineReducers({ student: studentReducer, + edition: editionReducer, }) export type AppState = ReturnType; diff --git a/src/state/store.ts b/src/state/store.ts index 9fafb4b..f35ec6d 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -8,7 +8,8 @@ const store = createStore( persistReducer( { key: 'state', - storage: sessionStorage + storage: sessionStorage, + blacklist: ['edition'] }, rootReducer ), diff --git a/translations/en.yaml b/translations/en.yaml index d0bc877..fd6824a 100644 --- a/translations/en.yaml +++ b/translations/en.yaml @@ -7,6 +7,14 @@ until: until {{ date }} late: late by {{ by }} left: '{{ left }} left' +student: + name: first name + surname: last name + course: course + semester: semester + email: e-mail + albumNumber: album number + sections: my-internship: header: "My internship" @@ -14,8 +22,21 @@ sections: steps: personal-data: header: "Fill personal data" + info: > + Your profile is incomplete. In order to continue your internship you have to supply information given below. In + case of problem with providing those information - please contact with your internship coordinator of your course. internship-proposal: header: "Internship proposal" form: "Internship proposal form" + info: "" plan: header: "Individual Internship Plan" + info: "" + template: "Download template" + submit: "Submit Individual Internship Plan" + insurance: + header: "Insurance" + report: + header: "Internship report" + grade: + header: "Your grade" diff --git a/translations/pl.yaml b/translations/pl.yaml index b931255..a08a967 100644 --- a/translations/pl.yaml +++ b/translations/pl.yaml @@ -11,17 +11,36 @@ sections: my-internship: header: "Moja praktyka" +student: + name: imię + surname: mazwisko + course: kierunek + semester: semestr + email: adres e-mail + albumNumber: numer albumu + steps: personal-data: header: "Uzupełnienie informacji" + info: > + Twój profil jest niekompletny. W celu kontynuacji praktyki musisz uzupełnić informacje podane poniżej. Jeżeli masz + problem z uzupełnieniem tych informacji - skontaktuj się z koordynatorem praktyk dla Twojego kierunku. + form: "Uzupełnij dane" internship-proposal: header: "Zgłoszenie praktyki" + info: > + Przed podjęciem praktyki należy ją zgłosić. form: "Formularz zgłaszania praktyki" plan: header: "Indywidualny Program Praktyki" + info: "" + template: "Pobierz szablon" + submit: "Wyślij Indywidualny Plan Praktyki" report: header: "Raport z praktyki" grade: header: "Ocena z praktyki" insurance: - header: "Ubezpieczenie NWW" + header: "Ubezpieczenie NNW" + +contact-coordinator: "Skontaktuj się z koordynatorem"