From 87068c1592452eb7bc1360a381d6802487eea663 Mon Sep 17 00:00:00 2001
From: Schwobik <michal.schwob@seznam.cz>
Date: Tue, 24 May 2022 15:28:17 +0200
Subject: [PATCH 1/3] Edit catalog items - start of implementation re #9819

---
 .../src/features/Catalog/EditCatalogItem.tsx  | 345 ++++++++++++++++++
 .../src/features/Catalog/catalogItemSlice.ts  |  69 ++++
 .../features/Catalog/catalogItemThunks.tsx    |  53 +++
 3 files changed, 467 insertions(+)
 create mode 100644 frontend/src/features/Catalog/EditCatalogItem.tsx
 create mode 100644 frontend/src/features/Catalog/catalogItemSlice.ts
 create mode 100644 frontend/src/features/Catalog/catalogItemThunks.tsx

diff --git a/frontend/src/features/Catalog/EditCatalogItem.tsx b/frontend/src/features/Catalog/EditCatalogItem.tsx
new file mode 100644
index 0000000..91b8453
--- /dev/null
+++ b/frontend/src/features/Catalog/EditCatalogItem.tsx
@@ -0,0 +1,345 @@
+import {
+    Button,
+    Dialog,
+    DialogContent,
+    Divider,
+    Grid,
+    Paper,
+    Stack, TextField,
+    Typography,
+} from '@mui/material'
+import { Fragment, FunctionComponent, useEffect, useState } from 'react'
+import { useParams } from 'react-router-dom'
+import axiosInstance from '../../api/api'
+import { CatalogItemDto } from '../../swagger/data-contracts'
+import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
+import ContentLoading from '../Reusables/ContentLoading'
+import CatalogItemMap from './CatalogItemMap'
+import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'
+import { Link as RouterLink } from 'react-router-dom'
+import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
+import {RootState} from "../redux/store"
+import {useDispatch, useSelector} from "react-redux"
+import * as yup from "yup"
+import {FieldArray, ErrorMessage, Field, Form, useFormik} from "formik"
+import {register} from "../Auth/userThunks"
+import {updateCatalogItem} from "./catalogItemThunks"
+
+const apiError =
+    'Error while fetching data from the server, please try again later.'
+
+export interface CatalogItemEditProps {
+    itemId: string
+    showReturnToDetailButton?: boolean
+}
+
+export interface RowData {
+    rowName: string
+    items: string[] | undefined,
+    isExpandable: boolean,
+    key: string
+}
+
+const EditCatalogItem: FunctionComponent<CatalogItemEditProps> = ({
+    itemId,
+    showReturnToDetailButton,
+}) => {
+    const [item, setItem] = useState<CatalogItemDto | undefined>(undefined)
+    const [isItemLoading, setIsItemLoading] = useState(true)
+    const [err, setErr] = useState<string | undefined>(undefined)
+
+    const canEditCatalog = useSelector(
+        (state: RootState) => state.user.roles.includes("WRITE")
+    )
+
+    const dispatch = useDispatch()
+
+    // Fetch the item from the api after mounting the component
+    useEffect(() => {
+        // Function to fetch the item from the api
+        const fetchItem = async () => {
+            try {
+                const { data, status } = await axiosInstance.get(
+                    `/catalog-items/${itemId}`
+                )
+                if (status !== 200) {
+                    setErr(apiError)
+                    return
+                }
+
+                setItem(data)
+                setIsItemLoading(false)
+            } catch (err: any) {
+                setErr(apiError)
+            }
+        }
+
+        fetchItem()
+    }, [itemId])
+
+    const validationSchema = yup.object().shape({
+        name: yup.string().required('Item name is required'),
+        longitude: yup.number().required('Longitude has to be a number'),
+        latitude: yup.number().required('Latitude has to be a number'),
+        certainty: yup.number().required('Certainty has to be a number'),
+    })
+
+    const formik = useFormik({
+        initialValues: {
+            name: item?.name,
+            allNames: item?.allNames,
+            writtenForms: item?.writtenForms,
+            types: item?.types,
+            countries: item?.countries,
+            bibliography: item?.bibliography,
+            longitude: item?.longitude,
+            latitude: item?.latitude,
+            certainty: item?.certainty,
+            description: item?.description,
+        },
+        validationSchema,
+        onSubmit: () => {
+            const response = dispatch(
+                updateCatalogItem({
+                    item: {
+                        id: item?.id,
+                        name: formik.values.name,
+                        allNames: formik.values.allNames,
+                        writtenForms: formik.values.writtenForms,
+                        types: formik.values.types,
+                        countries: formik.values.countries,
+                        bibliography: formik.values.bibliography,
+                        longitude: formik.values.longitude,
+                        latitude: formik.values.latitude,
+                        certainty: formik.values.certainty,
+                        description: formik.values.description,
+                    }
+                })
+            )
+
+        },
+    })
+
+
+    const handleFormChangeExpandable = (index: number, event: React.ChangeEvent<HTMLInputElement>, array: string[], key: string | undefined) => {
+        if (!rows.filter(v => v.key === key)[0].items) {
+            rows.filter(v => v.key === key)[0].items = [event.target.value]
+        } else {
+            // @ts-ignore
+            rows.filter(v => v.key === key)[0].items[index] = event.target.value
+        }
+    }
+
+    const handleFormChange = (event: React.ChangeEvent<HTMLInputElement>, array: string[], key: string | undefined) => {
+        if (!rows.filter(v => v.key === key)[0].items) {
+            rows.filter(v => v.key === key)[0].items = [event.target.value]
+        } else {
+            // @ts-ignore
+            rows.filter(v => v.key === key)[0].items[index] = event.target.value
+        }
+    }
+
+    // Maps catalogItem property to corresponding table row
+    const mapToRow = (row: RowData) => (
+        <Fragment>
+            <Grid sx={{ my: 2 }} container justifyContent="space-around">
+                <Grid item xs={4} sx={{ px: 1 }}>
+                    <Typography fontWeight={500}>{row.rowName}</Typography>
+                </Grid>
+                {/*<Grid item xs={8} sx={{ ml: 'auto' }}>*/}
+                {/*    {row.isExpandable ? (*/}
+                {/*        row.items?.map((value, index, array) => (*/}
+                {/*            <input*/}
+                {/*                name='name'*/}
+                {/*                placeholder='Name'*/}
+                {/*                value={formik.values[row.key]?.at(index)}*/}
+                {/*                onChange={event => handleFormChangeExpandable(index, event, array, row.key)}*/}
+                {/*            />*/}
+                {/*        ))*/}
+                {/*    ) : (*/}
+
+                {/*    )}*/}
+
+                {/*</Grid>*/}
+            </Grid>
+        </Fragment>
+    )
+
+
+
+    // Catalog item rows
+    const rows: RowData[] = [
+        {
+            rowName: 'Name',
+            items: [item?.name as string],
+            isExpandable: false,
+            key: 'name'
+        },
+        {
+            rowName: 'All Names',
+            items: item?.allNames,
+            isExpandable: true,
+            key: 'allNames'
+        },
+        {
+            rowName: 'Written Forms',
+            items: item?.writtenForms,
+            isExpandable: true,
+            key: 'writtenForms'
+        },
+        {
+            rowName: 'Type',
+            items: item?.types,
+            isExpandable: true,
+            key: 'types'
+        },
+        {
+            rowName: 'State or Territory',
+            items: item?.countries,
+            isExpandable: true,
+            key: 'countries'
+        },
+        {
+            rowName: 'Longitude',
+            // Must be in array otherwise the string gets iterated
+            items: [item?.longitude as unknown as string],
+            isExpandable: false,
+            key: 'countries'
+        },
+        {
+            rowName: 'Latitude',
+            // Must be in array otherwise the string gets iterated
+            items: [item?.latitude as unknown as string],
+            isExpandable: false,
+            key: 'latitude'
+        },
+        {
+            rowName: 'Certainty',
+            items: [item?.certainty as unknown as string],
+            isExpandable: false,
+            key: 'certainty'
+        },
+        {
+            rowName: 'Bibliography',
+            items: item?.bibliography,
+            isExpandable: false,
+            key: 'bibliography'
+        },
+    ]
+
+    return (
+        <Fragment>
+            {showReturnToDetailButton && (
+                <Stack
+                    direction="row"
+                    alignItems="flex-start"
+                    spacing={2}
+                    sx={{ mt: 1 }}
+                >
+                    <Button
+                        startIcon={<ArrowBackIosIcon />}
+                        variant="contained"
+                        component={RouterLink}
+                        to="/catalog"
+                        color="primary"
+                        sx={{ mb: 2 }}
+                    >
+                        Back To Detail
+                    </Button>
+                </Stack>
+            )}
+            <ShowErrorIfPresent err={err} />
+
+            <Paper style={{ minHeight: '100vh' }} variant="outlined">
+                {isItemLoading && !err ? <ContentLoading /> : null}
+                {!isItemLoading && item ? (
+                    <form onSubmit={formik.handleSubmit}>
+                        <Grid sx={{ my: 2 }} container justifyContent="space-around">
+                            <Grid item xs={4} sx={{ px: 1 }}>
+                                <Typography fontWeight={500}>Name</Typography>
+                            </Grid>
+                            <Grid item xs={8} sx={{ ml: 'auto' }}>
+                                <TextField
+                                    fullWidth
+                                    label="Name"
+                                    name="name"
+                                    sx={{ mb: 2 }}
+                                    value={formik.values.name}
+                                    onChange={formik.handleChange}
+                                    error={
+                                        Boolean(formik.errors.name) &&
+                                        formik.touched.name
+                                    }
+                                    helperText={
+                                        formik.errors.name &&
+                                        formik.touched.name &&
+                                        formik.errors.name
+                                    }
+                                />
+                            </Grid>
+                            <Grid item xs={4} sx={{ px: 1 }}>
+                                <Typography fontWeight={500}>All Names</Typography>
+                            </Grid>
+                            <Grid item xs={8} sx={{ ml: 'auto' }}>
+                                <FieldArray name="allNames">
+                                    {() => (formik.values.allNames?.map((allName, i) => {
+                                        return (
+                                            <div key={i} className="list-group list-group-flush">
+                                                <div className="list-group-item">
+                                                    <h5 className="card-title">Ticket {i + 1}</h5>
+                                                    <div className="form-row">
+                                                        <div className="form-group col-6">
+                                                            <label>Name</label>
+                                                            <Field name={`tickets.${i}.name`} type="text" />
+                                                            <ErrorMessage name={`tickets.${i}.name`} component="div" />
+                                                        </div>
+                                                        <div className="form-group col-6">
+                                                            <label>Email</label>
+                                                            <Field name={`tickets.${i}.email`} type="text" />
+                                                            <ErrorMessage name={`tickets.${i}.email`} component="div" />
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        );
+                                    }))}
+                                </FieldArray>
+
+                            </Grid>
+                        </Grid>
+                    </form>
+                ) : null}
+            </Paper>
+        </Fragment>
+    )
+}
+
+export const RoutedCatalogItemEdit = () => {
+    const { itemId } = useParams()
+    return <EditCatalogItem itemId={itemId ?? ''} />
+}
+
+export const DialogCatalogItemEdit: FunctionComponent<
+    CatalogItemEditProps
+> = ({ itemId }) => {
+    const [open, setOpen] = useState(false)
+    return (
+        <Fragment>
+            <Button variant="contained" onClick={() => setOpen(true)}>
+                Detail
+            </Button>
+            <Dialog
+                open={open}
+                onClose={() => setOpen(false)}
+                fullWidth
+                maxWidth="lg"
+            >
+                <DialogContent>
+                    <EditCatalogItem itemId={itemId} />
+                </DialogContent>
+            </Dialog>
+        </Fragment>
+    )
+}
+
+export default EditCatalogItem
diff --git a/frontend/src/features/Catalog/catalogItemSlice.ts b/frontend/src/features/Catalog/catalogItemSlice.ts
new file mode 100644
index 0000000..8aaf545
--- /dev/null
+++ b/frontend/src/features/Catalog/catalogItemSlice.ts
@@ -0,0 +1,69 @@
+import { createSlice } from '@reduxjs/toolkit'
+import { CatalogItemDto } from '../../swagger/data-contracts'
+import {updateCatalogItem, updateCatalogItemsBulk} from "./catalogItemThunks"
+
+export interface CatalogItemState {
+    loading: boolean // whether the catalog is loading
+    error?: string
+}
+
+export interface UpdateCatalogItem {
+    item: CatalogItemDto
+}
+
+export interface UpdateCatalogItemsBulk {
+    items: CatalogItemDto[]
+}
+
+const initialState: CatalogItemState = {
+    loading: false,
+    error: undefined
+}
+
+const catalogItemSlice = createSlice({
+    name: 'catalogItem',
+    initialState,
+    reducers: {
+        clear: (state: CatalogItemState) => ({ ...initialState }),
+        setLoading: (state: CatalogItemState) => ({ ...state, loading: true }),
+        resetLoading: (state: CatalogItemState) => ({ ...state, loading: false }),
+        consumeError: (state: CatalogItemState) => ({ ...state, error: undefined }),
+    },
+    extraReducers: (builder) => {
+        builder.addCase(updateCatalogItem.pending, (state: CatalogItemState) => ({
+            ...state,
+            loading: true,
+        }))
+        builder.addCase(updateCatalogItem.fulfilled, (state: CatalogItemState, action: any) => ({
+            ...state,
+            loading: false,
+        }))
+        builder.addCase(updateCatalogItem.rejected, (state: CatalogItemState, action: any) => ({
+            ...state,
+            loading: false,
+            error: action.error.message as string,
+        }))
+        builder.addCase(updateCatalogItemsBulk.pending, (state: CatalogItemState) => ({
+            ...state,
+            loading: true,
+        }))
+        builder.addCase(updateCatalogItemsBulk.fulfilled, (state: CatalogItemState, action: any) => ({
+            ...state,
+            loading: false,
+        }))
+        builder.addCase(updateCatalogItemsBulk.rejected, (state: CatalogItemState, action: any) => ({
+            ...state,
+            loading: false,
+            error: action.error.message as string,
+        }))
+    },
+})
+
+export const {
+    clear,
+    setLoading,
+    resetLoading,
+    consumeError,
+} = catalogItemSlice.actions
+const reducer = catalogItemSlice.reducer
+export default reducer
diff --git a/frontend/src/features/Catalog/catalogItemThunks.tsx b/frontend/src/features/Catalog/catalogItemThunks.tsx
new file mode 100644
index 0000000..10df9f4
--- /dev/null
+++ b/frontend/src/features/Catalog/catalogItemThunks.tsx
@@ -0,0 +1,53 @@
+import { createAsyncThunk } from '@reduxjs/toolkit'
+import axiosInstance from '../../api/api'
+import {CatalogItemState, UpdateCatalogItem, UpdateCatalogItemsBulk} from "./catalogItemSlice"
+
+const apiError = 'Error, server is currently unavailable.'
+
+
+// Thunk to update catalog item using API
+export const updateCatalogItem = createAsyncThunk(
+    'catalogItem/update',
+    async (catalogItem: UpdateCatalogItem) => {
+        try {
+            // Send request with the filter
+            const { data, status } = await axiosInstance.post(
+                '/catalog-items',
+                catalogItem.item
+            )
+
+            // If the request was successful return the items
+            if (status === 200) {
+                return data
+            }
+
+            return Promise.reject(apiError)
+        } catch (err: any) {
+            return Promise.reject(err.response.data)
+        }
+    }
+)
+
+// Thunk to update catalog item using API
+export const updateCatalogItemsBulk = createAsyncThunk(
+    'catalogItem/updateBulk',
+    async (bulkItems: UpdateCatalogItemsBulk) => {
+        try {
+
+            // Send request with the filter
+            const { data, status } = await axiosInstance.post(
+                '/catalog-items/batch',
+                bulkItems.items
+            )
+
+            // If the request was successful return the items
+            if (status === 200) {
+                return data
+            }
+
+            return Promise.reject(apiError)
+        } catch (err: any) {
+            return Promise.reject(err.response.data)
+        }
+    }
+)
-- 
GitLab


From 157be6e6d899e852d9972d6d5e67797f3240963b Mon Sep 17 00:00:00 2001
From: Schwobik <michal.schwob@seznam.cz>
Date: Tue, 24 May 2022 15:29:03 +0200
Subject: [PATCH 2/3] Edit catalog items - start of implementation re #9819

---
 frontend/src/App.tsx | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c2dd20d..81153ac 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -21,6 +21,8 @@ import Register from "./features/Auth/Register"
 import Bibliography from "./features/Bibliography/Bibliography"
 import EditBibliography from "./features/Bibliography/EditBibliography"
 import ExternalSources from "./features/ExternalSources/ExternalSources"
+import {RoutedCatalogItemEdit} from "./features/Catalog/EditCatalogItem"
+
 
 const App = () => {
     return (
@@ -37,6 +39,10 @@ const App = () => {
                                 path="/catalog/:itemId"
                                 element={<RoutedCatalogItemDetail />}
                             />
+                            <Route
+                                path="/catalog/edit/:itemId"
+                                element={<RoutedCatalogItemEdit />}
+                            />
                             <Route path="/login" element={<Login />} />
                             <Route path="/register" element={<Register />} />
                             <Route path="/logout" element={<Logout />} />
-- 
GitLab


From 6187bffd522a38ac12e8fad48acad615d9ebdd92 Mon Sep 17 00:00:00 2001
From: Schwobik <michal.schwob@seznam.cz>
Date: Thu, 26 May 2022 00:51:30 +0200
Subject: [PATCH 3/3] Edit catalog items - complete implementation re #9819

---
 frontend/src/App.tsx                          |   2 +-
 .../features/Catalog/CatalogItemDetail.tsx    |   2 +-
 .../src/features/Catalog/EditCatalogItem.tsx  | 620 ++++++++++++------
 .../src/features/Catalog/catalogItemSlice.ts  |  10 +-
 .../features/Catalog/catalogItemThunks.tsx    |   2 +-
 frontend/src/features/redux/store.ts          |   2 +
 6 files changed, 423 insertions(+), 215 deletions(-)

diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 5e3a9ff..8416401 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -22,7 +22,7 @@ import Bibliography from "./features/Bibliography/Bibliography"
 import EditBibliography from "./features/Bibliography/EditBibliography"
 import ExternalSources from "./features/ExternalSources/ExternalSources"
 import ChangePassword from "./features/Administration/ChangePassword"
-import {RoutedCatalogItemEdit} from "./features/Catalog/EditCatalogItem"
+import { RoutedCatalogItemEdit } from "./features/Catalog/EditCatalogItem"
 
 
 const App = () => {
diff --git a/frontend/src/features/Catalog/CatalogItemDetail.tsx b/frontend/src/features/Catalog/CatalogItemDetail.tsx
index 269349f..f511ccf 100644
--- a/frontend/src/features/Catalog/CatalogItemDetail.tsx
+++ b/frontend/src/features/Catalog/CatalogItemDetail.tsx
@@ -150,7 +150,7 @@ const CatalogItemDetail: FunctionComponent<CatalogItemDetailProps> = ({
                             startIcon={<EditIcon />}
                             variant="contained"
                             component={RouterLink}
-                            to="/catalog"
+                            to={`/catalog/edit/${itemId as string}`}
                             color="primary"
                         >
                             Edit
diff --git a/frontend/src/features/Catalog/EditCatalogItem.tsx b/frontend/src/features/Catalog/EditCatalogItem.tsx
index 91b8453..40a8683 100644
--- a/frontend/src/features/Catalog/EditCatalogItem.tsx
+++ b/frontend/src/features/Catalog/EditCatalogItem.tsx
@@ -1,5 +1,5 @@
 import {
-    Button,
+    Button, Container,
     Dialog,
     DialogContent,
     Divider,
@@ -8,8 +8,8 @@ import {
     Stack, TextField,
     Typography,
 } from '@mui/material'
-import { Fragment, FunctionComponent, useEffect, useState } from 'react'
-import { useParams } from 'react-router-dom'
+import {Fragment, FunctionComponent, MouseEventHandler, useEffect, useState} from 'react'
+import {useNavigate, useParams} from 'react-router-dom'
 import axiosInstance from '../../api/api'
 import { CatalogItemDto } from '../../swagger/data-contracts'
 import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
@@ -21,9 +21,13 @@ import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
 import {RootState} from "../redux/store"
 import {useDispatch, useSelector} from "react-redux"
 import * as yup from "yup"
-import {FieldArray, ErrorMessage, Field, Form, useFormik} from "formik"
-import {register} from "../Auth/userThunks"
+import {Formik, FieldArray, ErrorMessage, Field, Form, useFormik} from "formik"
 import {updateCatalogItem} from "./catalogItemThunks"
+import AddIcon from '@mui/icons-material/Add';
+import { IconButton } from '@mui/material';
+import ClearIcon from '@mui/icons-material/Clear';
+import SaveIcon from '@mui/icons-material/Save'
+import {resetRequestCompleted} from "./catalogItemSlice"
 
 const apiError =
     'Error while fetching data from the server, please try again later.'
@@ -47,18 +51,31 @@ const EditCatalogItem: FunctionComponent<CatalogItemEditProps> = ({
     const [item, setItem] = useState<CatalogItemDto | undefined>(undefined)
     const [isItemLoading, setIsItemLoading] = useState(true)
     const [err, setErr] = useState<string | undefined>(undefined)
+    const loading = useSelector((state: RootState) => state.catalogItem.loading)
+    const requestCompleted = useSelector((state: RootState) => state.catalogItem.isRequestCompleted)
 
     const canEditCatalog = useSelector(
         (state: RootState) => state.user.roles.includes("WRITE")
     )
 
+    const navigate = useNavigate()
     const dispatch = useDispatch()
 
+    useEffect(() => {
+        if (requestCompleted) {
+            dispatch(resetRequestCompleted())
+            navigate(`/catalog/${itemId as string}`)
+        }
+    }, [requestCompleted, navigate])
+
     // Fetch the item from the api after mounting the component
     useEffect(() => {
         // Function to fetch the item from the api
         const fetchItem = async () => {
             try {
+                if (!itemId || itemId === "") {
+                    return
+                }
                 const { data, status } = await axiosInstance.get(
                     `/catalog-items/${itemId}`
                 )
@@ -79,154 +96,28 @@ const EditCatalogItem: FunctionComponent<CatalogItemEditProps> = ({
 
     const validationSchema = yup.object().shape({
         name: yup.string().required('Item name is required'),
-        longitude: yup.number().required('Longitude has to be a number'),
-        latitude: yup.number().required('Latitude has to be a number'),
-        certainty: yup.number().required('Certainty has to be a number'),
+        longitude: yup.number()
+            .typeError('Longitude must be a number')
+            .positive('Longitude must be greater than zero'),
+        latitude: yup.number()
+            .typeError('Latitude must be a number'),
+        certainty: yup.number()
+            .typeError('Certainty must be a number')
     })
 
-    const formik = useFormik({
-        initialValues: {
-            name: item?.name,
-            allNames: item?.allNames,
-            writtenForms: item?.writtenForms,
-            types: item?.types,
-            countries: item?.countries,
-            bibliography: item?.bibliography,
-            longitude: item?.longitude,
-            latitude: item?.latitude,
-            certainty: item?.certainty,
-            description: item?.description,
-        },
-        validationSchema,
-        onSubmit: () => {
-            const response = dispatch(
-                updateCatalogItem({
-                    item: {
-                        id: item?.id,
-                        name: formik.values.name,
-                        allNames: formik.values.allNames,
-                        writtenForms: formik.values.writtenForms,
-                        types: formik.values.types,
-                        countries: formik.values.countries,
-                        bibliography: formik.values.bibliography,
-                        longitude: formik.values.longitude,
-                        latitude: formik.values.latitude,
-                        certainty: formik.values.certainty,
-                        description: formik.values.description,
-                    }
-                })
-            )
-
-        },
-    })
-
-
-    const handleFormChangeExpandable = (index: number, event: React.ChangeEvent<HTMLInputElement>, array: string[], key: string | undefined) => {
-        if (!rows.filter(v => v.key === key)[0].items) {
-            rows.filter(v => v.key === key)[0].items = [event.target.value]
-        } else {
-            // @ts-ignore
-            rows.filter(v => v.key === key)[0].items[index] = event.target.value
-        }
-    }
-
-    const handleFormChange = (event: React.ChangeEvent<HTMLInputElement>, array: string[], key: string | undefined) => {
-        if (!rows.filter(v => v.key === key)[0].items) {
-            rows.filter(v => v.key === key)[0].items = [event.target.value]
-        } else {
-            // @ts-ignore
-            rows.filter(v => v.key === key)[0].items[index] = event.target.value
-        }
+    const initialValues = {
+        name: item?.name ? item?.name : "",
+        allNames: item?.allNames ? item?.allNames : [""],
+        writtenForms: item?.writtenForms ? item?.writtenForms : [""],
+        types: item?.types ? item?.types : [""],
+        countries: item?.countries ? item?.countries : [""],
+        bibliography: item?.bibliography ? item?.bibliography : [""],
+        longitude: undefined,
+        latitude: undefined,
+        certainty: undefined,
+        description: "",
     }
 
-    // Maps catalogItem property to corresponding table row
-    const mapToRow = (row: RowData) => (
-        <Fragment>
-            <Grid sx={{ my: 2 }} container justifyContent="space-around">
-                <Grid item xs={4} sx={{ px: 1 }}>
-                    <Typography fontWeight={500}>{row.rowName}</Typography>
-                </Grid>
-                {/*<Grid item xs={8} sx={{ ml: 'auto' }}>*/}
-                {/*    {row.isExpandable ? (*/}
-                {/*        row.items?.map((value, index, array) => (*/}
-                {/*            <input*/}
-                {/*                name='name'*/}
-                {/*                placeholder='Name'*/}
-                {/*                value={formik.values[row.key]?.at(index)}*/}
-                {/*                onChange={event => handleFormChangeExpandable(index, event, array, row.key)}*/}
-                {/*            />*/}
-                {/*        ))*/}
-                {/*    ) : (*/}
-
-                {/*    )}*/}
-
-                {/*</Grid>*/}
-            </Grid>
-        </Fragment>
-    )
-
-
-
-    // Catalog item rows
-    const rows: RowData[] = [
-        {
-            rowName: 'Name',
-            items: [item?.name as string],
-            isExpandable: false,
-            key: 'name'
-        },
-        {
-            rowName: 'All Names',
-            items: item?.allNames,
-            isExpandable: true,
-            key: 'allNames'
-        },
-        {
-            rowName: 'Written Forms',
-            items: item?.writtenForms,
-            isExpandable: true,
-            key: 'writtenForms'
-        },
-        {
-            rowName: 'Type',
-            items: item?.types,
-            isExpandable: true,
-            key: 'types'
-        },
-        {
-            rowName: 'State or Territory',
-            items: item?.countries,
-            isExpandable: true,
-            key: 'countries'
-        },
-        {
-            rowName: 'Longitude',
-            // Must be in array otherwise the string gets iterated
-            items: [item?.longitude as unknown as string],
-            isExpandable: false,
-            key: 'countries'
-        },
-        {
-            rowName: 'Latitude',
-            // Must be in array otherwise the string gets iterated
-            items: [item?.latitude as unknown as string],
-            isExpandable: false,
-            key: 'latitude'
-        },
-        {
-            rowName: 'Certainty',
-            items: [item?.certainty as unknown as string],
-            isExpandable: false,
-            key: 'certainty'
-        },
-        {
-            rowName: 'Bibliography',
-            items: item?.bibliography,
-            isExpandable: false,
-            key: 'bibliography'
-        },
-    ]
-
     return (
         <Fragment>
             {showReturnToDetailButton && (
@@ -234,80 +125,386 @@ const EditCatalogItem: FunctionComponent<CatalogItemEditProps> = ({
                     direction="row"
                     alignItems="flex-start"
                     spacing={2}
-                    sx={{ mt: 1 }}
+                    sx={{mt: 1}}
                 >
                     <Button
-                        startIcon={<ArrowBackIosIcon />}
+                        startIcon={<ArrowBackIosIcon/>}
                         variant="contained"
                         component={RouterLink}
-                        to="/catalog"
+                        to={`/catalog/${itemId as string}`}
                         color="primary"
-                        sx={{ mb: 2 }}
+                        sx={{mb: 2}}
                     >
                         Back To Detail
                     </Button>
                 </Stack>
             )}
-            <ShowErrorIfPresent err={err} />
+            <ShowErrorIfPresent err={err}/>
 
-            <Paper style={{ minHeight: '100vh' }} variant="outlined">
-                {isItemLoading && !err ? <ContentLoading /> : null}
+            <Paper style={{minHeight: '100vh'}} variant="outlined">
+                {isItemLoading && !err ? <ContentLoading/> : null}
                 {!isItemLoading && item ? (
-                    <form onSubmit={formik.handleSubmit}>
-                        <Grid sx={{ my: 2 }} container justifyContent="space-around">
-                            <Grid item xs={4} sx={{ px: 1 }}>
-                                <Typography fontWeight={500}>Name</Typography>
-                            </Grid>
-                            <Grid item xs={8} sx={{ ml: 'auto' }}>
-                                <TextField
-                                    fullWidth
-                                    label="Name"
-                                    name="name"
-                                    sx={{ mb: 2 }}
-                                    value={formik.values.name}
-                                    onChange={formik.handleChange}
-                                    error={
-                                        Boolean(formik.errors.name) &&
-                                        formik.touched.name
-                                    }
-                                    helperText={
-                                        formik.errors.name &&
-                                        formik.touched.name &&
-                                        formik.errors.name
+                    <Formik
+                        initialValues={initialValues}
+                        validationSchema={validationSchema}
+                        onSubmit={values => {
+                            console.log("submit called")
+                            console.log(values)
+                            const response = dispatch(
+                                updateCatalogItem({
+                                    item: {
+                                        id: item?.id,
+                                        name: values.name,
+                                        allNames: values.allNames,
+                                        writtenForms: values.writtenForms,
+                                        types: values.types,
+                                        countries: values.countries,
+                                        bibliography: values.bibliography,
+                                        longitude: values.longitude,
+                                        latitude: values.latitude,
+                                        certainty: values.certainty,
+                                        description: values.description,
                                     }
+                                })
+                            )
+                            console.log(response)
+                        }}
+                        render={({values, handleChange, errors, touched}) => (
+
+
+                            <Form>
+                                <Container sx={{m: 2}} maxWidth={false}>
+                                    <TextField
+                                        fullWidth
+                                        label="Name"
+                                        name="name"
+                                        value={values.name}
+                                        onChange={handleChange}
+                                        error={
+                                            Boolean(errors.name) &&
+                                            touched.name
+                                        }
+                                        helperText={
+                                            errors.name &&
+                                            touched.name &&
+                                            errors.name
+                                        }
+                                    />
+                                </Container>
+                                <Divider textAlign="left">Alternative Names</Divider>
+                                <FieldArray
+                                    name="allNames"
+                                    render={arrayHelpers => (
+                                        <Container maxWidth={false}>
+                                            {values.allNames && values.allNames.length > 0 ? (
+                                                values.allNames.map((altName, i) => (
+                                                    <Grid
+                                                        sx={{m: 2}} container justifyContent="space-around"
+                                                        key={i}>
+                                                        <Grid item xs={8} md={10} xl={11} >
+                                                            <TextField
+                                                                fullWidth
+                                                                label="Alternative Name"
+                                                                name={`allNames.${i}`}
+
+                                                                value={values.allNames[i]}
+                                                                onChange={handleChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={4} md={2} lg={2} xl={1} sx={{p: 1}}>
+                                                            <IconButton
+                                                                color="primary"
+                                                                onClick={() => arrayHelpers.insert(i + 1, '')}
+
+                                                            >
+                                                                <AddIcon/>
+                                                            </IconButton>
+                                                            <IconButton
+                                                                type="button"
+                                                                color="error"
+                                                                key={i}
+                                                                onClick={() => arrayHelpers.remove(i)}
+
+                                                            >
+                                                                <ClearIcon/>
+                                                            </IconButton>
+                                                        </Grid>
+
+                                                    </Grid>
+
+                                                ))
+                                            ) : null}
+                                        </Container>
+                                    )}
+                                />
+                                <Divider textAlign="left">Written Forms</Divider>
+                                <FieldArray
+                                    name="writtenForms"
+                                    render={arrayHelpers => (
+                                        <Container maxWidth={false}>
+                                            {values.writtenForms && values.writtenForms.length > 0 ? (
+                                                values.writtenForms.map((writtenForm, i) => (
+                                                    <Grid
+                                                        sx={{m: 2}} container justifyContent="space-around"
+                                                        key={i}>
+                                                        <Grid item xs={8} md={10} xl={11} >
+                                                            <TextField
+                                                                fullWidth
+                                                                label="Written Form"
+                                                                name={`writtenForms.${i}`}
+
+                                                                value={values.writtenForms[i]}
+                                                                onChange={handleChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={4} md={2} lg={2} xl={1} sx={{p: 1}}>
+                                                            <IconButton
+                                                                color="primary"
+                                                                onClick={() => arrayHelpers.insert(i + 1, '')}
+
+                                                            >
+                                                                <AddIcon/>
+                                                            </IconButton>
+                                                            <IconButton
+                                                                type="button"
+                                                                color="error"
+                                                                key={i}
+                                                                onClick={() => arrayHelpers.remove(i)}
+
+                                                            >
+                                                                <ClearIcon/>
+                                                            </IconButton>
+                                                        </Grid>
+                                                    </Grid>
+                                                ))
+                                            ) : null}
+                                        </Container>
+                                    )}
+                                />
+                                <Divider textAlign="left">Types</Divider>
+                                <FieldArray
+                                    name="types"
+                                    render={arrayHelpers => (
+                                        <Container maxWidth={false}>
+                                            {values.types && values.types.length > 0 ? (
+                                                values.types.map((type, i) => (
+                                                    <Grid
+                                                        sx={{m: 2}} container justifyContent="space-around"
+                                                        key={i}>
+                                                        <Grid item xs={8} md={10} xl={11} >
+                                                            <TextField
+                                                                fullWidth
+                                                                label="Type"
+                                                                name={`types.${i}`}
+
+                                                                value={values.types[i]}
+                                                                onChange={handleChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={4} md={2} lg={2} xl={1} sx={{p: 1}}>
+                                                            <IconButton
+                                                                color="primary"
+                                                                onClick={() => arrayHelpers.insert(i + 1, '')}
+
+                                                            >
+                                                                <AddIcon/>
+                                                            </IconButton>
+                                                            <IconButton
+                                                                type="button"
+                                                                color="error"
+                                                                key={i}
+                                                                onClick={() => arrayHelpers.remove(i)}
+
+                                                            >
+                                                                <ClearIcon/>
+                                                            </IconButton>
+                                                        </Grid>
+                                                    </Grid>
+                                                ))
+                                            ) : null}
+                                        </Container>
+                                    )}
+                                />
+                                <Divider textAlign="left">State or Territory</Divider>
+                                <FieldArray
+                                    name="countries"
+                                    render={arrayHelpers => (
+                                        <Container maxWidth={false}>
+                                            {values.countries && values.countries.length > 0 ? (
+                                                values.countries.map((country, i) => (
+                                                    <Grid
+                                                        sx={{m: 2}} container justifyContent="space-around"
+                                                        key={i}>
+                                                        <Grid item xs={8} md={10} xl={11} >
+                                                            <TextField
+                                                                fullWidth
+                                                                label="State or Territory"
+                                                                name={`countries.${i}`}
+                                                                value={values.countries[i]}
+                                                                onChange={handleChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={4} md={2} lg={2} xl={1} sx={{p: 1}}>
+                                                            <IconButton
+                                                                color="primary"
+                                                                onClick={() => arrayHelpers.insert(i + 1, '')}
+                                                            >
+                                                                <AddIcon/>
+                                                            </IconButton>
+                                                            <IconButton
+                                                                type="button"
+                                                                color="error"
+                                                                key={i}
+                                                                onClick={() => arrayHelpers.remove(i)}
+                                                            >
+                                                                <ClearIcon/>
+                                                            </IconButton>
+                                                        </Grid>
+                                                    </Grid>
+                                                ))
+                                            ) : null}
+                                        </Container>
+                                    )}
+                                />
+                                <Divider textAlign="left">Coordinates</Divider>
+                                <Container maxWidth={false}>
+                                    <Grid
+                                        sx={{m: 2}} container justifyContent="space-around"
+                                    >
+                                        <Grid item xs={6} sx={{pr: 1}}>
+                                            <TextField
+                                                fullWidth
+                                                label="Latitude"
+                                                name="latitude"
+                                                value={values.latitude}
+                                                onChange={handleChange}
+                                                error={
+                                                    Boolean(errors.latitude) &&
+                                                    touched.latitude
+                                                }
+                                                helperText={
+                                                    errors.latitude &&
+                                                    touched.latitude &&
+                                                    errors.latitude
+                                                }
+                                            />
+                                        </Grid>
+                                        <Grid item xs={6} sx={{pl: 1}}>
+                                            <TextField
+                                                fullWidth
+                                                label="Longitude"
+                                                name="longitude"
+
+                                                value={values.latitude}
+                                                onChange={handleChange}
+                                                error={
+                                                    Boolean(errors.longitude) &&
+                                                    touched.longitude
+                                                }
+                                                helperText={
+                                                    errors.longitude &&
+                                                    touched.longitude &&
+                                                    errors.longitude
+                                                }
+                                            />
+                                        </Grid>
+                                    </Grid>
+                                </Container>
+                                <Divider textAlign="left">Certainty</Divider>
+                                <Container sx={{m: 2}} maxWidth={false}>
+                                    <TextField
+                                        fullWidth
+                                        label="Certainty"
+                                        name="certainty"
+
+                                        value={values.certainty}
+                                        onChange={handleChange}
+                                        error={
+                                            Boolean(errors.certainty) &&
+                                            touched.certainty
+                                        }
+                                        helperText={
+                                            errors.certainty &&
+                                            touched.certainty &&
+                                            errors.certainty
+                                        }
+                                    />
+                                </Container>
+                                <Divider textAlign="left">Bibliography</Divider>
+                                <FieldArray
+                                    name="bibliography"
+                                    render={arrayHelpers => (
+                                        <Container maxWidth={false}>
+                                            {values.bibliography && values.bibliography.length > 0 ? (
+                                                values.bibliography.map((b, i) => (
+                                                    <Grid
+                                                        sx={{m: 2}} container justifyContent="space-around"
+                                                        key={i}>
+                                                        <Grid item xs={8} md={10} xl={11}>
+                                                            <TextField
+                                                                fullWidth
+                                                                label="Bibliography"
+                                                                name={`bibliography.${i}`}
+                                                                value={values.bibliography[i]}
+                                                                onChange={handleChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={4} md={2} lg={2} xl={1} sx={{p: 1}}>
+                                                            <IconButton
+                                                                color="primary"
+                                                                onClick={() => arrayHelpers.insert(i + 1, '')}
+                                                            >
+                                                                <AddIcon/>
+                                                            </IconButton>
+                                                            <IconButton
+                                                                type="button"
+                                                                color="error"
+                                                                key={i}
+                                                                onClick={() => arrayHelpers.remove(i)}
+                                                            >
+                                                                <ClearIcon/>
+                                                            </IconButton>
+                                                        </Grid>
+                                                    </Grid>
+                                                ))
+                                            ) : null}
+                                        </Container>
+                                    )}
                                 />
-                            </Grid>
-                            <Grid item xs={4} sx={{ px: 1 }}>
-                                <Typography fontWeight={500}>All Names</Typography>
-                            </Grid>
-                            <Grid item xs={8} sx={{ ml: 'auto' }}>
-                                <FieldArray name="allNames">
-                                    {() => (formik.values.allNames?.map((allName, i) => {
-                                        return (
-                                            <div key={i} className="list-group list-group-flush">
-                                                <div className="list-group-item">
-                                                    <h5 className="card-title">Ticket {i + 1}</h5>
-                                                    <div className="form-row">
-                                                        <div className="form-group col-6">
-                                                            <label>Name</label>
-                                                            <Field name={`tickets.${i}.name`} type="text" />
-                                                            <ErrorMessage name={`tickets.${i}.name`} component="div" />
-                                                        </div>
-                                                        <div className="form-group col-6">
-                                                            <label>Email</label>
-                                                            <Field name={`tickets.${i}.email`} type="text" />
-                                                            <ErrorMessage name={`tickets.${i}.email`} component="div" />
-                                                        </div>
-                                                    </div>
-                                                </div>
-                                            </div>
-                                        );
-                                    }))}
-                                </FieldArray>
-
-                            </Grid>
-                        </Grid>
-                    </form>
+                                <Divider textAlign="left">Description</Divider>
+                                <Container sx={{m: 2}} maxWidth={false}>
+                                    <TextField
+                                        fullWidth
+                                        label="Description"
+                                        name="description"
+                                        multiline
+                                        rows={3}
+                                        value={values.description}
+                                        onChange={handleChange}
+                                    />
+                                </Container>
+                                <Container
+                                    maxWidth={false}
+                                    style={{
+                                        display: 'flex',
+                                        justifyContent: 'flex-end',
+                                        alignItems: 'flex-end'
+                                    }}
+                                >
+                                    <Button
+                                        sx={{ m: 2}}
+                                        startIcon={<SaveIcon />}
+                                        variant="contained"
+                                        color="primary"
+                                        type="submit"
+                                        disabled={loading}
+                                    >
+                                        Save
+                                    </Button>
+                                </Container>
+                            </Form>
+                        )}
+                    />
                 ) : null}
             </Paper>
         </Fragment>
@@ -316,7 +513,10 @@ const EditCatalogItem: FunctionComponent<CatalogItemEditProps> = ({
 
 export const RoutedCatalogItemEdit = () => {
     const { itemId } = useParams()
-    return <EditCatalogItem itemId={itemId ?? ''} />
+    return <EditCatalogItem
+        itemId={itemId ?? ''}
+        showReturnToDetailButton
+    />
 }
 
 export const DialogCatalogItemEdit: FunctionComponent<
diff --git a/frontend/src/features/Catalog/catalogItemSlice.ts b/frontend/src/features/Catalog/catalogItemSlice.ts
index 8aaf545..0330a4b 100644
--- a/frontend/src/features/Catalog/catalogItemSlice.ts
+++ b/frontend/src/features/Catalog/catalogItemSlice.ts
@@ -3,6 +3,7 @@ import { CatalogItemDto } from '../../swagger/data-contracts'
 import {updateCatalogItem, updateCatalogItemsBulk} from "./catalogItemThunks"
 
 export interface CatalogItemState {
+    isRequestCompleted: boolean
     loading: boolean // whether the catalog is loading
     error?: string
 }
@@ -16,6 +17,7 @@ export interface UpdateCatalogItemsBulk {
 }
 
 const initialState: CatalogItemState = {
+    isRequestCompleted: false,
     loading: false,
     error: undefined
 }
@@ -26,6 +28,7 @@ const catalogItemSlice = createSlice({
     reducers: {
         clear: (state: CatalogItemState) => ({ ...initialState }),
         setLoading: (state: CatalogItemState) => ({ ...state, loading: true }),
+        resetRequestCompleted: (state: CatalogItemState) => ({ ...state, isRequestCompleted: false }),
         resetLoading: (state: CatalogItemState) => ({ ...state, loading: false }),
         consumeError: (state: CatalogItemState) => ({ ...state, error: undefined }),
     },
@@ -37,6 +40,7 @@ const catalogItemSlice = createSlice({
         builder.addCase(updateCatalogItem.fulfilled, (state: CatalogItemState, action: any) => ({
             ...state,
             loading: false,
+            isRequestCompleted: true
         }))
         builder.addCase(updateCatalogItem.rejected, (state: CatalogItemState, action: any) => ({
             ...state,
@@ -50,6 +54,7 @@ const catalogItemSlice = createSlice({
         builder.addCase(updateCatalogItemsBulk.fulfilled, (state: CatalogItemState, action: any) => ({
             ...state,
             loading: false,
+            isRequestCompleted: true
         }))
         builder.addCase(updateCatalogItemsBulk.rejected, (state: CatalogItemState, action: any) => ({
             ...state,
@@ -64,6 +69,7 @@ export const {
     setLoading,
     resetLoading,
     consumeError,
+    resetRequestCompleted,
 } = catalogItemSlice.actions
-const reducer = catalogItemSlice.reducer
-export default reducer
+const catalogItemReducer = catalogItemSlice.reducer
+export default catalogItemReducer
diff --git a/frontend/src/features/Catalog/catalogItemThunks.tsx b/frontend/src/features/Catalog/catalogItemThunks.tsx
index 10df9f4..167cae7 100644
--- a/frontend/src/features/Catalog/catalogItemThunks.tsx
+++ b/frontend/src/features/Catalog/catalogItemThunks.tsx
@@ -18,7 +18,7 @@ export const updateCatalogItem = createAsyncThunk(
 
             // If the request was successful return the items
             if (status === 200) {
-                return data
+                return status
             }
 
             return Promise.reject(apiError)
diff --git a/frontend/src/features/redux/store.ts b/frontend/src/features/redux/store.ts
index 9dd6113..57e32e0 100644
--- a/frontend/src/features/redux/store.ts
+++ b/frontend/src/features/redux/store.ts
@@ -10,6 +10,7 @@ import trackingToolReducer from '../TrackingTool/trackingToolSlice'
 import usersDetailReducer from '../Administration/userDetailSlice'
 import { enableMapSet } from 'immer'
 import navigationReducer from '../Navigation/navigationSlice'
+import catalogItemReducer from "../Catalog/catalogItemSlice"
 
 enableMapSet()
 
@@ -24,6 +25,7 @@ const store = createStore(
         notification: notificationReducer,
         trackingTool: trackingToolReducer,
         usersDetail: usersDetailReducer,
+        catalogItem: catalogItemReducer,
         navigation: navigationReducer,
     }),
     process.env.REACT_APP_DEV_ENV === 'true'
-- 
GitLab