Static page management
This commit is contained in:
parent
999cde6726
commit
ac963d658e
@ -1,8 +1,12 @@
|
|||||||
import React, { HTMLProps } from "react";
|
import React, { HTMLProps } from "react";
|
||||||
import { useHorizontalSpacing } from "@/styles";
|
import { useHorizontalSpacing } from "@/styles";
|
||||||
|
|
||||||
export const Actions = (props: HTMLProps<HTMLDivElement>) => {
|
type ActionsProps = {
|
||||||
const classes = useHorizontalSpacing(2);
|
spacing?: number;
|
||||||
|
} & HTMLProps<HTMLDivElement>;
|
||||||
|
|
||||||
return <div className={ classes.root } { ...props } style={{ display: "flex", alignItems: "center" }}/>
|
export const Actions = ({ spacing = 2, ...props }: ActionsProps) => {
|
||||||
|
const classes = useHorizontalSpacing(spacing);
|
||||||
|
|
||||||
|
return <div className={ classes.root } { ...props } style={{ display: "flex", alignItems: "center", ...props.style }}/>
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,22 @@ type AsyncProps<TValue, TError = any> = {
|
|||||||
children: (value: TValue) => JSX.Element,
|
children: (value: TValue) => JSX.Element,
|
||||||
loading?: () => JSX.Element,
|
loading?: () => JSX.Element,
|
||||||
error?: (error: TError) => JSX.Element,
|
error?: (error: TError) => JSX.Element,
|
||||||
|
keepValue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLoading = () => <Loading />;
|
const defaultLoading = () => <Loading />;
|
||||||
const defaultError = (error: any) => <Alert severity="error">{ error.message }</Alert>;
|
const defaultError = (error: any) => <Alert severity="error">{ error.message }</Alert>;
|
||||||
|
|
||||||
export function Async<TValue, TError = any>(
|
export function Async<TValue, TError = any>(
|
||||||
{ async, children: render, loading = defaultLoading, error = defaultError }: AsyncProps<TValue, TError>
|
{ async, children: render, loading = defaultLoading, error = defaultError, keepValue = false }: AsyncProps<TValue, TError>
|
||||||
) {
|
) {
|
||||||
if (async.isLoading || (!async.error && !async.value)) {
|
if (async.value && (!async.isLoading || keepValue)) {
|
||||||
return loading();
|
return render(async.value as TValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof async.error !== "undefined") {
|
if (typeof async.error !== "undefined") {
|
||||||
return error(async.error);
|
return error(async.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(async.value as TValue);
|
return loading();
|
||||||
}
|
}
|
||||||
|
45
src/components/confirm.tsx
Normal file
45
src/components/confirm.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import React from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@material-ui/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export type ConfirmProps = {
|
||||||
|
children: (action: () => void) => React.ReactNode,
|
||||||
|
title?: string,
|
||||||
|
content?: React.ReactNode,
|
||||||
|
onConfirm?: () => void,
|
||||||
|
onCancel?: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Confirm({ children, title, content, onConfirm, onCancel }: ConfirmProps) {
|
||||||
|
const [ open, setOpen ] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(false);
|
||||||
|
onCancel?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
setOpen(false);
|
||||||
|
onConfirm?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{ children(() => { setOpen(true) }) }
|
||||||
|
{ createPortal(
|
||||||
|
<Dialog open={ open } onClose={ handleCancel }>
|
||||||
|
{ title && <DialogTitle>{ title }</DialogTitle>}
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>{ content || t('confirmation') }</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={ handleCancel }>{ t('cancel') }</Button>
|
||||||
|
<Button color="primary" autoFocus onClick={ handleConfirm }>{ t('confirm') }</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>,
|
||||||
|
document.getElementById("modals") as Element,
|
||||||
|
) }
|
||||||
|
</>
|
||||||
|
}
|
@ -26,3 +26,19 @@ export function throttle<TArgs extends any[]>(decorated: (...args: TArgs) => voi
|
|||||||
}, time);
|
}, time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function encapsulate<T>(value: T|T[]): T[] {
|
||||||
|
if (value instanceof Array) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ value ];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function one<T>(value: T|T[]): T {
|
||||||
|
if (value instanceof Array) {
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
@ -19,7 +19,6 @@ export function useAsync<T, TError = any>(supplier: Promise<T> | (() => Promise<
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setValue(undefined);
|
|
||||||
|
|
||||||
const myMagicNumber = semaphore.value + 1;
|
const myMagicNumber = semaphore.value + 1;
|
||||||
semaphore.value = myMagicNumber;
|
semaphore.value = myMagicNumber;
|
||||||
@ -54,9 +53,9 @@ export function useAsync<T, TError = any>(supplier: Promise<T> | (() => Promise<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAsyncState<T, TError = any>(initial: Promise<T> | undefined): AsyncState<T, TError> {
|
export function useAsyncState<T, TError = any>(initial?: Promise<T> | undefined): AsyncState<T, TError> {
|
||||||
const [promise, setPromise] = useState<Promise<T> | undefined>(initial);
|
const [promise, setPromise] = useState<Promise<T> | undefined>(initial);
|
||||||
const asyncState = useAsync(promise);
|
const asyncState = useAsync<T, TError>(promise);
|
||||||
|
|
||||||
return [ asyncState, setPromise ];
|
return [ asyncState, setPromise ];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Page } from "@/data/page";
|
import { Page } from "@/data/page";
|
||||||
import pageDtoTransformer, { PageDTO } from "@/api/dto/page";
|
import pageDtoTransformer, { PageDTO } from "@/api/dto/page";
|
||||||
import { axios } from "@/api";
|
import { axios } from "@/api";
|
||||||
|
import { STATIC_PAGE_ENDPOINT } from "@/api/page";
|
||||||
|
import { prepare } from "@/routing";
|
||||||
|
|
||||||
const STATIC_PAGE_INDEX_ENDPOINT = "/staticPage";
|
const STATIC_PAGE_INDEX_ENDPOINT = "/staticPage";
|
||||||
|
|
||||||
@ -10,3 +12,16 @@ export async function all(): Promise<Page[]> {
|
|||||||
const response = await axios.get<PageDTO[]>(STATIC_PAGE_INDEX_ENDPOINT);
|
const response = await axios.get<PageDTO[]>(STATIC_PAGE_INDEX_ENDPOINT);
|
||||||
return response.data.map(dto => pageDtoTransformer.transform(dto));
|
return response.data.map(dto => pageDtoTransformer.transform(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function remove(page: Pick<Page, "slug">): Promise<void> {
|
||||||
|
await axios.delete(prepare(STATIC_PAGE_ENDPOINT, { slug: page.slug }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function save(page: Page): Promise<Page> {
|
||||||
|
const response = await axios.put<PageDTO>(
|
||||||
|
STATIC_PAGE_INDEX_ENDPOINT,
|
||||||
|
pageDtoTransformer.reverseTransform(page),
|
||||||
|
);
|
||||||
|
|
||||||
|
return pageDtoTransformer.transform(response.data);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
import { Page } from "@/pages/base";
|
import { Page } from "@/pages/base";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAsync } from "@/hooks";
|
import { useAsync, useAsyncState } from "@/hooks";
|
||||||
import api from "@/management/api";
|
import api from "@/management/api";
|
||||||
import { Async } from "@/components/async";
|
import { Async } from "@/components/async";
|
||||||
import { Container, Typography } from "@material-ui/core";
|
import { Container, Typography } from "@material-ui/core";
|
||||||
@ -9,6 +9,8 @@ import MaterialTable, { Action, Column } from "material-table";
|
|||||||
import { Edition } from "@/data/edition";
|
import { Edition } from "@/data/edition";
|
||||||
import { Pencil } from "mdi-material-ui";
|
import { Pencil } from "mdi-material-ui";
|
||||||
import { Management } from "../main";
|
import { Management } from "../main";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { CreateStaticPageDialog } from "@/management/page/create";
|
||||||
|
|
||||||
export type EditionDetailsProps = {
|
export type EditionDetailsProps = {
|
||||||
edition: string;
|
edition: string;
|
||||||
|
40
src/management/page/create.tsx
Normal file
40
src/management/page/create.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@material-ui/core";
|
||||||
|
import React from "react";
|
||||||
|
import { Form, Formik } from "formik";
|
||||||
|
import { initialStaticPageFormValues, StaticPageForm, StaticPageFormValues, staticPageFormValuesTransformer } from "@/management/page/form";
|
||||||
|
import { Actions } from "@/components";
|
||||||
|
import { Save } from "@material-ui/icons";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Cancel } from "mdi-material-ui";
|
||||||
|
import { useSpacing } from "@/styles";
|
||||||
|
import { default as StaticPage } from "@/data/page";
|
||||||
|
|
||||||
|
export type CreateStaticPageDialogProps = {
|
||||||
|
onSave?: (page: StaticPage) => void;
|
||||||
|
} & DialogProps;
|
||||||
|
|
||||||
|
export function CreateStaticPageDialog({ onSave, ...props }: CreateStaticPageDialogProps) {
|
||||||
|
const { t } = useTranslation("management");
|
||||||
|
const spacing = useSpacing(3);
|
||||||
|
|
||||||
|
const handleSubmit = (values: StaticPageFormValues) => {
|
||||||
|
onSave?.(staticPageFormValuesTransformer.reverseTransform(values));
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Dialog { ...props } maxWidth="lg">
|
||||||
|
<Formik initialValues={ initialStaticPageFormValues } onSubmit={ handleSubmit }>
|
||||||
|
<Form className={ spacing.vertical }>
|
||||||
|
<DialogTitle>{ t("page.create.title") }</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<StaticPageForm />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Actions>
|
||||||
|
<Button variant="contained" color="primary" startIcon={ <Save /> } type="submit">{ t("save") }</Button>
|
||||||
|
<Button startIcon={ <Cancel /> } onClick={ ev => props.onClose?.(ev, "escapeKeyDown") }>{ t("cancel") }</Button>
|
||||||
|
</Actions>
|
||||||
|
</DialogActions>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
}
|
39
src/management/page/form.tsx
Normal file
39
src/management/page/form.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { default as StaticPage } from "@/data/page";
|
||||||
|
import { identityTransformer, Transformer } from "@/serialization";
|
||||||
|
import { Field, Form, FormikFormProps } from "formik";
|
||||||
|
import React from "react";
|
||||||
|
import { TextField as TextFieldFormik } from "formik-material-ui";
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useSpacing } from "@/styles";
|
||||||
|
|
||||||
|
export type StaticPageFormValues = StaticPage;
|
||||||
|
|
||||||
|
export const initialStaticPageFormValues: StaticPageFormValues = {
|
||||||
|
slug: "",
|
||||||
|
title: {
|
||||||
|
en: "",
|
||||||
|
pl: "",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
en: "",
|
||||||
|
pl: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const staticPageFormValuesTransformer: Transformer<StaticPage, StaticPageFormValues> = identityTransformer;
|
||||||
|
|
||||||
|
export function StaticPageForm() {
|
||||||
|
const { t } = useTranslation("management");
|
||||||
|
const spacing = useSpacing(2);
|
||||||
|
|
||||||
|
return <div className={ spacing.vertical }>
|
||||||
|
<Field label={ t("page.field.slug") } name="slug" fullWidth component={ TextFieldFormik }/>
|
||||||
|
<Typography variant="subtitle2">{ t("page.field.title") }</Typography>
|
||||||
|
<Field label={ t("translation:language.pl") } name="title.pl" fullWidth component={ TextFieldFormik }/>
|
||||||
|
<Field label={ t("translation:language.en") } name="title.en" fullWidth component={ TextFieldFormik }/>
|
||||||
|
<Typography variant="subtitle2">{ t("page.field.content") }</Typography>
|
||||||
|
<Field label={ t("translation:language.pl") } name="content.pl" fullWidth component={ TextFieldFormik }/>
|
||||||
|
<Field label={ t("translation:language.en") } name="content.en" fullWidth component={ TextFieldFormik }/>
|
||||||
|
</div>
|
||||||
|
}
|
137
src/management/page/list.tsx
Normal file
137
src/management/page/list.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Page } from "@/pages/base";
|
||||||
|
import { Management } from "@/management/main";
|
||||||
|
import { Box, Button, CircularProgress, Container, IconButton, Tooltip, Typography } from "@material-ui/core";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
import { useAsyncState } from "@/hooks";
|
||||||
|
import api from "@/management/api";
|
||||||
|
import { Async } from "@/components/async";
|
||||||
|
import MaterialTable, { Action, Column } from "material-table";
|
||||||
|
import { default as StaticPage } from "@/data/page";
|
||||||
|
import { Delete, FileFind, Pencil, Refresh } from "mdi-material-ui";
|
||||||
|
import { encapsulate, one } from "@/helpers";
|
||||||
|
import { Actions } from "@/components";
|
||||||
|
import { useSpacing } from "@/styles";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { Add } from "@material-ui/icons";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { CreateStaticPageDialog } from "@/management/page/create";
|
||||||
|
import { Confirm } from "@/components/confirm";
|
||||||
|
|
||||||
|
export const StaticPageManagement = () => {
|
||||||
|
const { t } = useTranslation("management");
|
||||||
|
const [ result, setPagesPromise ] = useAsyncState<StaticPage[]>();
|
||||||
|
const spacing = useSpacing(2);
|
||||||
|
|
||||||
|
const updatePageList = () => {
|
||||||
|
setPagesPromise(api.page.all());
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(updatePageList, []);
|
||||||
|
|
||||||
|
const CreateStaticPageAction = () => {
|
||||||
|
const [ open, setOpen ] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handlePageCreation = async (page: StaticPage) => {
|
||||||
|
await api.page.save(page);
|
||||||
|
setOpen(false);
|
||||||
|
updatePageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Button variant="contained" color="primary" startIcon={ <Add /> } onClick={ () => setOpen(true) }>{ t("create") }</Button>
|
||||||
|
{ createPortal(
|
||||||
|
<CreateStaticPageDialog open={ open } onSave={ handlePageCreation } onClose={ () => setOpen(false) }/>,
|
||||||
|
document.getElementById("modals") as Element
|
||||||
|
) }
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteStaticPageAction = ({ page }: { page: StaticPage }) => {
|
||||||
|
const handlePageDeletion = async () => {
|
||||||
|
await api.page.remove(page);
|
||||||
|
updatePageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmation = <>
|
||||||
|
<Trans i18nKey="page.confirm.delete">
|
||||||
|
Czy na pewno chcesz usunąć stronę <strong>{ page.title.pl }</strong>?
|
||||||
|
</Trans>
|
||||||
|
</>;
|
||||||
|
|
||||||
|
return <Confirm onConfirm={ handlePageDeletion } content={ confirmation }>
|
||||||
|
{ action => <Tooltip title={ t("actions.delete") as string }><IconButton onClick={ action }><Delete /></IconButton></Tooltip> }
|
||||||
|
</Confirm>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PreviewStaticPageAction = ({ page }: { page: StaticPage }) => {
|
||||||
|
const history = useHistory();
|
||||||
|
const handlePagePreview = async () => history.push(`/${page.slug}`);
|
||||||
|
|
||||||
|
return <Tooltip title={ t("actions.preview") as string }>
|
||||||
|
<IconButton onClick={ handlePagePreview }><FileFind /></IconButton>
|
||||||
|
</Tooltip>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: Column<StaticPage>[] = [
|
||||||
|
{
|
||||||
|
render: page => page.title.pl,
|
||||||
|
title: t("page.field.title"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "slug",
|
||||||
|
title: t("page.field.slug"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("actions.label"),
|
||||||
|
render: page => <Actions style={{ margin: "-1rem" }} spacing={ 0 }>
|
||||||
|
<DeleteStaticPageAction page={ page } />
|
||||||
|
<PreviewStaticPageAction page={ page } />
|
||||||
|
</Actions>,
|
||||||
|
sorting: false,
|
||||||
|
width: 0,
|
||||||
|
resizable: false,
|
||||||
|
removable: false,
|
||||||
|
searchable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PagePreview = ({ page }: { page: StaticPage }) =>
|
||||||
|
<Box className={ spacing.vertical } p={ 3 }>
|
||||||
|
<div>
|
||||||
|
<Typography variant="subtitle2">Polski</Typography>
|
||||||
|
<Typography variant="h2">{ page.title.pl }</Typography>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: page.content.pl }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography variant="subtitle2">English</Typography>
|
||||||
|
<Typography variant="h2">{ page.title.en }</Typography>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: page.content.en }} />
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
return <Page>
|
||||||
|
<Page.Header maxWidth="lg">
|
||||||
|
<Management.Breadcrumbs>
|
||||||
|
<Typography color="textPrimary">{ t("page.index.title") }</Typography>
|
||||||
|
</Management.Breadcrumbs>
|
||||||
|
<Page.Title>{ t("page.index.title") }</Page.Title>
|
||||||
|
</Page.Header>
|
||||||
|
<Container maxWidth="lg" className={ spacing.vertical }>
|
||||||
|
<Actions>
|
||||||
|
<CreateStaticPageAction />
|
||||||
|
<Button onClick={ updatePageList } startIcon={ <Refresh /> }>{ t("refresh") }</Button>
|
||||||
|
</Actions>
|
||||||
|
<Async async={ result } keepValue>{
|
||||||
|
pages => <MaterialTable
|
||||||
|
title={ <div style={{ display: "flex", alignItems: "center" }}>{ t("page.index.title") } { result.isLoading && <CircularProgress size="1.5rem" style={{ marginLeft: "1rem" }}/> }</div> }
|
||||||
|
columns={ columns }
|
||||||
|
data={ pages }
|
||||||
|
detailPanel={ page => <PagePreview page={ page } /> }
|
||||||
|
/>
|
||||||
|
}</Async>
|
||||||
|
</Container>
|
||||||
|
</Page>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StaticPageManagement;
|
@ -1,76 +0,0 @@
|
|||||||
import { Page } from "@/pages/base";
|
|
||||||
import { Management } from "@/management/main";
|
|
||||||
import { Box, Container, Typography } from "@material-ui/core";
|
|
||||||
import React, { useCallback } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useAsync } from "@/hooks";
|
|
||||||
import api from "@/management/api";
|
|
||||||
import { Async } from "@/components/async";
|
|
||||||
import MaterialTable, { Action, Column } from "material-table";
|
|
||||||
import { default as StaticPage } from "@/data/page";
|
|
||||||
import { Edition } from "@/data/edition";
|
|
||||||
import { Delete, Pencil } from "mdi-material-ui";
|
|
||||||
|
|
||||||
export const StaticPageManagement = () => {
|
|
||||||
const { t } = useTranslation("management");
|
|
||||||
const pages = useAsync(useCallback(api.page.all, []));
|
|
||||||
|
|
||||||
const columns: Column<StaticPage>[] = [
|
|
||||||
{
|
|
||||||
render: page => page.title.pl,
|
|
||||||
title: t("page.field.title"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "slug",
|
|
||||||
title: t("page.field.slug"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const actions: Action<StaticPage>[] = [
|
|
||||||
{
|
|
||||||
icon: () => <Pencil />,
|
|
||||||
onClick: () => {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: () => <Delete />,
|
|
||||||
onClick: () => {},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const PagePreview = ({ page }: { page: StaticPage }) =>
|
|
||||||
<>
|
|
||||||
<Box p={2}>
|
|
||||||
<Typography variant="subtitle2">Polski</Typography>
|
|
||||||
<Typography variant="h2">{ page.title.pl }</Typography>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: page.content.pl }} />
|
|
||||||
</Box>
|
|
||||||
<Box p={2}>
|
|
||||||
<Typography variant="subtitle2">English</Typography>
|
|
||||||
<Typography variant="h2">{ page.title.en }</Typography>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: page.content.en }} />
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
|
|
||||||
return <Page>
|
|
||||||
<Page.Header maxWidth="lg">
|
|
||||||
<Management.Breadcrumbs>
|
|
||||||
<Typography color="textPrimary">{ t("page.index.title") }</Typography>
|
|
||||||
</Management.Breadcrumbs>
|
|
||||||
<Page.Title>{ t("page.index.title") }</Page.Title>
|
|
||||||
</Page.Header>
|
|
||||||
<Container maxWidth="lg">
|
|
||||||
<Async async={ pages }>{
|
|
||||||
pages => <MaterialTable
|
|
||||||
title={ t("page.index.title") }
|
|
||||||
columns={ columns }
|
|
||||||
actions={ actions }
|
|
||||||
data={ pages }
|
|
||||||
detailPanel={ page => <PagePreview page={ page } /> }
|
|
||||||
options={{ actionsColumnIndex: -1 }}
|
|
||||||
/>
|
|
||||||
}</Async>
|
|
||||||
</Container>
|
|
||||||
</Page>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StaticPageManagement;
|
|
@ -3,7 +3,7 @@ import { isManagerMiddleware } from "@/management/middleware";
|
|||||||
import { EditionsManagement } from "@/management/edition/list";
|
import { EditionsManagement } from "@/management/edition/list";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ManagementIndex } from "@/management/main";
|
import { ManagementIndex } from "@/management/main";
|
||||||
import StaticPageManagement from "@/management/pages/list";
|
import StaticPageManagement from "@/management/page/list";
|
||||||
|
|
||||||
export const managementRoutes: Route[] = ([
|
export const managementRoutes: Route[] = ([
|
||||||
{ name: "index", path: "/", content: ManagementIndex, exact: true },
|
{ name: "index", path: "/", content: ManagementIndex, exact: true },
|
||||||
|
@ -19,3 +19,8 @@ export type SerializationTransformer<T, TSerialized = Serializable<T>> = Transfo
|
|||||||
export type OneWayTransformer<TFrom, TResult, TContext = never> = {
|
export type OneWayTransformer<TFrom, TResult, TContext = never> = {
|
||||||
transform(subject: TFrom, context?: TContext): TResult;
|
transform(subject: TFrom, context?: TContext): TResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const identityTransformer: Transformer<any, any> = {
|
||||||
|
transform: subject => subject,
|
||||||
|
reverseTransform: subject => subject
|
||||||
|
}
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
title: Zarządzanie
|
title: Zarządzanie
|
||||||
|
|
||||||
|
create: utwórz
|
||||||
|
refresh: $t(translation:refresh)
|
||||||
|
save: zapisz
|
||||||
|
cancel: anuluj
|
||||||
|
|
||||||
|
actions:
|
||||||
|
label: Akcje
|
||||||
|
preview: Podgląd
|
||||||
|
delete: Usuń
|
||||||
|
|
||||||
edition:
|
edition:
|
||||||
index:
|
index:
|
||||||
title: "Edycje praktyk"
|
title: "Edycje praktyk"
|
||||||
@ -16,3 +26,5 @@ page:
|
|||||||
title: Tytuł
|
title: Tytuł
|
||||||
content: Treść
|
content: Treść
|
||||||
slug: Adres
|
slug: Adres
|
||||||
|
create:
|
||||||
|
title: Utwórz stronę statyczną
|
||||||
|
@ -211,6 +211,10 @@ steps:
|
|||||||
instructions: >
|
instructions: >
|
||||||
Należy zgłosić się do pełnomocnika ds. praktyk Twojego kierunku i podpisać umowę ubezpieczenia. (TODO)
|
Należy zgłosić się do pełnomocnika ds. praktyk Twojego kierunku i podpisać umowę ubezpieczenia. (TODO)
|
||||||
|
|
||||||
|
language:
|
||||||
|
pl: Polski
|
||||||
|
en: Angielski
|
||||||
|
|
||||||
validation:
|
validation:
|
||||||
api:
|
api:
|
||||||
GreaterThanOrEqualValidator: Wartość pola "{{ PropertyName }}" musi być większa bądź równa {{ ComparisonValue }}.
|
GreaterThanOrEqualValidator: Wartość pola "{{ PropertyName }}" musi być większa bądź równa {{ ComparisonValue }}.
|
||||||
@ -224,3 +228,4 @@ validation:
|
|||||||
contact-coordinator: "Skontaktuj się z koordynatorem"
|
contact-coordinator: "Skontaktuj się z koordynatorem"
|
||||||
download: "pobierz"
|
download: "pobierz"
|
||||||
management: "zarządzanie"
|
management: "zarządzanie"
|
||||||
|
refresh: "odśwież"
|
||||||
|
Loading…
Reference in New Issue
Block a user