Add subject selection

This commit is contained in:
Kacper Donat 2020-11-07 23:33:23 +01:00
parent 9977f5678c
commit 0ff80a454d
32 changed files with 190 additions and 49 deletions

View File

@ -40,7 +40,8 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"material-ui-dropzone": "^3.3.0", "material-ui-dropzone": "^3.3.0",
"mdi-material-ui": "^6.17.0", "mdi-material-ui": "^6.17.0",
"moment": "^2.26.0", "moment-timezone": "^2.26.0",
"moment-timezone": "^0.5.31",
"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",
"postcss-flexbugs-fixes": "4.1.0", "postcss-flexbugs-fixes": "4.1.0",

View File

@ -1,15 +1,21 @@
import { Identifiable } from "@/data"; import { Identifiable, InternshipProgramEntry } from "@/data";
import { CourseDTO, courseDtoTransformer } from "@/api/dto/course"; import { CourseDTO, courseDtoTransformer } from "@/api/dto/course";
import { OneWayTransformer, Transformer } from "@/serialization"; import { OneWayTransformer, Transformer } from "@/serialization";
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import moment from "moment"; import moment from "moment-timezone";
import { Subset } from "@/helpers"; import { Subset } from "@/helpers";
export interface ProgramEntryDTO extends Identifiable {
description: string;
descriptionEng: string;
}
export interface EditionDTO extends Identifiable { export interface EditionDTO extends Identifiable {
editionStart: string, editionStart: string,
editionFinish: string, editionFinish: string,
reportingStart: string, reportingStart: string,
course: CourseDTO, course: CourseDTO,
availableSubjects: ProgramEntryDTO[],
} }
export interface EditionTeaserDTO extends Identifiable { export interface EditionTeaserDTO extends Identifiable {
@ -39,6 +45,7 @@ export const editionDtoTransformer: Transformer<EditionDTO, Edition> = {
editionStart: subject.startDate.toISOString(), editionStart: subject.startDate.toISOString(),
course: courseDtoTransformer.reverseTransform(subject.course), course: courseDtoTransformer.reverseTransform(subject.course),
reportingStart: subject.reportingStart.toISOString(), reportingStart: subject.reportingStart.toISOString(),
availableSubjects: [],
}; };
}, },
transform(subject: EditionDTO, context: undefined): Edition { transform(subject: EditionDTO, context: undefined): Edition {
@ -55,3 +62,19 @@ export const editionDtoTransformer: Transformer<EditionDTO, Edition> = {
}; };
} }
} }
export const programEntryDtoTransformer: Transformer<ProgramEntryDTO, InternshipProgramEntry> = {
transform(subject: ProgramEntryDTO, context: never): InternshipProgramEntry {
return {
id: subject.id,
description: subject.description,
}
},
reverseTransform(subject: InternshipProgramEntry, context: never): ProgramEntryDTO {
return {
id: subject.id,
description: subject.description,
descriptionEng: "",
}
},
}

View File

@ -3,7 +3,7 @@ import { momentSerializationTransformer, OneWayTransformer } from "@/serializati
import { Nullable } from "@/helpers"; import { Nullable } from "@/helpers";
import { MentorDTO, mentorDtoTransformer } from "@/api/dto/mentor"; import { MentorDTO, mentorDtoTransformer } from "@/api/dto/mentor";
import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type"; import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type";
import { Moment } from "moment"; import { Moment } from "moment-timezone";
import { sampleStudent } from "@/provider/dummy"; import { sampleStudent } from "@/provider/dummy";
import { UploadType } from "@/api/upload"; import { UploadType } from "@/api/upload";
@ -36,6 +36,7 @@ export interface InternshipRegistrationUpdate {
type: number, type: number,
mentor: MentorDTO, mentor: MentorDTO,
hours: number, hours: number,
subjects: string[],
} }
export interface InternshipRegistrationDTO extends Identifiable { export interface InternshipRegistrationDTO extends Identifiable {
@ -65,8 +66,8 @@ export interface InternshipInfoDTO {
export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = { export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = {
transform(subject: Nullable<Internship>, context?: unknown): Nullable<InternshipRegistrationUpdate> { transform(subject: Nullable<Internship>, context?: unknown): Nullable<InternshipRegistrationUpdate> {
return { return {
start: subject?.startDate?.toISOString() || null, start: momentSerializationTransformer.transform(subject?.startDate) || null,
end: subject?.endDate?.toISOString() || null, end: momentSerializationTransformer.transform(subject?.endDate) || null,
type: parseInt(subject?.type?.id || "0"), type: parseInt(subject?.type?.id || "0"),
mentor: mentorDtoTransformer.reverseTransform(subject.mentor as Mentor), mentor: mentorDtoTransformer.reverseTransform(subject.mentor as Mentor),
company: subject?.company?.id ? { company: subject?.company?.id ? {
@ -80,6 +81,7 @@ export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable
branchOffice: subject?.office?.address as NewBranchOffice branchOffice: subject?.office?.address as NewBranchOffice
}, },
hours: subject?.hours, hours: subject?.hours,
subjects: subject?.program?.map(program => program.id as string) || [],
} }
} }
} }

View File

@ -1,8 +1,9 @@
import { axios } from "@/api/index"; import { axios } from "@/api/index";
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import { prepare } from "@/routing"; import { prepare } from "@/routing";
import { EditionDTO, editionDtoTransformer, EditionTeaserDTO, editionTeaserDtoTransformer } from "@/api/dto/edition"; import { EditionDTO, editionDtoTransformer, EditionTeaserDTO, editionTeaserDtoTransformer, programEntryDtoTransformer } from "@/api/dto/edition";
import { Subset } from "@/helpers"; import { Subset } from "@/helpers";
import { InternshipProgramEntry } from "@/data";
const EDITIONS_ENDPOINT = "/editions"; const EDITIONS_ENDPOINT = "/editions";
const EDITION_INFO_ENDPOINT = "/editions/:key"; const EDITION_INFO_ENDPOINT = "/editions/:key";
@ -41,11 +42,17 @@ export async function get(key: string): Promise<Subset<Edition> | null> {
return editionTeaserDtoTransformer.transform(dto); return editionTeaserDtoTransformer.transform(dto);
} }
export async function current(): Promise<Edition> { export async function current(): Promise<{
edition: Edition,
program: InternshipProgramEntry[],
}> {
const response = await axios.get<EditionDTO>(EDITION_CURRENT_ENDPOINT); const response = await axios.get<EditionDTO>(EDITION_CURRENT_ENDPOINT);
const dto = response.data; const dto = response.data;
return editionDtoTransformer.transform(dto); return {
edition: editionDtoTransformer.transform(dto),
program: dto.availableSubjects.map(programEntryDtoTransformer.transform as any),
};
} }
export async function login(key: string): Promise<string> { export async function login(key: string): Promise<string> {

View File

@ -9,12 +9,11 @@ import '@/styles/overrides.scss'
import '@/styles/header.scss' import '@/styles/header.scss'
import '@/styles/footer.scss' import '@/styles/footer.scss'
import classNames from "classnames"; import classNames from "classnames";
import { Edition } from "@/data/edition";
import { SettingActions } from "@/state/actions/settings"; import { SettingActions } from "@/state/actions/settings";
import { useDispatch, UserActions } from "@/state/actions"; import { useDispatch, UserActions } from "@/state/actions";
import { getLocale, Locale } from "@/state/reducer/settings"; import { getLocale, Locale } from "@/state/reducer/settings";
import i18n from "@/i18n"; import i18n from "@/i18n";
import moment from "moment"; import moment from "moment-timezone";
import { Container } from "@material-ui/core"; import { Container } from "@material-ui/core";
const UserMenu = (props: HTMLProps<HTMLUListElement>) => { const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
@ -61,8 +60,6 @@ const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>)
} }
function App() { function App() {
const dispatch = useDispatch();
const edition = useSelector<AppState, Edition | null>(state => state.edition as any);
const { t } = useTranslation(); const { t } = useTranslation();
const locale = useSelector<AppState, Locale>(state => getLocale(state.settings)); const locale = useSelector<AppState, Locale>(state => getLocale(state.settings));

View File

@ -2,6 +2,7 @@ import { AsyncResult } from "@/hooks";
import React from "react"; import React from "react";
import { CircularProgress } from "@material-ui/core"; import { CircularProgress } from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { Loading } from "@/components/loading";
type AsyncProps<TValue, TError = any> = { type AsyncProps<TValue, TError = any> = {
async: AsyncResult<TValue>, async: AsyncResult<TValue>,
@ -10,7 +11,7 @@ type AsyncProps<TValue, TError = any> = {
error?: (error: TError) => JSX.Element, error?: (error: TError) => JSX.Element,
} }
const defaultLoading = () => <CircularProgress />; const defaultLoading = () => <Loading />;
const defaultError = (error: any) => <Alert severity="error">{ error.message }</Alert>; const defaultError = (error: any) => <Alert severity="error">{ error.message }</Alert>;
export function Async<TValue, TError = any>( export function Async<TValue, TError = any>(

View File

@ -0,0 +1,28 @@
import React from "react";
import { createStyles, makeStyles } from "@material-ui/core/styles";
import { CircularProgress, Typography } from "@material-ui/core";
const useStyles = makeStyles(theme => createStyles({
root: {
display: "flex",
flexDirection: "column",
alignItems: "center",
"& > :not(:last-child)": {
marginBottom: theme.spacing(2),
}
}
}))
export type LoadingProps = {
size?: string | number;
label?: string;
};
export function Loading({ size, label, ...props }: LoadingProps) {
const classes = useStyles();
return <div className={ classes.root } { ...props }>
<CircularProgress size={ size }/>
{ label && <Typography variant="subtitle1" color="primary">{ label }</Typography> }
</div>
}

View File

@ -4,7 +4,7 @@ import { Typography } from "@material-ui/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames"; import classNames from "classnames";
import { useVerticalSpacing } from "@/styles"; import { useVerticalSpacing } from "@/styles";
import moment from "moment"; import moment from "moment-timezone";
import { Label, Section } from "@/components/section"; import { Label, Section } from "@/components/section";
import { StudentPreview } from "@/pages/user/profile"; import { StudentPreview } from "@/pages/user/profile";

View File

@ -1,4 +1,4 @@
import moment, { Moment } from "moment"; import moment, { Moment } from "moment-timezone";
import { Box, Step as StepperStep, StepContent, StepLabel, StepProps as StepperStepProps, Typography } from "@material-ui/core"; import { Box, Step as StepperStep, StepContent, StepLabel, StepProps as StepperStepProps, Typography } from "@material-ui/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import React, { ReactChild, useMemo } from "react"; import React, { ReactChild, useMemo } from "react";

View File

@ -5,5 +5,4 @@ import { Identifiable } from "./common";
export interface Course extends Identifiable { export interface Course extends Identifiable {
name: string, name: string,
desiredSemesters: Semester[], desiredSemesters: Semester[],
possibleProgramEntries: InternshipProgramEntry[];
} }

View File

@ -1,4 +1,4 @@
import { Moment } from "moment"; import { Moment } from "moment-timezone";
import { Course } from "@/data/course"; import { Course } from "@/data/course";
import { Identifiable } from "@/data/common"; import { Identifiable } from "@/data/common";

View File

@ -1,4 +1,4 @@
import { Moment } from "moment"; import { Moment } from "moment-timezone";
import { Identifiable, Multilingual } from "./common"; import { Identifiable, Multilingual } from "./common";
import { Student } from "@/data/student"; import { Student } from "@/data/student";
import { Company, Office } from "@/data/company"; import { Company, Office } from "@/data/company";

View File

@ -1,12 +1,24 @@
import React, { HTMLProps, useMemo, useState } from "react"; import React, { HTMLProps, useEffect, useMemo, useState } from "react";
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Grid, TextField, Typography } from "@material-ui/core"; import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
Grid,
TextField,
Typography,
FormGroup,
FormControlLabel,
Checkbox
} 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";
import { sampleStudent } from "@/provider/dummy/student"; import { sampleStudent } from "@/provider/dummy/student";
import { Company, Internship, InternshipType, Office, Student } from "@/data"; import { Company, Internship, InternshipProgramEntry, InternshipType, Office, Student } from "@/data";
import { Nullable } from "@/helpers"; import { Nullable } from "@/helpers";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment-timezone";
import { computeWorkingHours } from "@/utils/date"; import { computeWorkingHours } from "@/utils/date";
import { Autocomplete } from "@material-ui/lab"; import { Autocomplete } from "@material-ui/lab";
import { emptyInternship } from "@/provider/dummy/internship"; import { emptyInternship } from "@/provider/dummy/internship";
@ -25,6 +37,8 @@ import { TextField as TextFieldFormik } from "formik-material-ui"
import { useCurrentEdition, useCurrentStudent, useInternshipTypes, useUpdateEffect } from "@/hooks"; import { useCurrentEdition, useCurrentStudent, useInternshipTypes, useUpdateEffect } from "@/hooks";
import { internshipRegistrationUpdateTransformer } from "@/api/dto/internship-registration"; import { internshipRegistrationUpdateTransformer } from "@/api/dto/internship-registration";
import api from "@/api"; import api from "@/api";
import FormLabel from "@material-ui/core/FormLabel";
import { CheckBox } from "@material-ui/icons";
export type InternshipFormValues = { export type InternshipFormValues = {
startDate: Moment | null; startDate: Moment | null;
@ -43,6 +57,7 @@ export type InternshipFormValues = {
mentorEmail: string; mentorEmail: string;
mentorPhone: string; mentorPhone: string;
kindOther: string | null; kindOther: string | null;
program: InternshipProgramEntry[];
// relations // relations
kind: InternshipType | null; kind: InternshipType | null;
@ -72,6 +87,7 @@ const emptyInternshipValues: InternshipFormValues = {
startDate: null, startDate: null,
student: sampleStudent, student: sampleStudent,
workingHours: 40, workingHours: 40,
program: [],
} }
export const InternshipTypeItem = ({ internshipType: type, ...props }: { internshipType: InternshipType } & HTMLProps<any>) => { export const InternshipTypeItem = ({ internshipType: type, ...props }: { internshipType: InternshipType } & HTMLProps<any>) => {
@ -86,9 +102,24 @@ export const InternshipTypeItem = ({ internshipType: type, ...props }: { interns
const InternshipProgramForm = () => { const InternshipProgramForm = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>(); const { values, handleBlur, setFieldValue, errors } = useFormikContext<InternshipFormValues>();
const [ selectedProgramEntries, setSelectedProgramEntries ] = useState<InternshipProgramEntry[]>(values.program);
const possibleProgramEntries = useSelector<AppState, InternshipProgramEntry[]>(state => state.edition.program);
const types = useInternshipTypes(); const types = useInternshipTypes();
const handleProgramEntryChange = (entry: InternshipProgramEntry) => (ev: any) => {
if (ev.target.checked) {
setSelectedProgramEntries([ ...selectedProgramEntries, entry ]);
} else {
setSelectedProgramEntries(selectedProgramEntries.filter(cur => cur != entry));
}
}
useEffect(() => {
setFieldValue("program", selectedProgramEntries);
}, [ selectedProgramEntries ])
return ( return (
<Grid container> <Grid container>
<Grid item md={ 4 }> <Grid item md={ 4 }>
@ -108,6 +139,20 @@ const InternshipProgramForm = () => {
{/* <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />*/} {/* <Field label={ t("forms.internship.fields.kind-other") } name="kindOther" fullWidth component={ TextFieldFormik } />*/}
{/* }*/} {/* }*/}
{/*</Grid>*/} {/*</Grid>*/}
<Grid item xs={ 12 }>
<FormGroup>
<FormLabel>{ t('forms.internship.fields.program', { count: 3 }) }</FormLabel>
{ possibleProgramEntries.map(
entry => <FormControlLabel
control={ <Checkbox /> }
checked={ selectedProgramEntries.includes(entry) }
onChange={ handleProgramEntryChange(entry) }
label={ entry.description }
key={ entry.id }
/>
) }
</FormGroup>
</Grid>
</Grid> </Grid>
) )
} }
@ -206,6 +251,7 @@ const converter: Transformer<Nullable<Internship>, InternshipFormValues, Interns
mentorLastName: internship.mentor?.surname || "", mentorLastName: internship.mentor?.surname || "",
mentorPhone: internship.mentor?.phone || "", mentorPhone: internship.mentor?.phone || "",
workingHours: 40, workingHours: 40,
program: internship.program || [],
} }
}, },
reverseTransform(form: InternshipFormValues, context: InternshipConverterContext): Nullable<Internship> { reverseTransform(form: InternshipFormValues, context: InternshipConverterContext): Nullable<Internship> {
@ -235,6 +281,7 @@ const converter: Transformer<Nullable<Internship>, InternshipFormValues, Interns
}, },
hours: form.hours ? form.hours : 0, hours: form.hours ? form.hours : 0,
type: form.kind as InternshipType, type: form.kind as InternshipType,
program: form.program,
} }
} }
} }
@ -280,6 +327,7 @@ export const InternshipForm: React.FunctionComponent = () => {
city: Yup.string().required(t("validation.required")), city: Yup.string().required(t("validation.required")),
postalCode: Yup.string().required(t("validation.required")), postalCode: Yup.string().required(t("validation.required")),
building: Yup.string().required(t("validation.required")), building: Yup.string().required(t("validation.required")),
program: Yup.array() as any,
// kindOther: Yup.string().when("kind", { // kindOther: Yup.string().when("kind", {
// is: (values: InternshipFormValues) => values?.kind === InternshipType.Other, // is: (values: InternshipFormValues) => values?.kind === InternshipType.Other,
// then: Yup.string().required(t("validation.required")) // then: Yup.string().required(t("validation.required"))

View File

@ -9,7 +9,7 @@ export const useCurrentStudent = () => useSelector<AppState, Student | null>(
) )
export const useCurrentEdition = () => useSelector<AppState, Edition | null>( export const useCurrentEdition = () => useSelector<AppState, Edition | null>(
state => state.edition && editionSerializationTransformer.reverseTransform(state.edition) state => state.edition?.edition && editionSerializationTransformer.reverseTransform(state.edition.edition)
) )
export const useDeadlines = () => { export const useDeadlines = () => {

View File

@ -4,7 +4,7 @@ import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
import "moment/locale/pl" import "moment/locale/pl"
import "moment/locale/en-gb" import "moment/locale/en-gb"
import moment, { isDuration, isMoment, unitOfTime } from "moment"; import moment, { isDuration, isMoment, unitOfTime } from "moment-timezone";
import { convertToRoman } from "@/utils/numbers"; import { convertToRoman } from "@/utils/numbers";
const resources = { const resources = {

View File

@ -7,7 +7,7 @@ import store, { persistor } from "@/state/store";
import { PersistGate } from "redux-persist/integration/react"; import { PersistGate } from "redux-persist/integration/react";
import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles"; import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles";
import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import { MuiPickersUtilsProvider } from "@material-ui/pickers";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment-timezone";
import { studentTheme } from "@/ui/theme"; import { studentTheme } from "@/ui/theme";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import MomentUtils from "@date-io/moment"; import MomentUtils from "@date-io/moment";

View File

@ -25,11 +25,12 @@ export const loginToEdition = (id: string) => async (dispatch: AppDispatch) => {
token, token,
}) })
const edition = await api.edition.current(); const { edition, program } = await api.edition.current();
dispatch({ dispatch({
type: EditionActions.Set, type: EditionActions.Set,
edition edition,
program,
}) })
} }

View File

@ -1,6 +1,6 @@
import React, { Dispatch, useEffect } from "react"; import React, { Dispatch, useEffect } from "react";
import { Page } from "@/pages/base"; import { Page } from "@/pages/base";
import { Button, Container } from "@material-ui/core"; import { Button, CircularProgress, Container, Typography } from "@material-ui/core";
import { Action, StudentActions, useDispatch } from "@/state/actions"; import { Action, StudentActions, useDispatch } from "@/state/actions";
import { Route, Switch, useHistory, useLocation, useRouteMatch } from "react-router-dom"; import { Route, Switch, useHistory, useLocation, useRouteMatch } from "react-router-dom";
import { route } from "@/routing"; import { route } from "@/routing";
@ -10,6 +10,8 @@ import { AppState } from "@/state/reducer";
import api from "@/api"; import api from "@/api";
import { UserActions } from "@/state/actions/user"; import { UserActions } from "@/state/actions/user";
import { getAuthorizeUrl } from "@/api/user"; import { getAuthorizeUrl } from "@/api/user";
import { useTranslation } from "react-i18next";
import { Loading } from "@/components/loading";
const authorizeUser = (code?: string) => async (dispatch: Dispatch<Action>, getState: () => AppState): Promise<void> => { const authorizeUser = (code?: string) => async (dispatch: Dispatch<Action>, getState: () => AppState): Promise<void> => {
const token = await api.user.login(code); const token = await api.user.login(code);
@ -32,6 +34,7 @@ export const UserLoginPage = () => {
const match = useRouteMatch(); const match = useRouteMatch();
const location = useLocation(); const location = useLocation();
const query = new URLSearchParams(useLocation().search); const query = new URLSearchParams(useLocation().search);
const { t } = useTranslation();
const handleSampleLogin = async () => { const handleSampleLogin = async () => {
await dispatch(authorizeUser()); await dispatch(authorizeUser());
@ -54,6 +57,8 @@ export const UserLoginPage = () => {
})(); })();
}, [ match.path ]); }, [ match.path ]);
const inProgress = <Loading size="4rem" label={ t("login-in-progress") }/>
return <Page> return <Page>
<Page.Header maxWidth="md"> <Page.Header maxWidth="md">
<Page.Title>Zaloguj się</Page.Title> <Page.Title>Zaloguj się</Page.Title>
@ -66,11 +71,13 @@ export const UserLoginPage = () => {
<Button fullWidth onClick={ handleSampleLogin } variant="contained" color="secondary">Zaloguj jako przykładowy student</Button> <Button fullWidth onClick={ handleSampleLogin } variant="contained" color="secondary">Zaloguj jako przykładowy student</Button>
</Container> </Container>
</Route> </Route>
<Route path={`${match.path}/pg`} render={ <Route path={`${match.path}/pg`} render={ () => {
() => (window.location.href = getAuthorizeUrl()) window.location.href = getAuthorizeUrl()
} />
return inProgress
} } />
<Route path={`${match.path}/check/pg`}> <Route path={`${match.path}/check/pg`}>
Kod: { query.get("code") } { inProgress }
</Route> </Route>
</Switch> </Switch>
</Container> </Container>

View File

@ -1,5 +1,5 @@
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import moment from "moment"; import moment from "moment-timezone";
import { sampleCourse } from "@/provider/dummy/student"; import { sampleCourse } from "@/provider/dummy/student";
export const sampleEdition: Edition = { export const sampleEdition: Edition = {

View File

@ -13,7 +13,7 @@ export const emptyInternship: Nullable<Internship> = {
endDate: null, endDate: null,
startDate: null, startDate: null,
type: null, type: null,
program: null, program: [],
isAccepted: false, isAccepted: false,
lengthInWeeks: 0, lengthInWeeks: 0,
mentor: emptyMentor, mentor: emptyMentor,

View File

@ -25,7 +25,6 @@ export const sampleCourse: Course = {
id: courseIdSequence(), id: courseIdSequence(),
name: "Informatyka", name: "Informatyka",
desiredSemesters: [6], desiredSemesters: [6],
possibleProgramEntries: sampleProgramEntries,
} }
export const sampleStudent: Student = { export const sampleStudent: Student = {

View File

@ -1,7 +1,7 @@
import { Serializable, SerializationTransformer } from "@/serialization/types"; import { Serializable, SerializationTransformer } from "@/serialization/types";
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import { momentSerializationTransformer } from "@/serialization/moment"; import { momentSerializationTransformer } from "@/serialization/moment";
import { Moment } from "moment"; import { Moment } from "moment-timezone";
export const editionSerializationTransformer: SerializationTransformer<Edition> = { export const editionSerializationTransformer: SerializationTransformer<Edition> = {
transform(subject: Edition, context?: unknown): Serializable<Edition> { transform(subject: Edition, context?: unknown): Serializable<Edition> {

View File

@ -1,7 +1,7 @@
import { Internship, InternshipType } from "@/data"; import { Internship, InternshipType } from "@/data";
import { Serializable, SerializationTransformer } from "@/serialization/types"; import { Serializable, SerializationTransformer } from "@/serialization/types";
import { momentSerializationTransformer } from "@/serialization/moment"; import { momentSerializationTransformer } from "@/serialization/moment";
import { Moment } from "moment"; import { Moment } from "moment-timezone";
export const internshipSerializationTransformer: SerializationTransformer<Internship> = { export const internshipSerializationTransformer: SerializationTransformer<Internship> = {
transform: (internship: Internship): Serializable<Internship> => ({ transform: (internship: Internship): Serializable<Internship> => ({

View File

@ -1,7 +1,7 @@
import { SerializationTransformer } from "@/serialization/types"; import { SerializationTransformer } from "@/serialization/types";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment-timezone";
export const momentSerializationTransformer: SerializationTransformer<Moment | null, string> = { export const momentSerializationTransformer: SerializationTransformer<Moment | null, string> = {
transform: (subject: Moment) => subject && subject.toISOString(), transform: (subject: Moment) => subject && subject.clone().utc(false).add(subject.utcOffset(), 'minutes').toISOString(),
reverseTransform: (subject: string) => subject ? moment(subject) : null, reverseTransform: (subject: string) => subject ? moment(subject) : null,
} }

View File

@ -1,4 +1,4 @@
import { Moment } from "moment"; import { Moment } from "moment-timezone";
type Simplify<T> = string | type Simplify<T> = string |
T extends string ? string : T extends string ? string :

View File

@ -1,5 +1,6 @@
import { Action } from "@/state/actions/base"; import { Action } from "@/state/actions/base";
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import { InternshipProgramEntry } from "@/data";
export enum EditionActions { export enum EditionActions {
Set = 'SET_EDITION', Set = 'SET_EDITION',
@ -7,6 +8,7 @@ export enum EditionActions {
export interface SetAction extends Action<EditionActions.Set> { export interface SetAction extends Action<EditionActions.Set> {
edition: Edition, edition: Edition,
program: InternshipProgramEntry[],
} }
export type EditionAction = SetAction; export type EditionAction = SetAction;

View File

@ -2,15 +2,26 @@ import { Edition } from "@/data/edition";
import { EditionAction, EditionActions } from "@/state/actions/edition"; import { EditionAction, EditionActions } from "@/state/actions/edition";
import { editionSerializationTransformer, Serializable } from "@/serialization"; import { editionSerializationTransformer, Serializable } from "@/serialization";
import { LoginAction, LogoutAction, UserActions } from "@/state/actions"; import { LoginAction, LogoutAction, UserActions } from "@/state/actions";
import { InternshipProgramEntry } from "@/data";
export type EditionState = Serializable<Edition> | null; export type EditionState = Serializable<{
edition: Edition | null,
program: InternshipProgramEntry[],
}>
const initialEditionState: EditionState = null; const initialEditionState: EditionState = {
edition: null,
program: [],
};
const editionReducer = (state: EditionState = initialEditionState, action: EditionAction | LogoutAction | LoginAction): EditionState => { const editionReducer = (state: EditionState = initialEditionState, action: EditionAction | LogoutAction | LoginAction): EditionState => {
switch (action.type) { switch (action.type) {
case EditionActions.Set: case EditionActions.Set:
return editionSerializationTransformer.transform(action.edition); return {
...state,
edition: editionSerializationTransformer.transform(action.edition),
program: action.program,
};
case UserActions.Login: case UserActions.Login:
case UserActions.Logout: case UserActions.Logout:
return initialEditionState; return initialEditionState;

View File

@ -22,4 +22,4 @@ export type AppState = ReturnType<typeof rootReducer>;
export default rootReducer; export default rootReducer;
export const isReady = (state: AppState) => !!state.edition; export const isReady = (state: AppState) => !!(state.edition?.edition);

View File

@ -1,7 +1,7 @@
import { DeanApproval } from "@/data/deanApproval"; import { DeanApproval } from "@/data/deanApproval";
import { Action } from "@/state/actions"; import { Action } from "@/state/actions";
import { momentSerializationTransformer } from "@/serialization"; import { momentSerializationTransformer } from "@/serialization";
import moment from "moment"; import moment from "moment-timezone";
import { ReceiveSubmissionApproveAction, ReceiveSubmissionDeclineAction, SubmissionAction } from "@/state/actions/submission"; import { ReceiveSubmissionApproveAction, ReceiveSubmissionDeclineAction, SubmissionAction } from "@/state/actions/submission";
export type SubmissionStatus = "draft" | "awaiting" | "accepted" | "declined"; export type SubmissionStatus = "draft" | "awaiting" | "accepted" | "declined";

View File

@ -20,3 +20,14 @@
.proposal__header:not(:first-child) { .proposal__header:not(:first-child) {
margin-top: 1rem; margin-top: 1rem;
} }
.loading-wrapper {
display: flex;
flex-direction: column;
align-items: center;
& > *:not(:last-child) {
margin-bottom: 1rem;
}
}

View File

@ -1,4 +1,4 @@
import { Moment } from "moment"; import { Moment } from "moment-timezone";
import Holidays from "date-holidays"; import Holidays from "date-holidays";
const holidays = new Holidays() const holidays = new Holidays()

View File

@ -2,6 +2,7 @@
copyright: Wydział ETI Politechniki Gdańskiej © {{ date, YYYY }} copyright: Wydział ETI Politechniki Gdańskiej © {{ date, YYYY }}
login: zaloguj się login: zaloguj się
login-in-progress: Logowanie w toku, proszę czekać...
logout: wyloguj się logout: wyloguj się
logged-in-as: zalogowany jako <1>{{ name }}</1> logged-in-as: zalogowany jako <1>{{ name }}</1>
@ -79,6 +80,7 @@ forms:
country: Kraj country: Kraj
street: Ulica street: Ulica
building: Nr budynku building: Nr budynku
program: Program praktyki (wybierz {{ count }})
help: help:
weeks: Wartość wyliczana automatycznie weeks: Wartość wyliczana automatycznie
working-hours: Liczba godzin w tygodniu roboczym working-hours: Liczba godzin w tygodniu roboczym
@ -155,7 +157,8 @@ steps:
header: "Zgłoszenie praktyki" header: "Zgłoszenie praktyki"
info: info:
draft: > draft: >
Przed podjęciem praktyki należy ją zgłosić. (TODO) Przed podjęciem praktyki należy ją zgłosić - w tym celu należy elektronicznie wypełnić formularz zgłoszenia
praktyki.
awaiting: > awaiting: >
Twoje zgłoszenie musi zostać zweryfikowane i zatwierdzone. Po weryfikacji zostaniesz poinformowany o Twoje zgłoszenie musi zostać zweryfikowane i zatwierdzone. Po weryfikacji zostaniesz poinformowany o
akceptacji bądź konieczności wprowadzenia zmian. akceptacji bądź konieczności wprowadzenia zmian.
@ -171,7 +174,8 @@ steps:
info: info:
draft: > draft: >
W porozumieniu z firmą w której odbywają się praktyki należy sporządzić Indywidualny Plan Praktyk zgodnie z W porozumieniu z firmą w której odbywają się praktyki należy sporządzić Indywidualny Plan Praktyk zgodnie z
załączonym szablonem a następnie wysłać go do weryfikacji. (TODO) załączonym szablonem a następnie wysłać go do weryfikacji. Indywidualny Plan Praktyk musi zostać zatwierdzony
oraz podpisany przez Twojego zakłądowego opiekuna praktyki.
awaiting: > awaiting: >
Twój indywidualny program praktyki został poprawnie zapisany w systemie. Musi on jeszcze zostać zweryfikowany i Twój indywidualny program praktyki został poprawnie zapisany w systemie. Musi on jeszcze zostać zweryfikowany i
zatwierdzony. Po weryfikacji zostaniesz poinformowany o akceptacji bądź konieczności wprowadzenia zmian. zatwierdzony. Po weryfikacji zostaniesz poinformowany o akceptacji bądź konieczności wprowadzenia zmian.