From d4980e3168b579bb7dd5f341600ad1446a9a214f Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Wed, 25 Oct 2023 12:51:02 +0200 Subject: [PATCH] Nomalize Redux & RTK architecture --- .../publicapi/ApiMissionsController.kt | 2 +- ...ayersAPI.js => administrativeLayersAPI.ts} | 0 frontend/src/api/ampsAPI.ts | 15 ++- frontend/src/api/api.ts | 15 ++- frontend/src/api/controlThemesAPI.ts | 12 +-- frontend/src/api/departmentAreasAPI.ts | 4 +- frontend/src/api/infractionsAPI.ts | 10 +- frontend/src/api/missionsAPI.ts | 21 ++--- .../src/api/{photonAPI.js => photonAPI.ts} | 2 +- ...oryLayersAPI.js => regulatoryLayersAPI.ts} | 16 ++-- frontend/src/api/reportingsAPI.ts | 23 ++--- frontend/src/api/semaphoresAPI.ts | 11 +-- frontend/src/domain/shared_slices/index.ts | 91 ------------------- .../usesCases/deleteControlUnitContact.ts | 4 +- .../usesCases/deleteControlUnitResource.ts | 4 +- .../useCases/handleModalConfirmation.ts | 4 +- frontend/src/hooks/useAppDispatch.ts | 4 +- frontend/src/store/index.ts | 23 ++--- frontend/src/store/reducers.ts | 75 +++++++++++++++ 19 files changed, 157 insertions(+), 179 deletions(-) rename frontend/src/api/{administrativeLayersAPI.js => administrativeLayersAPI.ts} (100%) rename frontend/src/api/{photonAPI.js => photonAPI.ts} (95%) rename frontend/src/api/{regulatoryLayersAPI.js => regulatoryLayersAPI.ts} (82%) delete mode 100644 frontend/src/domain/shared_slices/index.ts create mode 100644 frontend/src/store/reducers.ts diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiMissionsController.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiMissionsController.kt index 574747425..babf5e00a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiMissionsController.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiMissionsController.kt @@ -22,7 +22,7 @@ class ApiMissionsController( private val getMissionById: GetMissionById, private val deleteMission: DeleteMission, private val getEngagedControlUnits: GetEngagedControlUnits, - private val getMissionsByIds: GetMissionsByIds + private val getMissionsByIds: GetMissionsByIds, ) { @GetMapping("") diff --git a/frontend/src/api/administrativeLayersAPI.js b/frontend/src/api/administrativeLayersAPI.ts similarity index 100% rename from frontend/src/api/administrativeLayersAPI.js rename to frontend/src/api/administrativeLayersAPI.ts diff --git a/frontend/src/api/ampsAPI.ts b/frontend/src/api/ampsAPI.ts index 324366463..68d8775d5 100644 --- a/frontend/src/api/ampsAPI.ts +++ b/frontend/src/api/ampsAPI.ts @@ -1,7 +1,7 @@ import { type EntityState, createEntityAdapter, type Middleware } from '@reduxjs/toolkit' -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { boundingExtent } from 'ol/extent' +import { monitorenvPrivateApi } from './api' import { setToast } from '../domain/shared_slices/Global' import type { AMP, AMPFromAPI } from '../domain/entities/AMPs' @@ -11,11 +11,10 @@ const AMPAdapter = createEntityAdapter() const initialState = AMPAdapter.getInitialState() -export const ampsAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), - endpoints: build => ({ - getAMPs: build.query, void>({ - query: () => `amps`, +export const ampsAPI = monitorenvPrivateApi.injectEndpoints({ + endpoints: builder => ({ + getAMPs: builder.query, void>({ + query: () => `/v1/amps`, transformResponse: (response: AMPFromAPI[]) => AMPAdapter.setAll( initialState, @@ -29,10 +28,10 @@ export const ampsAPI = createApi({ }) ) }) - }), - reducerPath: 'amps' + }) }) +// TODO Migrate this middleware. export const ampsErrorLoggerMiddleware: Middleware = store => next => action => { if (ampsAPI.endpoints.getAMPs.matchRejected(action)) { store.dispatch(setToast({ message: "Nous n'avons pas pu récupérer les Zones AMP" })) diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index fc5a055af..de4eb47a7 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -1,15 +1,26 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { GEOSERVER_REMOTE_URL } from '../env' import { normalizeRtkBaseQuery } from '../utils/normalizeRtkBaseQuery' import type { BackendApiErrorResponse } from './types' +// ============================================================================= +// GeoServer API + +export const geoserverApi = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: `${GEOSERVER_REMOTE_URL}/geoserver` }), + endpoints: () => ({}), + reducerPath: 'monitorenvPrivateApi', + tagTypes: ['Missions', 'Reportings'] +}) + // ============================================================================= // Monitorenv Private API // We'll need that later on for authentication. const monitorenvPrivateApiBaseQuery = fetchBaseQuery({ - baseUrl: '/bff/v1' + baseUrl: '/bff' }) export const monitorenvPrivateApi = createApi({ baseQuery: async (args, api, extraOptions) => { @@ -27,7 +38,7 @@ export const monitorenvPrivateApi = createApi({ }, endpoints: () => ({}), reducerPath: 'monitorenvPrivateApi', - tagTypes: [] + tagTypes: ['Missions', 'Reportings'] }) // ============================================================================= diff --git a/frontend/src/api/controlThemesAPI.ts b/frontend/src/api/controlThemesAPI.ts index 960ef04a5..64f5b646f 100644 --- a/frontend/src/api/controlThemesAPI.ts +++ b/frontend/src/api/controlThemesAPI.ts @@ -1,18 +1,16 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { monitorenvPrivateApi } from './api' import type { ControlTheme } from '../domain/entities/controlThemes' -export const controlThemesAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), +export const controlThemesAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: build => ({ getControlTheme: build.query({ - query: ({ id }) => `controlthemes/${id}` + query: ({ id }) => `/v1/controlthemes/${id}` }), getControlThemes: build.query({ - query: () => `controlthemes` + query: () => `/v1/controlthemes` }) - }), - reducerPath: 'controlThemes' + }) }) export const { useGetControlThemesQuery } = controlThemesAPI diff --git a/frontend/src/api/departmentAreasAPI.ts b/frontend/src/api/departmentAreasAPI.ts index 5c4007318..0d6fb3b88 100644 --- a/frontend/src/api/departmentAreasAPI.ts +++ b/frontend/src/api/departmentAreasAPI.ts @@ -9,12 +9,12 @@ const GET_DEPARTMENT_AREAS_ERROR_MESSAGE = "Nous n'avons pas pu récupérer la l export const departmentAreasAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: builder => ({ getDepartmentArea: builder.query({ - query: departmentAreaId => `/department_areas/${departmentAreaId}`, + query: departmentAreaId => `/v1/department_areas/${departmentAreaId}`, transformErrorResponse: response => new FrontendApiError(GET_DEPARTMENT_AREA_ERROR_MESSAGE, response) }), getDepartmentAreas: builder.query({ - query: () => `/department_areas`, + query: () => `/v1/department_areas`, transformErrorResponse: response => new FrontendApiError(GET_DEPARTMENT_AREAS_ERROR_MESSAGE, response) }) }) diff --git a/frontend/src/api/infractionsAPI.ts b/frontend/src/api/infractionsAPI.ts index 5f88ea98a..4b4d7cf15 100644 --- a/frontend/src/api/infractionsAPI.ts +++ b/frontend/src/api/infractionsAPI.ts @@ -1,15 +1,13 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { monitorenvPrivateApi } from './api' import type { NatinfType } from '../domain/entities/natinfs' -export const infractionsAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), +export const infractionsAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: build => ({ getInfractions: build.query({ - query: () => `natinfs` + query: () => `/v1/natinfs` }) - }), - reducerPath: 'natinfs' + }) }) export const { useGetInfractionsQuery } = infractionsAPI diff --git a/frontend/src/api/missionsAPI.ts b/frontend/src/api/missionsAPI.ts index b542872d1..c09bd220b 100644 --- a/frontend/src/api/missionsAPI.ts +++ b/frontend/src/api/missionsAPI.ts @@ -1,6 +1,4 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' - -import { monitorenvPublicApi } from './api' +import { monitorenvPrivateApi, monitorenvPublicApi } from './api' import { ControlUnit } from '../domain/entities/controlUnit' import type { Mission } from '../domain/entities/missions' @@ -27,27 +25,26 @@ const getMissionTypesFilter = missionTypes => const getSeaFrontsFilter = seaFronts => seaFronts && seaFronts?.length > 0 && `seaFronts=${encodeURIComponent(seaFronts)}` -export const missionsAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), +export const missionsAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: build => ({ createMission: build.mutation>({ invalidatesTags: [{ id: 'LIST', type: 'Missions' }], query: mission => ({ body: mission, method: 'PUT', - url: `missions` + url: `/v1/missions` }) }), deleteMission: build.mutation({ invalidatesTags: [{ id: 'LIST', type: 'Missions' }], query: ({ id }) => ({ method: 'DELETE', - url: `missions/${id}` + url: `/v1/missions/${id}` }) }), getMission: build.query({ providesTags: (_, __, id) => [{ id, type: 'Missions' }], - query: id => `missions/${id}` + query: id => `/v1/missions/${id}` }), getMissions: build.query({ providesTags: result => @@ -58,7 +55,7 @@ export const missionsAPI = createApi({ [{ id: 'LIST', type: 'Missions' }], query: filter => [ - 'missions?', + '/v1/missions?', getStartDateFilter(filter?.startedAfterDateTime), getEndDateFilter(filter?.startedBeforeDateTime), getMissionSourceFilter(filter?.missionSource), @@ -78,12 +75,10 @@ export const missionsAPI = createApi({ query: ({ id, ...patch }) => ({ body: { id, ...patch }, method: 'PUT', - url: `missions/${id}` + url: `/v1/missions/${id}` }) }) - }), - reducerPath: 'missions', - tagTypes: ['Missions'] + }) }) export const publicMissionsAPI = monitorenvPublicApi.injectEndpoints({ diff --git a/frontend/src/api/photonAPI.js b/frontend/src/api/photonAPI.ts similarity index 95% rename from frontend/src/api/photonAPI.js rename to frontend/src/api/photonAPI.ts index a3b78148b..df18c1a56 100644 --- a/frontend/src/api/photonAPI.js +++ b/frontend/src/api/photonAPI.ts @@ -7,7 +7,7 @@ export const usePhotonAPI = (search, { lang = 'fr', latlon = undefined, limit = useEffect(() => { if (search) { - let searchParams = { + let searchParams: Record = { lang, limit, q: search diff --git a/frontend/src/api/regulatoryLayersAPI.js b/frontend/src/api/regulatoryLayersAPI.ts similarity index 82% rename from frontend/src/api/regulatoryLayersAPI.js rename to frontend/src/api/regulatoryLayersAPI.ts index 974d3e7c9..61f50613c 100644 --- a/frontend/src/api/regulatoryLayersAPI.js +++ b/frontend/src/api/regulatoryLayersAPI.ts @@ -1,5 +1,4 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' - +import { geoserverApi } from './api' import { Layers } from '../domain/entities/layers/constants' import { GEOSERVER_NAMESPACE, GEOSERVER_REMOTE_URL } from '../env' @@ -18,18 +17,21 @@ export function getAllRegulatoryLayersFromAPI() { return response.json() } response.text().then(text => { + // TODO Is this console log necessary? + // eslint-disable-next-line no-console console.error(text) }) throw Error(REGULATORY_ZONES_ERROR_MESSAGE) }) .catch(error => { + // TODO Is this console log necessary? + // eslint-disable-next-line no-console console.error(error) throw Error(REGULATORY_ZONES_ERROR_MESSAGE) }) } -export const regulatoryLayersAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: `${GEOSERVER_REMOTE_URL}/geoserver/` }), +export const regulatoryLayersAPI = geoserverApi.injectEndpoints({ endpoints: build => ({ getRegulatoryLayer: build.query({ query: ({ id }) => ({ @@ -44,14 +46,14 @@ export const regulatoryLayersAPI = createApi({ }, url: 'wfs' }), - transformResponse: response => response?.features[0] + // TODO Type that. + transformResponse: (response: any) => response?.features[0] }), getRegulatoryLayers: build.query({ query: () => `&propertyName=entity_name,url,layer_name,facade,ref_reg,observation,thematique,echelle,date,duree_validite,date_fin,temporalite,action,objet,type,signataire,geom` }) - }), - reducerPath: 'regulatoryLayers' + }) }) export const { useGetRegulatoryLayerQuery, useGetRegulatoryLayersQuery } = regulatoryLayersAPI diff --git a/frontend/src/api/reportingsAPI.ts b/frontend/src/api/reportingsAPI.ts index f08374ca4..fa3871366 100644 --- a/frontend/src/api/reportingsAPI.ts +++ b/frontend/src/api/reportingsAPI.ts @@ -1,6 +1,6 @@ import { type EntityState, createEntityAdapter } from '@reduxjs/toolkit' -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { monitorenvPrivateApi } from './api' import { getQueryString } from '../utils/getQueryStringFormatted' import type { Reporting, ReportingDetailed } from '../domain/entities/reporting' @@ -17,8 +17,7 @@ type ReportingsFilter = { const ReportingAdapter = createEntityAdapter() const initialState = ReportingAdapter.getInitialState() -export const reportingsAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), +export const reportingsAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: build => ({ archiveReportings: build.mutation({ invalidatesTags: (_, __, results) => @@ -28,7 +27,7 @@ export const reportingsAPI = createApi({ query: ({ ids }: { ids: number[] }) => ({ body: ids, method: 'PUT', - url: `reportings/archive` + url: `/v1/reportings/archive` }) }), createReporting: build.mutation, Partial>({ @@ -36,14 +35,14 @@ export const reportingsAPI = createApi({ query: reporting => ({ body: reporting, method: 'PUT', - url: 'reportings' + url: '/v1/reportings' }) }), deleteReporting: build.mutation({ invalidatesTags: [{ id: 'LIST', type: 'Reportings' }], query: ({ id }) => ({ method: 'DELETE', - url: `reportings/${id}` + url: `/v1/reportings/${id}` }) }), deleteReportings: build.mutation({ @@ -54,19 +53,19 @@ export const reportingsAPI = createApi({ query: ({ ids }: { ids: number[] }) => ({ body: ids, method: 'PUT', - url: `reportings/delete` + url: `/v1/reportings/delete` }) }), getReporting: build.query({ providesTags: (_, __, id) => [{ id, type: 'Reportings' }], - query: id => `reportings/${id}` + query: id => `/v1/reportings/${id}` }), getReportings: build.query, ReportingsFilter | void>({ providesTags: result => result?.ids ? [{ id: 'LIST', type: 'Reportings' }, ...result.ids.map(id => ({ id, type: 'Reportings' as const }))] : [{ id: 'LIST', type: 'Reportings' }], - query: filters => getQueryString('reportings', filters), + query: filters => getQueryString('/v1/reportings', filters), transformResponse: (response: ReportingDetailed[]) => ReportingAdapter.setAll(initialState, response) }), updateReporting: build.mutation>({ @@ -77,12 +76,10 @@ export const reportingsAPI = createApi({ query: ({ id, ...patch }) => ({ body: { id, ...patch }, method: 'PUT', - url: `reportings/${id}` + url: `/v1/reportings/${id}` }) }) - }), - reducerPath: 'reportings', - tagTypes: ['Reportings'] + }) }) export const { diff --git a/frontend/src/api/semaphoresAPI.ts b/frontend/src/api/semaphoresAPI.ts index a2a6b5592..7ac9ed89d 100644 --- a/frontend/src/api/semaphoresAPI.ts +++ b/frontend/src/api/semaphoresAPI.ts @@ -1,20 +1,19 @@ import { type EntityState, createEntityAdapter } from '@reduxjs/toolkit' -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { monitorenvPrivateApi } from './api' import type { Semaphore } from '../domain/entities/semaphore' const SemaphoreAdapter = createEntityAdapter() const initialState = SemaphoreAdapter.getInitialState() -export const semaphoresAPI = createApi({ - baseQuery: fetchBaseQuery({ baseUrl: '/bff/v1' }), +export const semaphoresAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: build => ({ getSemaphores: build.query, void>({ - query: () => 'semaphores', + query: () => '/v1/semaphores', transformResponse: (response: Semaphore[]) => SemaphoreAdapter.setAll(initialState, response) }) - }), - reducerPath: 'semaphores' + }) }) export const { useGetSemaphoresQuery } = semaphoresAPI diff --git a/frontend/src/domain/shared_slices/index.ts b/frontend/src/domain/shared_slices/index.ts deleted file mode 100644 index 11997d8f1..000000000 --- a/frontend/src/domain/shared_slices/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -// TODO We should move that into `/frontend/src/store` directory. - -import { combineReducers } from '@reduxjs/toolkit' - -import { administrativeSlicePersistedReducer } from './Administrative' -import { drawReducer } from './Draw' -import { globalReducer } from './Global' -import { interestPointSlicePersistedReducer } from './InterestPoint' -import { layerSidebarSlice } from './LayerSidebar' -import { mapSliceReducer } from './Map' -import { measurementSlicePersistedReducer } from './Measurement' -import { missionFiltersPersistedReducer } from './MissionFilters' -import { missionStateSliceReducer } from './MissionsState' -import { multiMissionsSliceReducer } from './MultiMissions' -import { regulatorySlicePersistedReducer } from './Regulatory' -import { regulatoryMetadataSliceReducer } from './RegulatoryMetadata' -import { reportingSliceReducer } from './reporting' -import { reportingFiltersPersistedReducer } from './ReportingsFilters' -import { selectedAmpSlicePersistedReducer } from './SelectedAmp' -import { semaphoresPersistedReducer } from './SemaphoresSlice' -import { ampsAPI, ampsErrorLoggerMiddleware } from '../../api/ampsAPI' -import { monitorenvPrivateApi, monitorenvPublicApi } from '../../api/api' -import { controlThemesAPI } from '../../api/controlThemesAPI' -import { infractionsAPI } from '../../api/infractionsAPI' -import { legacyControlUnitsAPI } from '../../api/legacyControlUnitsAPI' -import { missionsAPI } from '../../api/missionsAPI' -import { regulatoryLayersAPI } from '../../api/regulatoryLayersAPI' -import { reportingsAPI } from '../../api/reportingsAPI' -import { semaphoresAPI } from '../../api/semaphoresAPI' -import { administrationTablePersistedReducer } from '../../features/Administration/components/AdministrationTable/slice' -import { backOfficeReducer } from '../../features/BackOffice/slice' -import { baseTablePersistedReducer } from '../../features/Base/components/BaseTable/slice' -import { controlUnitDialogReducer } from '../../features/ControlUnit/components/ControlUnitDialog/slice' -import { controlUnitListDialogPersistedReducer } from '../../features/ControlUnit/components/ControlUnitListDialog/slice' -import { controlUnitTablePersistedReducer } from '../../features/ControlUnit/components/ControlUnitTable/slice' -import { layerSearchSliceReducer } from '../../features/layersSelector/search/slice' -import { mainWindowReducer } from '../../features/MainWindow/slice' -import { sideWindowReducer } from '../../features/SideWindow/slice' - -// TODO Maybe add a specifc store for the backoffice? -// But it won't be necessarily cleaner since current APIs are also needed in the home anyway. -export const homeReducers = combineReducers({ - [monitorenvPrivateApi.reducerPath]: monitorenvPrivateApi.reducer, - [monitorenvPublicApi.reducerPath]: monitorenvPublicApi.reducer, - - administrative: administrativeSlicePersistedReducer, - backOffice: backOfficeReducer, - backOfficeAdministrationList: administrationTablePersistedReducer, - backOfficeBaseList: baseTablePersistedReducer, - backOfficeControlUnitList: controlUnitTablePersistedReducer, - draw: drawReducer, - global: globalReducer, - interestPoint: interestPointSlicePersistedReducer, - layerSearch: layerSearchSliceReducer, - mainWindow: mainWindowReducer, - map: mapSliceReducer, - mapControlUnitDialog: controlUnitDialogReducer, - mapControlUnitListDialog: controlUnitListDialogPersistedReducer, - measurement: measurementSlicePersistedReducer, - missionFilters: missionFiltersPersistedReducer, - missionState: missionStateSliceReducer, - multiMissions: multiMissionsSliceReducer, - regulatory: regulatorySlicePersistedReducer, - regulatoryMetadata: regulatoryMetadataSliceReducer, - reporting: reportingSliceReducer, - [layerSidebarSlice.name]: layerSidebarSlice.reducer, - [ampsAPI.reducerPath]: ampsAPI.reducer, - [regulatoryLayersAPI.reducerPath]: regulatoryLayersAPI.reducer, - [missionsAPI.reducerPath]: missionsAPI.reducer, - [controlThemesAPI.reducerPath]: controlThemesAPI.reducer, - [legacyControlUnitsAPI.reducerPath]: legacyControlUnitsAPI.reducer, - [infractionsAPI.reducerPath]: infractionsAPI.reducer, - [semaphoresAPI.reducerPath]: semaphoresAPI.reducer, - reportingFilters: reportingFiltersPersistedReducer, - [reportingsAPI.reducerPath]: reportingsAPI.reducer, - selectedAmp: selectedAmpSlicePersistedReducer, - semaphoresSlice: semaphoresPersistedReducer, - sideWindow: sideWindowReducer -}) - -export const homeMiddlewares = [ - ampsAPI.middleware, - ampsErrorLoggerMiddleware, - missionsAPI.middleware, - regulatoryLayersAPI.middleware, - controlThemesAPI.middleware, - legacyControlUnitsAPI.middleware, - infractionsAPI.middleware, - semaphoresAPI.middleware, - reportingsAPI.middleware -] diff --git a/frontend/src/features/ControlUnit/usesCases/deleteControlUnitContact.ts b/frontend/src/features/ControlUnit/usesCases/deleteControlUnitContact.ts index 2e49c4056..90845dd9b 100644 --- a/frontend/src/features/ControlUnit/usesCases/deleteControlUnitContact.ts +++ b/frontend/src/features/ControlUnit/usesCases/deleteControlUnitContact.ts @@ -5,9 +5,9 @@ import { FrontendError } from '../../../libs/FrontendError' import { isUserError } from '../../../libs/UserError' import { mainWindowActions } from '../../MainWindow/slice' -import type { AppThunk } from '../../../store' +import type { HomeAppThunk } from '../../../store' -export const deleteControlUnitContact = (): AppThunk> => async (dispatch, getState) => { +export const deleteControlUnitContact = (): HomeAppThunk> => async (dispatch, getState) => { const { confirmationModal } = getState().mainWindow if (!confirmationModal) { throw new FrontendError('`confirmationModal` is undefined.') diff --git a/frontend/src/features/ControlUnit/usesCases/deleteControlUnitResource.ts b/frontend/src/features/ControlUnit/usesCases/deleteControlUnitResource.ts index 815f41e6a..cecc47190 100644 --- a/frontend/src/features/ControlUnit/usesCases/deleteControlUnitResource.ts +++ b/frontend/src/features/ControlUnit/usesCases/deleteControlUnitResource.ts @@ -3,9 +3,9 @@ import { logSoftError } from '@mtes-mct/monitor-ui' import { controlUnitResourcesAPI } from '../../../api/controlUnitResourcesAPI' import { FrontendError } from '../../../libs/FrontendError' -import type { AppThunk } from '../../../store' +import type { HomeAppThunk } from '../../../store' -export const deleteControlUnitResource = (): AppThunk> => async (dispatch, getState) => { +export const deleteControlUnitResource = (): HomeAppThunk> => async (dispatch, getState) => { const { confirmationModal } = getState().mainWindow if (!confirmationModal) { throw new FrontendError('`confirmationModal` is undefined.') diff --git a/frontend/src/features/MainWindow/useCases/handleModalConfirmation.ts b/frontend/src/features/MainWindow/useCases/handleModalConfirmation.ts index bca222237..978e0364d 100644 --- a/frontend/src/features/MainWindow/useCases/handleModalConfirmation.ts +++ b/frontend/src/features/MainWindow/useCases/handleModalConfirmation.ts @@ -4,9 +4,9 @@ import { deleteControlUnitResource } from '../../ControlUnit/usesCases/deleteCon import { mainWindowActions } from '../slice' import { MainWindowConfirmationModalActionType } from '../types' -import type { AppThunk } from '../../../store' +import type { HomeAppThunk } from '../../../store' -export const handleModalConfirmation = (): AppThunk => async (dispatch, getState) => { +export const handleModalConfirmation = (): HomeAppThunk => async (dispatch, getState) => { const { confirmationModal } = getState().mainWindow if (!confirmationModal) { throw new FrontendError('`confirmationModal` is undefined.') diff --git a/frontend/src/hooks/useAppDispatch.ts b/frontend/src/hooks/useAppDispatch.ts index fa5703137..d73fdeb4a 100644 --- a/frontend/src/hooks/useAppDispatch.ts +++ b/frontend/src/hooks/useAppDispatch.ts @@ -1,9 +1,9 @@ // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch } from 'react-redux' -import type { AppDispatch } from '../store' +import type { HomeAppDispatch } from '../store' /** * @see https://react-redux.js.org/using-react-redux/usage-with-typescript#typing-the-usedispatch-hook */ -export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppDispatch: () => HomeAppDispatch = useDispatch diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index 910ac49ac..596dea02d 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -1,9 +1,9 @@ -import { type AnyAction, type ThunkAction, configureStore, isPlain } from '@reduxjs/toolkit' +import { type AnyAction, type ThunkAction, configureStore, combineReducers, isPlain } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from 'redux-persist' +import { homeReducers } from './reducers' import { monitorenvPrivateApi, monitorenvPublicApi } from '../api/api' -import { homeReducers, homeMiddlewares } from '../domain/shared_slices' import { regulatoryActionSanitizer } from '../domain/shared_slices/Regulatory' const homeStore = configureStore({ @@ -12,18 +12,16 @@ const homeStore = configureStore({ }, middleware: getDefaultMiddleware => getDefaultMiddleware({ - immutableCheck: { - ignoredPaths: ['regulatory', 'layerSearch'] - }, - // TODO Create a Redux middleware to properly serialize/deserialize `Date`, `Error` objects into plain objects. // https://redux-toolkit.js.org/api/serializabilityMiddleware serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, 'regulatory/setRegulatoryLayers'], ignoredPaths: ['regulatory', 'layerSearch'], - isSerializable: (value: any) => isPlain(value) || value instanceof Date || value instanceof Error + // TODO Replace all Redux state Dates by strings & Error by a strict-typed POJO. + isSerializable: (value: any) => isPlain(value) || value instanceof Date || value instanceof Error, + serializableCheck: false } - }).concat(homeMiddlewares as any, monitorenvPrivateApi.middleware, monitorenvPublicApi.middleware), - reducer: homeReducers + }).concat(monitorenvPrivateApi.middleware, monitorenvPublicApi.middleware), + reducer: combineReducers(homeReducers) as unknown as typeof homeReducers }) setupListeners(homeStore.dispatch) @@ -31,9 +29,6 @@ setupListeners(homeStore.dispatch) export { homeStore } // https://react-redux.js.org/using-react-redux/usage-with-typescript#define-root-state-and-dispatch-types -// Infer the `RootState` and `AppDispatch` types from the store itself +export type HomeAppDispatch = typeof homeStore.dispatch +export type HomeAppThunk = ThunkAction export type HomeRootState = ReturnType -// Inferred type: { global: GlobalState, ... } -export type AppDispatch = typeof homeStore.dispatch -export type AppGetState = typeof homeStore.getState -export type AppThunk = ThunkAction diff --git a/frontend/src/store/reducers.ts b/frontend/src/store/reducers.ts new file mode 100644 index 000000000..bd63612fa --- /dev/null +++ b/frontend/src/store/reducers.ts @@ -0,0 +1,75 @@ +import { ampsAPI } from '../api/ampsAPI' +import { geoserverApi, monitorenvPrivateApi, monitorenvPublicApi } from '../api/api' +import { controlThemesAPI } from '../api/controlThemesAPI' +import { infractionsAPI } from '../api/infractionsAPI' +import { legacyControlUnitsAPI } from '../api/legacyControlUnitsAPI' +import { missionsAPI } from '../api/missionsAPI' +import { regulatoryLayersAPI } from '../api/regulatoryLayersAPI' +import { reportingsAPI } from '../api/reportingsAPI' +import { semaphoresAPI } from '../api/semaphoresAPI' +import { administrativeSlicePersistedReducer } from '../domain/shared_slices/Administrative' +import { drawReducer } from '../domain/shared_slices/Draw' +import { globalReducer } from '../domain/shared_slices/Global' +import { interestPointSlicePersistedReducer } from '../domain/shared_slices/InterestPoint' +import { layerSidebarSlice } from '../domain/shared_slices/LayerSidebar' +import { mapSliceReducer } from '../domain/shared_slices/Map' +import { measurementSlicePersistedReducer } from '../domain/shared_slices/Measurement' +import { missionFiltersPersistedReducer } from '../domain/shared_slices/MissionFilters' +import { missionStateSliceReducer } from '../domain/shared_slices/MissionsState' +import { multiMissionsSliceReducer } from '../domain/shared_slices/MultiMissions' +import { regulatorySlicePersistedReducer } from '../domain/shared_slices/Regulatory' +import { regulatoryMetadataSliceReducer } from '../domain/shared_slices/RegulatoryMetadata' +import { reportingSliceReducer } from '../domain/shared_slices/reporting' +import { reportingFiltersPersistedReducer } from '../domain/shared_slices/ReportingsFilters' +import { selectedAmpSlicePersistedReducer } from '../domain/shared_slices/SelectedAmp' +import { semaphoresPersistedReducer } from '../domain/shared_slices/SemaphoresSlice' +import { administrationTablePersistedReducer } from '../features/Administration/components/AdministrationTable/slice' +import { backOfficeReducer } from '../features/BackOffice/slice' +import { baseTablePersistedReducer } from '../features/Base/components/BaseTable/slice' +import { controlUnitDialogReducer } from '../features/ControlUnit/components/ControlUnitDialog/slice' +import { controlUnitListDialogPersistedReducer } from '../features/ControlUnit/components/ControlUnitListDialog/slice' +import { controlUnitTablePersistedReducer } from '../features/ControlUnit/components/ControlUnitTable/slice' +import { layerSearchSliceReducer } from '../features/layersSelector/search/slice' +import { mainWindowReducer } from '../features/MainWindow/slice' +import { sideWindowReducer } from '../features/SideWindow/slice' + +// TODO Maybe add a specifc store for the backoffice (to make it lighter)? +export const homeReducers = { + [geoserverApi.reducerPath]: geoserverApi.reducer, + [monitorenvPrivateApi.reducerPath]: monitorenvPrivateApi.reducer, + [monitorenvPublicApi.reducerPath]: monitorenvPublicApi.reducer, + + administrative: administrativeSlicePersistedReducer, + backOffice: backOfficeReducer, + backOfficeAdministrationList: administrationTablePersistedReducer, + backOfficeBaseList: baseTablePersistedReducer, + backOfficeControlUnitList: controlUnitTablePersistedReducer, + draw: drawReducer, + global: globalReducer, + interestPoint: interestPointSlicePersistedReducer, + layerSearch: layerSearchSliceReducer, + mainWindow: mainWindowReducer, + map: mapSliceReducer, + mapControlUnitDialog: controlUnitDialogReducer, + mapControlUnitListDialog: controlUnitListDialogPersistedReducer, + measurement: measurementSlicePersistedReducer, + missionFilters: missionFiltersPersistedReducer, + missionState: missionStateSliceReducer, + multiMissions: multiMissionsSliceReducer, + regulatory: regulatorySlicePersistedReducer, + regulatoryMetadata: regulatoryMetadataSliceReducer, + reporting: reportingSliceReducer, + [layerSidebarSlice.name]: layerSidebarSlice.reducer, + [ampsAPI.reducerPath]: ampsAPI.reducer, + [regulatoryLayersAPI.reducerPath]: regulatoryLayersAPI.reducer, + [missionsAPI.reducerPath]: missionsAPI.reducer, + [controlThemesAPI.reducerPath]: controlThemesAPI.reducer, + [legacyControlUnitsAPI.reducerPath]: legacyControlUnitsAPI.reducer, + [infractionsAPI.reducerPath]: infractionsAPI.reducer, + [semaphoresAPI.reducerPath]: semaphoresAPI.reducer, + reportingFilters: reportingFiltersPersistedReducer, + [reportingsAPI.reducerPath]: reportingsAPI.reducer, + selectedAmp: selectedAmpSlicePersistedReducer, + semaphoresSlice: semaphoresPersistedReducer, + sideWindow: sideWindowReducer +}