commit 394f2d16c393b4371d877144fcc27fe279842da3
Author: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date:   Thu Apr 21 18:02:11 2022 +0200

    catalog slice
    
    re #9545

diff --git a/frontend/.env b/frontend/.env
index ae3abf8..d6e54cd 100644
--- a/frontend/.env
+++ b/frontend/.env
@@ -1 +1,2 @@
-REACT_APP_API_BASE_URL=/api
\ No newline at end of file
+REACT_APP_API_BASE_URL=/api
+REACT_APP_DEV_ENV=true
\ No newline at end of file
diff --git a/frontend/src/config/conf.ts b/frontend/src/config/conf.ts
index 7c592a3..04125db 100644
--- a/frontend/src/config/conf.ts
+++ b/frontend/src/config/conf.ts
@@ -1,5 +1,8 @@
 const conf = {
-    baseUrl: '/api'
+    baseUrl:
+        process.env.REACT_APP_DEV_ENV === 'true'
+            ? 'http://localhost:8080'
+            : '/api',
 }
 
-export default conf;
\ No newline at end of file
+export default conf
diff --git a/frontend/src/features/Catalog/Catalog.tsx b/frontend/src/features/Catalog/Catalog.tsx
index eac9af3..c3b9e5a 100644
--- a/frontend/src/features/Catalog/Catalog.tsx
+++ b/frontend/src/features/Catalog/Catalog.tsx
@@ -49,11 +49,6 @@ const Catalog = () => {
                                             id="type"
                                             label="Type"
                                         />
-                                        <TextField
-                                            size="small"
-                                            id="coordinates"
-                                            label="Coordinates"
-                                        />
                                     </Stack>
                                     <Stack direction="row" spacing={2}>
                                         <TextField
diff --git a/frontend/src/features/Catalog/CatalogTable.tsx b/frontend/src/features/Catalog/CatalogTable.tsx
index d1318bf..098cb78 100644
--- a/frontend/src/features/Catalog/CatalogTable.tsx
+++ b/frontend/src/features/Catalog/CatalogTable.tsx
@@ -13,10 +13,10 @@ import { Link as RouterLink } from 'react-router-dom'
 import { CatalogItemDto } from '../../swagger/data-contracts'
 import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
 import ContentLoading from '../Reusables/ContentLoading'
-import axiosInstance from '../../api/api'
-
-const apiError =
-    'Error while fetching data from the server, please try again later.'
+import { RootState } from '../redux/store'
+import { useDispatch, useSelector } from 'react-redux'
+import { consumeError, setLoading } from './catalogSlice'
+import { fetchItems } from './catalogThunks'
 
 // Catalog table component
 const CatalogTable = () => {
@@ -28,9 +28,12 @@ const CatalogTable = () => {
         rowsPerPage[0]
     )
 
-    const [items, setItems] = useState<CatalogItemDto[]>([])
-    const [areItemsLoading, setAreItemsLoading] = useState(true)
-    const [err, setErr] = useState<string | undefined>(undefined)
+    // Subscribe to the store
+    const items = useSelector((state: RootState) => state.catalog.items)
+    const loading = useSelector((state: RootState) => state.catalog.loading)
+    const apiError = useSelector((state: RootState) => state.catalog.error)
+
+    const [displayError, setDisplayError] = useState<string | undefined>(undefined)
 
     // When changing rows per page set the selected number and reset to the first page
     const onRowsPerPageChange = (
@@ -40,28 +43,27 @@ const CatalogTable = () => {
         setPage(0)
     }
 
-    // Use effect hook to fetch rows from the server
+    const dispatch = useDispatch()
+
     useEffect(() => {
-        // Function to fetch items from the API
-        const fetchItems = async () => {
-            try {
-                const { data, status } = await axiosInstance.get(
-                    '/catalog-items'
-                )
-                if (status !== 200) {
-                    setErr(apiError)
-                    return
-                }
+        // Fetch items when the component is mounted
+        // This will automatically search whenever the filter changes
+        dispatch(fetchItems())
 
-                setItems(data)
-                setAreItemsLoading(false)
-            } catch (err: any) {
-                setErr(apiError)
-            }
+        return () => {
+            // Invalidate the state when unmounting so that the old list is not rerendered when the user returns to the page
+            dispatch(setLoading())
+        }
+    }, [dispatch])
+
+    // Use effect to read the error and consume it
+    useEffect(() => {
+        if (apiError) {
+            setDisplayError(apiError)
+            dispatch(consumeError())
         }
+    }, [apiError, dispatch])
 
-        fetchItems()
-    }, [])
 
     // Name of columns in the header
     const columns = [
@@ -107,10 +109,10 @@ const CatalogTable = () => {
 
     return (
         <Fragment>
-            <ShowErrorIfPresent err={err} />
-            {areItemsLoading && !err ? <ContentLoading /> : null}
-            {!areItemsLoading && !err ? (
-                <Fragment>
+            <ShowErrorIfPresent err={displayError} />
+            {loading && !displayError ? <ContentLoading /> : null}
+            {!loading && !displayError ? (
+                 <Fragment>
                     <TableContainer>
                         <Table
                             stickyHeader
diff --git a/frontend/src/features/Catalog/catalogSlice.tsx b/frontend/src/features/Catalog/catalogSlice.tsx
new file mode 100644
index 0000000..7a0159c
--- /dev/null
+++ b/frontend/src/features/Catalog/catalogSlice.tsx
@@ -0,0 +1,62 @@
+import { createSlice } from '@reduxjs/toolkit'
+import { CatalogItemDto } from '../../swagger/data-contracts'
+import { fetchItems } from './catalogThunks'
+
+export interface CatalogFilter {
+    name?: string
+    type?: string
+    country?: string
+}
+
+export interface CatalogState {
+    items: CatalogItemDto[] // list of all fetched items
+    filter: CatalogFilter // filter object
+    loading: boolean // whether the catalog is loading
+    error?: string
+}
+
+const initialState: CatalogState = {
+    items: [],
+    filter: {},
+    loading: true,
+    error: undefined,
+}
+
+const catalogSlice = createSlice({
+    name: 'catalog',
+    initialState,
+    reducers: {
+        setCatalogFilter: (state, action) => ({
+            ...state,
+            catalogFilter: action.payload.catalogFilter,
+        }),
+        clearFilter: (state, action) => ({
+            ...state,
+            loading: true,
+            catalogFilter: {},
+        }),
+        clear: (state) => ({ ...initialState }),
+        setLoading: (state) => ({ ...state, loading: true }),
+        consumeError: (state) => ({ ...state, error: undefined }),
+    },
+    extraReducers: (builder) => {
+        builder.addCase(fetchItems.pending, (state) => ({
+            ...state,
+            loading: true,
+        }))
+        builder.addCase(fetchItems.fulfilled, (state, action) => ({
+            ...state,
+            items: action.payload,
+            loading: false,
+        }))
+        builder.addCase(fetchItems.rejected, (state, action) => ({
+            ...state,
+            loading: false,
+            error: action.payload as string,
+        }))
+    },
+})
+
+export const { setCatalogFilter, clearFilter, clear, setLoading, consumeError } = catalogSlice.actions
+const reducer = catalogSlice.reducer
+export default reducer
diff --git a/frontend/src/features/Catalog/catalogThunks.tsx b/frontend/src/features/Catalog/catalogThunks.tsx
new file mode 100644
index 0000000..7e2e9ef
--- /dev/null
+++ b/frontend/src/features/Catalog/catalogThunks.tsx
@@ -0,0 +1,40 @@
+import { createAsyncThunk } from '@reduxjs/toolkit'
+import axiosInstance from '../../api/api'
+import { CatalogFilter, CatalogState } from './catalogSlice'
+
+const apiError = 'Error, server is currently unavailable.'
+
+// Builds query string from the filter object
+const buildQuery = (catalogFilter: CatalogFilter): string => {
+    return Object.entries(catalogFilter).length === 0
+        ? ''
+        : `?${Object.entries(catalogFilter)
+              .map(([key, value]) => `${key}=${value}`)
+              .join('&')}`
+}
+
+// Thunk to fetch catalog items from the API
+export const fetchItems = createAsyncThunk(
+    'catalog/fetchItems',
+    async (dispatch, { getState }) => {
+        try {
+            // To make typescript happy we fool it like this
+            const { catalog } = getState() as { catalog: CatalogState }
+
+            
+            // Send request with the filter
+            const { data, status } = await axiosInstance.get(
+                `/catalog-items${buildQuery(catalog.filter)}`
+            )
+
+            // If the request was successful return the items
+            if (status === 200) {
+                return data
+            }
+
+            return Promise.reject(apiError)
+        } catch (err: any) {
+            return Promise.reject(apiError)
+        }
+    }
+)
diff --git a/frontend/src/features/redux/store.ts b/frontend/src/features/redux/store.ts
index 945ae90..c759e31 100644
--- a/frontend/src/features/redux/store.ts
+++ b/frontend/src/features/redux/store.ts
@@ -4,11 +4,12 @@ import { persistStore } from 'redux-persist'
 import thunk from 'redux-thunk'
 import userReducer from '../Auth/userSlice'
 import themeReducer from '../Theme/themeReducer'
+import catalogReducer from '../Catalog/catalogSlice'
 
 
 // Store holds shared state in the application
 const store = createStore(
-    combineReducers({ user: userReducer, theme: themeReducer }),
+    combineReducers({ user: userReducer, theme: themeReducer, catalog: catalogReducer }),
     applyMiddleware(thunk) // Thunk middleware so we can async fetch data from the api
 )
 
diff --git a/frontend/src/swagger/CatalogItems.ts b/frontend/src/swagger/CatalogItems.ts
index cfd3c2e..1b0cb07 100644
--- a/frontend/src/swagger/CatalogItems.ts
+++ b/frontend/src/swagger/CatalogItems.ts
@@ -25,7 +25,6 @@ export class CatalogItems<SecurityDataType = unknown> extends HttpClient<Securit
     this.request<CatalogItemDto, any>({
       path: `/catalog-items/${id}`,
       method: "GET",
-      format: "json",
       ...params,
     });
   /**
diff --git a/frontend/src/swagger/Path.ts b/frontend/src/swagger/Path.ts
new file mode 100644
index 0000000..945e865
--- /dev/null
+++ b/frontend/src/swagger/Path.ts
@@ -0,0 +1,30 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { PathDto } from "./data-contracts";
+import { HttpClient, RequestParams } from "./http-client";
+
+export class Path<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags path-controller
+   * @name GetPath
+   * @request GET:/path
+   */
+  getPath = (query: { pathDto: PathDto }, params: RequestParams = {}) =>
+    this.request<PathDto, any>({
+      path: `/path`,
+      method: "GET",
+      query: query,
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/data-contracts.ts b/frontend/src/swagger/data-contracts.ts
index c8808aa..07a88c8 100644
--- a/frontend/src/swagger/data-contracts.ts
+++ b/frontend/src/swagger/data-contracts.ts
@@ -13,20 +13,21 @@ export interface CatalogItemDto {
   /** @format uuid */
   id?: string;
   name?: string;
-
-  /** @format int32 */
-  certainty?: number;
+  alternativeNames?: string[];
+  writtenForms?: string[];
+  types?: string[];
+  countries?: string[];
+  bibliography?: string[];
 
   /** @format double */
   longitude?: number;
 
   /** @format double */
   latitude?: number;
-  bibliography?: string[];
-  countries?: string[];
-  writtenForms?: string[];
-  alternativeNames?: string[];
-  types?: string[];
+
+  /** @format int32 */
+  certainty?: number;
+  description?: string;
 }
 
 export interface PasswordDto {
@@ -51,3 +52,8 @@ export interface TitlePageDto {
   title?: string;
   content?: string;
 }
+
+export interface PathDto {
+  text?: string;
+  foundCatalogItems?: CatalogItemDto[][];
+}
diff --git a/frontend/src/swagger/http-client.ts b/frontend/src/swagger/http-client.ts
index e994ab9..ab33905 100644
--- a/frontend/src/swagger/http-client.ts
+++ b/frontend/src/swagger/http-client.ts
@@ -54,7 +54,7 @@ export enum ContentType {
 }
 
 export class HttpClient<SecurityDataType = unknown> {
-  public baseUrl: string = "/api";
+  public baseUrl: string = "http://localhost:8080";
   private securityData: SecurityDataType | null = null;
   private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
   private abortControllers = new Map<CancelToken, AbortController>();
