Add edition breadcrumb

This commit is contained in:
Kacper Donat 2021-01-06 20:51:39 +01:00
parent 1deaebda3d
commit a38409e2d0
6 changed files with 111 additions and 73 deletions

View File

@ -1,6 +1,6 @@
import React, { HTMLProps, useEffect } from 'react'; import React, { HTMLProps, useEffect } from 'react';
import { Link, Route, Switch } from "react-router-dom" import { Link, Route, Switch } from "react-router-dom"
import { processMiddlewares, route, routes } from "@/routing"; import { processMiddlewares, route, Routes, routes } from "@/routing";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { AppState } from "@/state/reducer"; import { AppState } from "@/state/reducer";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
@ -97,14 +97,7 @@ function App() {
</Container> </Container>
</header> </header>
<main id="content"> <main id="content">
{ <Switch> <Routes routes={ routes.filter(route => !route.tags || route.tags.length == 0) }/>
{ routes.map(({ name, content, middlewares = [], ...route }) =>
<Route { ...route } key={ name } render={ () => {
const Next = () => processMiddlewares([ ...middlewares, content ])
return <Next />
} } />
) }
</Switch> }
</main> </main>
<footer className="footer"> <footer className="footer">
<Container style={{ display: 'flex', alignItems: "center" }}> <Container style={{ display: 'flex', alignItems: "center" }}>

View File

@ -1,17 +1,17 @@
import React, { useCallback } from "react"; import React, { useCallback, useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouteMatch } from "react-router-dom"; import { useRouteMatch, Link as RouterLink } from "react-router-dom";
import { useAsync } from "@/hooks";
import api from "@/management/api";
import { Page } from "@/pages/base"; import { Page } from "@/pages/base";
import { Container, Paper, Typography } from "@material-ui/core"; import { Container, Link, Paper, Typography } from "@material-ui/core";
import { Management, ManagementLink } from "@/management/main"; import { Management, ManagementLink } from "@/management/main";
import { Edition } from "@/data/edition"; import { Edition } from "@/data/edition";
import { Async } from "@/components/async";
import { AccountMultiple, BriefcaseAccount, CertificateOutline, CogOutline, FileChartOutline, FormatPageBreak } from "mdi-material-ui"; import { AccountMultiple, BriefcaseAccount, CertificateOutline, CogOutline, FileChartOutline, FormatPageBreak } from "mdi-material-ui";
import { route } from "@/routing"; import { route, routes, Routes } from "@/routing";
import { useSpacing } from "@/styles"; import { useSpacing } from "@/styles";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import { useAsync } from "@/hooks";
import api from "@/management/api";
import { Async } from "@/components/async";
const useSectionStyles = makeStyles((theme: Theme) => createStyles({ const useSectionStyles = makeStyles((theme: Theme) => createStyles({
header: { header: {
@ -23,60 +23,85 @@ const useSectionStyles = makeStyles((theme: Theme) => createStyles({
})) }))
export function title(edition: Edition) { export function title(edition: Edition) {
return `${edition.course.name} - ${edition.startDate.year()}` return `${ edition.course.name } - ${ edition.startDate.year() }`
} }
export const ManageEditionPage = () => { export const EditionContext = React.createContext<Edition | null>(null);
export const EditionManagement = ({ edition }: EditionManagementProps) => {
const { t } = useTranslation("management"); const { t } = useTranslation("management");
const { params } = useRouteMatch(); const { params } = useRouteMatch();
const edition = useAsync<Edition>(useCallback(() => api.edition.details(params.edition), [params.edition]))
const spacing = useSpacing(2); const spacing = useSpacing(2);
const classes = useSectionStyles(); const classes = useSectionStyles();
return <Page> return <Page>
<Async async={ edition }>{ edition => <Page.Header>
<> <Management.Breadcrumbs>
<Page.Header> <Typography color="textPrimary">{ title(edition) }</Typography>
<Management.Breadcrumbs> </Management.Breadcrumbs>
<Typography color="textPrimary">{ title(edition) }</Typography> <Page.Title>{ title(edition) }</Page.Title>
</Management.Breadcrumbs> </Page.Header>
<Page.Title>{ title(edition) }</Page.Title> <Container className={ spacing.vertical }>
</Page.Header> <Paper elevation={ 2 }>
<Container className={ spacing.vertical }> <Typography className={ classes.header }>{ t("edition.manage.internships") }</Typography>
<Paper elevation={ 2 }> <Management.Menu>
<Typography className={ classes.header }>{ t("edition.manage.internships") }</Typography> <ManagementLink icon={ <AccountMultiple/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
<Management.Menu> { t("management:edition.students.title") }
<ManagementLink icon={ <AccountMultiple/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.students.title") } <ManagementLink icon={ <BriefcaseAccount/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
</ManagementLink> { t("management:edition.internships.title") }
<ManagementLink icon={ <BriefcaseAccount/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.internships.title") } <ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
</ManagementLink> { t("management:edition.ipp.title") }
<ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.ipp.title") } <ManagementLink icon={ <FileChartOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
</ManagementLink> { t("management:edition.reports.title") }
<ManagementLink icon={ <FileChartOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.reports.title") } <ManagementLink icon={ <CertificateOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
</ManagementLink> { t("management:edition.dean-approvals.title") }
<ManagementLink icon={ <CertificateOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.dean-approvals.title") } </Management.Menu>
</ManagementLink> </Paper>
</Management.Menu> <Paper elevation={ 2 }>
</Paper> <Typography className={ classes.header }>{ t("edition.manage.management") }</Typography>
<Paper elevation={ 2 }> <Management.Menu>
<Typography className={ classes.header }>{ t("edition.manage.management") }</Typography> <ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
<Management.Menu> { t("management:edition.report-fields.title") }
<ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.report-fields.title") } <ManagementLink icon={ <CogOutline/> } route={ route("management:edition_settings", { edition: edition.id || "" }) }>
</ManagementLink> { t("management:edition.settings.title") }
<ManagementLink icon={ <CogOutline/> } route={ route("management:edition_settings", { edition: edition.id || "" }) }> </ManagementLink>
{ t("management:edition.settings.title") } </Management.Menu>
</ManagementLink> </Paper>
</Management.Menu> </Container>
</Paper>
</Container>
</>
}</Async>
</Page> </Page>
} }
EditionManagement.Breadcrumbs = ({ children }: { children: React.ReactChild }) => {
const edition = useContext<Edition | null>(EditionContext);
return <Management.Breadcrumbs>
{ edition && (children
? <Link to={ route("management:edition_manage", { edition: edition.id || "" }) } component={ RouterLink }>{ title(edition) }</Link>
: <Typography color="textPrimary">{ title(edition) }</Typography>
) }
{ children }
</Management.Breadcrumbs>
}
export type EditionManagementProps = {
edition: Edition;
}
export const EditionRouter = () => {
const { params } = useRouteMatch();
const edition = useAsync<Edition>(useCallback(() => api.edition.details(params.edition), [params.edition]));
return <Async async={ edition }>{
result => <EditionContext.Provider value={ result }>
<Routes routes={ routes.filter(route => (route.tags || []).includes("edition")) } edition={ result }/>
</EditionContext.Provider>
}</Async>
}

View File

@ -14,6 +14,7 @@ import { Edit } from "@material-ui/icons";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { createDeleteAction } from "@/management/common/DeleteResourceAction"; import { createDeleteAction } from "@/management/common/DeleteResourceAction";
import { EditFieldDefinitionDialog } from "@/management/edition/report/fields/edit"; import { EditFieldDefinitionDialog } from "@/management/edition/report/fields/edit";
import { EditionManagement } from "../../manage";
const title = "edition.report-fields.title"; const title = "edition.report-fields.title";
@ -67,9 +68,9 @@ export const EditionReportFields = () => {
return <Page> return <Page>
<Page.Header maxWidth="lg"> <Page.Header maxWidth="lg">
<Management.Breadcrumbs> <EditionManagement.Breadcrumbs>
<Typography color="textPrimary">{ t(title) }</Typography> <Typography color="textPrimary">{ t(title) }</Typography>
</Management.Breadcrumbs> </EditionManagement.Breadcrumbs>
<Page.Title>{ t(title) }</Page.Title> <Page.Title>{ t(title) }</Page.Title>
</Page.Header> </Page.Header>
<Container maxWidth="lg"> <Container maxWidth="lg">

View File

@ -14,6 +14,7 @@ import { EditionForm } from "@/management/edition/form";
import { Actions } from "@/components"; import { Actions } from "@/components";
import { Save } from "@material-ui/icons"; import { Save } from "@material-ui/icons";
import { Cancel } from "mdi-material-ui"; import { Cancel } from "mdi-material-ui";
import { EditionManagement } from "./manage";
const title = "edition.settings.title"; const title = "edition.settings.title";
@ -28,9 +29,9 @@ export function EditionSettings() {
return <Page> return <Page>
<Page.Header maxWidth="md"> <Page.Header maxWidth="md">
<Management.Breadcrumbs> <EditionManagement.Breadcrumbs>
<Typography color="textPrimary">{ t(title) }</Typography> <Typography color="textPrimary">{ t(title) }</Typography>
</Management.Breadcrumbs> </EditionManagement.Breadcrumbs>
<Page.Title>{ t(title) }</Page.Title> <Page.Title>{ t(title) }</Page.Title>
</Page.Header> </Page.Header>
<Container maxWidth="md"> <Container maxWidth="md">

View File

@ -5,16 +5,17 @@ import React from "react";
import { ManagementIndex } from "@/management/main"; import { ManagementIndex } from "@/management/main";
import StaticPageManagement from "@/management/page/list"; import StaticPageManagement from "@/management/page/list";
import { InternshipTypeManagement } from "@/management/type/list"; import { InternshipTypeManagement } from "@/management/type/list";
import { ManageEditionPage } from "@/management/edition/manage"; import { EditionRouter, EditionManagement } from "@/management/edition/manage";
import { EditionReportFields } from "@/management/edition/report/fields/list"; import { EditionReportFields } from "@/management/edition/report/fields/list";
import { EditionSettings } from "@/management/edition/settings"; import { EditionSettings } from "@/management/edition/settings";
export const managementRoutes: Route[] = ([ export const managementRoutes: Route[] = ([
{ name: "index", path: "/", content: ManagementIndex, exact: true }, { name: "index", path: "/", content: ManagementIndex, exact: true },
{ name: "edition_report_form", path: "/editions/:edition/report", content: EditionReportFields }, { name: "edition_router", path: "/editions/:edition", content: EditionRouter },
{ name: "edition_settings", path: "/editions/:edition/settings", content: EditionSettings }, { name: "edition_report_form", path: "/editions/:edition/report", content: EditionReportFields, tags: ["edition"] },
{ name: "edition_manage", path: "/editions/:edition", content: ManageEditionPage }, { name: "edition_settings", path: "/editions/:edition/settings", content: EditionSettings, tags: ["edition"] },
{ name: "edition_manage", path: "/editions/:edition", content: EditionManagement, tags: ["edition"] },
{ name: "editions", path: "/editions", content: EditionsManagement }, { name: "editions", path: "/editions", content: EditionsManagement },
{ name: "types", path: "/types", content: InternshipTypeManagement }, { name: "types", path: "/types", content: InternshipTypeManagement },

View File

@ -1,6 +1,6 @@
import React, { ReactComponentElement } from "react"; import React, { ReactComponentElement } from "react";
import { MainPage } from "@/pages/main"; import { MainPage } from "@/pages/main";
import { RouteProps } from "react-router-dom"; import { RouteProps, Switch, Route as RouteComponent } from "react-router-dom";
import { InternshipProposalFormPage, InternshipProposalPreviewPage } from "@/pages/internship/proposal"; import { InternshipProposalFormPage, InternshipProposalPreviewPage } from "@/pages/internship/proposal";
import { FallbackPage } from "@/pages/fallback"; import { FallbackPage } from "@/pages/fallback";
import SubmitPlanPage from "@/pages/internship/plan"; import SubmitPlanPage from "@/pages/internship/plan";
@ -15,7 +15,8 @@ import SubmitReportPage from "@/pages/internship/report";
export type Route = { export type Route = {
name?: string; name?: string;
content: () => ReactComponentElement<any>, tags?: string[];
content: (props?: any) => ReactComponentElement<any>,
condition?: () => boolean, condition?: () => boolean,
middlewares?: Middleware<any, any>[], middlewares?: Middleware<any, any>[],
} & RouteProps; } & RouteProps;
@ -56,7 +57,7 @@ export const routes: Route[] = [
// fallback route for 404 pages // fallback route for 404 pages
{ name: "fallback", path: "*", content: () => <FallbackPage/> }, { name: "fallback", path: "*", content: () => <FallbackPage/> },
] ].map(route => ({ tags: [], ...route }))
const routeNameMap = new Map(routes.filter(({ name }) => !!name).map(({ name, path }) => [name, path instanceof Array ? path[0] : path])) as Map<string, string> const routeNameMap = new Map(routes.filter(({ name }) => !!name).map(({ name, path }) => [name, path instanceof Array ? path[0] : path])) as Map<string, string>
@ -80,3 +81,19 @@ export const query = (url: string, params: URLParams) => {
return url + (query.length > 0 ? `?${ query }` : ''); return url + (query.length > 0 ? `?${ query }` : '');
} }
export type RoutesProps = {
routes: Route[];
[prop: string]: any;
};
export function Routes({ routes, ...props }: RoutesProps) {
return <Switch>
{ routes.map(({ name, content, middlewares = [], ...route }) =>
<RouteComponent { ...route } key={ name } render={ () => {
const Next = () => processMiddlewares([ ...middlewares, (_, ...props) => content(...props) ], props)
return <Next />
} } />
) }
</Switch>
}