Add simple proposal previwe

This commit is contained in:
Kacper Donat 2020-08-09 19:21:47 +02:00
parent e7831cf1e5
commit 5316630a29
11 changed files with 177 additions and 16 deletions

View File

@ -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>
}

View File

@ -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>

View File

@ -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>

View File

@ -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");
}

View File

@ -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";

View File

@ -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;

View File

@ -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 }

View File

@ -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

View File

@ -12,3 +12,7 @@
font-weight: 400;
line-height: 1.5;
}
.proposal__primary {
font-size: 1.675rem;
}

32
src/utils/numbers.ts Normal file
View File

@ -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;
}

View File

@ -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"