master #7
@ -17,6 +17,15 @@ const plugins = [
 | 
			
		||||
    },
 | 
			
		||||
    'core'
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'babel-plugin-import',
 | 
			
		||||
    {
 | 
			
		||||
      'libraryName': 'mdi-material-ui',
 | 
			
		||||
      'libraryDirectory': '.',
 | 
			
		||||
      'camel2DashComponentName': false
 | 
			
		||||
    },
 | 
			
		||||
    'mdi-material-ui'
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'babel-plugin-import',
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import moment, { Moment } from "moment";
 | 
			
		||||
import { Box, Step as StepperStep, StepContent, StepLabel, StepProps as StepperStepProps, Typography } from "@material-ui/core";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import React, { ReactChild, useMemo } from "react";
 | 
			
		||||
import { StepIcon } from "@/components/stepIcon";
 | 
			
		||||
 | 
			
		||||
type StepProps = StepperStepProps & {
 | 
			
		||||
    until?: Moment;
 | 
			
		||||
@ -9,24 +10,21 @@ type StepProps = StepperStepProps & {
 | 
			
		||||
    label: string;
 | 
			
		||||
    state?: ReactChild | null;
 | 
			
		||||
 | 
			
		||||
    /** this roughly translates into completed */
 | 
			
		||||
    accepted?: boolean;
 | 
			
		||||
 | 
			
		||||
    /** this roughly translates into error */
 | 
			
		||||
    waiting?: boolean;
 | 
			
		||||
    declined?: boolean;
 | 
			
		||||
    sent?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const now = moment();
 | 
			
		||||
 | 
			
		||||
export const Step = ({ until, label, completedOn, children, accepted = false, declined = false, completed = false, state = null, ...props }: StepProps) => {
 | 
			
		||||
export const Step = (props: StepProps) => {
 | 
			
		||||
    const { until, label, completedOn, children, completed = false, declined = false, waiting = false, state = null, ...rest } = props;
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
    const isLate = useMemo(() => until?.isBefore(completedOn || now), [completedOn, until]);
 | 
			
		||||
    const left = useMemo(() => moment.duration(now.diff(until)), [until]);
 | 
			
		||||
 | 
			
		||||
    return <StepperStep { ...props } completed={ completed }>
 | 
			
		||||
        <StepLabel error={ declined }>
 | 
			
		||||
    return <StepperStep { ...rest } completed={ completed }>
 | 
			
		||||
        <StepLabel error={ declined } StepIconComponent={ StepIcon } StepIconProps={{ ...props, waiting } as any}>
 | 
			
		||||
            { label }
 | 
			
		||||
            { until && <Box>
 | 
			
		||||
                { state && <>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								src/components/stepIcon.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/stepIcon.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
import { StepIcon as MuiStepIcon, StepIconProps as MuiStepIconProps, Theme } from "@material-ui/core";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { TimerSand } from "mdi-material-ui";
 | 
			
		||||
import { createStyles, makeStyles } from "@material-ui/core/styles";
 | 
			
		||||
 | 
			
		||||
type StepIconProps = MuiStepIconProps & {
 | 
			
		||||
    waiting: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useStyles = makeStyles((theme: Theme) => createStyles({
 | 
			
		||||
    root: {
 | 
			
		||||
        color: theme.palette.primary.main
 | 
			
		||||
    }
 | 
			
		||||
}))
 | 
			
		||||
 | 
			
		||||
export const StepIcon = ({ waiting, ...props }: StepIconProps) => {
 | 
			
		||||
    const classes = useStyles();
 | 
			
		||||
 | 
			
		||||
    return waiting
 | 
			
		||||
        ? <TimerSand className={ classes.root }/>
 | 
			
		||||
        : <MuiStepIcon { ...props } />;
 | 
			
		||||
}
 | 
			
		||||
@ -4,6 +4,7 @@ import { Link as RouterLink } from "react-router-dom";
 | 
			
		||||
import { route } from "@/routing";
 | 
			
		||||
import { InternshipForm } from "@/forms/internship";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { ProposalComment } from "@/pages";
 | 
			
		||||
 | 
			
		||||
export const InternshipProposalPage = () => {
 | 
			
		||||
    return <Page title="Zgłoszenie praktyki">
 | 
			
		||||
@ -15,6 +16,7 @@ export const InternshipProposalPage = () => {
 | 
			
		||||
            <Page.Title>Zgłoszenie praktyki</Page.Title>
 | 
			
		||||
        </Page.Header>
 | 
			
		||||
        <Container maxWidth={ "md" }>
 | 
			
		||||
            <ProposalComment />
 | 
			
		||||
            <InternshipForm/>
 | 
			
		||||
        </Container>
 | 
			
		||||
    </Page>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import React, { useMemo } from "react";
 | 
			
		||||
import React, { HTMLProps, useMemo } from "react";
 | 
			
		||||
import { Page } from "@/pages/base";
 | 
			
		||||
import { Button, Container, Stepper, StepProps, Theme, Typography } from "@material-ui/core";
 | 
			
		||||
import { Box, Button, ButtonProps, Container, Stepper, StepProps, Theme, Typography } from "@material-ui/core";
 | 
			
		||||
import { Link as RouterLink } from "react-router-dom";
 | 
			
		||||
import { route } from "@/routing";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
@ -12,6 +12,8 @@ import { Description as DescriptionIcon } from "@material-ui/icons"
 | 
			
		||||
import { Actions, Step } from "@/components";
 | 
			
		||||
import { getInternshipProposalStatus, InternshipProposalState, InternshipProposalStatus } from "@/state/reducer/proposal";
 | 
			
		||||
import { createStyles, makeStyles } from "@material-ui/core/styles";
 | 
			
		||||
import { ClipboardEditOutline, CommentQuestion, FileFind } from "mdi-material-ui";
 | 
			
		||||
import { Alert, AlertTitle } from "@material-ui/lab";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const getColorByStatus = (status: InternshipProposalStatus, theme: Theme) => {
 | 
			
		||||
@ -51,18 +53,70 @@ const ProposalStatus = () => {
 | 
			
		||||
    return <span className={ classes.foreground }>{ t(`proposal.status.${status}`) }</span>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProposalActions = () => {
 | 
			
		||||
    const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(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 <Alert severity={declined ? "error" : "warning"} {...props as any}>
 | 
			
		||||
        <AlertTitle>{ t('comments') }</AlertTitle>
 | 
			
		||||
        { comment }
 | 
			
		||||
    </Alert>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProposalStep = (props: StepProps) => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
    const { sent } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
 | 
			
		||||
    const { sent, comment, declined } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
 | 
			
		||||
    const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(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 } until={ deadlines.proposal } state={ <ProposalStatus /> }>
 | 
			
		||||
        <p>{ t('steps.internship-proposal.info') }</p>
 | 
			
		||||
 | 
			
		||||
        <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink }>
 | 
			
		||||
            { t('steps.internship-proposal.form') }
 | 
			
		||||
        </Button>
 | 
			
		||||
    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>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Nullable } from "@/helpers";
 | 
			
		||||
import { emptyCompany, Internship, Mentor } from "@/data";
 | 
			
		||||
import { emptyBranchOffice, emptyCompany, Internship, Mentor } from "@/data";
 | 
			
		||||
 | 
			
		||||
export const emptyMentor: Mentor = {
 | 
			
		||||
    phone: null,
 | 
			
		||||
@ -18,4 +18,6 @@ export const emptyInternship: Nullable<Internship> = {
 | 
			
		||||
    lengthInWeeks: 0,
 | 
			
		||||
    mentor: emptyMentor,
 | 
			
		||||
    company: emptyCompany,
 | 
			
		||||
    hours: 0,
 | 
			
		||||
    office: emptyBranchOffice,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export interface SendProposalAction extends Action<InternshipProposalActions.Sen
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ReceiveProposalApproveAction extends Action<InternshipProposalActions.Approve> {
 | 
			
		||||
 | 
			
		||||
    comment: string | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ReceiveProposalDeclineAction extends Action<InternshipProposalActions.Decline> {
 | 
			
		||||
 | 
			
		||||
@ -52,7 +52,7 @@ const internshipProposalReducer = (state: InternshipProposalState = defaultInter
 | 
			
		||||
                ...state,
 | 
			
		||||
                accepted: true,
 | 
			
		||||
                declined: false,
 | 
			
		||||
                comment: ""
 | 
			
		||||
                comment: action.comment,
 | 
			
		||||
            }
 | 
			
		||||
        case InternshipProposalActions.Decline:
 | 
			
		||||
            return {
 | 
			
		||||
@ -74,6 +74,7 @@ const internshipProposalReducer = (state: InternshipProposalState = defaultInter
 | 
			
		||||
                sentOn: momentSerializationTransformer.transform(moment()),
 | 
			
		||||
                accepted: false,
 | 
			
		||||
                declined: false,
 | 
			
		||||
                comment: null,
 | 
			
		||||
            }
 | 
			
		||||
        default:
 | 
			
		||||
            return state;
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,12 @@ left: jeszcze {{ left, humanize }}
 | 
			
		||||
confirm: zatwierdź
 | 
			
		||||
go-back: wstecz
 | 
			
		||||
 | 
			
		||||
make-changes: wprowadź zmiany
 | 
			
		||||
review: podgląd
 | 
			
		||||
fix-errors: popraw uwagi
 | 
			
		||||
contact: skontaktuj się z pełnomocnikiem
 | 
			
		||||
comments: Zgłoszone uwagi
 | 
			
		||||
 | 
			
		||||
dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać"
 | 
			
		||||
 | 
			
		||||
sections:
 | 
			
		||||
@ -48,9 +54,19 @@ steps:
 | 
			
		||||
    form: "Uzupełnij dane"
 | 
			
		||||
  internship-proposal:
 | 
			
		||||
    header: "Zgłoszenie praktyki"
 | 
			
		||||
    info: >
 | 
			
		||||
      Przed podjęciem praktyki należy ją zgłosić.
 | 
			
		||||
    info:
 | 
			
		||||
      draft: >
 | 
			
		||||
        Przed podjęciem praktyki należy ją zgłosić.
 | 
			
		||||
      awaiting: >
 | 
			
		||||
        Twoje zgłoszenie musi zostać zweryfikowane i zatwierdzone. Po weryfikacji zostaniesz poinformowany o
 | 
			
		||||
        akceptacji bądź konieczności wprowadzenia zmian.
 | 
			
		||||
      accepted: >
 | 
			
		||||
        Twoje zgłoszenie zostało zweryfikowane i zaakceptowane.
 | 
			
		||||
      declined: >
 | 
			
		||||
        Twoje zgłoszenie zostało zweryfikowane i odrzucone. Popraw zgłoszone uwagi i wyślij zgłoszenie ponownie. W razie
 | 
			
		||||
        pytań możesz również skontaktować się z pełnomocnikiem ds. praktyk Twojego kierunku.
 | 
			
		||||
    form: "Formularz zgłaszania praktyki"
 | 
			
		||||
    action: "zgłoś praktykę"
 | 
			
		||||
  plan:
 | 
			
		||||
    header: "Indywidualny Program Praktyki"
 | 
			
		||||
    info: ""
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user