Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 4a2ac9a8

Přidáno uživatelem Václav Honzík před více než 2 roky(ů)

re #9369 item + catalog refactor

Zobrazit rozdíly:

frontend/src/features/Catalog/CatalogItemDetail.tsx
7 7
    Stack,
8 8
    Typography,
9 9
} from '@mui/material'
10
import { Fragment, useEffect } from 'react'
11
import { useDispatch, useSelector } from 'react-redux'
10
import { Fragment, useEffect, useState } from 'react'
12 11
import { useParams } from 'react-router-dom'
13
import { RootState } from '../redux/store'
14
import ConsumeLastError from '../Reusables/ConsumeLastError'
12
import axiosInstance from '../../api/api'
13
import { CatalogItemDto } from '../../swagger/data-contracts'
14
import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
15 15
import ContentLoading from '../Reusables/ContentLoading'
16
import { consumeCatalogItemLastErr, setItemLoading } from './catalogSlice'
17
import { getCatalogItem } from './catalogThunks'
16

  
17
const apiError =
18
    'Error while fetching data from the server, please try again later.'
18 19

  
19 20
const CatalogItemDetail = () => {
20 21
    // itemId from query params
21 22
    const { itemId } = useParams()
22 23

  
23
    // Loaded item
24
    const item = useSelector((state: RootState) => state.catalog.catalogItem)
25

  
26
    // Whether the item is loading
27
    const isItemLoading = useSelector(
28
        (state: RootState) => state.catalog.isItemLoading
29
    )
30

  
31
    // Redux dispatch
32
    const dispatch = useDispatch()
33

  
34
    const lastErr = useSelector(
35
        (state: RootState) => state.catalog.catalogItemLastErr
36
    )
24
    const [item, setItem] = useState<CatalogItemDto | undefined>(undefined)
25
    const [isItemLoading, setIsItemLoading] = useState(true)
26
    const [err, setErr] = useState<string | undefined>(undefined)
37 27

  
38 28
    // Fetch the item from the api after mounting the component
39 29
    useEffect(() => {
40
        dispatch(setItemLoading(true))
41
        dispatch(getCatalogItem(itemId as string))
42
    }, [dispatch, itemId])
30
        // Function to fetch the item from the api
31
        const fetchItem = async () => {
32
            try {
33
                const { data, status } = await axiosInstance.get(
34
                    `/catalog-items/${itemId}`
35
                )
36
                if (status !== 200) {
37
                    setErr(apiError)
38
                    return
39
                }
40

  
41
                setItem(data)
42
                setIsItemLoading(false)
43
            } catch (err: any) {
44
                setErr(apiError)
45
            }
46
        }
47

  
48
        fetchItem()
49
    }, [])
43 50

  
44 51
    // Maps catalogItem property to corresponding table row
45 52
    const mapToRow = (rowName: string, items: string[]) => (
......
96 103
    return (
97 104
        // TODO remove min height
98 105
        <Paper style={{ minHeight: '100vh', borderRadius: 0 }} elevation={2}>
99
            <ConsumeLastError
100
                consumeFn={consumeCatalogItemLastErr}
101
                err={lastErr}
102
            />
106
            <ShowErrorIfPresent err={err} />
103 107

  
104
            {isItemLoading ? <ContentLoading /> : null}
108
            {isItemLoading && !err ? <ContentLoading /> : null}
105 109
            {!isItemLoading && item ? (
106 110
                <Grid container justifyContent="space-around">
107 111
                    <Grid item xs={6} sx={{ px: 2 }}>
frontend/src/features/Catalog/CatalogTable.tsx
9 9
    TableRow,
10 10
} from '@mui/material'
11 11
import { Fragment, useEffect, useState } from 'react'
12
import { useDispatch, useSelector } from 'react-redux'
13 12
import { Link as RouterLink } from 'react-router-dom'
14 13
import { CatalogItemDto } from '../../swagger/data-contracts'
15
import { RootState } from '../redux/store'
16
import ConsumeLastError from '../Reusables/ConsumeLastError'
14
import ShowErrorIfPresent from '../Reusables/ShowErrorIfPresent'
17 15
import ContentLoading from '../Reusables/ContentLoading'
18
import { consumeCatalogLastErr } from './catalogSlice'
19
import { getCatalogItems } from './catalogThunks'
16
import axiosInstance from '../../api/api'
17

  
18
const apiError =
19
    'Error while fetching data from the server, please try again later.'
20 20

  
21 21
// Catalog table component
22 22
const CatalogTable = () => {
......
28 28
        rowsPerPage[0]
29 29
    )
30 30

  
31
    // Fetched items from the store
32
    const items = useSelector((state: RootState) => state.catalog.catalogItems)
33
    const areItemsLoading = useSelector(
34
        (state: RootState) => state.catalog.isListLoading
35
    )
36
    const fetchError = useSelector(
37
        (state: RootState) => state.catalog.catalogLastErr
38
    )
39

  
40
    // Redux dispatch
41
    const dispatch = useDispatch()
31
    const [items, setItems] = useState<CatalogItemDto[]>([])
32
    const [areItemsLoading, setAreItemsLoading] = useState(true)
33
    const [err, setErr] = useState<string | undefined>(undefined)
42 34

  
43 35
    // When changing rows per page set the selected number and reset to the first page
44 36
    const onRowsPerPageChange = (
......
50 42

  
51 43
    // Use effect hook to fetch rows from the server
52 44
    useEffect(() => {
53
        dispatch(getCatalogItems())
54
    }, [dispatch])
45
        // Function to fetch items from the API
46
        const fetchItems = async () => {
47
            try {
48
                const { data, status } = await axiosInstance.get(
49
                    '/catalog-items'
50
                )
51
                if (status !== 200) {
52
                    setErr(apiError)
53
                    return
54
                }
55

  
56
                setItems(data)
57
                setAreItemsLoading(false)
58
            } catch (err: any) {
59
                setErr(apiError)
60
            }
61
        }
62

  
63
        fetchItems()
64
    }, [])
55 65

  
56 66
    // Name of columns in the header
57 67
    const columns = [
......
73 83
        <Fragment>
74 84
            {/* {mapValueOrDefault(item.name)} */}
75 85
            <TableCell align="center">
76
                <Link component={RouterLink} to={`/catalog/${item.id as string}`}>{item.name}</Link>
86
                <Link
87
                    component={RouterLink}
88
                    to={`/catalog/${item.id as string}`}
89
                >
90
                    {item.name}
91
                </Link>
77 92
            </TableCell>
78 93
            {mapValueOrDefault(item.alternativeNames?.join(', '))}
79 94
            {mapValueOrDefault(item.writtenForms?.join(', '))}
......
92 107

  
93 108
    return (
94 109
        <Fragment>
95
            <ConsumeLastError
96
                err={fetchError}
97
                consumeFn={consumeCatalogLastErr}
98
            />
99
            {areItemsLoading && !fetchError ? <ContentLoading /> : null}
100
            {!areItemsLoading && !fetchError ? (
110
            <ShowErrorIfPresent err={err} />
111
            {areItemsLoading && !err ? <ContentLoading /> : null}
112
            {!areItemsLoading && !err ? (
101 113
                <Fragment>
102 114
                    <TableContainer>
103 115
                        <Table
frontend/src/features/Catalog/catalogSlice.ts
1
import { Action, createSlice } from '@reduxjs/toolkit'
2
import { CatalogItemDto } from '../../swagger/data-contracts'
3
import { getCatalogItem, getCatalogItems } from './catalogThunks'
4

  
5
export interface CatalogState {
6
    catalogItems: CatalogItemDto[] // Items shown in the table
7
    isListLoading: boolean
8
    catalogLastErr?: string // Error while fetching all catalog items
9
    catalogItem?: CatalogItemDto // Item shown in detail
10
    isItemLoading: boolean
11
    catalogItemLastErr?: string // Error while fetching catalog item
12
}
13

  
14
const initialState: CatalogState = {
15
    catalogItems: [],
16
    isListLoading: true,
17
    isItemLoading: true,
18
}
19

  
20
export const catalogSlice = createSlice({
21
    name: 'catalog',
22
    initialState,
23
    reducers: {
24
        setItemListLoading: (state, action) => ({
25
            ...state,
26
            isListLoading: action.payload,
27
        }),
28
        consumeCatalogLastErr: (state) => ({
29
            ...state,
30
            catalogLastErr: undefined,
31
        }),
32
        setItemLoading: (state, action) => ({
33
            ...state,
34
            isItemLoading: action.payload,
35
        }),
36
        consumeCatalogItemLastErr: (state) => ({
37
            ...state,
38
            catalogItemLastErr: undefined,
39
        }),
40
    },
41

  
42
    extraReducers: (builder) => {
43
        builder.addCase(getCatalogItems.fulfilled, (state, action) => ({
44
            ...state,
45
            catalogItems: action.payload,
46
            isListLoading: false,
47
        }))
48
        builder.addCase(getCatalogItems.rejected, (state, action) => ({
49
            ...state,
50
            lastErr: action.payload as string,
51
        }))
52
        builder.addCase(getCatalogItem.fulfilled, (state, action) => ({
53
            ...state,
54
            catalogItem: action.payload,
55
            isItemLoading: false,
56
        }))
57
        builder.addCase(getCatalogItem.rejected, (state, action) => ({
58
            ...state,
59
            lastErr: action.payload as string,
60
        }))
61
    },
62
})
63

  
64
export const {
65
    setItemListLoading,
66
    setItemLoading,
67
    consumeCatalogItemLastErr,
68
    consumeCatalogLastErr,
69
} = catalogSlice.actions
70

  
71
export const catalogReducer = catalogSlice.reducer
frontend/src/features/Catalog/catalogThunks.ts
1
import { createAsyncThunk } from '@reduxjs/toolkit'
2
import axiosInstance from '../../api/api'
3

  
4
const apiError =
5
    'Error while fetching data from the server, please try again later.'
6

  
7
export const getCatalogItems = createAsyncThunk(
8
    'catalog/getCatalogItems',
9
    async () => {
10
        try {
11
            const { data, status } = await axiosInstance.get('/catalog-items')
12
            if (status !== 200) {
13
                return Promise.reject(apiError)
14
            }
15
            return data
16
        } catch (err: any) {
17
            return Promise.reject(apiError)
18
        }
19
    }
20
)
21

  
22
export const getCatalogItem = createAsyncThunk(
23
    'catalog/getCatalogItem',
24
    async (id: string) => {
25
        try {
26
            const { data, status } = await axiosInstance.get(
27
                `/catalog-items/${id}`
28
            )
29
            if (status !== 200) {
30
                return Promise.reject(apiError)
31
            }
32
            return data
33
        } catch (err: any) {
34
            return Promise.reject(apiError)
35
        }
36
    }
37
)
frontend/src/features/Home/Home.tsx
1 1
import { Button } from '@mui/material'
2 2
import { useDispatch, useSelector } from 'react-redux'
3 3
import { logout } from '../Auth/userSlice'
4
import CatalogItemDetail from '../Catalog/CatalogItemDetail'
5 4
import { RootState } from '../redux/store'
6 5

  
7 6
const Home = () => {
......
24 23
                    Logout
25 24
                </Button>
26 25
            ) : null}
27
            <CatalogItemDetail />
28 26
        </>
29 27
    )
30 28
}
frontend/src/features/Reusables/ConsumeLastError.tsx
1
import { Typography } from '@mui/material'
2
import { Fragment, FunctionComponent, useEffect, useState } from 'react'
3
import { useDispatch } from 'react-redux'
4

  
5
export interface ConsumeLastErrorProps {
6
    // Observable error from the store
7
    err?: string
8
    // Consume function that clears the error in the store
9
    consumeFn: () => void
10
}
11

  
12
// Utility component that consumes the last error from the store and shows it
13
const ConsumeLastError: FunctionComponent<ConsumeLastErrorProps> = ({
14
    err: error,
15
    consumeFn,
16
}) => {
17
    const dispatch = useDispatch()
18
    const [err, setErr] = useState('')
19

  
20
    useEffect(() => {
21
        if (error) {
22
            setErr(`${error}`)
23
            dispatch(consumeFn())
24
        }
25
    }, [consumeFn, dispatch, error])
26

  
27
    return <Fragment>
28
        {err ? <Typography variant="h6" fontWeight="400">{err}</Typography> : null}
29
    </Fragment>
30
}
31

  
32
export default ConsumeLastError
frontend/src/features/Reusables/ShowErrorIfPresent.tsx
1
import { Typography } from '@mui/material'
2
import { Fragment, FunctionComponent } from 'react'
3

  
4
export interface ShowErrorProps {
5
    err?: string
6
}
7

  
8
// Utility component for showing error messages
9
const ShowErrorIfPresent: FunctionComponent<ShowErrorProps> = ({ err }) => (
10
    <Fragment>
11
        {err ? (
12
            <Typography align="center" variant="h6" fontWeight="400">
13
                {err}
14
            </Typography>
15
        ) : null}
16
    </Fragment>
17
)
18

  
19
export default ShowErrorIfPresent
frontend/src/features/redux/store.ts
3 3
import { persistStore } from 'redux-persist'
4 4
import thunk from 'redux-thunk'
5 5
import userReducer from '../Auth/userSlice'
6
import { catalogReducer } from '../Catalog/catalogSlice'
7 6
import themeReducer from '../Theme/themeReducer'
8 7

  
9 8

  
10 9
// Store holds shared state in the application
11 10
const store = createStore(
12
    combineReducers({ user: userReducer, theme: themeReducer, catalog: catalogReducer }),
11
    combineReducers({ user: userReducer, theme: themeReducer }),
13 12
    applyMiddleware(thunk) // Thunk middleware so we can async fetch data from the api
14 13
)
15 14

  

Také k dispozici: Unified diff