Add IPP state management
This commit is contained in:
parent
886153afb5
commit
5cc9f51584
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,3 +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 { 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>
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { HTMLProps, useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Page } from "@/pages/base";
|
import { Page } from "@/pages/base";
|
||||||
import { Box, Button, ButtonProps, Container, Stepper, StepProps, Theme, 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";
|
||||||
@ -8,118 +8,9 @@ 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, Step } from "@/components";
|
import { ProposalStep } from "@/pages/steps/proposal";
|
||||||
import { InternshipProposalState } from "@/state/reducer/proposal";
|
import { PlanStep } from "@/pages/steps/plan";
|
||||||
import { createStyles, makeStyles } from "@material-ui/core/styles";
|
|
||||||
import { ClipboardEditOutline, CommentQuestion, FileFind } from "mdi-material-ui";
|
|
||||||
import { Alert, AlertTitle } from "@material-ui/lab";
|
|
||||||
import { getSubmissionStatus, SubmissionStatus } from "@/state/reducer/submission";
|
|
||||||
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStatusStyles = makeStyles((theme: Theme) => {
|
|
||||||
const colorByStatusGetter = ({ status }: any) => getColorByStatus(status, theme);
|
|
||||||
|
|
||||||
return createStyles({
|
|
||||||
foreground: {
|
|
||||||
color: colorByStatusGetter
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
backgroundColor: colorByStatusGetter
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const ProposalStatus = () => {
|
|
||||||
const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal))
|
|
||||||
const classes = useStatusStyles({ status });
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return <span className={ classes.foreground }>{ t(`proposal.status.${status}`) }</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProposalStep = (props: StepProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { sent, comment, declined } = useSelector<AppState, InternshipProposalState>(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
|
|
||||||
|
|
||||||
return <Step { ...props }
|
|
||||||
label={ t('steps.internship-proposal.header') }
|
|
||||||
active={ true } completed={ sent } declined={ declined } waiting={ status == "awaiting" }
|
|
||||||
until={ deadlines.proposal }
|
|
||||||
state={ <ProposalStatus /> }>
|
|
||||||
<p>{ t(`steps.internship-proposal.info.${status}`) }</p>
|
|
||||||
{ comment && <Box pb={2}><ProposalComment /></Box> }
|
|
||||||
<ProposalActions />
|
|
||||||
</Step>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MainPage = () => {
|
export const MainPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -147,18 +38,7 @@ export const MainPage = () => {
|
|||||||
</> }
|
</> }
|
||||||
</Step>
|
</Step>
|
||||||
<ProposalStep />
|
<ProposalStep />
|
||||||
<Step label={ t('steps.plan.header') } active={ missingStudentData.length === 0 } until={ deadlines.proposal }>
|
<PlanStep />
|
||||||
<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>;
|
||||||
|
}
|
@ -5,16 +5,18 @@ import { InternshipProposalAction, InternshipProposalActions } from "@/state/act
|
|||||||
import { Dispatch } from "react";
|
import { Dispatch } from "react";
|
||||||
|
|
||||||
import { useDispatch as useReduxDispatch } from "react-redux";
|
import { useDispatch as useReduxDispatch } from "react-redux";
|
||||||
|
import { InternshipPlanAction, InternshipPlanActions } from "@/state/actions/plan";
|
||||||
|
|
||||||
export * from "./base"
|
export * from "./base"
|
||||||
export * from "./edition"
|
export * from "./edition"
|
||||||
export * from "./settings"
|
export * from "./settings"
|
||||||
export * from "./student"
|
export * from "./student"
|
||||||
export * from "./proposal"
|
export * from "./proposal"
|
||||||
|
export * from "./plan"
|
||||||
|
|
||||||
export type Action = StudentAction | EditionAction | SettingsAction | InternshipProposalAction;
|
export type Action = StudentAction | EditionAction | SettingsAction | InternshipProposalAction | InternshipPlanAction;
|
||||||
|
|
||||||
export const Actions = { ...StudentActions, ...EditionActions, ...SettingActions, ...InternshipProposalActions }
|
export const Actions = { ...StudentActions, ...EditionActions, ...SettingActions, ...InternshipProposalActions, ...InternshipPlanActions }
|
||||||
export type Actions = typeof Actions;
|
export type Actions = typeof Actions;
|
||||||
|
|
||||||
export const useDispatch = () => useReduxDispatch<Dispatch<Action>>()
|
export const useDispatch = () => useReduxDispatch<Dispatch<Action>>()
|
||||||
|
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;
|
@ -20,15 +20,12 @@ export interface SendProposalAction extends SendSubmissionAction<InternshipPropo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ReceiveProposalApproveAction extends ReceiveSubmissionApproveAction<InternshipProposalActions.Approve> {
|
export interface ReceiveProposalApproveAction extends ReceiveSubmissionApproveAction<InternshipProposalActions.Approve> {
|
||||||
comment: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAction<InternshipProposalActions.Decline> {
|
export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAction<InternshipProposalActions.Decline> {
|
||||||
comment: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
|
export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
|
export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
|
||||||
|
@ -4,12 +4,14 @@ 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 settingsReducer from "@/state/reducer/settings";
|
||||||
import internshipProposalReducer from "@/state/reducer/proposal";
|
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,
|
settings: settingsReducer,
|
||||||
proposal: internshipProposalReducer,
|
proposal: internshipProposalReducer,
|
||||||
|
plan: internshipPlanReducer,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AppState = ReturnType<typeof rootReducer>;
|
export type AppState = ReturnType<typeof rootReducer>;
|
||||||
|
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;
|
@ -17,6 +17,7 @@ review: podgląd
|
|||||||
fix-errors: popraw uwagi
|
fix-errors: popraw uwagi
|
||||||
contact: skontaktuj się z pełnomocnikiem
|
contact: skontaktuj się z pełnomocnikiem
|
||||||
comments: Zgłoszone uwagi
|
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ć"
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ student:
|
|||||||
email: adres e-mail
|
email: adres e-mail
|
||||||
albumNumber: numer albumu
|
albumNumber: numer albumu
|
||||||
|
|
||||||
proposal:
|
submission:
|
||||||
status:
|
status:
|
||||||
awaiting: "wysłano, oczekuje na weryfikacje"
|
awaiting: "wysłano, oczekuje na weryfikacje"
|
||||||
accepted: "zaakceptowano"
|
accepted: "zaakceptowano"
|
||||||
@ -69,7 +70,15 @@ steps:
|
|||||||
action: "zgłoś praktykę"
|
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:
|
||||||
|
Loading…
Reference in New Issue
Block a user