commit 812b9f90c3bb3f96e87cde393d49f634eb7c37c8
Author: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date:   Tue May 17 19:55:09 2022 +0200

    draggable list start
    
    re #9741

diff --git a/frontend/package.json b/frontend/package.json
index 7b77e77..5400565 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,6 +17,7 @@
     "jwt-decode": "^3.1.2",
     "leaflet": "^1.8.0",
     "react": "^17.0.2",
+    "react-beautiful-dnd": "^13.1.0",
     "react-dom": "^17.0.2",
     "react-html-parser": "^2.0.2",
     "react-leaflet": "3.2.5",
@@ -31,6 +32,7 @@
     "swagger-typescript-api": "^9.3.1",
     "ts-node": "^10.7.0",
     "typescript": "^4.4.2",
+    "uuid": "^8.3.2",
     "web-vitals": "^2.1.0",
     "yup": "^0.32.11"
   },
@@ -68,9 +70,11 @@
     "@types/leaflet": "^1.7.9",
     "@types/node": "^16.7.13",
     "@types/react": "^17.0.43",
+    "@types/react-beautiful-dnd": "^13.1.2",
     "@types/react-dom": "^17.0.9",
     "@types/react-redux": "^7.1.23",
     "@types/redux-persist": "^4.3.1",
+    "@types/uuid": "^8.3.4",
     "@types/yup": "^0.29.13",
     "redux-devtools-extension": "^2.13.9"
   }
diff --git a/frontend/src/features/Reusables/DraggableMarker.tsx b/frontend/src/features/Reusables/DraggableMarker.tsx
deleted file mode 100644
index 1bbd775..0000000
--- a/frontend/src/features/Reusables/DraggableMarker.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { EventedProps } from '@react-leaflet/core'
-import { LatLngExpression, Marker, MarkerOptions } from 'leaflet'
-import { FunctionComponent, ReactNode, useCallback, useMemo, useRef, useState } from 'react'
-
-export interface DraggableMarkerProps extends MarkerOptions, EventedProps {
-    children?: ReactNode
-    position: LatLngExpression
-}
-
-const DraggableMarker: FunctionComponent<DraggableMarkerProps> = ({ children, position }) => {
-    const [draggable, setDraggable] = useState(false)
-    const [pos, setPos] = useState(position)
-    const markerRef = useRef(null)
-    const eventHandlers = useMemo(
-        () => ({
-            dragend() {
-                const marker: any = markerRef.current
-                if (marker != null) {
-                    setPos(marker.getLatLng())
-                }
-            },
-        }),
-        []
-    )
-    const toggleDraggable = useCallback(() => {
-        setDraggable((d) => !d)
-    }, [])
-
-    return (
-        // @ts-ignore
-        <Marker
-            draggable={draggable}
-            eventHandlers={eventHandlers}
-            position={pos}
-            ref={markerRef}
-        >
-            {children}
-        </Marker>
-    )
-}
-
-export default DraggableMarker
diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx
new file mode 100644
index 0000000..267b962
--- /dev/null
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx
@@ -0,0 +1,32 @@
+import { memo } from 'react'
+import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd'
+import { MapPoint } from '../Map/pathUtils'
+import DraggableListItem from './DraggableListItem'
+
+export interface DraggableListProps {
+    items: MapPoint[]
+    onDragEnd: OnDragEndResponder
+}
+
+const DraggableList = memo(({ items, onDragEnd }: DraggableListProps) => {
+    return (
+        <DragDropContext onDragEnd={onDragEnd}>
+            <Droppable droppableId="droppable-list">
+                {(provided) => (
+                    <div ref={provided.innerRef} {...provided.droppableProps}>
+                        {items.map((item, index) => (
+                            <DraggableListItem
+                                list={items}
+                                index={index}
+                                key={item.id}
+                            />
+                        ))}
+                        {provided.placeholder}
+                    </div>
+                )}
+            </Droppable>
+        </DragDropContext>
+    )
+})
+
+export default DraggableList
diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx
new file mode 100644
index 0000000..e29eb02
--- /dev/null
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx
@@ -0,0 +1,74 @@
+import {
+    Avatar,
+    Checkbox,
+    ListItem,
+    ListItemAvatar,
+    ListItemText,
+} from '@mui/material'
+import makeStyles from '@mui/material/styles/makeStyles'
+import { Draggable } from 'react-beautiful-dnd'
+import { MapPoint, PathVariant } from '../Map/pathUtils'
+import LocationOnIcon from '@mui/icons-material/LocationOn'
+import { CatalogItemDto } from '../../../swagger/data-contracts'
+import { useDispatch } from 'react-redux'
+import { updateMapMarker, updateMapMarkerWithId } from '../trackingToolSlice'
+import { Fragment, useEffect, useState } from 'react'
+
+export type DraggableListItemProps = {
+    list: PathVariant
+    index: number
+}
+
+const getFormattedLocationOrEmpty = (catalogItem: CatalogItemDto) => {
+    if (!catalogItem || !catalogItem.latitude || !catalogItem.longitude) {
+        return 'Location Unknown'
+    }
+
+    return `${catalogItem.latitude}°, ${catalogItem.longitude}°`
+}
+
+const DraggableListItem = ({ list, index }: DraggableListItemProps) => {
+    const item = list[index]
+    const dispatch = useDispatch()
+    const toggleHidden = () => {
+        dispatch(
+            updateMapMarkerWithId({
+                item: {
+                    ...item,
+                    active: !item?.active,
+                } as MapPoint,
+                id: item.id,
+            })
+        )
+    }
+
+    return (
+        <Draggable key={`${item.id}`} draggableId={`${item.id}`} index={index}>
+            {(provided, snapshot) => (
+                <ListItem
+                    ref={provided.innerRef}
+                    {...provided.draggableProps}
+                    {...provided.dragHandleProps}
+                    sx={{
+                        background: snapshot.isDragging ? 'rgb(235,235,235)' : 'inherit',
+                    }}
+                >
+                    <ListItemAvatar>
+                        <Avatar>
+                            <LocationOnIcon />
+                        </Avatar>
+                    </ListItemAvatar>
+                    <ListItemText
+                        primary={item.catalogItem.name ?? 'Unknown name'}
+                        secondary={getFormattedLocationOrEmpty(
+                            item.catalogItem
+                        )}
+                    />
+                    <Checkbox checked={item.active} onChange={toggleHidden} />
+                </ListItem>
+            )}
+        </Draggable>
+    )
+}
+
+export default DraggableListItem
diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx
new file mode 100644
index 0000000..42dce2c
--- /dev/null
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx
@@ -0,0 +1,56 @@
+import { Paper } from '@mui/material'
+import { useEffect, useState } from 'react'
+import { DropResult } from 'react-beautiful-dnd'
+import { useDispatch, useSelector } from 'react-redux'
+import { RootState } from '../../redux/store'
+import { swapMapMarkerIndices } from '../trackingToolSlice'
+import { PathVariant } from '../Map/pathUtils'
+import DraggableList from './DraggableList'
+
+const DraggableMarkerList = () => {
+    const dispatch = useDispatch()
+
+    // List of all paths
+    const paths = useSelector(
+        (state: RootState) => state.trackingTool.pathVariants
+    )
+
+    // Primary path index - i.e. the selected path
+    const primaryPathIdx = useSelector(
+        (state: RootState) => state.trackingTool.primaryPathIdx
+    )
+
+    // Selected path as local state
+    const [path, setPath] = useState<PathVariant | undefined>()
+
+    // Set localstate path whenever it changes in the store
+    useEffect(() => {
+        if (!paths || paths.length < primaryPathIdx) {
+            setPath(undefined)
+            return
+        }
+
+        setPath(paths[primaryPathIdx])
+    }, [paths, primaryPathIdx])
+
+    const onDragEnd = ({ destination, source }: DropResult) => {
+        if (!destination || !source || destination.index === source.index) {
+            return
+        }
+
+        dispatch(
+            swapMapMarkerIndices({
+                source: source.index,
+                destination: destination.index,
+            })
+        )
+    }
+
+    return (
+        <Paper variant="outlined">
+            <DraggableList items={path ?? []} onDragEnd={onDragEnd} />
+        </Paper>
+    )
+}
+
+export default DraggableMarkerList
diff --git a/frontend/src/features/TrackingTool/Map/MapMarker.tsx b/frontend/src/features/TrackingTool/Map/MapMarker.tsx
new file mode 100644
index 0000000..db39848
--- /dev/null
+++ b/frontend/src/features/TrackingTool/Map/MapMarker.tsx
@@ -0,0 +1,48 @@
+import { LatLngTuple, Marker as MarkerPOJO } from 'leaflet'
+import { FunctionComponent, ReactNode, useMemo, useRef, useState } from 'react'
+import { Marker } from 'react-leaflet'
+
+export interface MapMarkerProps {
+    position: LatLngTuple
+    children?: ReactNode
+    color?: 'external' | 'disabled' | 'localCatalog'
+    updatePositionCallbackFn: (position: LatLngTuple) => void // Callback function to notify MapPath to rerender the path
+}
+
+// Custom Map Marker component
+const MapMarker: FunctionComponent<MapMarkerProps> = ({
+    position,
+    children,
+    updatePositionCallbackFn,
+}) => {
+    const [currentPosition, setCurrentPosition] = useState(position)
+    const markerRef = useRef<MarkerPOJO | null>(null)
+    const eventHandlers = useMemo(
+        () => ({
+            dragend: () => {
+                const marker = markerRef.current
+                console.log(markerRef)
+                if (!marker) {
+                    return
+                }
+                const latlng = marker.getLatLng()
+                setCurrentPosition([latlng.lat, latlng.lng])
+                updatePositionCallbackFn([latlng.lat, latlng.lng])
+            },
+        }),
+        [updatePositionCallbackFn]
+    )
+
+    return (
+        <Marker
+            draggable={true}
+            position={currentPosition}
+            eventHandlers={eventHandlers}
+            ref={markerRef}
+        >
+            {children}
+        </Marker>
+    )
+}
+
+export default MapMarker
diff --git a/frontend/src/features/TrackingTool/Map/MapPath.tsx b/frontend/src/features/TrackingTool/Map/MapPath.tsx
new file mode 100644
index 0000000..d1df67d
--- /dev/null
+++ b/frontend/src/features/TrackingTool/Map/MapPath.tsx
@@ -0,0 +1,187 @@
+import { Fragment, FunctionComponent, useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { RootState } from '../../redux/store'
+import { PathVariant, MapPoint, isMapPointDisplayable } from './pathUtils'
+import TextPath from 'react-leaflet-textpath'
+import { setPrimaryIdx, updateMapMarker } from '../trackingToolSlice'
+import MapMarker from './MapMarker'
+import { LatLngTuple } from 'leaflet'
+import { Popup, Tooltip } from 'react-leaflet'
+import { Checkbox, FormControlLabel, Stack, Typography } from '@mui/material'
+import { formatHtmlStringToReactDom } from '../../../utils/formatting/HtmlUtils'
+import { DialogCatalogItemDetail as CatalogItemDetailDialog } from '../../Catalog/CatalogItemDetail'
+
+export interface MapPathProps {
+    idx: number // index of the path in the list
+}
+
+type EdgeElement = any
+
+// Blue
+export const primaryPathColor = '#346eeb'
+
+// Grey
+export const secondaryPathColor = '#878e9c'
+
+const MapPath: FunctionComponent<MapPathProps> = ({ idx }) => {
+    const dispatch = useDispatch()
+
+    // Get list of all paths from the store
+    // And extract path from them
+    const paths = useSelector(
+        (state: RootState) => state.trackingTool.pathVariants
+    )
+    const [path, setPath] = useState<PathVariant>([])
+    useEffect(() => {
+        // Either set the path if it exists or set it to an empty array
+        setPath(paths && paths.length > idx ? paths[idx] : [])
+    }, [idx, paths])
+
+    // Primary path index to set the correct color
+    const primaryPathIdx = useSelector(
+        (state: RootState) => state.trackingTool.primaryPathIdx
+    )
+
+    // List of all active map points
+    const [displayableMapPoints, setDisplayableMapPoints] = useState<
+        MapPoint[]
+    >([])
+    useEffect(() => {
+        // Set all displayable vertices
+        setDisplayableMapPoints(
+            path.filter((mapPoint) => isMapPointDisplayable(mapPoint))
+        )
+    }, [path])
+
+    // List of all edges in the path
+    const [edges, setEdges] = useState<EdgeElement[]>([])
+    useEffect(() => {
+        // Get all active map points
+        const activeMapPoints = displayableMapPoints.filter(
+            (item) => item.active
+        )
+        if (activeMapPoints.length < 2) {
+            setEdges([])
+            return
+        }
+
+        // Build edges
+        const edges = []
+        for (let i = 0; i < activeMapPoints.length - 1; i += 1) {
+            const [start, end] = [
+                activeMapPoints[i].catalogItem,
+                activeMapPoints[i + 1].catalogItem,
+            ]
+            edges.push(
+                <TextPath
+                    // Somehow this refuses to work so let it rerender everything ...
+                    key={`${start.id}-${end.id}:${start.latitude},${start.longitude}-${end.latitude},${end.longitude}`}
+                    positions={[
+                        [start.latitude, start.longitude],
+                        [end.latitude, end.longitude],
+                    ]}
+                    text="►"
+                    // text=" > > > > "
+                    attributes={{
+                        'font-size': 19,
+                        // Set to primaryPathColor if primary index in the tracking tool is equal to this index
+                        fill:
+                            primaryPathIdx === idx
+                                ? primaryPathColor
+                                : secondaryPathColor,
+                    }}
+                    onClick={() => dispatch(setPrimaryIdx(idx))}
+                    repeat
+                    center
+                    weight={0}
+                />
+            )
+        }
+        setEdges(edges)
+    }, [dispatch, displayableMapPoints, idx, primaryPathIdx])
+
+    // List of vertices to display
+    const [vertices, setVertices] = useState<JSX.Element[]>([])
+    useEffect(() => {
+        // Iterate over all displayable map points and map them to MapMarker
+        setVertices(
+            displayableMapPoints.map((item) => (
+                <MapMarker
+                    key={`${item.catalogItem.latitude}${item.catalogItem.longitude}`}
+                    position={[
+                        item.catalogItem.latitude as number,
+                        item.catalogItem.longitude as number,
+                    ]}
+                    updatePositionCallbackFn={(position: LatLngTuple) => {
+                        dispatch(
+                            updateMapMarker({
+                                item: {
+                                    ...item,
+                                    catalogItem: {
+                                        ...item.catalogItem,
+                                        latitude: position[0],
+                                        longitude: position[1],
+                                    },
+                                },
+                            })
+                        )
+                    }}
+                >
+                    <Fragment>
+                        <Tooltip>
+                            {/* <Typography> */}
+                            {item.catalogItem.name ?? ''}
+                            {/* </Typography> */}
+                        </Tooltip>
+                        <Popup>
+                            <Fragment>
+                                <Stack direction="column" sx={{ m: 0 }}>
+                                    <Typography
+                                        variant="h6"
+                                        fontWeight="bold"
+                                        fontSize={16}
+                                    >
+                                        {formatHtmlStringToReactDom(
+                                            item.catalogItem.name as string
+                                        )}
+                                    </Typography>
+                                    <FormControlLabel
+                                        control={
+                                            <Checkbox
+                                                checked={item.active}
+                                                onChange={() => {
+                                                    dispatch(
+                                                        updateMapMarker({
+                                                            item: {
+                                                                ...item,
+                                                                active: !item.active,
+                                                            },
+                                                        })
+                                                    )
+                                                }}
+                                            />
+                                        }
+                                        labelPlacement="end"
+                                        label="Active"
+                                    />
+                                    <CatalogItemDetailDialog
+                                        itemId={item.catalogItem.id ?? ''}
+                                    />
+                                </Stack>
+                            </Fragment>
+                        </Popup>
+                    </Fragment>
+                </MapMarker>
+            ))
+        )
+    }, [dispatch, displayableMapPoints, idx])
+
+    return (
+        <Fragment>
+            {vertices}
+            {edges}
+        </Fragment>
+    )
+}
+
+export default MapPath
diff --git a/frontend/src/features/TrackingTool/Map/pathUtils.ts b/frontend/src/features/TrackingTool/Map/pathUtils.ts
new file mode 100644
index 0000000..7b2385c
--- /dev/null
+++ b/frontend/src/features/TrackingTool/Map/pathUtils.ts
@@ -0,0 +1,70 @@
+// Business logic for tracking tool
+
+import { CatalogItemDto, PathDto } from '../../../swagger/data-contracts'
+import generateUuid from '../../../utils/id/uuidGenerator'
+
+// For more comprehensive code alias CatalogItemDto[] as path variant
+export type PathVariant = MapPoint[]
+
+export enum MapPointType {
+    LocalCatalog, // Fetched from local catalog
+    ExternalCatalog, // Fetched from external catalog
+    GeoJson, // From GeoJSON file
+    FromCoordinates, // From coordinates
+}
+
+// Represents a point on the map - wrapper for CatalogItemDto to make it easier to work with
+export interface MapPoint {
+    id: string // unique id for react
+    idx: number,
+    active: boolean,
+    catalogItem: CatalogItemDto,
+    type: MapPointType
+}
+
+export const isMapPointDisplayable = (mapPoint: MapPoint): boolean =>
+    !!mapPoint.catalogItem.latitude && !!mapPoint.catalogItem.longitude
+
+/**
+ * Cartesian product of two arrays
+ * @param sets
+ * @returns
+ */
+const cartesianProduct = (sets: CatalogItemDto[][]): CatalogItemDto[][] =>
+    sets.reduce<CatalogItemDto[][]>(
+        (results, ids) =>
+            results
+                .map((result) => ids.map((id) => [...result, id]))
+                .reduce((nested, result) => [...nested, ...result]),
+        [[]]
+    )
+
+/**
+ * Builds a list of all possible path variants from pathDto
+ * @param pathDto
+ * @returns
+ */
+export const buildPathVariants = (pathDto: PathDto, mapPointType: MapPointType = MapPointType.LocalCatalog): PathVariant[] => {
+    if (!pathDto.foundCatalogItems) {
+        return []
+    }
+
+    return (
+        pathDto.foundCatalogItems.length === 1
+            ? pathDto.foundCatalogItems
+            : cartesianProduct(pathDto.foundCatalogItems)
+    ).map((variant, _) =>
+        variant.map(
+            (catalogItem, idx) => (
+                {
+                    id: generateUuid(),
+                    idx,
+                    active: !!catalogItem.latitude && !!catalogItem.longitude,
+                    catalogItem,
+                    type: mapPointType,
+                })
+        )
+    )
+}
+
+export default buildPathVariants
diff --git a/frontend/src/features/TrackingTool/MapMarker.tsx b/frontend/src/features/TrackingTool/MapMarker.tsx
deleted file mode 100644
index 98c2499..0000000
--- a/frontend/src/features/TrackingTool/MapMarker.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { LatLngTuple, Marker as MarkerPOJO } from "leaflet"
-import { FunctionComponent, ReactNode, useMemo, useRef, useState } from "react"
-import { Marker } from "react-leaflet"
-
-export interface MapMarkerProps {
-    position: LatLngTuple
-    children?: ReactNode
-    color?: "external" | "disabled" | "localCatalog"
-    updatePositionCallbackFn: (position: LatLngTuple) => void // Callback function to notify MapPath to rerender the path
-}
-
-// Custom Map Marker component
-const MapMarker: FunctionComponent<MapMarkerProps> = ({
-    position,
-    children,
-    updatePositionCallbackFn,
-}) => {
-    const [currentPosition, setCurrentPosition] = useState(position)
-    const markerRef = useRef<MarkerPOJO | null>(null)
-    const eventHandlers = useMemo(
-        () => ({
-            dragend: () => {
-                const marker = markerRef.current
-                console.log(markerRef)
-                if (!marker) {
-                    return
-                }
-                const latlng = marker.getLatLng()
-                setCurrentPosition([latlng.lat, latlng.lng])
-                updatePositionCallbackFn([latlng.lat, latlng.lng])
-            },
-        }),
-        [updatePositionCallbackFn]
-    )
-
-    return (
-        <Marker
-            draggable={true}
-            position={currentPosition}
-            eventHandlers={eventHandlers}
-            ref={markerRef}
-        >
-            {children}
-        </Marker>
-    )
-}
-
-export default MapMarker
diff --git a/frontend/src/features/TrackingTool/MapPath.tsx b/frontend/src/features/TrackingTool/MapPath.tsx
deleted file mode 100644
index 5492598..0000000
--- a/frontend/src/features/TrackingTool/MapPath.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import { Fragment, FunctionComponent, useEffect, useState } from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { RootState } from '../redux/store'
-import { PathVariant, MapPoint, isMapPointDisplayable } from './pathUtils'
-import TextPath from 'react-leaflet-textpath'
-import { setPrimaryIdx, updateMapMarker } from './trackingToolSlice'
-import MapMarker from './MapMarker'
-import { LatLngTuple } from 'leaflet'
-import { Popup, Tooltip } from 'react-leaflet'
-import { Checkbox, FormControlLabel, Stack, Typography } from '@mui/material'
-import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
-import { DialogCatalogItemDetail as CatalogItemDetailDialog } from '../Catalog/CatalogItemDetail'
-
-export interface MapPathProps {
-    idx: number // index of the path in the list
-}
-
-type EdgeElement = any
-
-// Blue
-export const primaryPathColor = '#346eeb'
-
-// Grey
-export const secondaryPathColor = '#878e9c'
-
-const MapPath: FunctionComponent<MapPathProps> = ({ idx }) => {
-    const dispatch = useDispatch()
-
-    // Get list of all paths from the store
-    // And extract path from them
-    const paths = useSelector(
-        (state: RootState) => state.trackingTool.pathVariants
-    )
-    const [path, setPath] = useState<PathVariant>([])
-    useEffect(() => {
-        // Either set the path if it exists or set it to an empty array
-        setPath(paths && paths.length > idx ? paths[idx] : [])
-    }, [idx, paths])
-
-    // Primary path index to set the correct color
-    const primaryPathIdx = useSelector(
-        (state: RootState) => state.trackingTool.primaryPathIdx
-    )
-
-    // List of all active map points
-    const [displayableMapPoints, setDisplayableMapPoints] = useState<
-        MapPoint[]
-    >([])
-    useEffect(() => {
-        // Set all displayable vertices
-        setDisplayableMapPoints(
-            path.filter((mapPoint) => isMapPointDisplayable(mapPoint))
-        )
-    }, [path])
-
-    // List of all edges in the path
-    const [edges, setEdges] = useState<EdgeElement[]>([])
-    useEffect(() => {
-        // Get all active map points
-        const activeMapPoints = displayableMapPoints.filter(
-            (item) => item.active
-        )
-        if (activeMapPoints.length < 2) {
-            setEdges([])
-            return
-        }
-
-        // Build edges
-        const edges = []
-        for (let i = 0; i < activeMapPoints.length - 1; i += 1) {
-            const [start, end] = [
-                activeMapPoints[i].catalogItem,
-                activeMapPoints[i + 1].catalogItem,
-            ]
-            edges.push(
-                <TextPath
-                    // Somehow this refuses to work so let it rerender everything ...
-                    key={`${start.id}-${end.id}:${start.latitude},${start.longitude}-${end.latitude},${end.longitude}`}
-                    positions={[
-                        [start.latitude, start.longitude],
-                        [end.latitude, end.longitude],
-                    ]}
-                    text="►"
-                    // text=" > > > > "
-                    attributes={{
-                        'font-size': 19,
-                        // Set to primaryPathColor if primary index in the tracking tool is equal to this index
-                        fill:
-                            primaryPathIdx === idx
-                                ? primaryPathColor
-                                : secondaryPathColor,
-                    }}
-                    onClick={() => dispatch(setPrimaryIdx(idx))}
-                    repeat
-                    center
-                    weight={0}
-                />
-            )
-        }
-        setEdges(edges)
-    }, [dispatch, displayableMapPoints, idx, primaryPathIdx])
-
-    // List of vertices to display
-    const [vertices, setVertices] = useState<JSX.Element[]>([])
-    useEffect(() => {
-        // Iterate over all displayable map points and map them to MapMarker
-        setVertices(
-            displayableMapPoints.map((item) => (
-                <MapMarker
-                    key={`${item.catalogItem.latitude}${item.catalogItem.longitude}`}
-                    position={[
-                        item.catalogItem.latitude as number,
-                        item.catalogItem.longitude as number,
-                    ]}
-                    updatePositionCallbackFn={(position: LatLngTuple) => {
-                        dispatch(
-                            updateMapMarker({
-                                idx,
-                                item: {
-                                    idx: item.idx,
-                                    active: item.active,
-                                    catalogItem: {
-                                        ...item.catalogItem,
-                                        latitude: position[0],
-                                        longitude: position[1],
-                                    },
-                                },
-                            })
-                        )
-                    }}
-                >
-                    <Fragment>
-                        <Tooltip>
-                            {/* <Typography> */}
-                            {item.catalogItem.name ?? ''}
-                            {/* </Typography> */}
-                        </Tooltip>
-                        <Popup>
-                            <Fragment>
-                                <Stack direction="column" sx={{ m: 0 }}>
-                                    <Typography
-                                        variant="h6"
-                                        fontWeight="bold"
-                                        fontSize={16}
-                                    >
-                                        {formatHtmlStringToReactDom(
-                                            item.catalogItem.name as string
-                                        )}
-                                    </Typography>
-                                    <FormControlLabel
-                                        control={
-                                            <Checkbox
-                                                checked={item.active}
-                                                onChange={() => {
-                                                    dispatch(
-                                                        updateMapMarker({
-                                                            idx,
-                                                            item: {
-                                                                ...item,
-                                                                active: !item.active,
-                                                            },
-                                                        })
-                                                    )
-                                                }}
-                                            />
-                                        }
-                                        labelPlacement="end"
-                                        label="Active"
-                                    />
-                                    <CatalogItemDetailDialog
-                                        itemId={item.catalogItem.id ?? ''}
-                                    />
-                                </Stack>
-                            </Fragment>
-                        </Popup>
-                    </Fragment>
-                </MapMarker>
-            ))
-        )
-    }, [dispatch, displayableMapPoints, idx])
-
-    return (
-        <Fragment>
-            {vertices}
-            {edges}
-        </Fragment>
-    )
-}
-
-export default MapPath
diff --git a/frontend/src/features/TrackingTool/ProcessedText/ProcessedTextDisplay.tsx b/frontend/src/features/TrackingTool/ProcessedText/ProcessedTextDisplay.tsx
new file mode 100644
index 0000000..5a19b8e
--- /dev/null
+++ b/frontend/src/features/TrackingTool/ProcessedText/ProcessedTextDisplay.tsx
@@ -0,0 +1,36 @@
+import { Card, CardContent, Stack, Typography } from '@mui/material'
+import { Fragment } from 'react'
+import { useSelector } from 'react-redux'
+import { formatHtmlStringToReactDom } from '../../../utils/formatting/HtmlUtils'
+import { RootState } from '../../redux/store'
+
+const ProcessedTextDisplay = () => {
+    const pathDto = useSelector(
+        (state: RootState) => state.trackingTool.pathDto
+    )
+
+    return (
+        <Fragment>
+            {pathDto && (
+                <Card variant="outlined" sx={{maxHeight: '50vh'}}>
+                    <CardContent>
+                        <Stack direction="column">
+                            <Typography
+                                variant="h5"
+                                sx={{ mb: 1 }}
+                                fontWeight="600"
+                            >
+                                Processed Text
+                            </Typography>
+                            <Typography variant="body2">
+                                {formatHtmlStringToReactDom(pathDto.text ?? '')}
+                            </Typography>
+                        </Stack>
+                    </CardContent>
+                </Card>
+            )}
+        </Fragment>
+    )
+}
+
+export default ProcessedTextDisplay
diff --git a/frontend/src/features/TrackingTool/TrackingTool.tsx b/frontend/src/features/TrackingTool/TrackingTool.tsx
index a9ca5dc..f938a55 100644
--- a/frontend/src/features/TrackingTool/TrackingTool.tsx
+++ b/frontend/src/features/TrackingTool/TrackingTool.tsx
@@ -13,7 +13,7 @@ import PlaintextUpload from './Upload/PlaintextUpload'
 import FileUpload from './Upload/FileUpload'
 import { Map } from 'leaflet'
 import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
-import MapPath from './MapPath'
+import MapPath from './Map/MapPath'
 import { useDispatch, useSelector } from 'react-redux'
 import { RootState } from '../redux/store'
 import { clear, consumeErr as consumeError } from './trackingToolSlice'
@@ -21,6 +21,8 @@ import { showNotification } from '../Notification/notificationSlice'
 import ClearIcon from '@mui/icons-material/Clear'
 import GeoJsonExportButton from './Upload/GeoJsonExportButton'
 import GeoJsonImportDialog from './Upload/GeoJsonImportDialog'
+import ProcessedTextDisplay from './ProcessedText/ProcessedTextDisplay'
+import DraggableMarkerList from './DraggableList/DraggableMarkerList'
 
 // Page with tracking tool
 const TrackingTool = () => {
@@ -150,28 +152,14 @@ const TrackingTool = () => {
                             <MapPath idx={idx} />
                         ))}
                     </MapContainer>
-                    {pathDto && (
-                        <Fragment>
-                            <Card variant="outlined" sx={{ mt: 2 }}>
-                                <CardContent>
-                                    <Stack direction="column">
-                                        <Typography
-                                            variant="h5"
-                                            sx={{ mb: 1 }}
-                                            fontWeight="600"
-                                        >
-                                            Processed Text
-                                        </Typography>
-                                        <Typography variant="body2">
-                                            {formatHtmlStringToReactDom(
-                                                pathDto.text ?? ''
-                                            )}
-                                        </Typography>
-                                    </Stack>
-                                </CardContent>
-                            </Card>
-                        </Fragment>
-                    )}
+                </Grid>
+                <Grid container sx={{ mt: 1, mb: 20 }} spacing={1}>
+                    <Grid item xs={12} md={6}>
+                        <DraggableMarkerList />
+                    </Grid>
+                    <Grid item xs={12} md={6}>
+                        <ProcessedTextDisplay />
+                    </Grid>
                 </Grid>
             </Grid>
         </Fragment>
diff --git a/frontend/src/features/TrackingTool/TrackingToolState.ts b/frontend/src/features/TrackingTool/TrackingToolState.ts
index b435c34..3ff4359 100644
--- a/frontend/src/features/TrackingTool/TrackingToolState.ts
+++ b/frontend/src/features/TrackingTool/TrackingToolState.ts
@@ -1,6 +1,6 @@
 import { LatLngTuple } from 'leaflet'
 import { PathDto } from '../../swagger/data-contracts'
-import { PathVariant } from './pathUtils'
+import { PathVariant } from './Map/pathUtils'
 
 export default interface TrackingToolState {
     isLoading: boolean // whether the data is being loaded
diff --git a/frontend/src/features/TrackingTool/Upload/GeoJsonExportButton.tsx b/frontend/src/features/TrackingTool/Upload/GeoJsonExportButton.tsx
index 01ff04d..cf71c8a 100644
--- a/frontend/src/features/TrackingTool/Upload/GeoJsonExportButton.tsx
+++ b/frontend/src/features/TrackingTool/Upload/GeoJsonExportButton.tsx
@@ -2,7 +2,7 @@ import { Button } from '@mui/material'
 import { useEffect, useState } from 'react'
 import { useSelector } from 'react-redux'
 import { RootState } from '../../redux/store'
-import { isMapPointDisplayable, PathVariant } from '../pathUtils'
+import { isMapPointDisplayable, PathVariant } from '../Map/pathUtils'
 import { exportAsGeoJsonString } from './GeoJsonIo'
 
 const GeoJsonExportButton = () => {
diff --git a/frontend/src/features/TrackingTool/Upload/GeoJsonImportDialog.tsx b/frontend/src/features/TrackingTool/Upload/GeoJsonImportDialog.tsx
index 054ada4..f23ef8a 100644
--- a/frontend/src/features/TrackingTool/Upload/GeoJsonImportDialog.tsx
+++ b/frontend/src/features/TrackingTool/Upload/GeoJsonImportDialog.tsx
@@ -1,10 +1,8 @@
-import { Button, Dialog, DialogContent, DialogTitle } from '@mui/material'
+import { DialogContent, DialogTitle } from '@mui/material'
 import { useFormik } from 'formik'
-import { Fragment, useState } from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { RootState } from '../../redux/store'
+import { useState } from 'react'
+import { useDispatch } from 'react-redux'
 import ButtonOpenableDialog from '../../Reusables/ButtonOpenableDialog'
-import { PathVariant } from '../pathUtils'
 import * as yup from 'yup'
 import { showNotification } from '../../Notification/notificationSlice'
 import SingleFileSelectionForm from '../../Reusables/SingleFileSelectionForm'
@@ -58,8 +56,9 @@ const GeoJsonImportDialog = () => {
             reader.readAsText(values.file as File)
             reader.onload = async () => {
                 try {
-                    const pathVariant = parseGeoJsonToPathVariant(reader.result as string)
-                    console.log(pathVariant)
+                    const pathVariant = parseGeoJsonToPathVariant(
+                        reader.result as string
+                    )
                     // Merge current path variant with the new one
                     dispatch(mergeWithCurrentPath(pathVariant))
                     onClose()
diff --git a/frontend/src/features/TrackingTool/Upload/GeoJsonIo.ts b/frontend/src/features/TrackingTool/Upload/GeoJsonIo.ts
index 51ce46c..37a828f 100644
--- a/frontend/src/features/TrackingTool/Upload/GeoJsonIo.ts
+++ b/frontend/src/features/TrackingTool/Upload/GeoJsonIo.ts
@@ -1,5 +1,6 @@
-import { isMapPointDisplayable, PathVariant } from '../pathUtils'
+import { isMapPointDisplayable, MapPoint, MapPointType, PathVariant } from '../Map/pathUtils'
 import * as yup from 'yup'
+import generateUuid from '../../../utils/id/uuidGenerator'
 
 export const exportAsGeoJsonString = (path: PathVariant) => JSON.stringify({
     type: 'FeatureCollection',
@@ -28,10 +29,10 @@ export const exportAsGeoJsonString = (path: PathVariant) => JSON.stringify({
 })
 
 const catalogItemValidationSchema = yup.object({
-    id: yup.string().required(),
-    name: yup.string().required(),
-    allNames: yup.array().of(yup.string()).required(),
-    description: yup.string(),
+    id: yup.string().optional(),
+    name: yup.string().optional(),
+    allNames: yup.array().of(yup.string()).optional(),
+    description: yup.string().optional(),
     latitude: yup.number().required(),
     longitude: yup.number().required(),
 })
@@ -52,7 +53,7 @@ export const parseGeoJsonToPathVariant = (geoJson: string) => {
     }
     const path: PathVariant = features.map((feature: any) => {
         const catalogItemDto = feature.properties.catalogItem
-        
+
         if (!catalogItemDto) {
             throw new Error('GeoJson file does not have a valid structure')
         }
@@ -60,6 +61,7 @@ export const parseGeoJsonToPathVariant = (geoJson: string) => {
         const catalogItem = catalogItemValidationSchema.validateSync(catalogItemDto)
 
         return {
+            id: generateUuid(),
             idx: feature.properties.idx,
             active: true,
             catalogItem: {
@@ -69,7 +71,8 @@ export const parseGeoJsonToPathVariant = (geoJson: string) => {
                 latitude: catalogItem.latitude,
                 longitude: catalogItem.longitude,
             },
-        }
+            type: MapPointType.GeoJson,
+        } as MapPoint
     })
     return path
 }
diff --git a/frontend/src/features/TrackingTool/pathUtils.ts b/frontend/src/features/TrackingTool/pathUtils.ts
deleted file mode 100644
index e05187f..0000000
--- a/frontend/src/features/TrackingTool/pathUtils.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// Business logic for tracking tool
-
-import { CatalogItemDto, PathDto } from '../../swagger/data-contracts'
-
-// For more comprehensive code alias CatalogItemDto[] as path variant
-export type PathVariant = MapPoint[]
-
-export interface MapPoint {
-    idx: number,
-    active: boolean,
-    catalogItem: CatalogItemDto,
-}
-
-export const isMapPointDisplayable = (mapPoint: MapPoint): boolean =>
-    !!mapPoint.catalogItem.latitude && !!mapPoint.catalogItem.longitude
-
-/**
- * Cartesian product of two arrays
- * @param sets
- * @returns
- */
-const cartesianProduct = (sets: CatalogItemDto[][]): CatalogItemDto[][] =>
-    sets.reduce<CatalogItemDto[][]>(
-        (results, ids) =>
-            results
-                .map((result) => ids.map((id) => [...result, id]))
-                .reduce((nested, result) => [...nested, ...result]),
-        [[]]
-    )
-
-/**
- * Builds a list of all possible path variants from pathDto
- * @param pathDto
- * @returns
- */
-export const buildPathVariants = (pathDto: PathDto): PathVariant[] => {
-    if (!pathDto.foundCatalogItems) {
-        return []
-    }
-
-    return (
-        pathDto.foundCatalogItems.length === 1
-            ? pathDto.foundCatalogItems
-            : cartesianProduct(pathDto.foundCatalogItems)
-    ).map((variant, _) =>
-        variant.map(
-            (catalogItem, idx) => (
-                {
-                    idx,
-                    active: !!catalogItem.latitude && !!catalogItem.longitude,
-                    catalogItem
-                })
-        )
-    )
-}
-
-export default buildPathVariants
diff --git a/frontend/src/features/TrackingTool/trackingToolSlice.ts b/frontend/src/features/TrackingTool/trackingToolSlice.ts
index 05eddf6..b87f854 100644
--- a/frontend/src/features/TrackingTool/trackingToolSlice.ts
+++ b/frontend/src/features/TrackingTool/trackingToolSlice.ts
@@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit"
 import { LatLngTuple } from "leaflet"
 import mapConfig from "../../config/mapConfig"
 import { PathDto } from "../../swagger/data-contracts"
-import buildPathVariants, { isMapPointDisplayable, MapPoint, PathVariant } from "./pathUtils"
+import buildPathVariants, { isMapPointDisplayable, MapPoint, PathVariant } from "./Map/pathUtils"
 import { sendTextForProcessing } from "./trackingToolThunks"
 import storage from "redux-persist/lib/storage"
 import TrackingToolState from './TrackingToolState'
@@ -60,8 +60,30 @@ export const trackingToolSlice = createSlice({
             ...state,
             currentPage: action.payload,
         }),
-        updateMapMarker: (state: TrackingToolState, action: { payload: { idx: number, item: MapPoint } }) => {
-            const { idx, item } = action.payload
+        // Updates map marker while ignoring its idx property
+        updateMapMarkerWithId: (state: TrackingToolState, action: { payload: { id: string, item: MapPoint } }) => {
+            const { item } = action.payload
+            const idx = state.primaryPathIdx
+            if (!state.pathVariants || state.pathVariants.length <= idx) {
+                return state
+            }
+
+            const mapMarkerIdx = state.pathVariants[idx].findIndex((item) => item.id === action.payload.id)
+            if (mapMarkerIdx === -1) {
+                return state
+            }
+
+            const newPathVariant = [...state.pathVariants[idx]]
+            newPathVariant[mapMarkerIdx] = item
+            return {
+                ...state,
+                pathVariants: [...state.pathVariants.slice(0, idx), newPathVariant, ...state.pathVariants.slice(idx + 1)],
+            }
+        },
+        // Updates map marker based on its idx property
+        updateMapMarker: (state: TrackingToolState, action: { payload: { item: MapPoint } }) => {
+            const { item } = action.payload
+            const idx = state.primaryPathIdx
             if (!state.pathVariants || state.pathVariants.length <= idx) {
                 return state
             }
@@ -81,6 +103,31 @@ export const trackingToolSlice = createSlice({
                 })
             }
         },
+        swapMapMarkerIndices: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
+            const { destination, source } = action.payload
+            if (!state.pathVariants || state.pathVariants.length === 0) {
+                return state
+            }
+
+            return {
+                ...state,
+                pathVariants: state.pathVariants.map((pathVariant, i) => {
+                    if (state.primaryPathIdx !== i) {
+                        return [...pathVariant]
+                    }
+
+                    if (pathVariant.length <= destination || pathVariant.length <= source) {
+                        return [...pathVariant]
+                    }
+
+                    // JS dark magic splice
+                    const result = [...pathVariant]
+                    const [removed] = result.splice(source, 1)
+                    result.splice(destination, 0, removed)
+                    return result
+                })
+            }
+        },
         clear: () => ({ ...initialState }),
         mergeWithCurrentPath: (state: TrackingToolState, action: { payload: PathVariant }) => {
             const { payload: jsonPath } = action
@@ -104,28 +151,28 @@ export const trackingToolSlice = createSlice({
             const pathMap = new Map(path.map((item) => [item.catalogItem.id as string, item]))
 
             // Create an array of items to be replaced and items to be added to the end
-            const itemsToReplace: MapPoint[] = []
+            // const itemsToReplace: MapPoint[] = []
             const itemsToAdd: MapPoint[] = []
             jsonPath.forEach((item) => {
                 if (!pathMap.has(item.catalogItem.id as string)) {
                     itemsToAdd.push(item)
                     return
                 }
-                
-                const idx = pathMap.get(item.catalogItem.id as string)!.idx
-                item.idx = idx
-                itemsToReplace.push(item)
+
+                // const idx = pathMap.get(item.catalogItem.id as string)!.idx
+                // item.idx = idx
+                // itemsToReplace.push(item)
             })
 
             // Iterate over items to replace and replace them
             const newPath = [...path]
-            itemsToReplace.forEach((item) => {
-                newPath[item.idx] = item
-            })
+            // itemsToReplace.forEach((item) => {
+            //     newPath[item.idx] = item
+            // })
 
             // Add items to the end
             itemsToAdd.forEach((item) => {
-                item.active = false
+                item.active = !state.pathVariants || state.pathVariants.length === 0
                 item.idx = newPath.length
                 newPath.push(item)
             })
@@ -176,7 +223,15 @@ export const trackingToolSlice = createSlice({
     },
 })
 
-export const { consumeErr, setPrimaryIdx, resetDialogApiCallSuccess, clear, updateMapMarker, mergeWithCurrentPath } =
-    trackingToolSlice.actions
+export const {
+    consumeErr,
+    setPrimaryIdx,
+    resetDialogApiCallSuccess,
+    clear,
+    updateMapMarker,
+    mergeWithCurrentPath,
+    swapMapMarkerIndices,
+    updateMapMarkerWithId
+} = trackingToolSlice.actions
 const trackingToolReducer = trackingToolSlice.reducer
 export default trackingToolReducer
diff --git a/frontend/src/features/TrackingTool/trackingToolThunks.ts b/frontend/src/features/TrackingTool/trackingToolThunks.ts
index 53a1796..b5005ec 100644
--- a/frontend/src/features/TrackingTool/trackingToolThunks.ts
+++ b/frontend/src/features/TrackingTool/trackingToolThunks.ts
@@ -1,7 +1,7 @@
 import { createAsyncThunk } from '@reduxjs/toolkit'
 import axiosInstance from '../../api/api'
 import { RootState } from '../redux/store'
-import { MapPoint, PathVariant } from './pathUtils'
+import { MapPoint, PathVariant } from './Map/pathUtils'
 
 export const sendTextForProcessing = createAsyncThunk(
     'trackingTool/sendTextForProcessing',
