diff --git a/src/components/proposalPreview.tsx b/src/components/proposalPreview.tsx new file mode 100644 index 0000000..96a5d9b --- /dev/null +++ b/src/components/proposalPreview.tsx @@ -0,0 +1,75 @@ +import { Internship } from "@/data"; +import React from "react"; +import { Paper, PaperProps, Typography, TypographyProps } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; +import { createStyles, makeStyles } from "@material-ui/core/styles"; +import classNames from "classnames"; + +export type ProposalPreviewProps = { + proposal: Internship; +} + +const Label = ({ children }: TypographyProps) => { + return <Typography variant="subtitle2" className="proposal__header">{ children }</Typography> +} + +const useSectionStyles = makeStyles(theme => createStyles({ + root: { + padding: theme.spacing(2), + marginTop: theme.spacing(3) + } +})) + +const Section = ({ children, ...props }: PaperProps) => { + const classes = useSectionStyles(); + + return <Paper {...props} className={ classNames(classes.root, props.className ) }> + { children } + </Paper> +} + +export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => { + const { t } = useTranslation(); + + return <div className="proposal"> + <Typography className="proposal__primary">{ proposal.intern.name } { proposal.intern.surname }</Typography> + <Typography className="proposal__secondary"> + { t('internship.intern.semester', { semester: proposal.intern.semester }) } + { ", " } + { t('internship.intern.album', { album: proposal.intern.albumNumber }) } + </Typography> + + <Section> + <Label>{ t('internship.sections.kind') }</Label> + <Typography className="proposal__primary">{ proposal.type }</Typography> + </Section> + + <Section> + <Label>{ t('internship.sections.duration') }</Label> + <Typography className="proposal__primary"> + { t('internship.date-range', { start: proposal.startDate, end: proposal.endDate }) } + </Typography> + <Typography className="proposal__secondary"> + { t('internship.duration', { duration: 0 })} + { ", " } + { t('internship.hours', { hours: proposal.hours })} + </Typography> + </Section> + + <Section> + <Label>{ t('internship.sections.place') }</Label> + <Typography className="proposal__primary"> + { proposal.company.name } + </Typography> + <Typography className="proposal__secondary"> + NIP: { proposal.company.nip } + </Typography> + </Section> + + <Section> + <Label>{ t('internship.office') }</Label> + <Typography className="proposal__primary">{ t('internship.address.city', proposal.office.address) }</Typography> + <Typography className="proposal__secondary">{ t('internship.address.street', proposal.office.address) }</Typography> + </Section> + </div> +} diff --git a/src/forms/internship.tsx b/src/forms/internship.tsx index 752bf28..fb5b9ca 100644 --- a/src/forms/internship.tsx +++ b/src/forms/internship.tsx @@ -182,13 +182,13 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop return ( <div className="internship-form"> - <Typography variant="h3" className="section-header">Dane osoby odbywającej praktykę</Typography> + <Typography variant="h3" className="section-header">{ t('internship.intern-info') }</Typography> <StudentForm student={ sampleStudent }/> - <Typography variant="h3" className="section-header">Rodzaj i program praktyki</Typography> + <Typography variant="h3" className="section-header">{ t('internship.kind' )}</Typography> <InternshipProgramForm internship={ internship } onChange={ setInternship }/> - <Typography variant="h3" className="section-header">Czas trwania praktyki</Typography> + <Typography variant="h3" className="section-header">{ t('internship.duration') }</Typography> <InternshipDurationForm internship={ internship } onChange={ setInternship }/> - <Typography variant="h3" className="section-header">Miejsce odbywania praktyki</Typography> + <Typography variant="h3" className="section-header">{ t('internship.place') }</Typography> <CompanyForm internship={ internship } onChange={ setInternship }/> <Actions> <Button variant="contained" color="primary" onClick={ handleSubmitConfirmation }>{ t("confirm") }</Button> diff --git a/src/forms/plan.tsx b/src/forms/plan.tsx index 24a8fa7..0c96203 100644 --- a/src/forms/plan.tsx +++ b/src/forms/plan.tsx @@ -32,7 +32,7 @@ export const PlanForm = () => { </Button> </Grid> <Grid item> - <DropzoneArea acceptedFiles={["image/*", "application/x-pdf"]} filesLimit={ 1 } dropzoneText={ t("dropzone") }/> + <DropzoneArea acceptedFiles={["image/*", "application/pdf"]} filesLimit={ 1 } dropzoneText={ t("dropzone") }/> <FormHelperText>{ t('forms.plan.dropzone-help') }</FormHelperText> </Grid> <Grid item> diff --git a/src/i18n.ts b/src/i18n.ts index eb71721..c01bc57 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -5,6 +5,7 @@ import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector"; import "moment/locale/pl" import "moment/locale/en-gb" import moment, { isDuration, isMoment } from "moment"; +import { convertToRoman } from "@/utils/numbers"; const resources = { en: { @@ -24,6 +25,10 @@ i18n interpolation: { escapeValue: false, format: (value, format, lng) => { + if (typeof value === "number" && format == "roman") { + return convertToRoman(value); + } + if (isMoment(value)) { return value.locale(lng || "pl").format(format || "DD MMM YYYY"); } diff --git a/src/pages/index.ts b/src/pages/index.ts index bb1a4b5..c5f71a3 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,7 +1,3 @@ export * from "./internship/proposal"; export * from "./errors/not-found" 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"; diff --git a/src/pages/internship/proposal.tsx b/src/pages/internship/proposal.tsx index e45b4cb..09b8ca8 100644 --- a/src/pages/internship/proposal.tsx +++ b/src/pages/internship/proposal.tsx @@ -4,9 +4,15 @@ 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"; +import { ProposalComment } from "@/pages/steps/proposal"; +import { useTranslation } from "react-i18next"; +import { ProposalPreview } from "@/components/proposalPreview"; +import { useSelector } from "react-redux"; +import { Internship } from "@/data"; +import { AppState } from "@/state/reducer"; +import { internshipSerializationTransformer } from "@/serialization"; -export const InternshipProposalPage = () => { +export const InternshipProposalFormPage = () => { return <Page title="Zgłoszenie praktyki"> <Page.Header maxWidth="md"> <Page.Breadcrumbs> @@ -22,4 +28,23 @@ export const InternshipProposalPage = () => { </Page> } -export default InternshipProposalPage; +export const InternshipProposalPreviewPage = () => { + const { t } = useTranslation(); + const proposal = useSelector<AppState, Internship | null>(state => state.proposal.proposal && internshipSerializationTransformer.reverseTransform(state.proposal.proposal)); + + return <Page title={ t("") }> + <Page.Header maxWidth="md"> + <Page.Breadcrumbs> + <Link component={ RouterLink } to={ route("home") }>Moja praktyka</Link> + <Typography color="textPrimary">Podgląd zgłoszenia</Typography> + </Page.Breadcrumbs> + <Page.Title>Moje zgłoszenie</Page.Title> + </Page.Header> + <Container maxWidth={ "md" }> + <ProposalComment /> + { proposal && <ProposalPreview proposal={ proposal } /> } + </Container> + </Page> +} + +export default InternshipProposalFormPage; diff --git a/src/pages/steps/proposal.tsx b/src/pages/steps/proposal.tsx index b6f6cd1..f7646d9 100644 --- a/src/pages/steps/proposal.tsx +++ b/src/pages/steps/proposal.tsx @@ -18,7 +18,11 @@ const ProposalActions = () => { const { t } = useTranslation(); const ReviewAction = (props: ButtonProps) => - <Button startIcon={ <FileFind/> } { ...props }>{ t('review') }</Button> + <Button startIcon={ <FileFind/> } + component={ RouterLink } to={ route("internship_proposal_preview") } + { ...props as any }> + { t('review') } + </Button> const FormAction = ({ children = t('steps.internship-proposal.form'), ...props }: ButtonProps) => <Button to={ route("internship_proposal") } variant="contained" color="primary" component={ RouterLink } diff --git a/src/routing.tsx b/src/routing.tsx index adb533a..c0e3e2e 100644 --- a/src/routing.tsx +++ b/src/routing.tsx @@ -1,7 +1,7 @@ import React, { ReactComponentElement } from "react"; import { MainPage } from "@/pages/main"; import { RouteProps } from "react-router-dom"; -import { InternshipProposalPage } from "@/pages/internship/proposal"; +import { InternshipProposalFormPage, InternshipProposalPreviewPage } from "@/pages/internship/proposal"; import { NotFoundPage } from "@/pages/errors/not-found"; import SubmitPlanPage from "@/pages/internship/plan"; @@ -13,7 +13,8 @@ type Route = { export const routes: Route[] = [ { name: "home", path: "/", exact: true, content: () => <MainPage/> }, - { name: "internship_proposal", path: "/internship/proposal", exact: true, content: () => <InternshipProposalPage/> }, + { name: "internship_proposal", path: "/internship/proposal", exact: true, content: () => <InternshipProposalFormPage/> }, + { name: "internship_proposal_preview", path: "/internship/preview/proposal", exact: true, content: () => <InternshipProposalPreviewPage/> }, { name: "internship_plan", path: "/internship/plan", exact: true, content: () => <SubmitPlanPage/> }, // fallback route for 404 pages diff --git a/src/styles/page.scss b/src/styles/page.scss index 8b41597..f81763a 100644 --- a/src/styles/page.scss +++ b/src/styles/page.scss @@ -12,3 +12,7 @@ font-weight: 400; line-height: 1.5; } + +.proposal__primary { + font-size: 1.675rem; +} diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts new file mode 100644 index 0000000..090761b --- /dev/null +++ b/src/utils/numbers.ts @@ -0,0 +1,32 @@ +const roman = { + M: 1000, + CM: 900, + D: 500, + CD: 400, + C: 100, + XC: 90, + L: 50, + XL: 40, + X: 10, + IX: 9, + V: 5, + IV: 4, + I: 1 +}; + +type RomanLiteral = keyof typeof roman; + +// shamefully stolen from https://stackoverflow.com/questions/9083037/convert-a-number-into-a-roman-numeral-in-javascript +export function convertToRoman(number: number) { + let result = ''; + + for (const i in roman) { + const q = Math.floor(number / roman[i as RomanLiteral]); + + number -= q * roman[i as RomanLiteral]; + + result += i.repeat(q); + } + + return result; +} diff --git a/translations/pl.yaml b/translations/pl.yaml index 2cfd0fa..ac7d8dd 100644 --- a/translations/pl.yaml +++ b/translations/pl.yaml @@ -20,6 +20,7 @@ comments: Zgłoszone uwagi send-again: wyślij ponownie cancel: anuluj + dropzone: "Przeciągnij i upuść plik bądź kliknij, aby wybrać" sections: @@ -31,7 +32,7 @@ forms: send-confirmation: > Po wysłaniu zgłoszenia nie będzie możliwości jego zmiany do czasu zweryfikowania go przez pełnomocnika ds. Twojego kierunku. Czy na pewno chcesz wysłać zgłoszenie praktyki w tej formie? - program: + plan: instructions: > Wypełnij i zeskanuj Indywidualny program Praktyk a następnie wyślij go z pomocą tego formularza. <więcej informacji> dropzone-help: Skan dokumentu w formacie PDF @@ -51,6 +52,24 @@ submission: declined: "do poprawy" draft: "wersja robocza" +internship: + intern: + semester: semestr {{ semester, roman }} + album: "numer albumu {{ album }}" + date-range: "{{ start, DD MMMM YYYY }} - {{ end, DD MMMM YYYY }}" + duration: "{{ duration, humanize }} tygodni" + hours: "{{ hours }} godzin" + office: "Oddział / adres" + address: + city: "{{ city }}, {{ country }}" + street: "{{ postalCode }}, {{ street }} {{ building }}" + sections: + intern-info: "Dane osoby odbywającej praktykę" + duration: "Czas trwania praktyki" + place: "Miejsce odbywania praktyki" + kind: "Rodzaj i program praktyki" + + steps: personal-data: header: "Uzupełnienie informacji"