Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 0d90d67b

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

map dialogs + catalog filter fix

re #9547 #9545

Zobrazit rozdíly:

frontend/src/App.tsx
11 11
import CatalogItemDetail from './features/Catalog/CatalogItemDetail'
12 12
import Navigation from './features/Navigation/Navigation'
13 13
import TrackingTool from './features/TrackingTool/TrackingTool'
14
import Logout from './features/Auth/Logout'
15
import ThemeWrapper from './features/Theme/ThemeWrapper'
14 16

  
15 17
const App = () => {
16
    const theme: Theme = useSelector((state: RootState) => state.theme.theme)
17 18

  
18 19
    return (
19
        <ThemeProvider theme={theme}>
20
        <ThemeWrapper>
20 21
            <Navigation>
21 22
                <Container>
22 23
                    <Routes>
......
27 28
                            element={<CatalogItemDetail />}
28 29
                        />
29 30
                        <Route path="/login" element={<Login />} />
31
                        <Route path="/logout" element={<Logout />} />
30 32
                        <Route path="/map" element={<TrackingTool />} />
31 33
                        <Route path="*" element={<NotFound />} />
32 34
                    </Routes>
33 35
                </Container>
34 36
            </Navigation>
35
        </ThemeProvider>
37
        </ThemeWrapper>
36 38
    )
37 39
}
38 40

  
frontend/src/features/Auth/Login.tsx
50 50
    return (
51 51
        <Fragment>
52 52
            <Typography variant="h3">Login</Typography>
53
            <p>Credentials = admin:password</p>
54 53

  
55 54
            <form onSubmit={formik.handleSubmit}>
56 55
                <TextField
frontend/src/features/Auth/Logout.tsx
1
import { useEffect } from 'react'
2
import { useDispatch, useSelector } from 'react-redux'
3
import { Navigate } from 'react-router-dom'
4
import { RootState } from '../redux/store'
5
import { logout } from './userSlice'
6

  
7
// Component that logs the user out if they are logged in
8
const Logout = () => {
9
    const userLoggedIn = useSelector(
10
        (state: RootState) => state.user.isLoggedIn
11
    )
12

  
13
    const dispatch = useDispatch()
14
    
15
    // Check whether the user is logged in and if so log them out
16
    useEffect(() => {
17
        if (userLoggedIn) {
18
            dispatch(logout())
19
        }
20
    }, [dispatch, userLoggedIn])
21

  
22
    return <Navigate to="/" />
23
}
24

  
25
export default Logout
frontend/src/features/Auth/userSlice.ts
52 52
            return ({ ...state, ...action.payload })
53 53
        })
54 54
        builder.addCase(logIn.rejected, (state, action) => {
55
            if (action.payload && typeof action.payload === 'string') {
56
                return ({ ...state, lastErr: action.payload })
55
            if (action.payload && typeof action.error.message === 'string') {
56
                return ({ ...state, lastErr: action.error.message })
57 57
            }
58 58
        })
59 59
    },
frontend/src/features/Catalog/CatalogFilter.tsx
6 6
    Stack,
7 7
    TextField,
8 8
} from '@mui/material'
9
import { Fragment } from 'react'
9
import { Fragment, useEffect } from 'react'
10 10
import { useDispatch, useSelector } from 'react-redux'
11 11
import { setFilter, setFilterOpen } from './catalogSlice'
12 12
import { fetchItems } from './catalogThunks'
......
29 29
        dispatch(fetchItems())
30 30
    }
31 31

  
32
    useEffect(() => {
33
        console.log(filter)
34
    }, [filter])
35

  
32 36
    return (
33 37
        <Fragment>
34 38
            <Button
......
51 55
                                    id="name"
52 56
                                    label="Name"
53 57
                                    onChange={(e: any) => {
54
                                        filter.name = e.target.value
55
                                        dispatch(setFilter(filter))
58
                                        dispatch(setFilter({
59
                                            ...filter,
60
                                            name: e.target.value
61
                                        }))
56 62
                                    }}
57 63
                                    value={filter.name}
58 64
                                />
......
61 67
                                    id="type"
62 68
                                    label="Type"
63 69
                                    onChange={(e: any) => {
64
                                        filter.type = e.target.value
65
                                        dispatch(setFilter(filter))
70
                                        dispatch(setFilter({
71
                                            ...filter,
72
                                            type: e.target.value
73
                                        }))
66 74
                                    }}
67 75
                                    value={filter.type}
68 76
                                />
......
78 86
                                    id="stateOrTerritory"
79 87
                                    label="State or territory"
80 88
                                    onChange={(e: any) => {
81
                                        filter.country = e.target.value
82
                                        dispatch(setFilter(filter))
89
                                        dispatch(setFilter({
90
                                            ...filter,
91
                                            country: e.target.value
92
                                        }))
83 93
                                    }}
84 94
                                    value={filter.country}
85 95
                                />
frontend/src/features/Catalog/catalogSlice.tsx
68 68
        builder.addCase(fetchItems.rejected, (state, action) => ({
69 69
            ...state,
70 70
            loading: false,
71
            error: action.payload as string,
71
            error: action.error.message as string,
72 72
        }))
73 73
    },
74 74
})
frontend/src/features/Home/Home.tsx
5 5
import { RootState } from '../redux/store'
6 6

  
7 7
const Home = () => {
8
    const dispatch = useDispatch()
9

  
10
    const userLoggedIn = useSelector(
11
        (state: RootState) => state.user.isLoggedIn
12
    )
13

  
14 8
    return (
15 9
        <Fragment>
16 10
            <h1>Home</h1>
17
            {userLoggedIn ? (
18
                <Button
19
                    size="large"
20
                    variant="contained"
21
                    color="primary"
22
                    onClick={() => dispatch(logout())}
23
                >
24
                    Logout
25
                </Button>
26
            ) : null}
27 11
        </Fragment>
28 12
    )
29 13
}
frontend/src/features/Navigation/Navigation.tsx
5 5
import CssBaseline from '@mui/material/CssBaseline'
6 6
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'
7 7
import Toolbar from '@mui/material/Toolbar'
8
import List from '@mui/material/List'
9 8
import Typography from '@mui/material/Typography'
10
import Divider from '@mui/material/Divider'
11 9
import IconButton from '@mui/material/IconButton'
12 10
import MenuIcon from '@mui/icons-material/Menu'
13
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
14
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
15
import ListItem from '@mui/material/ListItem'
16
import ListItemIcon from '@mui/material/ListItemIcon'
17
import ListItemText from '@mui/material/ListItemText'
18
import InboxIcon from '@mui/icons-material/MoveToInbox'
19
import MailIcon from '@mui/icons-material/Mail'
20 11
import { Fragment, FunctionComponent } from 'react'
21 12
import NavigationMenu from './NavigationMenu'
22
import { Paper } from '@mui/material'
13
import { Paper, Stack } from '@mui/material'
14
import { useDispatch, useSelector } from 'react-redux'
15
import { toggleTheme } from '../Theme/themeSlice'
16
import { RootState } from '../redux/store'
17

  
18
import DarkModeIcon from '@mui/icons-material/DarkMode'
19
import LightModeIcon from '@mui/icons-material/LightMode'
23 20

  
24 21
const drawerWidth = 240
25 22

  
......
83 80
        setOpen(true)
84 81
    }
85 82

  
83
    const colorThemeMode = useSelector(
84
        (state: RootState) => state.theme.paletteMode
85
    )
86

  
87
    const dispatch = useDispatch()
88

  
89
    const onToggleTheme = () => {
90
        dispatch(toggleTheme())
91
    }
92

  
86 93
    return (
87 94
        <Fragment>
88 95
            <Paper style={{ minHeight: '100vh', borderRadius: 0 }}>
......
99 106
                            >
100 107
                                <MenuIcon />
101 108
                            </IconButton>
102
                            <Typography variant="h6" noWrap component="div">
103
                                Assyrian Toponyms App Prototype
104
                            </Typography>
109
                            {/* <Box> */}
110
                                <Typography variant="h6" noWrap component="div">
111
                                    Assyrian Toponyms App Prototype
112
                                </Typography>
113
                                <Stack sx={{ml: 'auto'}} alignItems="flex-end">
114
                                {colorThemeMode === 'dark' ? (
115
                                    <IconButton onClick={onToggleTheme}>
116
                                        <LightModeIcon />
117
                                    </IconButton>
118
                                ) : (
119
                                    <IconButton onClick={onToggleTheme}>
120
                                        <DarkModeIcon />
121
                                    </IconButton>
122
                                )}
123
                                </Stack>
124
                            {/* </Box> */}
105 125
                        </Toolbar>
106 126
                    </AppBar>
107 127
                    <NavigationMenu
frontend/src/features/Navigation/navigationMenuItems.ts
6 6
import PersonIcon from '@mui/icons-material/Person'
7 7
import LoginIcon from '@mui/icons-material/Login'
8 8
import { SvgIconTypeMap } from '@mui/material'
9
import DataSaverOffIcon from '@mui/icons-material/DataSaverOff'
10
import LogoutIcon from '@mui/icons-material/Logout'
9 11

  
10 12
export interface NavigationMenuItem {
11 13
    name: string
......
18 20

  
19 21
const visitorRole = 'VISITOR'
20 22
const visitorRoleOnly = 'VISITOR_ONLY'
23
const loggedInRole = 'LOGGED_IN'
21 24
const visitorAccess = new Set([visitorRole])
25
const adminAccess = new Set(['ADMIN'])
26
const loggedInAccess = new Set([loggedInRole])
22 27

  
23 28
const items: NavigationMenuItem[] = [
24 29
    {
......
53 58
    {
54 59
        name: 'Admin',
55 60
        path: '/admin',
56
        accessibleTo: new Set(['ADMIN']),
61
        accessibleTo: adminAccess,
57 62
        icon: PersonIcon,
58
        position: 4,
63
        position: 5,
59 64
    },
60 65
    // TODO move this to the top
61 66
    {
......
65 70
        icon: LoginIcon,
66 71
        position: 5,
67 72
    },
73
    {
74
        name: 'Statistics',
75
        path: '/stats',
76
        accessibleTo: adminAccess,
77
        icon: DataSaverOffIcon,
78
        position: 4,
79
    },
80
    {
81
        name: 'Logout',
82
        path: '/logout',
83
        accessibleTo: loggedInAccess,
84
        icon: LogoutIcon,
85
        position: 1337,
86
    }
68 87
]
69 88

  
70 89
const getNavigationItems = (_userRoles: string[]): NavigationMenuItem[] => {
......
74 93
        userRoles.push( visitorRole, visitorRoleOnly)
75 94
    } else {
76 95
        userRoles.push(visitorRole)
96
        userRoles.push(loggedInRole)
77 97
    }
78 98

  
79 99
    return items // else return everything the user has privileges to
frontend/src/features/Reusables/ButtonOpenableDialog.tsx
1
import { Button, Dialog } from '@mui/material'
2
import { Fragment, FunctionComponent, ReactNode, useState } from 'react'
3

  
4
export interface ButtonOpenableDialogProps {
5
    onOpenCallback?: () => void // this callback is always executed when the dialog is opened
6
    onCloseCallback?: () => void // this callback is always executed when the dialog is closed
7
    buttonText: string // the text of the button that opens the dialog
8
    buttonColor: // the color of the button that opens the dialog
9
    'primary' | 'secondary' | 'warning' | 'error' | 'success' | 'inherit'
10
    buttonVariant: 'text' | 'outlined' | 'contained' // the variant of the button that opens the dialog
11
    children: ReactNode // the content of the dialog
12
    maxWidth?: 'xs' | 'sm' | 'md' | 'lg' // the max width of the dialog
13
}
14

  
15
// Generic dialog that can be opened by a button and closed by clicking on the backdrop.
16
const ButtonOpenableDialog: FunctionComponent<ButtonOpenableDialogProps> = ({
17
    onOpenCallback,
18
    onCloseCallback,
19
    buttonText,
20
    buttonColor,
21
    buttonVariant,
22
    children,
23
    maxWidth,
24
}) => {
25
    const [open, setOpen] = useState(false)
26

  
27
    // Change maxWidth to large if its undefined
28
    maxWidth = maxWidth ?? 'lg'
29

  
30
    const onOpen = () => {
31
        if (onOpenCallback) {
32
            // execute the callback if exists
33
            onOpenCallback()
34
        }
35
        setOpen(true)
36
    }
37
    const onClose = () => {
38
        if (onCloseCallback) {
39
            // execute the callback if exists
40
            onCloseCallback()
41
        }
42
        setOpen(false)
43
    }
44

  
45
    return (
46
        <Fragment>
47
            <Button
48
                onClick={onOpen}
49
                color={buttonColor}
50
                variant={buttonVariant}
51
            >
52
                {buttonText}
53
            </Button>
54
            <Dialog
55
                fullWidth={true}
56
                open={open}
57
                onClose={onClose}
58
                maxWidth={maxWidth}
59
            >
60
                {children}
61
            </Dialog>
62
        </Fragment>
63
    )
64
}
65

  
66
export default ButtonOpenableDialog
frontend/src/features/Theme/ThemeWrapper.tsx
1
import { PaletteMode } from "@mui/material"
2
import { createTheme, Theme, ThemeProvider } from '@mui/material/styles'
3
import { FunctionComponent, ReactNode, useEffect, useState } from "react"
4
import { useSelector } from "react-redux"
5
import { RootState } from "../redux/store"
6

  
7
export interface ThemeWrapperProps {
8
    children: ReactNode
9
}
10

  
11
const ThemeWrapper: FunctionComponent<ThemeWrapperProps> = ({ children }) => {
12
    
13
    const buildTheme = (paletteMode: PaletteMode) =>
14
        createTheme({
15
            palette: {
16
                mode: paletteMode,
17
            },
18
            shape: {
19
                borderRadius: 16
20
            },
21
            typography: {
22
                fontFamily: [
23
                    '-apple-system',
24
                    'BlinkMacSystemFont',
25
                    '"Segoe UI"',
26
                    'Roboto',
27
                    '"Helvetica Neue"',
28
                    'Arial',
29
                    'sans-serif',
30
                    '"Apple Color Emoji"',
31
                    '"Segoe UI Emoji"',
32
                    '"Segoe UI Symbol"',
33
                ].join(','),
34
            },
35
        })
36

  
37
    const paletteMode = useSelector(
38
        (state: RootState) => state.theme.paletteMode
39
    )
40

  
41
    const [theme, setTheme] = useState<Theme>(buildTheme(paletteMode))
42
    useEffect(() => {
43
        setTheme(() => {
44
            return buildTheme(paletteMode)
45
        })
46
    }, [paletteMode])
47

  
48
    return (
49
        <ThemeProvider theme={theme}>
50
            {children}
51
            </ThemeProvider>
52
    )
53
}
54

  
55
export default ThemeWrapper
frontend/src/features/Theme/themeReducer.ts
1
import { createTheme, Theme } from '@mui/material/styles'
2
import { AnyAction } from 'redux'
3
import { persist } from '../../utils/statePersistence'
4

  
5
export interface ThemeState {
6
    theme: Theme
7
    themeType: 'Light' | 'Dark'
8
}
9

  
10
const statePersistName = 'theme'
11

  
12
const initialTheme = createTheme({
13
    palette: {
14
        mode: 'light'
15
    },
16
    typography: {
17
        fontFamily: [
18
      '-apple-system',
19
      'BlinkMacSystemFont',
20
      '"Segoe UI"',
21
      'Roboto',
22
      '"Helvetica Neue"',
23
      'Arial',
24
      'sans-serif',
25
      '"Apple Color Emoji"',
26
      '"Segoe UI Emoji"',
27
      '"Segoe UI Symbol"',
28
    ].join(','),
29
    }
30
})
31
const initialState: ThemeState = {
32
    theme: initialTheme,
33
    themeType: 'Light',
34
}
35

  
36
export enum ThemeStateActions {
37
    TOGGLE_THEME = 'TOGGLE_THEME',
38
    SET_LIGHT_MODE = 'SET_LIGHT_MODE',
39
    SET_DARK_MODE = 'SET_DARK_MODE',
40
}
41

  
42
const themeReducer = (state: ThemeState = initialState, action: AnyAction) => {
43
    // TODO add all the actions
44
    switch (action.type) {
45
        case ThemeStateActions.TOGGLE_THEME:
46
            return persist(statePersistName, state)
47

  
48
        default:
49
            return state
50
    }
51
}
52

  
53
export default themeReducer
frontend/src/features/Theme/themeSlice.ts
1
import { createSlice } from '@reduxjs/toolkit'
2
import { persistReducer } from 'redux-persist'
3
import storage from 'redux-persist/lib/storage'
4
import { PaletteMode } from '@mui/material'
5

  
6
export interface ThemeState {
7
    paletteMode: PaletteMode
8
}
9

  
10
const persistConfig = {
11
    key: 'theme',
12
    storage, // localStorage for browsers
13
}
14

  
15
const initialState: ThemeState = {
16
    paletteMode: 'light',
17
}
18

  
19
const themeSlice = createSlice({
20
    name: 'theme',
21
    initialState,
22
    reducers: {
23
        toggleTheme: (state) => ({
24
            ...state,
25
            paletteMode: state.paletteMode === 'light' ? 'dark' : 'light',
26
        }),
27
    },
28
})
29

  
30
const themeReducer = persistReducer(persistConfig, themeSlice.reducer)
31
// const themeReducer = themeSlice.reducer
32
export const { toggleTheme } = themeSlice.actions
33
export default themeReducer
frontend/src/features/TrackingTool/FileUpload.tsx
1
import {
2
    Button,
3
    DialogContent,
4
    DialogTitle,
5
    Link,
6
    Stack,
7
    Typography,
8
} from '@mui/material'
9
import { useFormik } from 'formik'
10
import { Fragment, useState } from 'react'
11
import * as yup from 'yup'
12
import axiosInstance from '../../api/api'
13
import ButtonOpenableDialog from '../Reusables/ButtonOpenableDialog'
14
import AttachmentIcon from '@mui/icons-material/Attachment'
15
import DeleteIcon from '@mui/icons-material/Delete'
16
import SendIcon from '@mui/icons-material/Send'
17

  
18
const FileUpload = () => {
19
    const [filename, setFilename] = useState<string | undefined>(undefined)
20

  
21
    const validationSchema = yup.object().shape({
22
        file: yup.mixed().required('File is required'),
23
    })
24

  
25
    const [submitButtonEnabled, setSubmitButtonEnabled] = useState(true)
26

  
27
    const formik = useFormik({
28
        initialValues: {
29
            file: undefined,
30
        },
31
        validationSchema,
32
        onSubmit: async (values) => {
33
            // TODO actually send the file somewhere
34
            // TODO implement me
35

  
36
            const formData = new FormData()
37
            // @ts-ignore for now
38
            formData.append('file', values.file as File)
39

  
40
            const { data } = await axiosInstance.post('/path', formData, {
41
                headers: {
42
                    'Content-Type': 'multipart/form-data',
43
                },
44
            })
45
        },
46
    })
47

  
48
    // Callback when user selects the file
49
    const onFileSelected = (event: any) => {
50
        const file = event.currentTarget.files[0]
51
        if (file) {
52
            setFilename(file.name)
53
            formik.setFieldValue('file', file)
54
        }
55
    }
56

  
57
    const onClose = () => {
58
        setFilename(undefined)
59
        formik.resetForm()
60
    }
61

  
62
    const onClearSelectedFile = () => {
63
        setFilename(undefined)
64
        formik.setFieldValue('file', undefined)
65
    }
66

  
67
    return (
68
        <ButtonOpenableDialog
69
            buttonText="Upload File"
70
            buttonColor="primary"
71
            buttonVariant="contained"
72
            onCloseCallback={onClose}
73
            maxWidth="xs"
74
        >
75
            <DialogTitle>Upload New File</DialogTitle>
76
            <DialogContent>
77
                <form onSubmit={formik.handleSubmit}>
78
                    {!filename ? (
79
                        <Fragment>
80
                            <Stack
81
                                direction="row"
82
                                justifyContent="flex-end"
83
                                alignItems="center"
84
                            >
85
                                <Button
86
                                    variant="contained"
87
                                    color="primary"
88
                                    component="label"
89
                                    // size="small"
90
                                    startIcon={<AttachmentIcon />}
91
                                >
92
                                    Select File
93
                                    <input
94
                                        id="file"
95
                                        name="file"
96
                                        type="file"
97
                                        hidden
98
                                        onChange={onFileSelected}
99
                                    />
100
                                </Button>
101
                            </Stack>
102
                        </Fragment>
103
                    ) : (
104
                        <Fragment>
105
                            <Stack direction="row" spacing={1}>
106
                                <Typography
107
                                    sx={{
108
                                        // textOverflow: 'ellipsis',
109
                                        // overflow: 'hidden',
110
                                    }}
111
                                    variant="body1"
112
                                >
113
                                    Selected File:{' '}
114
                                </Typography>
115
                                <Typography
116
                                    sx={{
117
                                        textOverflow: 'ellipsis',
118
                                        overflow: 'hidden',
119
                                    }}
120
                                    // color="text.secondary"
121
                                    component={Link}
122
                                    // download={(formik.values?.file as File).}
123
                                    // align="right"
124
                                >
125
                                    {filename}
126
                                </Typography>
127
                            </Stack>
128
                            <Stack
129
                                direction="row"
130
                                justifyContent="flex-end"
131
                                alignItems="center"
132
                                spacing={2}
133
                                sx={{mt: 2}}
134
                            >
135
                                <Button
136
                                    // sx={{ mb: 2, mt: 1 }}
137
                                    variant="contained"
138
                                    size="small"
139
                                    endIcon={<DeleteIcon />}
140
                                    onClick={onClearSelectedFile}
141
                                >
142
                                    Remove Selection
143
                                </Button>
144
                                <Button size="small" type="submit" variant="contained" startIcon={<SendIcon />}>
145
                                Submit
146
                            </Button>
147
                            </Stack>
148
                            
149
                            
150
                        </Fragment>
151
                    )}
152
                </form>
153
            </DialogContent>
154
        </ButtonOpenableDialog>
155
    )
156
}
157

  
158
export default FileUpload
frontend/src/features/TrackingTool/PlaintextUpload.tsx
1
import {
2
    Button,
3
    DialogContent,
4
    DialogTitle,
5
    Stack,
6
    TextField,
7
} from '@mui/material'
8
import { useFormik } from 'formik'
9
import { Fragment } from 'react'
10
import ButtonOpenableDialog from '../Reusables/ButtonOpenableDialog'
11
import SendIcon from '@mui/icons-material/Send'
12
import ClearIcon from '@mui/icons-material/Clear'
13

  
14
const PlaintextUpload = () => {
15
    const formik = useFormik({
16
        initialValues: {
17
            text: '',
18
        },
19
        onSubmit: () => {
20
        },
21
    })
22

  
23
    const resetForm = () => {
24
        formik.resetForm()
25
    }
26

  
27
    return (
28
        <Fragment>
29
            <ButtonOpenableDialog
30
                buttonText="Plaintext"
31
                buttonColor="primary"
32
                buttonVariant="contained"
33
            >
34
                <DialogTitle>Plaintext Input</DialogTitle>
35
                <DialogContent>
36
                    <form onSubmit={formik.handleChange}>
37
                        <TextField
38
                            sx={{ my: 2 }}
39
                            fullWidth
40
                            multiline
41
                            label="Plaintext input"
42
                            rows={10}
43
                            name="text"
44
                            value={formik.values.text}
45
                            onChange={formik.handleChange}
46
                        />
47
                        <Stack
48
                            alignItems="flex-end"
49
                            justifyContent="flex-end"
50
                            spacing={2}
51
                            direction="row"
52
                        >
53
                            <Button
54
                                variant="contained"
55
                                color="secondary"
56
                                onClick={resetForm}
57
                                startIcon={<ClearIcon />}
58
                            >
59
                                Clear
60
                            </Button>
61
                            <Button type="submit" variant="contained" startIcon={<SendIcon />}>
62
                                Submit
63
                            </Button>
64
                        </Stack>
65
                    </form>
66
                </DialogContent>
67
            </ButtonOpenableDialog>
68
        </Fragment>
69
    )
70
}
71

  
72
export default PlaintextUpload
frontend/src/features/TrackingTool/TrackingTool.tsx
4 4
import { MapContainer, Marker, Polyline, Popup, TileLayer } from 'react-leaflet'
5 5
import mapConfig from '../../config/mapConfig'
6 6
import TextPath from 'react-leaflet-textpath'
7
import PlaintextUpload from './PlaintextUpload'
8
import FileUpload from './FileUpload'
7 9

  
8 10
// Page with tracking tool
9 11
const TrackingTool = () => {
10
    const generateDummyPath = () => {
12
    const createDummyPathCoords = () => {
11 13
        // Sample dummy path to display
12 14
        const dummyPath = []
13 15
        for (let i = 0; i < 10; i += 1) {
......
25 27
        return dummyPath
26 28
    }
27 29

  
28
    const createDummyPathPolylines = () => {
29
        const dummyPath = generateDummyPath()
30
    const createDummyPath = () => {
31
        const coords = createDummyPathCoords()
30 32
        const polylines: any[] = []
31 33

  
32
        if (dummyPath.length < 2) {
34
        if (coords.length < 2) {
33 35
            return []
34 36
        }
35 37

  
36
        for (let i = 0; i < dummyPath.length - 1; i += 1) {
38
        for (let i = 0; i < coords.length - 1; i += 1) {
37 39
            polylines.push(
38 40
                // <Polyline
39 41
                //     key={i}
......
51 53
                //     </Polyline>
52 54
                <TextPath
53 55
                    positions={[
54
                        [dummyPath[i].latitude, dummyPath[i].longitude],
55
                        [dummyPath[i + 1].latitude, dummyPath[i + 1].longitude],
56
                        [coords[i].latitude, coords[i].longitude],
57
                        [coords[i + 1].latitude, coords[i + 1].longitude],
56 58
                    ]}
57 59
                    text="►"
58 60
                    attributes={{
59 61
                        'font-size': 25,
60
                        'fill': 'blue'
62
                        fill: 'blue',
61 63
                    }}
62 64
                    repeat
63 65
                    center
......
68 70
            )
69 71
        }
70 72

  
71
        return polylines
73
        return [polylines, coords]
72 74
    }
73 75

  
74
    const polylines = createDummyPathPolylines()
76
    const [polylines, coords] = createDummyPath()
75 77

  
76 78
    return (
77 79
        <Fragment>
......
94 96
                        >
95 97
                            Upload:
96 98
                        </Typography>
97
                        <Button
98
                            variant="contained"
99
                            color="primary"
100
                            startIcon={<AddIcon />}
101
                        >
102
                            Plaintext
103
                        </Button>
104
                        <Button
105
                            variant="contained"
106
                            color="primary"
107
                            startIcon={<AddIcon />}
108
                        >
109
                            File
110
                        </Button>
99
                        <PlaintextUpload />
100
                        <FileUpload />
111 101
                    </Stack>
112 102
                </Grid>
113 103
                <Grid
......
132 122
                            attribution={mapConfig.attribution}
133 123
                            url={mapConfig.url}
134 124
                        />
125
                        {coords.map(({ latitude, longitude }, idx) => (
126
                            <Marker position={[latitude, longitude]} />
127
                        ))}
135 128
                        {polylines}
136 129
                    </MapContainer>
137 130
                </Grid>
frontend/src/features/redux/store.ts
2 2
import { persistStore } from 'redux-persist'
3 3
import thunk from 'redux-thunk'
4 4
import userReducer from '../Auth/userSlice'
5
import themeReducer from '../Theme/themeReducer'
5
import themeReducer from '../Theme/themeSlice'
6 6
import catalogReducer from '../Catalog/catalogSlice'
7 7
import { composeWithDevTools } from 'redux-devtools-extension'
8 8

  
......
16 16
        catalog: catalogReducer,
17 17
    }),
18 18
    process.env.REACT_APP_DEV_ENV === 'true'
19
        ? composeEnhancers(
19
        ? composeEnhancers( // ComposeEnhancers will inject redux-devtools-extension
20 20
              applyMiddleware(thunk) // Thunk middleware so we can async fetch data from the api
21 21
          )
22 22
        : applyMiddleware(thunk)

Také k dispozici: Unified diff