From d0a3bf9d3e4913ff73136f90981e4bd1b37a2bd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Mon, 23 Oct 2023 15:51:27 +0200
Subject: [PATCH] feat: add map base data

---
 .env                                          |  1 +
 next.config.js                                |  6 +++
 src/constants/index.ts                        |  1 +
 src/models/mapBackground.ts                   | 21 ++++++++++
 src/models/mapModel.ts                        | 19 +++++++++
 src/models/mapOverlay.ts                      | 14 +++++++
 src/models/overviewImageView.ts               | 26 ++++++++++++
 src/models/project.ts                         | 28 +------------
 src/redux/apiPath.ts                          |  7 ++++
 src/redux/backgrounds/background.selectors.ts | 28 +++++++++++++
 src/redux/backgrounds/backgrounds.reducers.ts | 12 ++++++
 src/redux/backgrounds/backgrounds.slice.ts    | 20 ++++++++++
 src/redux/backgrounds/backgrounds.thunks.ts   | 20 ++++++++++
 src/redux/backgrounds/backgrounds.types.ts    |  4 ++
 src/redux/map/map.constants.ts                |  1 +
 src/redux/map/map.types.ts                    |  1 +
 src/redux/models/models.reducers.ts           | 12 ++++++
 src/redux/models/models.selectors.ts          | 13 ++++++
 src/redux/models/models.slice.ts              | 20 ++++++++++
 src/redux/models/models.thunks.ts             | 20 ++++++++++
 src/redux/models/models.types.ts              |  4 ++
 src/redux/overlays/overlays.reducers.ts       | 12 ++++++
 src/redux/overlays/overlays.selectors.ts      |  9 +++++
 src/redux/overlays/overlays.slice.ts          | 20 ++++++++++
 src/redux/overlays/overlays.thunks.ts         | 20 ++++++++++
 src/redux/overlays/overlays.types.ts          |  4 ++
 src/redux/project/project.selectors.ts        |  6 +++
 src/redux/project/project.slice.ts            |  4 +-
 src/redux/project/project.thunks.ts           |  6 +--
 src/redux/project/project.types.ts            |  4 +-
 src/redux/root/mapStages.selectors.ts         | 40 +++++++++++++++++++
 src/redux/store.ts                            | 10 ++++-
 src/types/models.ts                           |  8 ++++
 33 files changed, 386 insertions(+), 35 deletions(-)
 create mode 100644 src/models/mapBackground.ts
 create mode 100644 src/models/mapModel.ts
 create mode 100644 src/models/mapOverlay.ts
 create mode 100644 src/models/overviewImageView.ts
 create mode 100644 src/redux/backgrounds/background.selectors.ts
 create mode 100644 src/redux/backgrounds/backgrounds.reducers.ts
 create mode 100644 src/redux/backgrounds/backgrounds.slice.ts
 create mode 100644 src/redux/backgrounds/backgrounds.thunks.ts
 create mode 100644 src/redux/backgrounds/backgrounds.types.ts
 create mode 100644 src/redux/models/models.reducers.ts
 create mode 100644 src/redux/models/models.selectors.ts
 create mode 100644 src/redux/models/models.slice.ts
 create mode 100644 src/redux/models/models.thunks.ts
 create mode 100644 src/redux/models/models.types.ts
 create mode 100644 src/redux/overlays/overlays.reducers.ts
 create mode 100644 src/redux/overlays/overlays.selectors.ts
 create mode 100644 src/redux/overlays/overlays.slice.ts
 create mode 100644 src/redux/overlays/overlays.thunks.ts
 create mode 100644 src/redux/overlays/overlays.types.ts
 create mode 100644 src/redux/project/project.selectors.ts
 create mode 100644 src/redux/root/mapStages.selectors.ts

diff --git a/.env b/.env
index 88c76981..099bf13e 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,4 @@
 
 NEXT_PUBLIC_BASE_API_URL = 'https://corsproxy.io/?https://pdmap.uni.lu/minerva/api'
+BASE_MAP_IMAGES_URL = 'https://pdmap.uni.lu'
 NEXT_PUBLIC_PROJECT_ID = 'pd_map_winter_23'
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index 254ffd6c..1b307ecc 100644
--- a/next.config.js
+++ b/next.config.js
@@ -4,6 +4,12 @@ const nextConfig = {
   experimental: {
     fontLoaders: [{ loader: '@next/font/google', options: { subsets: ['latin'] } }],
   },
+  env: {
+    BASE_API_URL: process.env.NEXT_PUBLIC_BASE_API_URL || '',
+    BASE_MAP_IMAGES_URL: process.env.BASE_MAP_IMAGES_URL || '',
+    PROJECT_ID: process.env.NEXT_PUBLIC_PROJECT_ID || '',
+    ZOD_SEED: 997,
+  },
 };
 
 module.exports = nextConfig;
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 43423ae1..2b1791dc 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,3 +1,4 @@
 export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || '';
+export const BASE_MAP_IMAGES_URL = process.env.BASE_MAP_IMAGES_URL || '';
 export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || '';
 export const ZOD_SEED = 997;
diff --git a/src/models/mapBackground.ts b/src/models/mapBackground.ts
new file mode 100644
index 00000000..a8a96052
--- /dev/null
+++ b/src/models/mapBackground.ts
@@ -0,0 +1,21 @@
+import { z } from 'zod';
+
+export const mapBackground = z.object({
+  id: z.number(),
+  name: z.string(),
+  defaultOverlay: z.boolean(),
+  project: z.object({ projectId: z.string() }),
+  creator: z.object({ login: z.string() }),
+  status: z.string(),
+  progress: z.number(),
+  description: z.null(),
+  order: z.number(),
+  images: z.array(
+    z.object({
+      id: z.number(),
+      model: z.object({ id: z.number() }),
+      projectBackground: z.object({ id: z.number() }),
+      path: z.string(),
+    }),
+  ),
+});
diff --git a/src/models/mapModel.ts b/src/models/mapModel.ts
new file mode 100644
index 00000000..e50dd6c4
--- /dev/null
+++ b/src/models/mapModel.ts
@@ -0,0 +1,19 @@
+import { z } from 'zod';
+
+export const mapModel = z.object({
+  idObject: z.number(),
+  width: z.number(),
+  height: z.number(),
+  defaultCenterX: z.number().nullable(),
+  defaultCenterY: z.number().nullable(),
+  description: z.string(),
+  name: z.string(),
+  defaultZoomLevel: z.number().nullable(),
+  tileSize: z.number(),
+  references: z.array(z.unknown()),
+  authors: z.array(z.unknown()),
+  creationDate: z.unknown(),
+  modificationDates: z.array(z.unknown()),
+  minZoom: z.number(),
+  maxZoom: z.number(),
+});
diff --git a/src/models/mapOverlay.ts b/src/models/mapOverlay.ts
new file mode 100644
index 00000000..b76cd45a
--- /dev/null
+++ b/src/models/mapOverlay.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod';
+
+export const mapOverlay = z.object({
+  name: z.string(),
+  googleLicenseConsent: z.boolean(),
+  creator: z.string(),
+  description: z.string(),
+  genomeType: z.null(),
+  genomeVersion: z.null(),
+  idObject: z.number(),
+  publicOverlay: z.boolean(),
+  type: z.string(),
+  order: z.number(),
+});
diff --git a/src/models/overviewImageView.ts b/src/models/overviewImageView.ts
new file mode 100644
index 00000000..82587a89
--- /dev/null
+++ b/src/models/overviewImageView.ts
@@ -0,0 +1,26 @@
+import { z } from 'zod';
+
+export const overviewImageView = z.object({
+  idObject: z.number(),
+  filename: z.string(),
+  width: z.number(),
+  height: z.number(),
+  links: z.array(
+    z.union([
+      z.object({
+        idObject: z.number(),
+        polygon: z.array(z.object({ x: z.number(), y: z.number() })),
+        imageLinkId: z.number(),
+        type: z.string(),
+      }),
+      z.object({
+        idObject: z.number(),
+        polygon: z.array(z.object({ x: z.number(), y: z.number() })),
+        zoomLevel: z.number(),
+        modelPoint: z.object({ x: z.number(), y: z.number() }),
+        modelLinkId: z.number(),
+        type: z.string(),
+      }),
+    ]),
+  ),
+});
diff --git a/src/models/project.ts b/src/models/project.ts
index 051e5ca1..9f763751 100644
--- a/src/models/project.ts
+++ b/src/models/project.ts
@@ -1,6 +1,7 @@
 import { z } from 'zod';
 import { disease } from './disease';
 import { organism } from './organism';
+import { overviewImageView } from './overviewImageView';
 
 export const projectSchema = z.object({
   version: z.string(),
@@ -18,30 +19,5 @@ export const projectSchema = z.object({
   projectId: z.string(),
   creationDate: z.string(),
   mapCanvasType: z.string(),
-  overviewImageViews: z.array(
-    z.object({
-      idObject: z.number(),
-      filename: z.string(),
-      width: z.number(),
-      height: z.number(),
-      links: z.array(
-        z.union([
-          z.object({
-            idObject: z.number(),
-            polygon: z.array(z.object({ x: z.number(), y: z.number() })),
-            imageLinkId: z.number(),
-            type: z.string(),
-          }),
-          z.object({
-            idObject: z.number(),
-            polygon: z.array(z.object({ x: z.number(), y: z.number() })),
-            zoomLevel: z.number(),
-            modelPoint: z.object({ x: z.number(), y: z.number() }),
-            modelLinkId: z.number(),
-            type: z.string(),
-          }),
-        ]),
-      ),
-    }),
-  ),
+  overviewImageViews: z.array(overviewImageView),
 });
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index 8469a3d1..28512d6a 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -9,4 +9,11 @@ export const apiPath = {
     `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`,
   getChemicalsStringWithQuery: (searchQuery: string): string =>
     `projects/${PROJECT_ID}/chemicals:search?query=${searchQuery}`,
+  getAllModelsByProjectIdQuery: (projectId: string): string => `projects/${projectId}/models/*/`,
+  getAllOverlaysByProjectIdQuery: (
+    projectId: string,
+    { publicOverlay }: { publicOverlay: boolean },
+  ): string => `projects/${projectId}/overlays/?publicOverlay=${String(publicOverlay)}`,
+  getAllBackgroundsByProjectIdQuery: (projectId: string): string =>
+    `projects/${projectId}/backgrounds/`,
 };
diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts
new file mode 100644
index 00000000..01b6ed81
--- /dev/null
+++ b/src/redux/backgrounds/background.selectors.ts
@@ -0,0 +1,28 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { mapDataSelector } from '../map/map.selectors';
+import { rootSelector } from '../root/root.selectors';
+
+export const backgroundsSelector = createSelector(rootSelector, state => state.backgrounds);
+
+export const backgroundsDataSelector = createSelector(
+  backgroundsSelector,
+  backgrounds => backgrounds.data || [],
+);
+
+export const currentBackgroundSelector = createSelector(
+  backgroundsDataSelector,
+  mapDataSelector,
+  (backgrounds, mapData) => backgrounds.find(background => background.id === mapData.backgroundId),
+);
+
+export const currentBackgroundImageSelector = createSelector(
+  mapDataSelector,
+  currentBackgroundSelector,
+  (mapData, background) =>
+    background ? background.images.find(image => image.model.id === mapData.modelId) : undefined,
+);
+
+export const currentBackgroundImagePathSelector = createSelector(
+  currentBackgroundImageSelector,
+  image => (image ? image.path : ''),
+);
diff --git a/src/redux/backgrounds/backgrounds.reducers.ts b/src/redux/backgrounds/backgrounds.reducers.ts
new file mode 100644
index 00000000..6c099f17
--- /dev/null
+++ b/src/redux/backgrounds/backgrounds.reducers.ts
@@ -0,0 +1,12 @@
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { getAllBackgroundsByProjectId } from './backgrounds.thunks';
+import { BackgroundsState } from './backgrounds.types';
+
+export const getAllBackgroundsByProjectIdReducer = (
+  builder: ActionReducerMapBuilder<BackgroundsState>,
+): void => {
+  builder.addCase(getAllBackgroundsByProjectId.fulfilled, (state, action) => {
+    state.data = action.payload || [];
+    state.loading = 'succeeded';
+  });
+};
diff --git a/src/redux/backgrounds/backgrounds.slice.ts b/src/redux/backgrounds/backgrounds.slice.ts
new file mode 100644
index 00000000..491e981d
--- /dev/null
+++ b/src/redux/backgrounds/backgrounds.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { getAllBackgroundsByProjectIdReducer } from './backgrounds.reducers';
+import { BackgroundsState } from './backgrounds.types';
+
+const initialState: BackgroundsState = {
+  data: [],
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+const backgroundsState = createSlice({
+  name: 'backgrounds',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    getAllBackgroundsByProjectIdReducer(builder);
+  },
+});
+
+export default backgroundsState.reducer;
diff --git a/src/redux/backgrounds/backgrounds.thunks.ts b/src/redux/backgrounds/backgrounds.thunks.ts
new file mode 100644
index 00000000..ee0b860f
--- /dev/null
+++ b/src/redux/backgrounds/backgrounds.thunks.ts
@@ -0,0 +1,20 @@
+import { mapBackground } from '@/models/mapBackground';
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { MapBackground } from '@/types/models';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { z } from 'zod';
+import { apiPath } from '../apiPath';
+
+export const getAllBackgroundsByProjectId = createAsyncThunk(
+  'models/getAllBackgroundsByProjectId',
+  async (projectId: string): Promise<MapBackground[]> => {
+    const response = await axiosInstance.get<MapBackground[]>(
+      apiPath.getAllBackgroundsByProjectIdQuery(projectId),
+    );
+
+    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapBackground));
+
+    return isDataValid ? response.data : [];
+  },
+);
diff --git a/src/redux/backgrounds/backgrounds.types.ts b/src/redux/backgrounds/backgrounds.types.ts
new file mode 100644
index 00000000..5457757c
--- /dev/null
+++ b/src/redux/backgrounds/backgrounds.types.ts
@@ -0,0 +1,4 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { MapBackground } from '@/types/models';
+
+export type BackgroundsState = FetchDataState<MapBackground[] | []>;
diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts
index 19bf5fb5..a6307772 100644
--- a/src/redux/map/map.constants.ts
+++ b/src/redux/map/map.constants.ts
@@ -11,6 +11,7 @@ export const MAP_DATA_INITIAL_STATE: MapData = {
   projectId: PROJECT_ID,
   meshId: '',
   modelId: 0,
+  backgroundId: 0,
   overlaysIds: [],
   position: DEFAULT_CENTER_POINT,
   show: {
diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts
index 95fafc9a..ac46d776 100644
--- a/src/redux/map/map.types.ts
+++ b/src/redux/map/map.types.ts
@@ -13,6 +13,7 @@ export type MapData = {
   projectId: string;
   meshId: string;
   modelId: number;
+  backgroundId: number;
   overlaysIds: number[];
   size: MapSize;
   position: Point;
diff --git a/src/redux/models/models.reducers.ts b/src/redux/models/models.reducers.ts
new file mode 100644
index 00000000..7c8f1849
--- /dev/null
+++ b/src/redux/models/models.reducers.ts
@@ -0,0 +1,12 @@
+import { getAllModelsByProjectId } from '@/redux/models/models.thunks';
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { ModelsState } from './models.types';
+
+export const getAllModelsByProjectIdReducer = (
+  builder: ActionReducerMapBuilder<ModelsState>,
+): void => {
+  builder.addCase(getAllModelsByProjectId.fulfilled, (state, action) => {
+    state.data = action.payload || [];
+    state.loading = 'succeeded';
+  });
+};
diff --git a/src/redux/models/models.selectors.ts b/src/redux/models/models.selectors.ts
new file mode 100644
index 00000000..ec3bbe24
--- /dev/null
+++ b/src/redux/models/models.selectors.ts
@@ -0,0 +1,13 @@
+import { rootSelector } from '@/redux/root/root.selectors';
+import { createSelector } from '@reduxjs/toolkit';
+import { mapDataSelector } from '../map/map.selectors';
+
+export const modelsSelector = createSelector(rootSelector, state => state.models);
+
+export const modelsDataSelector = createSelector(modelsSelector, models => models.data || []);
+
+export const currentModelSelector = createSelector(
+  modelsSelector,
+  mapDataSelector,
+  (models, mapData) => models.find(model => model.idObject === mapData.modelId),
+);
diff --git a/src/redux/models/models.slice.ts b/src/redux/models/models.slice.ts
new file mode 100644
index 00000000..f47b5aa8
--- /dev/null
+++ b/src/redux/models/models.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { getAllModelsByProjectIdReducer } from './models.reducers';
+import { ModelsState } from './models.types';
+
+const initialState: ModelsState = {
+  data: [],
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+const modelsSlice = createSlice({
+  name: 'models',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    getAllModelsByProjectIdReducer(builder);
+  },
+});
+
+export default modelsSlice.reducer;
diff --git a/src/redux/models/models.thunks.ts b/src/redux/models/models.thunks.ts
new file mode 100644
index 00000000..c7cb548e
--- /dev/null
+++ b/src/redux/models/models.thunks.ts
@@ -0,0 +1,20 @@
+import { mapModel } from '@/models/mapModel';
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { MapModel } from '@/types/models';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { z } from 'zod';
+import { apiPath } from '../apiPath';
+
+export const getAllModelsByProjectId = createAsyncThunk(
+  'models/getAllModelsByProjectId',
+  async (projectId: string): Promise<MapModel[]> => {
+    const response = await axiosInstance.get<MapModel[]>(
+      apiPath.getAllModelsByProjectIdQuery(projectId),
+    );
+
+    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapModel));
+
+    return isDataValid ? response.data : [];
+  },
+);
diff --git a/src/redux/models/models.types.ts b/src/redux/models/models.types.ts
new file mode 100644
index 00000000..06bd1892
--- /dev/null
+++ b/src/redux/models/models.types.ts
@@ -0,0 +1,4 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { MapModel } from '@/types/models';
+
+export type ModelsState = FetchDataState<MapModel[] | []>;
diff --git a/src/redux/overlays/overlays.reducers.ts b/src/redux/overlays/overlays.reducers.ts
new file mode 100644
index 00000000..09863b89
--- /dev/null
+++ b/src/redux/overlays/overlays.reducers.ts
@@ -0,0 +1,12 @@
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { getAllPublicOverlaysByProjectId } from './overlays.thunks';
+import { OverlaysState } from './overlays.types';
+
+export const getAllPublicOverlaysByProjectIdReducer = (
+  builder: ActionReducerMapBuilder<OverlaysState>,
+): void => {
+  builder.addCase(getAllPublicOverlaysByProjectId.fulfilled, (state, action) => {
+    state.data = action.payload || [];
+    state.loading = 'succeeded';
+  });
+};
diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts
new file mode 100644
index 00000000..80b0439b
--- /dev/null
+++ b/src/redux/overlays/overlays.selectors.ts
@@ -0,0 +1,9 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { rootSelector } from '../root/root.selectors';
+
+export const overlaysSelector = createSelector(rootSelector, state => state.overlays);
+
+export const overlaysDataSelector = createSelector(
+  overlaysSelector,
+  overlays => overlays.data || [],
+);
diff --git a/src/redux/overlays/overlays.slice.ts b/src/redux/overlays/overlays.slice.ts
new file mode 100644
index 00000000..8d259288
--- /dev/null
+++ b/src/redux/overlays/overlays.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { getAllPublicOverlaysByProjectIdReducer } from './overlays.reducers';
+import { OverlaysState } from './overlays.types';
+
+const initialState: OverlaysState = {
+  data: [],
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+const overlaysState = createSlice({
+  name: 'overlays',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    getAllPublicOverlaysByProjectIdReducer(builder);
+  },
+});
+
+export default overlaysState.reducer;
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
new file mode 100644
index 00000000..3dc3c70e
--- /dev/null
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -0,0 +1,20 @@
+import { mapOverlay } from '@/models/mapOverlay';
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { MapOverlay } from '@/types/models';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { z } from 'zod';
+import { apiPath } from '../apiPath';
+
+export const getAllPublicOverlaysByProjectId = createAsyncThunk(
+  'models/getAllPublicOverlaysByProjectId',
+  async (projectId: string): Promise<MapOverlay[]> => {
+    const response = await axiosInstance.get<MapOverlay[]>(
+      apiPath.getAllOverlaysByProjectIdQuery(projectId, { publicOverlay: true }),
+    );
+
+    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
+
+    return isDataValid ? response.data : [];
+  },
+);
diff --git a/src/redux/overlays/overlays.types.ts b/src/redux/overlays/overlays.types.ts
new file mode 100644
index 00000000..ee00e945
--- /dev/null
+++ b/src/redux/overlays/overlays.types.ts
@@ -0,0 +1,4 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { MapOverlay } from '@/types/models';
+
+export type OverlaysState = FetchDataState<MapOverlay[] | []>;
diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts
new file mode 100644
index 00000000..9ba0ec03
--- /dev/null
+++ b/src/redux/project/project.selectors.ts
@@ -0,0 +1,6 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { rootSelector } from '../root/root.selectors';
+
+export const projectSelector = createSelector(rootSelector, state => state.project);
+
+export const projectDataSelector = createSelector(projectSelector, project => project.data);
diff --git a/src/redux/project/project.slice.ts b/src/redux/project/project.slice.ts
index 994cb484..a6e0d9d0 100644
--- a/src/redux/project/project.slice.ts
+++ b/src/redux/project/project.slice.ts
@@ -1,9 +1,9 @@
-import { createSlice } from '@reduxjs/toolkit';
 import { ProjectState } from '@/redux/project/project.types';
+import { createSlice } from '@reduxjs/toolkit';
 import { getProjectByIdReducer } from './project.reducers';
 
 const initialState: ProjectState = {
-  data: [],
+  data: undefined,
   loading: 'idle',
   error: { name: '', message: '' },
 };
diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts
index 944895cf..d99f5d55 100644
--- a/src/redux/project/project.thunks.ts
+++ b/src/redux/project/project.thunks.ts
@@ -1,11 +1,11 @@
-import { createAsyncThunk } from '@reduxjs/toolkit';
+import { projectSchema } from '@/models/project';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { Project } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
-import { projectSchema } from '@/models/project';
+import { createAsyncThunk } from '@reduxjs/toolkit';
 
 export const getProjectById = createAsyncThunk(
-  'project/getUsersByIdStatus',
+  'project/getProjectById',
   async (id: string): Promise<Project | undefined> => {
     const response = await axiosInstance.get<Project>(`projects/${id}`);
 
diff --git a/src/redux/project/project.types.ts b/src/redux/project/project.types.ts
index f88c4b6b..c92be2c4 100644
--- a/src/redux/project/project.types.ts
+++ b/src/redux/project/project.types.ts
@@ -1,8 +1,8 @@
-import { Project } from '@/types/models';
 import { Loading } from '@/types/loadingState';
+import { Project } from '@/types/models';
 
 export type ProjectState = {
-  data: Project | undefined | [];
+  data: Project | undefined;
   loading: Loading;
   error: Error;
 };
diff --git a/src/redux/root/mapStages.selectors.ts b/src/redux/root/mapStages.selectors.ts
new file mode 100644
index 00000000..cda0f3b2
--- /dev/null
+++ b/src/redux/root/mapStages.selectors.ts
@@ -0,0 +1,40 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { backgroundsSelector } from '../backgrounds/background.selectors';
+import { modelsSelector } from '../models/models.selectors';
+import { overlaysSelector } from '../overlays/overlays.selectors';
+import { projectSelector } from '../project/project.selectors';
+
+export const mapLoadingFirstStageInitializedSelector = createSelector(
+  projectSelector,
+  project => project.loading !== 'idle',
+);
+
+export const mapLoadingFirstStageCompletedSelector = createSelector(
+  projectSelector,
+  project => project.loading === 'succeeded',
+);
+
+export const mapLoadingSecondStageInitializedSelector = createSelector(
+  backgroundsSelector,
+  modelsSelector,
+  overlaysSelector,
+  (backgrounds, models, overlays) =>
+    [backgrounds.loading, models.loading, overlays.loading].every(loading => loading !== 'idle'),
+);
+
+export const mapLoadingSecondStageCompletedSelector = createSelector(
+  backgroundsSelector,
+  modelsSelector,
+  overlaysSelector,
+  (backgrounds, models, overlays) =>
+    [backgrounds.loading, models.loading, overlays.loading].every(
+      loading => loading === 'succeeded',
+    ),
+);
+
+export const mapLoadingAllStagesCompletedSelector = createSelector(
+  mapLoadingFirstStageCompletedSelector,
+  mapLoadingSecondStageCompletedSelector,
+  (firstStageCompleted, secondStageCompleted) =>
+    [firstStageCompleted, secondStageCompleted].every(completed => completed === true),
+);
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 0136870f..556abf07 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -1,17 +1,23 @@
 import drawerReducer from '@/redux/drawer/drawer.slice';
 import drugsReducer from '@/redux/drugs/drugs.slice';
 import mapReducer from '@/redux/map/map.slice';
-import projectSlice from '@/redux/project/project.slice';
+import projectReducer from '@/redux/project/project.slice';
 import searchReducer from '@/redux/search/search.slice';
 import { configureStore } from '@reduxjs/toolkit';
+import backgroundsReducer from './backgrounds/backgrounds.slice';
+import modelsReducer from './models/models.slice';
+import overlaysReducer from './overlays/overlays.slice';
 
 export const store = configureStore({
   reducer: {
     search: searchReducer,
-    project: projectSlice,
+    project: projectReducer,
     drugs: drugsReducer,
     drawer: drawerReducer,
     map: mapReducer,
+    backgrounds: backgroundsReducer,
+    overlays: overlaysReducer,
+    models: modelsReducer,
   },
   devTools: true,
 });
diff --git a/src/types/models.ts b/src/types/models.ts
index abadf02a..6b290755 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -2,12 +2,20 @@ import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
 import { chemicalSchema } from '@/models/chemicalSchema';
 import { disease } from '@/models/disease';
 import { drugSchema } from '@/models/drugSchema';
+import { mapModel } from '@/models/mapModel';
+import { mapOverlay } from '@/models/mapOverlay';
 import { mirnaSchema } from '@/models/mirnaSchema';
 import { organism } from '@/models/organism';
+import { overviewImageView } from '@/models/overviewImageView';
 import { projectSchema } from '@/models/project';
 import { z } from 'zod';
+import { mapBackground } from '../models/mapBackground';
 
 export type Project = z.infer<typeof projectSchema>;
+export type OverviewImageView = z.infer<typeof overviewImageView>;
+export type MapModel = z.infer<typeof mapModel>;
+export type MapOverlay = z.infer<typeof mapOverlay>;
+export type MapBackground = z.infer<typeof mapBackground>;
 export type Organism = z.infer<typeof organism>;
 export type Disease = z.infer<typeof disease>;
 export type Drug = z.infer<typeof drugSchema>;
-- 
GitLab