From f3fd265dad5fbc9aa582633d0dd32121a12811b1 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Wed, 18 Nov 2020 23:46:21 +0100
Subject: [PATCH] Add ability to update and add internship types

---
 src/management/common/LabelWithIcon.tsx | 28 +++++++++++++
 src/management/type/edit.tsx            | 47 +++++++++++++++++++++
 src/management/type/form.tsx            | 55 +++++++++++++++++++++++++
 src/management/type/list.tsx            | 51 +++++++++++++++++++++--
 translations/management.pl.yaml         |  6 ++-
 5 files changed, 183 insertions(+), 4 deletions(-)
 create mode 100644 src/management/common/LabelWithIcon.tsx
 create mode 100644 src/management/type/edit.tsx
 create mode 100644 src/management/type/form.tsx

diff --git a/src/management/common/LabelWithIcon.tsx b/src/management/common/LabelWithIcon.tsx
new file mode 100644
index 0000000..e1d2649
--- /dev/null
+++ b/src/management/common/LabelWithIcon.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+
+const useStyles = makeStyles((theme: Theme) => createStyles({
+    root: {
+        display: "flex",
+        alignItems: "center"
+    },
+    icon: {
+        marginRight: theme.spacing(1),
+        display: "flex",
+        alignItems: "center"
+    }
+}))
+
+export type LabelWithIconProps = {
+    icon: React.ReactNode,
+    children: React.ReactChildren,
+}
+
+export function LabelWithIcon({ icon, children }: LabelWithIconProps) {
+    const classes = useStyles();
+
+    return <div className={ classes.root }>
+        <div className={ classes.icon }>{ icon }</div>
+        { children }
+    </div>
+}
diff --git a/src/management/type/edit.tsx b/src/management/type/edit.tsx
new file mode 100644
index 0000000..6b15745
--- /dev/null
+++ b/src/management/type/edit.tsx
@@ -0,0 +1,47 @@
+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 { initialInternshipTypeFormValues, InternshipTypeForm, InternshipTypeFormValues, internshipTypeFormValuesTransformer } from "@/management/type/form";
+import { InternshipType } from "@/data";
+
+export type EditInternshipTypeDialogProps = {
+    onSave?: (page: InternshipType) => void;
+    value?: InternshipType;
+} & DialogProps;
+
+export function EditInternshipTypeDialog({ onSave, value, ...props }: EditInternshipTypeDialogProps) {
+    const { t } = useTranslation("management");
+    const spacing = useSpacing(3);
+
+    const handleSubmit = (values: InternshipTypeFormValues) => {
+        onSave?.(internshipTypeFormValuesTransformer.reverseTransform(values));
+    };
+
+    const initialValues = value
+        ? internshipTypeFormValuesTransformer.transform(value)
+        : initialInternshipTypeFormValues;
+
+    return <Dialog { ...props } maxWidth="lg">
+        <Formik initialValues={ initialValues } onSubmit={ handleSubmit }>
+            <Form className={ spacing.vertical }>
+                <DialogTitle>{ t(value ? "type.edit.title" : "type.create.title") }</DialogTitle>
+                <DialogContent>
+                    <InternshipTypeForm />
+                </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/type/form.tsx b/src/management/type/form.tsx
new file mode 100644
index 0000000..5b7a04c
--- /dev/null
+++ b/src/management/type/form.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { InternshipType } from "@/data";
+import { useTranslation } from "react-i18next";
+import { useSpacing } from "@/styles";
+import { Field } from "formik";
+import { TextField as TextFieldFormik, Checkbox as CheckboxFormik } from "formik-material-ui";
+import { FormControlLabel, FormGroup, Typography } from "@material-ui/core";
+import { CKEditorField } from "@/field/ckeditor";
+import { AccountCheck, ShieldCheck } from "mdi-material-ui";
+import { identityTransformer, Transformer } from "@/serialization";
+import { LabelWithIcon } from "@/management/common/LabelWithIcon";
+
+export type InternshipTypeFormValues = Omit<InternshipType, 'id'>;
+
+export const initialInternshipTypeFormValues: InternshipTypeFormValues = {
+    label: {
+        pl: "",
+        en: "",
+    },
+    description: {
+        pl: "",
+        en: "",
+    },
+    requiresInsurance: false,
+    requiresDeanApproval: false,
+}
+
+export const internshipTypeFormValuesTransformer: Transformer<InternshipType, InternshipTypeFormValues> = identityTransformer;
+
+export function InternshipTypeForm() {
+    const { t } = useTranslation("management");
+    const spacing = useSpacing(2);
+
+    return <div className={ spacing.vertical }>
+        <Typography variant="subtitle2">{ t("type.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("type.field.description") }</Typography>
+        <Field label={ t("translation:language.pl") } name="description.pl" fullWidth component={ TextFieldFormik }/>
+        <Field label={ t("translation:language.en") } name="description.en" fullWidth component={ TextFieldFormik }/>
+
+        <Typography variant="subtitle2">{ t("type.field.flags") }</Typography>
+        <FormGroup>
+            <FormControlLabel
+                control={ <Field name="requiresDeanApproval" component={ CheckboxFormik }/> }
+                label={ <LabelWithIcon icon={ <AccountCheck /> }>{ t("type.flag.dean-approval") }</LabelWithIcon> }
+            />
+            <FormControlLabel
+                control={ <Field name="requiresInsurance" component={ CheckboxFormik }/> }
+                label={ <LabelWithIcon icon={ <ShieldCheck /> }>{ t("type.flag.insurance") }</LabelWithIcon> }
+            />
+        </FormGroup>
+    </div>
+
+}
diff --git a/src/management/type/list.tsx b/src/management/type/list.tsx
index aad97a3..7d34c66 100644
--- a/src/management/type/list.tsx
+++ b/src/management/type/list.tsx
@@ -5,7 +5,7 @@ import { useAsyncState } from "@/hooks";
 import { InternshipType } from "@/data";
 import api from "@/management/api";
 import { Management } from "@/management/main";
-import { Button, Container, Tooltip, Typography } from "@material-ui/core";
+import { Button, Container, IconButton, Tooltip, Typography } from "@material-ui/core";
 import { Async } from "@/components/async";
 import MaterialTable, { Column } from "material-table";
 import { MaterialTableTitle } from "@/management/common/MaterialTableTitle";
@@ -17,6 +17,11 @@ 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";
 
@@ -41,6 +46,44 @@ export const InternshipTypeManagement = () => {
 
     const DeleteTypeAction = createDeleteAction({ label, onDelete: handleTypeDelete });
 
+    const CreateTypeAction = () => {
+        const [ open, setOpen ] = useState<boolean>(false);
+
+        const handleTypeCreation = async (value: InternshipType) => {
+            await api.type.save(value);
+            setOpen(false);
+            updateTypeList();
+        }
+
+        return <>
+            <Button variant="contained" color="primary" startIcon={ <Add /> } onClick={ () => setOpen(true) }>{ t("create") }</Button>
+            { open && createPortal(
+                <EditInternshipTypeDialog open={ open } onSave={ handleTypeCreation } onClose={ () => setOpen(false) }/>,
+                document.getElementById("modals") as Element
+            ) }
+        </>
+    }
+
+    const EditTypeAction = ({ resource }: { resource: InternshipType }) => {
+        const [ open, setOpen ] = useState<boolean>(false);
+
+        const handleTypeCreation = async (value: InternshipType) => {
+            await api.type.save(value);
+            setOpen(false);
+            updateTypeList();
+        }
+
+        return <>
+            <Tooltip title={ t("actions.edit") as any }>
+                <IconButton onClick={ () => setOpen(true) }><Edit /></IconButton>
+            </Tooltip>
+            { open && createPortal(
+                <EditInternshipTypeDialog open={ open } onSave={ handleTypeCreation } value={ resource } onClose={ () => setOpen(false) }/>,
+                document.getElementById("modals") as Element
+            ) }
+        </>
+    }
+
     const columns: Column<InternshipType>[] = [
         {
             field: "id",
@@ -61,16 +104,17 @@ export const InternshipTypeManagement = () => {
         },
         {
             title: t("type.field.flags"),
-            render: type => <>
+            render: type => <div style={{ display: "flex", flexDirection: "column" }}>
                 { type.requiresDeanApproval && <Tooltip title={ t("type.flag.dean-approval") as string }><AccountCheck/></Tooltip> }
                 { type.requiresInsurance && <Tooltip title={ t("type.flag.insurance") as string }><ShieldCheck/></Tooltip> }
-            </>,
+            </div>,
             width: 0,
             filtering: true,
             sorting: false,
         },
         actionsColumn(type => <>
             <DeleteTypeAction resource={ type }/>
+            <EditTypeAction resource={ type }/>
         </>)
     ];
 
@@ -83,6 +127,7 @@ export const InternshipTypeManagement = () => {
         </Page.Header>
         <Container maxWidth="lg" className={ spacing.vertical }>
             <Actions>
+                <CreateTypeAction />
                 <Button onClick={ updateTypeList } startIcon={ <Refresh /> }>{ t("refresh") }</Button>
             </Actions>
             { selected.length > 0 && <BulkActions>
diff --git a/translations/management.pl.yaml b/translations/management.pl.yaml
index 02a238b..1367f4e 100644
--- a/translations/management.pl.yaml
+++ b/translations/management.pl.yaml
@@ -24,10 +24,14 @@ edition:
 type:
   index:
     title: "Rodzeje praktyki"
+  edit:
+    title: "Edytuj rodzaj praktyki"
+  create:
+    title: "Utwórz rodzaj praktyki"
   field:
     label: "Rodzaj praktyki"
     description: "Opis"
-    flags: "Flagi"
+    flags: "Wymogi"
   flag:
     dean-approval: "Wymaga zgody dziekana"
     insurance: "Wymaga ubezpieczenia"