Add subject selection
This commit is contained in:
parent
9977f5678c
commit
0ff80a454d
@ -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",
|
||||||
|
@ -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: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -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) || [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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>(
|
||||||
|
28
src/components/loading.tsx
Normal file
28
src/components/loading.tsx
Normal 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>
|
||||||
|
}
|
@ -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";
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
@ -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[];
|
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
@ -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"))
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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";
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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 = {
|
||||||
|
@ -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,
|
||||||
|
@ -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 = {
|
||||||
|
@ -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> {
|
||||||
|
@ -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> => ({
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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 :
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user