diff --git a/frontend/cypress/e2e/side_window/mission/create_mission.spec.ts b/frontend/cypress/e2e/side_window/mission/create_mission.spec.ts index ec27afde9..4a9dde27c 100644 --- a/frontend/cypress/e2e/side_window/mission/create_mission.spec.ts +++ b/frontend/cypress/e2e/side_window/mission/create_mission.spec.ts @@ -149,4 +149,17 @@ context('Mission', () => { // Then cy.get('*[data-cy="delete-mission"]').should('be.disabled') }) + + it('A warning should be displayed When a control unit is already engaged in a mission ', () => { + // Given + cy.wait(200) + cy.intercept('GET', '/api/v1/missions/engaged_control_units').as('getEngagedControlUnits') + + // When + cy.get('*[data-cy="edit-mission-43"]').click({ force: true }) + cy.wait('@getEngagedControlUnits') + + // Then + cy.get('body').contains('Cette unité est actuellement sélectionnée dans une autre mission en cours.') + }) }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4b1a18b4d..f040b981d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { - "@mtes-mct/monitor-ui": "10.1.1", + "@dnd-kit/core": "^4.0.3", + "@dnd-kit/modifiers": "^4.0.0", + "@mtes-mct/monitor-ui": "10.4.0", "@reduxjs/toolkit": "1.9.5", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", @@ -2367,6 +2369,50 @@ "node": ">=10.0.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.0.0", + "@dnd-kit/utilities": "^3.0.1", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.0.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^4.0.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.0", "license": "MIT", @@ -5053,9 +5099,9 @@ } }, "node_modules/@mtes-mct/monitor-ui": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-10.1.1.tgz", - "integrity": "sha512-9guDaraFHXQyUJRMEkkm+ML5wD4bdPv7qnbubj7fLmaK/4GPmOMVy6ArqzPxfFOIAw+UO06X5Wr7iY6/bkrrcw==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-10.4.0.tgz", + "integrity": "sha512-JOT6aCOVwA3cPGVKaaGxP3qgx8gLRUmSSPDImmqCCsz0JtvLpG08j5nmoy+EahYhgQ2RpVfZApNFH06DI2rFSw==", "dependencies": { "@babel/runtime": "7.22.15", "@tanstack/react-table": "8.9.7", diff --git a/frontend/package.json b/frontend/package.json index 2d9d6af20..dd660d5e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ "test:unit:watch": "npm run test:unit -- --watch" }, "dependencies": { - "@mtes-mct/monitor-ui": "10.1.1", + "@mtes-mct/monitor-ui": "10.4.0", "@reduxjs/toolkit": "1.9.5", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", diff --git a/frontend/src/api/missionsAPI.ts b/frontend/src/api/missionsAPI.ts index 72eb46e58..b542872d1 100644 --- a/frontend/src/api/missionsAPI.ts +++ b/frontend/src/api/missionsAPI.ts @@ -1,5 +1,8 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { monitorenvPublicApi } from './api' +import { ControlUnit } from '../domain/entities/controlUnit' + import type { Mission } from '../domain/entities/missions' type MissionsResponse = Mission[] @@ -83,6 +86,14 @@ export const missionsAPI = createApi({ tagTypes: ['Missions'] }) +export const publicMissionsAPI = monitorenvPublicApi.injectEndpoints({ + endpoints: builder => ({ + getEngagedControlUnits: builder.query({ + query: () => `/v1/missions/engaged_control_units` + }) + }) +}) + export const { useCreateMissionMutation, useDeleteMissionMutation, @@ -90,3 +101,5 @@ export const { useGetMissionsQuery, useUpdateMissionMutation } = missionsAPI + +export const { useGetEngagedControlUnitsQuery } = publicMissionsAPI diff --git a/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx b/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx index f40bd8e89..f3d60f299 100644 --- a/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx +++ b/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx @@ -1,12 +1,14 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { FieldError, FormikTextInput } from '@mtes-mct/monitor-ui' +import { FieldError, FormikTextInput, Level, Message } from '@mtes-mct/monitor-ui' import { useField } from 'formik' import _ from 'lodash' import { type MutableRefObject, useMemo, useRef } from 'react' import { Form, IconButton, TagPicker } from 'rsuite' import styled from 'styled-components' +import { FIVE_MINUTES } from '../../../api/APIWorker' import { useGetLegacyControlUnitsQuery } from '../../../api/legacyControlUnitsAPI' +import { useGetEngagedControlUnitsQuery } from '../../../api/missionsAPI' import { FormikErrorWrapper } from '../../../uiMonitor/CustomFormikFields/FormikErrorWrapper' import { SelectPicker } from '../../../uiMonitor/CustomRsuite/SelectPicker' import { ReactComponent as DeleteSVG } from '../../../uiMonitor/icons/Delete.svg' @@ -24,9 +26,22 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC ) const resourcesRef = useRef() as MutableRefObject - const { data, isError, isLoading } = useGetLegacyControlUnitsQuery() + const { data: controlUnitsData, isError, isLoading } = useGetLegacyControlUnitsQuery() - const filteredControlUnits = useMemo(() => data?.filter(unit => !unit.isArchived) || [], [data]) + const filteredControlUnits = useMemo( + () => controlUnitsData?.filter(unit => !unit.isArchived) || [], + [controlUnitsData] + ) + + const { data: engagedControlUnitsData } = useGetEngagedControlUnitsQuery(undefined, { pollingInterval: FIVE_MINUTES }) + + const engagedControlUnits = useMemo(() => { + if (!engagedControlUnitsData) { + return [] + } + + return engagedControlUnitsData + }, [engagedControlUnitsData]) const administrationList = _.chain(filteredControlUnits) .map(unit => unit.administration) @@ -91,9 +106,12 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC if (isError) { return
Erreur
} + if (isLoading) { return
Chargement
} + + const isEngaged = !!engagedControlUnits.find(engaged => engaged.id === unitField.value) const resourceUnitIndexDisplayed = controlUnitIndex + 1 return ( @@ -138,6 +156,11 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC /> {unitNameMeta.error && {unitNameMeta.error}} + {isEngaged && ( + + Cette unité est actuellement sélectionnée dans une autre mission en cours. + + )} @@ -169,6 +192,10 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC ) } +const StyledMessage = styled(Message)` + margin-top: 8px; +` + const RessourceUnitWrapper = styled.div` margin-bottom: 14px; display: flex;