From bb7887cb9367b45b7513d7c5de87cf8f03cf4cd0 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Sat, 2 Jan 2021 23:11:33 +0100
Subject: [PATCH] Forms for editiing reporting form

---
 src/components/async.tsx                      |   1 -
 src/data/report.ts                            |  36 ++---
 src/forms/report.tsx                          |  40 +++---
 src/management/edition/list.tsx               |  41 +++---
 src/management/edition/manage.tsx             |  82 +++++++++++
 src/management/edition/report/fields/edit.tsx |  45 ++++++
 src/management/edition/report/fields/form.tsx | 104 ++++++++++++++
 src/management/edition/report/fields/list.tsx |  84 +++++++++++
 src/management/main.tsx                       |  21 +--
 src/management/routing.tsx                    |   5 +
 src/management/type/list.tsx                  |   2 -
 src/provider/dummy/report.ts                  | 130 ++++++++++--------
 translations/management.pl.yaml               |  19 +++
 13 files changed, 489 insertions(+), 121 deletions(-)
 create mode 100644 src/management/edition/manage.tsx
 create mode 100644 src/management/edition/report/fields/edit.tsx
 create mode 100644 src/management/edition/report/fields/form.tsx
 create mode 100644 src/management/edition/report/fields/list.tsx

diff --git a/src/components/async.tsx b/src/components/async.tsx
index f99fbca..82b7e8e 100644
--- a/src/components/async.tsx
+++ b/src/components/async.tsx
@@ -1,6 +1,5 @@
 import { AsyncResult } from "@/hooks";
 import React from "react";
-import { CircularProgress } from "@material-ui/core";
 import { Alert } from "@material-ui/lab";
 import { Loading } from "@/components/loading";
 
diff --git a/src/data/report.ts b/src/data/report.ts
index d399ce8..a5eeafc 100644
--- a/src/data/report.ts
+++ b/src/data/report.ts
@@ -4,34 +4,38 @@ interface PredefinedChoices {
     choices: Multilingual<string>[];
 }
 
-export interface BaseField {
+export interface BaseFieldDefinition {
     name: string;
     description: Multilingual<string>;
     label: Multilingual<string>;
 }
 
-export interface TextField extends BaseField {
-    type: "text";
-    value: string | null;
+export interface TextFieldDefinition extends BaseFieldDefinition {
+    type: "short-text" | "long-text";
 }
 
-export interface MultiChoiceField extends BaseField, PredefinedChoices {
-    type: "multi-choice";
-    value: Multilingual<string>[] | null;
+export type TextFieldValue = string;
+
+export interface MultiChoiceFieldDefinition extends BaseFieldDefinition, PredefinedChoices {
+    type: "checkbox";
 }
 
-export interface SingleChoiceField extends BaseField, PredefinedChoices {
-    type: "single-choice";
-    value: Multilingual<string> | null
+export type MultiChoiceValue = Multilingual<string>[];
+
+export interface SingleChoiceFieldDefinition extends BaseFieldDefinition, PredefinedChoices {
+    type: "radio" | "select";
 }
 
-export interface SelectField extends BaseField, PredefinedChoices {
-    type: "select";
-    value: Multilingual<string> | null
-}
+export type SingleChoiceValue = Multilingual<string>;
 
-export type ReportField = TextField | MultiChoiceField | SingleChoiceField | SelectField;
+export type ReportFieldDefinition = TextFieldDefinition | MultiChoiceFieldDefinition | SingleChoiceFieldDefinition;
+export type ReportFieldValue = TextFieldValue | MultiChoiceValue | SingleChoiceValue;
+export type ReportFieldValues = { [field: string]: ReportFieldValue };
+export type ReportSchema = ReportFieldDefinition[];
 
 export interface Report {
-    fields: ReportField[];
+    fields: ReportFieldValues;
 }
+
+export const reportFieldTypes = ["short-text", "long-text", "checkbox", "radio", "select"];
+
diff --git a/src/forms/report.tsx b/src/forms/report.tsx
index a4936bf..dfb08f7 100644
--- a/src/forms/report.tsx
+++ b/src/forms/report.tsx
@@ -1,5 +1,5 @@
 import React from "react";
-import { emptyReport } from "@/provider/dummy/report";
+import { emptyReport, sampleReportSchema } from "@/provider/dummy/report";
 import {
     Button,
     FormControl,
@@ -18,22 +18,23 @@ import { Actions } from "@/components";
 import { Link as RouterLink } from "react-router-dom";
 import { route } from "@/routing";
 import { useTranslation } from "react-i18next";
-import { MultiChoiceField, Report, ReportField, SelectField, SingleChoiceField } from "@/data/report";
+import { MultiChoiceFieldDefinition, Report, ReportFieldDefinition, ReportFieldValues, SingleChoiceFieldDefinition } from "@/data/report";
 import { TextField as TextFieldFormik } from "formik-material-ui";
 import { Field, Form, Formik, useFormik, useFormikContext } from "formik";
 import { Multilingual } from "@/data";
 import { Transformer } from "@/serialization";
 
-export type ReportFieldProps<TField = ReportField> = {
+export type ReportFieldProps<TField = ReportFieldDefinition> = {
     field: TField;
 }
 
-const CustomField = ({ field, ...props }: ReportFieldProps) => {
+export const CustomField = ({ field, ...props }: ReportFieldProps) => {
     switch (field.type) {
-        case "text":
+        case "short-text":
+        case "long-text":
             return <CustomField.Text {...props} field={ field } />
-        case "single-choice":
-        case "multi-choice":
+        case "checkbox":
+        case "radio":
             return <CustomField.Choice {...props} field={ field }/>
         case "select":
             return <CustomField.Select {...props} field={ field }/>
@@ -42,12 +43,16 @@ const CustomField = ({ field, ...props }: ReportFieldProps) => {
 
 CustomField.Text = ({ field }: ReportFieldProps) => {
     return <>
-        <Field label={ field.label.pl } name={ field.name } fullWidth component={ TextFieldFormik }/>
+        <Field label={ field.label.pl } name={ field.name }
+               fullWidth
+               rows={ field.type == "long-text" ? 4 : 1 } multiline={ field.type == "long-text" }
+               component={ TextFieldFormik }
+        />
         <Typography variant="caption" color="textSecondary" dangerouslySetInnerHTML={{ __html: field.description.pl }}/>
     </>
 }
 
-CustomField.Select = ({ field }: ReportFieldProps<SelectField>) => {
+CustomField.Select = ({ field }: ReportFieldProps<SingleChoiceFieldDefinition>) => {
     const { t } = useTranslation();
     const id = `custom-field-${field.name}`;
     const { values, setFieldValue } = useFormikContext<any>();
@@ -63,24 +68,24 @@ CustomField.Select = ({ field }: ReportFieldProps<SelectField>) => {
     </FormControl>
 }
 
-CustomField.Choice = ({ field }: ReportFieldProps<SingleChoiceField | MultiChoiceField>) => {
+CustomField.Choice = ({ field }: ReportFieldProps<SingleChoiceFieldDefinition | MultiChoiceFieldDefinition>) => {
     const { t } = useTranslation();
     const { values, setFieldValue } = useFormikContext<any>();
 
     const value = values[field.name];
 
-    const isSelected = field.type == 'single-choice'
+    const isSelected = field.type == 'radio'
         ? (checked: Multilingual<string>) => value == checked
         : (checked: Multilingual<string>) => (value || []).includes(checked)
 
-    const handleChange = field.type == 'single-choice'
+    const handleChange = field.type == 'radio'
         ? (choice: Multilingual<string>) => () => setFieldValue(field.name, choice, false)
         : (choice: Multilingual<string>) => () => {
             const current = value || [];
             setFieldValue(field.name, !current.includes(choice) ? [ ...current, choice ] : current.filter((c: Multilingual<string>) => c != choice), false);
         }
 
-    const Component = field.type == 'single-choice' ? Radio : Checkbox;
+    const Component = field.type == 'radio' ? Radio : Checkbox;
 
     return <FormControl component="fieldset">
         <FormLabel component="legend">{ field.label.pl }</FormLabel>
@@ -94,19 +99,20 @@ CustomField.Choice = ({ field }: ReportFieldProps<SingleChoiceField | MultiChoic
     </FormControl>
 }
 
-export type ReportFormValues = { [field: string]: any };
+export type ReportFormValues = ReportFieldValues;
 
 const reportToFormValuesTransformer: Transformer<Report, ReportFormValues, { report: Report }> = {
     reverseTransform(subject: ReportFormValues, context: { report: Report }): Report {
-        return { ...context.report };
+        return { ...context.report, fields: subject };
     },
     transform(subject: Report, context: undefined): ReportFormValues {
-        return Object.fromEntries(subject.fields.map(field => [ field.name, field.value ]));
+        return subject.fields;
     }
 }
 
 export default function ReportForm() {
     const report = emptyReport;
+    const schema = sampleReportSchema;
     const { t } = useTranslation();
 
     const handleSubmit = async () => {};
@@ -117,7 +123,7 @@ export default function ReportForm() {
                 <Grid item xs={12}>
                     <Typography variant="body1" component="p">{ t('forms.report.instructions') }</Typography>
                 </Grid>
-                { report.fields.map(field => <Grid item xs={12}><CustomField field={ field }/></Grid>) }
+                { schema.map(field => <Grid item xs={12}><CustomField field={ field }/></Grid>) }
                 <Grid item xs={12}>
                     <Actions>
                         <Button variant="contained" color="primary" onClick={ submitForm }>
diff --git a/src/management/edition/list.tsx b/src/management/edition/list.tsx
index d718651..14b1248 100644
--- a/src/management/edition/list.tsx
+++ b/src/management/edition/list.tsx
@@ -1,23 +1,24 @@
-import React, { useCallback, useEffect } from "react";
+import React, { useCallback } from "react";
 import { Page } from "@/pages/base";
 import { useTranslation } from "react-i18next";
-import { useAsync, useAsyncState } from "@/hooks";
+import { useAsync } from "@/hooks";
 import api from "@/management/api";
 import { Async } from "@/components/async";
-import { Container, Typography } from "@material-ui/core";
-import MaterialTable, { Action, Column } from "material-table";
+import { Container, IconButton, Tooltip, Typography } from "@material-ui/core";
+import MaterialTable, { Column } from "material-table";
 import { Edition } from "@/data/edition";
-import { Pencil } from "mdi-material-ui";
+import { FileFind } from "mdi-material-ui";
 import { Management } from "../main";
-import { createPortal } from "react-dom";
-import { EditStaticPageDialog } from "@/management/page/create";
+import { useHistory } from "react-router-dom";
+import { route } from "@/routing";
+import { actionsColumn } from "@/management/common/helpers";
 
 export type EditionDetailsProps = {
     edition: string;
 }
 
 export function EditionDetails({ edition, ...props }: EditionDetailsProps) {
-    const result = useAsync(useCallback(() => api.edition.details(edition), [ edition ]));
+    const result = useAsync(useCallback(() => api.edition.details(edition), [edition]));
 
     return <Async async={ result }>{ edition => <pre>{ JSON.stringify(edition, null, 2) }</pre> }</Async>
 }
@@ -26,6 +27,15 @@ export function EditionsManagement() {
     const { t } = useTranslation("management");
     const editions = useAsync(useCallback(api.edition.all, []));
 
+    const ManageEditionAction = ({ edition }: { edition: Edition }) => {
+        const history = useHistory();
+        const handlePagePreview = async () => history.push(route('management:edition_manage', { edition: edition.id || "" }));
+
+        return <Tooltip title={ t("actions.manage") as string }>
+            <IconButton onClick={ handlePagePreview }><FileFind/></IconButton>
+        </Tooltip>;
+    }
+
     const columns: Column<Edition>[] = [
         {
             title: t("edition.field.id"),
@@ -47,13 +57,9 @@ export function EditionsManagement() {
             customSort: (a, b) => a.course.name.localeCompare(b.course.name),
             render: edition => edition.course.name,
         },
-    ]
-
-    const actions: Action<Edition>[] = [
-        {
-            icon: () => <Pencil />,
-            onClick: () => {},
-        }
+        actionsColumn(edition => <>
+            <ManageEditionAction edition={ edition }/>
+        </>),
     ]
 
     return <Page>
@@ -69,10 +75,9 @@ export function EditionsManagement() {
                     <MaterialTable
                         columns={ columns }
                         data={ editions }
-                        actions={ actions }
-                        detailPanel={ edition => <EditionDetails edition={ edition.id as string } /> }
+                        detailPanel={ edition => <EditionDetails edition={ edition.id as string }/> }
                         title={ t("edition.index.title") }
-                        options={{ search: false, actionsColumnIndex: -1 }}
+                        options={ { search: false } }
                     />
                 }
             </Async>
diff --git a/src/management/edition/manage.tsx b/src/management/edition/manage.tsx
new file mode 100644
index 0000000..7ba0a5c
--- /dev/null
+++ b/src/management/edition/manage.tsx
@@ -0,0 +1,82 @@
+import React, { useCallback } from "react";
+import { useTranslation } from "react-i18next";
+import { useRouteMatch } from "react-router-dom";
+import { useAsync } from "@/hooks";
+import api from "@/management/api";
+import { Page } from "@/pages/base";
+import { Container, Paper, Typography } from "@material-ui/core";
+import { Management, ManagementLink } from "@/management/main";
+import { Edition } from "@/data/edition";
+import { Async } from "@/components/async";
+import { AccountMultiple, BriefcaseAccount, CertificateOutline, CogOutline, FileChartOutline, FormatPageBreak } from "mdi-material-ui";
+import { route } from "@/routing";
+import { useSpacing } from "@/styles";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+
+const useSectionStyles = makeStyles((theme: Theme) => createStyles({
+    header: {
+        padding: theme.spacing(2),
+        paddingBottom: 0,
+        fontWeight: 'bold',
+        color: theme.palette.grey["800"],
+    },
+}))
+
+export function title(edition: Edition) {
+    return `${edition.course.name} - ${edition.startDate.year()}`
+}
+
+export const ManageEditionPage = () => {
+    const { t } = useTranslation("management");
+    const { params } = useRouteMatch();
+
+    const edition = useAsync<Edition>(useCallback(() => api.edition.details(params.edition), [params.edition]))
+    const spacing = useSpacing(2);
+    const classes = useSectionStyles();
+
+    return <Page>
+        <Async async={ edition }>{ edition =>
+            <>
+            <Page.Header>
+                <Management.Breadcrumbs>
+                    <Typography color="textPrimary">{ title(edition) }</Typography>
+                </Management.Breadcrumbs>
+                <Page.Title>{ title(edition) }</Page.Title>
+            </Page.Header>
+                <Container className={ spacing.vertical }>
+                    <Paper elevation={ 2 }>
+                        <Typography className={ classes.header }>{ t("edition.manage.internships") }</Typography>
+                        <Management.Menu>
+                            <ManagementLink icon={ <AccountMultiple/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.students.title") }
+                            </ManagementLink>
+                            <ManagementLink icon={ <BriefcaseAccount/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.internships.title") }
+                            </ManagementLink>
+                            <ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.ipp.title") }
+                            </ManagementLink>
+                            <ManagementLink icon={ <FileChartOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.reports.title") }
+                            </ManagementLink>
+                            <ManagementLink icon={ <CertificateOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.dean-approvals.title") }
+                            </ManagementLink>
+                        </Management.Menu>
+                    </Paper>
+                    <Paper elevation={ 2 }>
+                        <Typography className={ classes.header }>{ t("edition.manage.management") }</Typography>
+                        <Management.Menu>
+                            <ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
+                                { t("management:edition.report-form.title") }
+                            </ManagementLink>
+                            <ManagementLink icon={ <CogOutline/> } route={ route("management:edition_settings", { edition: edition.id || "" }) }>
+                                { t("management:edition.settings.title") }
+                            </ManagementLink>
+                        </Management.Menu>
+                    </Paper>
+                </Container>
+            </>
+        }</Async>
+    </Page>
+}
diff --git a/src/management/edition/report/fields/edit.tsx b/src/management/edition/report/fields/edit.tsx
new file mode 100644
index 0000000..0ced08d
--- /dev/null
+++ b/src/management/edition/report/fields/edit.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { Button, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@material-ui/core";
+import { ReportFieldDefinition } from "@/data/report";
+import { useTranslation } from "react-i18next";
+import { useSpacing } from "@/styles";
+import { Form, Formik } from "formik";
+import { Actions } from "@/components";
+import { Save } from "@material-ui/icons";
+import { Cancel } from "mdi-material-ui";
+import { FieldDefinitionForm, FieldDefinitionFormValues, fieldFormValuesTransformer, initialFieldFormValues } from "@/management/edition/report/fields/form";
+
+export type EditFieldDialogProps = {
+    onSave?: (field: ReportFieldDefinition) => void;
+    field?: ReportFieldDefinition;
+} & DialogProps;
+
+export function EditFieldDefinitionDialog({ onSave, field, ...props }: EditFieldDialogProps) {
+    const { t } = useTranslation("management");
+    const spacing = useSpacing(3);
+
+    const handleSubmit = (values: FieldDefinitionFormValues) => {
+        onSave?.(fieldFormValuesTransformer.reverseTransform(values));
+    };
+
+    const initialValues = field
+        ? fieldFormValuesTransformer.transform(field)
+        : initialFieldFormValues;
+
+    return <Dialog { ...props } maxWidth="md">
+        <Formik initialValues={ initialValues } onSubmit={ handleSubmit }>
+            <Form className={ spacing.vertical }>
+                <DialogTitle>{ t(field ? "report-field.edit.title" : "report-field.create.title") }</DialogTitle>
+                <DialogContent>
+                    <FieldDefinitionForm />
+                </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>
+}
diff --git a/src/management/edition/report/fields/form.tsx b/src/management/edition/report/fields/form.tsx
new file mode 100644
index 0000000..2c720df
--- /dev/null
+++ b/src/management/edition/report/fields/form.tsx
@@ -0,0 +1,104 @@
+import React from "react";
+import { ReportFieldDefinition, reportFieldTypes } from "@/data/report";
+import { identityTransformer, Transformer } from "@/serialization";
+import { useTranslation } from "react-i18next";
+import { useSpacing } from "@/styles";
+import { Field, FieldArray, FieldProps, useFormikContext } from "formik";
+import { TextField as TextFieldFormik, Select } from "formik-material-ui";
+import { FormControl, InputLabel, Typography, MenuItem, Card, Box, Button, CardContent, CardHeader, IconButton } from "@material-ui/core";
+import { CKEditorField } from "@/field/ckeditor";
+import { Multilingual } from "@/data";
+import { Actions } from "@/components";
+import { Add } from "@material-ui/icons";
+import { TrashCan } from "mdi-material-ui";
+import { FieldPreview } from "@/management/edition/report/fields/list";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+
+export type FieldDefinitionFormValues = ReportFieldDefinition | { type: string };
+
+export const initialFieldFormValues: FieldDefinitionFormValues = {
+    type: "short-text",
+    name: "",
+    description: {
+        pl: "",
+        en: "",
+    },
+    label: {
+        pl: "",
+        en: "",
+    },
+    choices: [],
+}
+
+export const fieldFormValuesTransformer: Transformer<ReportFieldDefinition, FieldDefinitionFormValues> = identityTransformer;
+export type ChoiceFieldProps = { name: string };
+
+const ChoiceField = ({ field, form, meta }: FieldProps) => {
+    const { name } = field;
+    const { t } = useTranslation("management");
+    const spacing = useSpacing(2);
+
+    return <div className={ spacing.vertical }>
+        <Field label={ t("translation:language.pl") } name={`${name}.pl`} fullWidth component={ TextFieldFormik }/>
+        <Field label={ t("translation:language.en") } name={`${name}.en`} fullWidth component={ TextFieldFormik }/>
+    </div>
+}
+
+const useStyles = makeStyles((theme: Theme) => createStyles({
+    preview: {
+        padding: theme.spacing(2),
+        backgroundColor: "#e9f0f5",
+    },
+}))
+
+export function FieldDefinitionForm() {
+    const { t } = useTranslation("management");
+    const spacing = useSpacing(2);
+    const { values } = useFormikContext<any>();
+    const classes = useStyles();
+
+    return <div className={ spacing.vertical }>
+        <Field label={ t("report-field.field.name") } name="name" fullWidth component={ TextFieldFormik }/>
+        <FormControl variant="outlined">
+            <InputLabel htmlFor="report-field-type">{ t("report-field.field.type") }</InputLabel>
+            <Field
+                component={Select}
+                name="type"
+                label={ t("report-field.field.name") }
+                inputProps={{ id: 'report-field-type', }}
+            >
+                { reportFieldTypes.map(type => <MenuItem value={ type }>{ t(`report-field.type.${type}`) }</MenuItem>)}
+            </Field>
+        </FormControl>
+        <Typography variant="subtitle2">{ t("report-field.field.label") }</Typography>
+        <Field label={ t("translation:language.pl") } name="label.pl" fullWidth component={ TextFieldFormik }/>
+        <Field label={ t("translation:language.en") } name="label.en" fullWidth component={ TextFieldFormik }/>
+        <Typography variant="subtitle2">{ t("report-field.field.description") }</Typography>
+        <Field label={ t("translation:language.pl") } name="description.pl" fullWidth component={ CKEditorField }/>
+        <Field label={ t("translation:language.en") } name="description.en" fullWidth component={ CKEditorField }/>
+
+        { ["radio", "select", "checkbox"].includes(values.type) && <>
+            <Typography variant="subtitle2">{ t("report-field.field.choices") }</Typography>
+            <FieldArray name="choices" render={ helper => <>
+                { values.choices.map((value: Multilingual<string>, index: number) => <Card>
+                    <CardHeader subheader={ t("report-field.field.choice", { index }) } action={ <>
+                        <IconButton onClick={ () => helper.remove(index) }>
+                            <TrashCan />
+                        </IconButton>
+                    </> }/>
+                    <CardContent>
+                        <Field name={`choices[${index}]`} component={ ChoiceField } />
+                    </CardContent>
+                </Card>) }
+                <Actions>
+                    <Button variant="contained" startIcon={ <Add /> } color="primary" onClick={() => helper.push({ pl: "", en: "" })}>{ t("actions.add") }</Button>
+                </Actions>
+            </> } />
+        </> }
+
+        <div className={ classes.preview }>
+            <Typography variant="subtitle2">{ t("report-field.preview") }</Typography>
+            <FieldPreview field={ fieldFormValuesTransformer.reverseTransform(values) }/>
+        </div>
+    </div>
+}
diff --git a/src/management/edition/report/fields/list.tsx b/src/management/edition/report/fields/list.tsx
new file mode 100644
index 0000000..2e62e15
--- /dev/null
+++ b/src/management/edition/report/fields/list.tsx
@@ -0,0 +1,84 @@
+import React, { useState } from "react";
+import { Page } from "@/pages/base";
+import { Management } from "@/management/main";
+import { Box, Container, IconButton, Tooltip, Typography } from "@material-ui/core";
+import { useTranslation } from "react-i18next";
+import { sampleReportSchema } from "@/provider/dummy/report";
+import MaterialTable, { Column } from "material-table";
+import { actionsColumn, fieldComparator, multilingualStringComparator } from "@/management/common/helpers";
+import { MultilingualCell } from "@/management/common/MultilangualCell";
+import { ReportFieldDefinition } from "@/data/report";
+import { Formik } from "formik";
+import { CustomField } from "@/forms/report";
+import { Edit } from "@material-ui/icons";
+import { createPortal } from "react-dom";
+import { createDeleteAction } from "@/management/common/DeleteResourceAction";
+import { EditFieldDefinitionDialog } from "@/management/edition/report/fields/edit";
+
+const title = "edition.report-fields.title";
+
+export const FieldPreview = ({ field }: { field: ReportFieldDefinition }) => {
+    return <Formik initialValues={{}} onSubmit={() => {}}>
+        <CustomField field={ field }/>
+    </Formik>
+}
+
+export const EditionReportFields = () => {
+    const { t } = useTranslation("management");
+    const schema = sampleReportSchema;
+
+    const handleFieldDeletion = () => {}
+
+    const DeleteFieldAction  = createDeleteAction<ReportFieldDefinition>({ label: field => field.label.pl, onDelete: handleFieldDeletion })
+    const EditFieldAction = ({ field }: { field: ReportFieldDefinition }) => {
+        const [ open, setOpen ] = useState<boolean>(false);
+
+        const handleFieldSave = async (field: ReportFieldDefinition) => {
+        }
+
+        return <>
+            <Tooltip title={ t("actions.edit") as any }>
+                <IconButton onClick={ () => setOpen(true) }><Edit /></IconButton>
+            </Tooltip>
+            { open && createPortal(
+                <EditFieldDefinitionDialog open={ open } onSave={ handleFieldSave } field={ field } onClose={ () => setOpen(false) }/>,
+                document.getElementById("modals") as Element
+            ) }
+        </>
+    }
+
+    const columns: Column<ReportFieldDefinition>[] = [
+        {
+            title: t("report-field.field.label"),
+            customSort: fieldComparator('label', multilingualStringComparator),
+            cellStyle: { whiteSpace: "nowrap" },
+            render: field => <MultilingualCell value={ field.label }/>,
+        },
+        {
+            title: t("report-field.field.type"),
+            cellStyle: { whiteSpace: "nowrap" },
+            render: field => t(`report-field.type.${field.type}`),
+        },
+        actionsColumn(field => <>
+            <EditFieldAction field={ field }/>
+            <DeleteFieldAction resource={ field }/>
+        </>),
+    ]
+
+    return <Page>
+        <Page.Header maxWidth="lg">
+            <Management.Breadcrumbs>
+                <Typography color="textPrimary">{ t(title) }</Typography>
+            </Management.Breadcrumbs>
+            <Page.Title>{ t(title) }</Page.Title>
+        </Page.Header>
+        <Container maxWidth="lg">
+            <MaterialTable
+                columns={ columns }
+                data={ schema }
+                title={ t(title) }
+                detailPanel={ field => <Box p={3}><FieldPreview field={ field } /></Box> }
+            />
+        </Container>
+    </Page>
+}
diff --git a/src/management/main.tsx b/src/management/main.tsx
index 4a990ce..73b8dcb 100644
--- a/src/management/main.tsx
+++ b/src/management/main.tsx
@@ -6,6 +6,13 @@ import { route } from "@/routing";
 import { useTranslation } from "react-i18next";
 import { CalendarClock, FileCertificateOutline, FileDocumentMultipleOutline } from "mdi-material-ui";
 
+
+export const ManagementLink = ({ icon, route, children }: ManagementLinkProps) =>
+    <ListItem button component={ RouterLink } to={ route }>
+        <ListItemIcon>{ icon }</ListItemIcon>
+        <ListItemText>{ children }</ListItemText>
+    </ListItem>
+
 export const Management = {
     Breadcrumbs: ({ children, ...props }: BreadcrumbsProps) => {
         const { t } = useTranslation();
@@ -14,7 +21,9 @@ export const Management = {
             <Link component={ RouterLink } to={ route("management:index") }>{ t("management:title") }</Link>
             { children }
         </Page.Breadcrumbs>;
-    }
+    },
+    Menu: List,
+    MenuItem: ManagementLink,
 }
 
 type ManagementLinkProps = React.PropsWithChildren<{
@@ -22,12 +31,6 @@ type ManagementLinkProps = React.PropsWithChildren<{
     route: string,
 }>;
 
-const ManagementLink = ({ icon, route, children }: ManagementLinkProps) =>
-    <ListItem button component={ RouterLink } to={ route }>
-        <ListItemIcon>{ icon }</ListItemIcon>
-        <ListItemText>{ children }</ListItemText>
-    </ListItem>
-
 export const ManagementIndex = () => {
     const { t } = useTranslation();
 
@@ -37,7 +40,7 @@ export const ManagementIndex = () => {
         </Page.Header>
         <Container>
             <Paper elevation={ 2 }>
-                <List>
+                <Management.Menu>
                     <ManagementLink icon={ <CalendarClock /> } route={ route("management:editions") }>
                         { t("management:edition.index.title") }
                     </ManagementLink>
@@ -47,7 +50,7 @@ export const ManagementIndex = () => {
                     <ManagementLink icon={ <FileDocumentMultipleOutline /> } route={ route("management:static_pages") }>
                         { t("management:page.index.title") }
                     </ManagementLink>
-                </List>
+                </Management.Menu>
             </Paper>
         </Container>
     </Page>
diff --git a/src/management/routing.tsx b/src/management/routing.tsx
index a994e67..8e35409 100644
--- a/src/management/routing.tsx
+++ b/src/management/routing.tsx
@@ -5,11 +5,16 @@ import React from "react";
 import { ManagementIndex } from "@/management/main";
 import StaticPageManagement from "@/management/page/list";
 import { InternshipTypeManagement } from "@/management/type/list";
+import { ManageEditionPage } from "@/management/edition/manage";
+import { EditionReportFields } from "@/management/edition/report/fields/list";
 
 export const managementRoutes: Route[] = ([
     { name: "index", path: "/", content: ManagementIndex, exact: true },
 
+    { name: "edition_report_form", path: "/editions/:edition/report", content: EditionReportFields },
+    { name: "edition_manage", path: "/editions/:edition", content: ManageEditionPage },
     { name: "editions", path: "/editions", content: EditionsManagement },
+
     { name: "types", path: "/types", content: InternshipTypeManagement },
     { name: "static_pages", path: "/static-pages", content: StaticPageManagement }
 ] as Route[]).map(
diff --git a/src/management/type/list.tsx b/src/management/type/list.tsx
index 7d34c66..ebad003 100644
--- a/src/management/type/list.tsx
+++ b/src/management/type/list.tsx
@@ -17,10 +17,8 @@ import { BulkActions } from "@/management/common/BulkActions";
 import { useSpacing } from "@/styles";
 import { Actions } from "@/components";
 import { MultilingualCell } from "@/management/common/MultilangualCell";
-import { default as StaticPage } from "@/data/page";
 import { Add, Edit } from "@material-ui/icons";
 import { createPortal } from "react-dom";
-import { EditStaticPageDialog } from "@/management/page/edit";
 import { EditInternshipTypeDialog } from "@/management/type/edit";
 
 const title = "type.index.title";
diff --git a/src/provider/dummy/report.ts b/src/provider/dummy/report.ts
index ad77aad..8973586 100644
--- a/src/provider/dummy/report.ts
+++ b/src/provider/dummy/report.ts
@@ -1,66 +1,80 @@
-import { Report } from "@/data/report";
+import { Report, ReportSchema } from "@/data/report";
 
 const choices = [1, 2, 3, 4, 5].map(n => ({
     pl: `Wybór ${n}`,
     en: `Choice ${n}`
 }))
 
+export const sampleReportSchema: ReportSchema = [
+    {
+        type: "short-text",
+        name: "short",
+        description: {
+            en: "Text field, with <strong>HTML</strong> description",
+            pl: "Pole tekstowe, z opisem w formacie <strong>HTML</strong>"
+        },
+        label: {
+            en: "Text Field",
+            pl: "Pole tekstowe",
+        },
+    },
+    {
+        type: "long-text",
+        name: "long",
+        description: {
+            en: "Long text field, with <strong>HTML</strong> description",
+            pl: "Długie pole tekstowe, z opisem w formacie <strong>HTML</strong>"
+        },
+        label: {
+            en: "Long Text Field",
+            pl: "Długie Pole tekstowe",
+        },
+    },
+    {
+        type: "radio",
+        name: "radio",
+        description: {
+            en: "single choice field, with <strong>HTML</strong> description",
+            pl: "Pole jednokrotnego wyboru, z opisem w formacie <strong>HTML</strong>"
+        },
+        choices,
+        label: {
+            en: "Single choice field",
+            pl: "Pole jednokrotnego wyboru",
+        },
+    },
+    {
+        type: "select",
+        name: "select",
+        description: {
+            en: "select field, with <strong>HTML</strong> description",
+            pl: "Pole jednokrotnego wyboru z selectboxem, z opisem w formacie <strong>HTML</strong>"
+        },
+        choices,
+        label: {
+            en: "Select field",
+            pl: "Pole jednokrotnego wyboru (selectbox)",
+        },
+    },
+    {
+        name: "multi",
+        type: "checkbox",
+        description: {
+            en: "Multiple choice field, with <strong>HTML</strong> description",
+            pl: "Pole wielokrotnego wyboru, z opisem w formacie <strong>HTML</strong>"
+        },
+        choices,
+        label: {
+            en: "Multi choice field",
+            pl: "Pole wielokrotnego wyboru",
+        },
+    },
+]
+
 export const emptyReport: Report = {
-    fields: [
-        {
-            type: "text",
-            name: "text",
-            description: {
-                en: "Text field, with <strong>HTML</strong> description",
-                pl: "Pole tekstowe, z opisem w formacie <strong>HTML</strong>"
-            },
-            value: null,
-            label: {
-                en: "Text Field",
-                pl: "Pole tekstowe",
-            },
-        },
-        {
-            type: "single-choice",
-            name: "single",
-            description: {
-                en: "single choice field, with <strong>HTML</strong> description",
-                pl: "Pole jednokrotnego wyboru, z opisem w formacie <strong>HTML</strong>"
-            },
-            value: null,
-            choices,
-            label: {
-                en: "Single choice field",
-                pl: "Pole jednokrotnego wyboru",
-            },
-        },
-        {
-            type: "select",
-            name: "select",
-            description: {
-                en: "select field, with <strong>HTML</strong> description",
-                pl: "Pole jednokrotnego wyboru z selectboxem, z opisem w formacie <strong>HTML</strong>"
-            },
-            value: choices[2],
-            choices,
-            label: {
-                en: "Select field",
-                pl: "Pole jednokrotnego wyboru (selectbox)",
-            },
-        },
-        {
-            name: "multi",
-            type: "multi-choice",
-            description: {
-                en: "Multiple choice field, with <strong>HTML</strong> description",
-                pl: "Pole wielokrotnego wyboru, z opisem w formacie <strong>HTML</strong>"
-            },
-            value: [ choices[0], choices[3] ],
-            choices,
-            label: {
-                en: "Multi choice field",
-                pl: "Pole wielokrotnego wyboru",
-            },
-        },
-    ]
+    fields: {
+        "short": "Testowa wartość",
+        "select": choices[0],
+        "multi": [ choices[1], choices[2] ],
+    }
 }
diff --git a/translations/management.pl.yaml b/translations/management.pl.yaml
index 1367f4e..993c35b 100644
--- a/translations/management.pl.yaml
+++ b/translations/management.pl.yaml
@@ -11,6 +11,7 @@ actions:
   preview: Podgląd
   delete: Usuń
   edit: Edytuj
+  add: Dodaj
 
 edition:
   index:
@@ -20,6 +21,24 @@ edition:
     start: Początek
     end: Koniec
     course: Kierunek
+  report-fields:
+    title: "Pola formularza raportu praktyki"
+
+report-field:
+  preview: Podgląd
+  field:
+    type: "Rodzaj"
+    name: "Unikalny identyfikator"
+    label: "Etykieta"
+    description: "Opis"
+    choices: "Możliwe wybory"
+    choice: "Wybór #{{ index }}"
+  type:
+    select: "Pole wyboru"
+    radio: "Jednokrotny wybór (radio)"
+    checkbox: "Wielokrotny wybór (checkboxy)"
+    short-text: "Pole krótkiej odpowiedzi"
+    long-text: "Pole długiej odpowiedzi"
 
 type:
   index: