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"