From cfc89717a5b764b018c25f41fda3b9b67e343b1e Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 13 Oct 2023 18:33:43 +0200 Subject: [PATCH] [Reporting] some bug fixes --- .../reportings/CreateOrUpdateReporting.kt | 27 +- .../bff/outputs/ReportingDataOutput.kt | 189 +++++------ .../database/model/MissionModel.kt | 165 +++++---- .../database/model/ReportingModel.kt | 312 +++++++++--------- .../repositories/JpaReportingRepository.kt | 102 +++--- .../interfaces/IDBReportingRepository.kt | 2 +- .../domain/use_cases/missions/addMission.ts | 11 +- .../missions/editMissionInLocalStore.ts | 2 - .../domain/use_cases/missions/switchTab.ts | 15 + .../reporting/createMissionFromReporting.ts | 5 +- .../index.tsx | 0 .../Overlays/missionToAttach/index.tsx | 24 -- .../ReportingForm/AttachMission/index.tsx | 5 +- .../Reportings/ReportingForm/Form.tsx | 6 +- .../Reportings/ReportingForm/index.tsx | 6 +- .../hooks/useSyncFormValuesWithRedux.ts | 9 +- frontend/src/features/map/index.tsx | 4 +- .../Layers/ReportingToAttach/index.tsx | 2 +- .../missions/MissionForm/MissionForm.tsx | 4 +- .../missions/MissionForm/mission.json | 260 --------------- .../index.tsx | 0 .../Overlays/reportingToAttach/index.tsx | 44 --- frontend/src/store/reducers.ts | 2 - 23 files changed, 441 insertions(+), 755 deletions(-) rename frontend/src/features/Reportings/Overlays/{MissionToAttach => MissionToAttachToReporting}/index.tsx (100%) delete mode 100644 frontend/src/features/Reportings/Overlays/missionToAttach/index.tsx delete mode 100644 frontend/src/features/missions/MissionForm/mission.json rename frontend/src/features/missions/Overlays/{ReportingToAttach => ReportingToAttachToMission}/index.tsx (100%) delete mode 100644 frontend/src/features/missions/Overlays/reportingToAttach/index.tsx diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt index eaffd678c..e0f4ca11a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt @@ -4,17 +4,17 @@ import fr.gouv.cacem.monitorenv.config.UseCase import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity import fr.gouv.cacem.monitorenv.domain.repositories.* import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDTO -import java.time.ZonedDateTime import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.time.ZonedDateTime @UseCase class CreateOrUpdateReporting( - private val reportingRepository: IReportingRepository, - private val controlUnitRepository: IControlUnitRepository, - private val semaphoreRepository: ISemaphoreRepository, - private val facadeRepository: IFacadeAreasRepository, - private val missionRepository: IMissionRepository, + private val reportingRepository: IReportingRepository, + private val controlUnitRepository: IControlUnitRepository, + private val semaphoreRepository: ISemaphoreRepository, + private val facadeRepository: IFacadeAreasRepository, + private val missionRepository: IMissionRepository, ) { private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateReporting::class.java) @@ -32,6 +32,7 @@ class CreateOrUpdateReporting( var attachedToMissionAtUtc: ZonedDateTime? = null var detachedFromMissionAtUtc: ZonedDateTime? = null if (reporting.missionId != null) { + // TO CHECK if the date is with or without UTC attachedToMissionAtUtc = ZonedDateTime.now() detachedFromMissionAtUtc = null } @@ -41,13 +42,13 @@ class CreateOrUpdateReporting( } val savedReport = - reportingRepository.save( - reporting.copy( - seaFront = seaFront, - attachedToMissionAtUtc = attachedToMissionAtUtc, - detachedFromMissionAtUtc = detachedFromMissionAtUtc - ) - ) + reportingRepository.save( + reporting.copy( + seaFront = seaFront, + attachedToMissionAtUtc = attachedToMissionAtUtc, + detachedFromMissionAtUtc = detachedFromMissionAtUtc, + ), + ) return savedReport } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt index 6840bfc85..e0168395e 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt @@ -8,110 +8,111 @@ import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetDetailsEntity import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetTypeEnum import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDTO import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.ControlUnitDataOutput +import org.locationtech.jts.geom.Geometry import java.time.ZonedDateTime import java.util.UUID -import org.locationtech.jts.geom.Geometry data class ReportingDataOutput( - val id: Int, - val reportingId: Long? = null, - val sourceType: SourceTypeEnum? = null, - val semaphoreId: Int? = null, - val semaphore: SemaphoreDataOutput? = null, - val controlUnitId: Int? = null, - val controlUnit: ControlUnitDataOutput? = null, - val displayedSource: String? = null, - val sourceName: String? = null, - val targetType: TargetTypeEnum? = null, - val vehicleType: VehicleTypeEnum? = null, - val targetDetails: List? = listOf(), - val geom: Geometry? = null, - val seaFront: String? = null, - val description: String? = null, - val reportType: ReportingTypeEnum? = null, - val theme: String? = null, - val subThemes: List? = listOf(), - val actionTaken: String? = null, - val isControlRequired: Boolean? = null, - val hasNoUnitAvailable: Boolean? = null, - val createdAt: ZonedDateTime, - val validityTime: Int? = null, - val isArchived: Boolean, - val openBy: String? = null, - val missionId: Int? = null, - val attachedToMissionAtUtc: ZonedDateTime? = null, - val detachedFromMissionAtUtc: ZonedDateTime? = null, - val attachedEnvActionId: UUID? = null, - val attachedMission: ReportingMissionDataOutput? = null, - val controlStatus: ControlStatusEnum? = null, + val id: Int, + val reportingId: Long? = null, + val sourceType: SourceTypeEnum? = null, + val semaphoreId: Int? = null, + val semaphore: SemaphoreDataOutput? = null, + val controlUnitId: Int? = null, + val controlUnit: ControlUnitDataOutput? = null, + val displayedSource: String? = null, + val sourceName: String? = null, + val targetType: TargetTypeEnum? = null, + val vehicleType: VehicleTypeEnum? = null, + val targetDetails: List? = listOf(), + val geom: Geometry? = null, + val seaFront: String? = null, + val description: String? = null, + val reportType: ReportingTypeEnum? = null, + val theme: String? = null, + val subThemes: List? = listOf(), + val actionTaken: String? = null, + val isControlRequired: Boolean? = null, + val hasNoUnitAvailable: Boolean? = null, + val createdAt: ZonedDateTime, + val validityTime: Int? = null, + val isArchived: Boolean, + val openBy: String? = null, + val missionId: Int? = null, + val attachedToMissionAtUtc: ZonedDateTime? = null, + val detachedFromMissionAtUtc: ZonedDateTime? = null, + val attachedEnvActionId: UUID? = null, + val attachedMission: ReportingMissionDataOutput? = null, + val controlStatus: ControlStatusEnum? = null, ) { companion object { fun fromReportingDTO( - dto: ReportingDTO, + dto: ReportingDTO, ): ReportingDataOutput { requireNotNull(dto.reporting.id) { "ReportingEntity.id cannot be null" } return ReportingDataOutput( - id = dto.reporting.id!!, - reportingId = dto.reporting.reportingId, - sourceType = dto.reporting.sourceType, - semaphoreId = dto.reporting.semaphoreId, - semaphore = - if (dto.semaphore != null) { - SemaphoreDataOutput.fromSemaphoreEntity( - dto.semaphore, - ) - } else { - null - }, - controlUnitId = dto.reporting.controlUnitId, - controlUnit = - if (dto.controlUnit != null) { - ControlUnitDataOutput.fromFullControlUnit( - dto.controlUnit, - ) - } else { - null - }, - displayedSource = - when (dto.reporting.sourceType) { - SourceTypeEnum.SEMAPHORE -> dto?.semaphore?.unit - ?: dto?.semaphore?.name - // TODO This is really strange : `fullControlUnit?.controlUnit` - // can't be null and I have to add another `?`... - SourceTypeEnum.CONTROL_UNIT -> dto?.controlUnit?.controlUnit?.name - SourceTypeEnum.OTHER -> dto.reporting.sourceName - else -> "" - }, - sourceName = dto.reporting.sourceName, - targetType = dto.reporting.targetType, - vehicleType = dto.reporting.vehicleType, - targetDetails = dto.reporting.targetDetails, - geom = dto.reporting.geom, - seaFront = dto.reporting.seaFront, - description = dto.reporting.description, - reportType = dto.reporting.reportType, - theme = dto.reporting.theme, - subThemes = dto.reporting.subThemes, - actionTaken = dto.reporting.actionTaken, - isControlRequired = dto.reporting.isControlRequired, - hasNoUnitAvailable = dto.reporting.hasNoUnitAvailable, - createdAt = dto.reporting.createdAt, - validityTime = dto.reporting.validityTime, - isArchived = dto.reporting.isArchived, - openBy = dto.reporting.openBy, - controlStatus = dto.controlStatus, - missionId = dto.reporting.missionId, - attachedToMissionAtUtc = dto.reporting.attachedToMissionAtUtc, - detachedFromMissionAtUtc = dto.reporting.detachedFromMissionAtUtc, - attachedEnvActionId = dto.reporting.attachedEnvActionId, - attachedMission = - if (dto.attachedMission != null) { - ReportingMissionDataOutput.fromMission( - dto.attachedMission, - ) - } else { - null - }, + id = dto.reporting.id!!, + reportingId = dto.reporting.reportingId, + sourceType = dto.reporting.sourceType, + semaphoreId = dto.reporting.semaphoreId, + semaphore = + if (dto.semaphore != null) { + SemaphoreDataOutput.fromSemaphoreEntity( + dto.semaphore, + ) + } else { + null + }, + controlUnitId = dto.reporting.controlUnitId, + controlUnit = + if (dto.controlUnit != null) { + ControlUnitDataOutput.fromFullControlUnit( + dto.controlUnit, + ) + } else { + null + }, + displayedSource = + when (dto.reporting.sourceType) { + SourceTypeEnum.SEMAPHORE -> + dto?.semaphore?.unit + ?: dto?.semaphore?.name + // TODO This is really strange : `fullControlUnit?.controlUnit` + // can't be null and I have to add another `?`... + SourceTypeEnum.CONTROL_UNIT -> dto?.controlUnit?.controlUnit?.name + SourceTypeEnum.OTHER -> dto.reporting.sourceName + else -> "" + }, + sourceName = dto.reporting.sourceName, + targetType = dto.reporting.targetType, + vehicleType = dto.reporting.vehicleType, + targetDetails = dto.reporting.targetDetails, + geom = dto.reporting.geom, + seaFront = dto.reporting.seaFront, + description = dto.reporting.description, + reportType = dto.reporting.reportType, + theme = dto.reporting.theme, + subThemes = dto.reporting.subThemes, + actionTaken = dto.reporting.actionTaken, + isControlRequired = dto.reporting.isControlRequired, + hasNoUnitAvailable = dto.reporting.hasNoUnitAvailable, + createdAt = dto.reporting.createdAt, + validityTime = dto.reporting.validityTime, + isArchived = dto.reporting.isArchived, + openBy = dto.reporting.openBy, + controlStatus = dto.controlStatus, + missionId = dto.reporting.missionId, + attachedToMissionAtUtc = dto.reporting.attachedToMissionAtUtc, + detachedFromMissionAtUtc = dto.reporting.detachedFromMissionAtUtc, + attachedEnvActionId = dto.reporting.attachedEnvActionId, + attachedMission = + if (dto.attachedMission != null) { + ReportingMissionDataOutput.fromMission( + dto.attachedMission, + ) + } else { + null + }, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/MissionModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/MissionModel.kt index ef11a1ade..1c9467b1d 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/MissionModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/MissionModel.kt @@ -49,60 +49,33 @@ data class MissionModel( @Basic(optional = false) @Column(name = "id", unique = true, nullable = false) val id: Int? = null, - @Type( ListArrayType::class, parameters = [Parameter(name = SQL_ARRAY_TYPE, value = "text")], ) @Column(name = "mission_types", columnDefinition = "text[]") val missionTypes: List, - - @Column(name = "open_by") - val openBy: String? = null, - - @Column(name = "closed_by") - val closedBy: String? = null, - - @Column(name = "observations_cacem") - val observationsCacem: String? = null, - - @Column(name = "observations_cnsp") - val observationsCnsp: String? = null, - - @Column(name = "facade") - val facade: String? = null, - + @Column(name = "open_by") val openBy: String? = null, + @Column(name = "closed_by") val closedBy: String? = null, + @Column(name = "observations_cacem") val observationsCacem: String? = null, + @Column(name = "observations_cnsp") val observationsCnsp: String? = null, + @Column(name = "facade") val facade: String? = null, @JsonSerialize(using = GeometrySerializer::class) @JsonDeserialize(contentUsing = GeometryDeserializer::class) @Column(name = "geom") val geom: MultiPolygon? = null, - - @Column(name = "start_datetime_utc") - val startDateTimeUtc: Instant, - - @Column(name = "end_datetime_utc") - val endDateTimeUtc: Instant? = null, - - @Column(name = "closed", nullable = false) - val isClosed: Boolean, - - @Column(name = "deleted", nullable = false) - val isDeleted: Boolean, - + @Column(name = "start_datetime_utc") val startDateTimeUtc: Instant, + @Column(name = "end_datetime_utc") val endDateTimeUtc: Instant? = null, + @Column(name = "closed", nullable = false) val isClosed: Boolean, + @Column(name = "deleted", nullable = false) val isDeleted: Boolean, @Column(name = "mission_source", nullable = false, columnDefinition = "mission_source_type") @Enumerated(EnumType.STRING) @Type(PostgreSQLEnumType::class) val missionSource: MissionSourceEnum, - - @Column(name = "has_mission_order", nullable = false) - val hasMissionOrder: Boolean, - + @Column(name = "has_mission_order", nullable = false) val hasMissionOrder: Boolean, @Column(name = "is_geometry_computed_from_controls", nullable = false) val isGeometryComputedFromControls: Boolean, - - @Column(name = "is_under_jdp", nullable = false) - val isUnderJdp: Boolean, - + @Column(name = "is_under_jdp", nullable = false) val isUnderJdp: Boolean, @OneToMany( mappedBy = "mission", cascade = [CascadeType.ALL], @@ -112,7 +85,6 @@ data class MissionModel( @JsonManagedReference @Fetch(value = FetchMode.SUBSELECT) val envActions: MutableList? = ArrayList(), - @OneToMany( mappedBy = "mission", cascade = [CascadeType.ALL], @@ -122,7 +94,6 @@ data class MissionModel( @JsonManagedReference @Fetch(value = FetchMode.SUBSELECT) val controlResources: MutableList? = ArrayList(), - @OneToMany( mappedBy = "mission", cascade = [CascadeType.ALL], @@ -132,26 +103,33 @@ data class MissionModel( @JsonManagedReference @Fetch(value = FetchMode.SUBSELECT) val controlUnits: MutableList? = ArrayList(), - @OneToMany(mappedBy = "mission") @JsonManagedReference @Fetch(value = FetchMode.SUBSELECT) val attachedReportings: List? = listOf(), ) { fun toMissionEntity(objectMapper: ObjectMapper): MissionEntity { - val controlUnits = controlUnits.mapOrElseEmpty { missionControlUnitModel -> - val maybeMissionControlResourceModels = controlResources - ?.filter { missionControlResourceModel -> - missionControlResourceModel.ressource.controlUnit.id == missionControlUnitModel.unit.id - } - - val controlUnitResources = maybeMissionControlResourceModels.mapOrElseEmpty { it.toControlUnitResource() } - - missionControlUnitModel.unit.toLegacyControlUnit().copy( - contact = missionControlUnitModel.contact, - resources = controlUnitResources, - ) - } + val controlUnits = + controlUnits.mapOrElseEmpty { missionControlUnitModel -> + val maybeMissionControlResourceModels = + controlResources?.filter { missionControlResourceModel -> + missionControlResourceModel.ressource.controlUnit.id == + missionControlUnitModel.unit.id + } + + val controlUnitResources = + maybeMissionControlResourceModels.mapOrElseEmpty { + it.toControlUnitResource() + } + + missionControlUnitModel + .unit + .toLegacyControlUnit() + .copy( + contact = missionControlUnitModel.contact, + resources = controlUnitResources, + ) + } return MissionEntity( id, @@ -178,7 +156,10 @@ data class MissionModel( fun toMissionDTO(objectMapper: ObjectMapper): MissionDTO { return MissionDTO( mission = this.toMissionEntity(objectMapper), - attachedReportings = this.attachedReportings?.map { it.toReportingDTO(objectMapper) } ?: listOf(), + attachedReportingIds = this.attachedReportings?.map { it.id as Int } ?: listOf(), + attachedReportings = + this.attachedReportings?.map { it.toReportingDTO(objectMapper) } + ?: listOf(), ) } @@ -188,46 +169,51 @@ data class MissionModel( mapper: ObjectMapper, baseModelMap: Map, ): MissionModel { - val missionModel = MissionModel( - id = mission.id, - missionTypes = mission.missionTypes, - openBy = mission.openBy, - closedBy = mission.closedBy, - observationsCacem = mission.observationsCacem, - observationsCnsp = mission.observationsCnsp, - facade = mission.facade, - geom = mission.geom, - startDateTimeUtc = mission.startDateTimeUtc.toInstant(), - endDateTimeUtc = mission.endDateTimeUtc?.toInstant(), - isClosed = mission.isClosed, - isDeleted = false, - missionSource = mission.missionSource, - hasMissionOrder = mission.hasMissionOrder, - isUnderJdp = mission.isUnderJdp, - isGeometryComputedFromControls = mission.isGeometryComputedFromControls, - ) + val missionModel = + MissionModel( + id = mission.id, + missionTypes = mission.missionTypes, + openBy = mission.openBy, + closedBy = mission.closedBy, + observationsCacem = mission.observationsCacem, + observationsCnsp = mission.observationsCnsp, + facade = mission.facade, + geom = mission.geom, + startDateTimeUtc = mission.startDateTimeUtc.toInstant(), + endDateTimeUtc = mission.endDateTimeUtc?.toInstant(), + isClosed = mission.isClosed, + isDeleted = false, + missionSource = mission.missionSource, + hasMissionOrder = mission.hasMissionOrder, + isUnderJdp = mission.isUnderJdp, + isGeometryComputedFromControls = mission.isGeometryComputedFromControls, + ) mission.envActions?.map { - missionModel.envActions?.add(EnvActionModel.fromEnvActionEntity(it, missionModel, mapper)) + missionModel.envActions?.add( + EnvActionModel.fromEnvActionEntity(it, missionModel, mapper), + ) } mission.controlUnits.map { - val controlUnitModel = MissionControlUnitModel.fromLegacyControlUnit( - it, - missionModel, - ) - missionModel.controlUnits?.add(controlUnitModel) - - val missionControlUnitResourceModels = it.resources.map { controlUnitResource -> - val baseModel = requireNotNull(baseModelMap[controlUnitResource.baseId]) - - MissionControlResourceModel.fromControlUnitResource( - controlUnitResource, - baseModel, + val controlUnitModel = + MissionControlUnitModel.fromLegacyControlUnit( + it, missionModel, - controlUnitModel.unit, ) - } + missionModel.controlUnits?.add(controlUnitModel) + + val missionControlUnitResourceModels = + it.resources.map { controlUnitResource -> + val baseModel = requireNotNull(baseModelMap[controlUnitResource.baseId]) + + MissionControlResourceModel.fromControlUnitResource( + controlUnitResource, + baseModel, + missionModel, + controlUnitModel.unit, + ) + } missionModel.controlResources?.addAll(missionControlUnitResourceModels) } @@ -247,6 +233,7 @@ data class MissionModel( @Override override fun toString(): String { - return this::class.simpleName + "(id = $id , missionTypes = $missionTypes , openBy = $openBy , closedBy = $closedBy , observationsCacem = $observationsCacem, observationsCnsp = $observationsCnsp , facade = $facade , geom = $geom , startDateTimeUtc = $startDateTimeUtc , endDateTimeUtc = $endDateTimeUtc, isClosed = $isClosed, isDeleted = $isDeleted, missionSource = $missionSource )" + return this::class.simpleName + + "(id = $id , missionTypes = $missionTypes , openBy = $openBy , closedBy = $closedBy , observationsCacem = $observationsCacem, observationsCnsp = $observationsCnsp , facade = $facade , geom = $geom , startDateTimeUtc = $startDateTimeUtc , endDateTimeUtc = $endDateTimeUtc, isClosed = $isClosed, isDeleted = $isDeleted, missionSource = $missionSource )" } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt index ff81c1372..a847a88b4 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt @@ -25,9 +25,6 @@ import jakarta.persistence.Id import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.Table -import java.time.Instant -import java.time.ZoneOffset.UTC -import java.util.UUID import org.hibernate.Hibernate import org.hibernate.annotations.Generated import org.hibernate.annotations.GenerationTime @@ -37,134 +34,137 @@ import org.hibernate.type.descriptor.jdbc.UUIDJdbcType import org.locationtech.jts.geom.Geometry import org.n52.jackson.datatype.jts.GeometryDeserializer import org.n52.jackson.datatype.jts.GeometrySerializer +import java.time.Instant +import java.time.ZoneOffset.UTC +import java.util.UUID @Entity @Table(name = "reportings") data class ReportingModel( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", unique = true, nullable = false) - val id: Int? = null, - @Generated(GenerationTime.INSERT) - @Column( - name = "reporting_id", - unique = true, - nullable = false, - updatable = false, - insertable = false, - ) - val reportingId: Long? = null, - @Column(name = "source_type", columnDefinition = "reportings_source_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val sourceType: SourceTypeEnum? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "semaphore_id", nullable = true) - @JsonBackReference - val semaphore: SemaphoreModel? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "control_unit_id", nullable = true) - @JsonBackReference - val controlUnit: ControlUnitModel? = null, - @Column(name = "source_name") val sourceName: String? = null, - @Column(name = "target_type", columnDefinition = "reportings_target_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val targetType: TargetTypeEnum? = null, - @Column(name = "vehicle_type", columnDefinition = "reportings_vehicle_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val vehicleType: VehicleTypeEnum? = null, - @Column(name = "target_details", columnDefinition = "jsonb") - @Type(JsonBinaryType::class) - val targetDetails: List? = listOf(), - @JsonSerialize(using = GeometrySerializer::class) - @JsonDeserialize(contentUsing = GeometryDeserializer::class) - @Column(name = "geom") - val geom: Geometry? = null, - @Column(name = "sea_front") val seaFront: String? = null, - @Column(name = "description") val description: String? = null, - @Column(name = "report_type", columnDefinition = "reportings_report_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val reportType: ReportingTypeEnum? = null, - @Column(name = "theme") val theme: String? = null, - @Column(name = "sub_themes") - @Type(ListArrayType::class) - val subThemes: List? = listOf(), - @Column(name = "action_taken") val actionTaken: String? = null, - @Column(name = "is_control_required") val isControlRequired: Boolean? = null, - @Column(name = "has_no_unit_available") val hasNoUnitAvailable: Boolean? = null, - @Column(name = "created_at") val createdAt: Instant, - @Column(name = "validity_time") val validityTime: Int? = null, - @Column(name = "is_archived", nullable = false) val isArchived: Boolean, - @Column(name = "is_deleted", nullable = false) val isDeleted: Boolean, - @Column(name = "open_by") val openBy: String? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "mission_id", nullable = true) - @JsonBackReference - val mission: MissionModel? = null, - @Column(name = "attached_to_mission_at_utc") val attachedToMissionAtUtc: Instant? = null, - @Column(name = "detached_from_mission_at_utc") - val detachedFromMissionAtUtc: Instant? = null, - @JdbcType(UUIDJdbcType::class) - @Column(name = "attached_env_action_id", columnDefinition = "uuid") - val attachedEnvActionId: UUID? = null, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", unique = true, nullable = false) + val id: Int? = null, + @Generated(GenerationTime.INSERT) + @Column( + name = "reporting_id", + unique = true, + nullable = false, + updatable = false, + insertable = false, + ) + val reportingId: Long? = null, + @Column(name = "source_type", columnDefinition = "reportings_source_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val sourceType: SourceTypeEnum? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "semaphore_id", nullable = true) + @JsonBackReference + val semaphore: SemaphoreModel? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "control_unit_id", nullable = true) + @JsonBackReference + val controlUnit: ControlUnitModel? = null, + @Column(name = "source_name") val sourceName: String? = null, + @Column(name = "target_type", columnDefinition = "reportings_target_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val targetType: TargetTypeEnum? = null, + @Column(name = "vehicle_type", columnDefinition = "reportings_vehicle_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val vehicleType: VehicleTypeEnum? = null, + @Column(name = "target_details", columnDefinition = "jsonb") + @Type(JsonBinaryType::class) + val targetDetails: List? = listOf(), + @JsonSerialize(using = GeometrySerializer::class) + @JsonDeserialize(contentUsing = GeometryDeserializer::class) + @Column(name = "geom") + val geom: Geometry? = null, + @Column(name = "sea_front") val seaFront: String? = null, + @Column(name = "description") val description: String? = null, + @Column(name = "report_type", columnDefinition = "reportings_report_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val reportType: ReportingTypeEnum? = null, + @Column(name = "theme") val theme: String? = null, + @Column(name = "sub_themes") + @Type(ListArrayType::class) + val subThemes: List? = listOf(), + @Column(name = "action_taken") val actionTaken: String? = null, + @Column(name = "is_control_required") val isControlRequired: Boolean? = null, + @Column(name = "has_no_unit_available") val hasNoUnitAvailable: Boolean? = null, + @Column(name = "created_at") val createdAt: Instant, + @Column(name = "validity_time") val validityTime: Int? = null, + @Column(name = "is_archived", nullable = false) val isArchived: Boolean, + @Column(name = "is_deleted", nullable = false) val isDeleted: Boolean, + @Column(name = "open_by") val openBy: String? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "mission_id", nullable = true) + @JsonBackReference + val mission: MissionModel? = null, + @Column(name = "attached_to_mission_at_utc") val attachedToMissionAtUtc: Instant? = null, + @Column(name = "detached_from_mission_at_utc") + val detachedFromMissionAtUtc: Instant? = null, + @JdbcType(UUIDJdbcType::class) + @Column(name = "attached_env_action_id", columnDefinition = "uuid") + val attachedEnvActionId: UUID? = null, ) { fun toReporting() = - ReportingEntity( - id = id, - reportingId = reportingId, - sourceType = sourceType, - semaphoreId = semaphore?.id, - controlUnitId = controlUnit?.id, - sourceName = sourceName, - targetType = targetType, - vehicleType = vehicleType, - targetDetails = targetDetails, - geom = geom, - seaFront = seaFront, - description = description, - reportType = reportType, - theme = theme, - subThemes = subThemes, - actionTaken = actionTaken, - isControlRequired = isControlRequired, - hasNoUnitAvailable = hasNoUnitAvailable, - createdAt = createdAt.atZone(UTC), - validityTime = validityTime, - isArchived = isArchived, - isDeleted = isDeleted, - openBy = openBy, - missionId = mission?.id, - attachedToMissionAtUtc = attachedToMissionAtUtc?.atZone(UTC), - detachedFromMissionAtUtc = detachedFromMissionAtUtc?.atZone(UTC), - attachedEnvActionId = attachedEnvActionId, - ) + ReportingEntity( + id = id, + reportingId = reportingId, + sourceType = sourceType, + semaphoreId = semaphore?.id, + controlUnitId = controlUnit?.id, + sourceName = sourceName, + targetType = targetType, + vehicleType = vehicleType, + targetDetails = targetDetails, + geom = geom, + seaFront = seaFront, + description = description, + reportType = reportType, + theme = theme, + subThemes = subThemes, + actionTaken = actionTaken, + isControlRequired = isControlRequired, + hasNoUnitAvailable = hasNoUnitAvailable, + createdAt = createdAt.atZone(UTC), + validityTime = validityTime, + isArchived = isArchived, + isDeleted = isDeleted, + openBy = openBy, + missionId = mission?.id, + attachedToMissionAtUtc = attachedToMissionAtUtc?.atZone(UTC), + detachedFromMissionAtUtc = detachedFromMissionAtUtc?.atZone(UTC), + attachedEnvActionId = attachedEnvActionId, + ) fun toReportingDTO(objectMapper: ObjectMapper) = - ReportingDTO( - reporting = this.toReporting(), - controlUnit = controlUnit?.toFullControlUnit(), - semaphore = semaphore?.toSemaphore(), - attachedMission = - if (detachedFromMissionAtUtc == null && attachedToMissionAtUtc != null - ) { - mission?.toMissionEntity( - objectMapper, - ) - } else { - null - }, - detachedMission = - if (detachedFromMissionAtUtc != null) { - mission?.toMissionEntity( - objectMapper, - ) - } else { - null - }, - ) + ReportingDTO( + reporting = this.toReporting(), + controlUnit = controlUnit?.toFullControlUnit(), + semaphore = semaphore?.toSemaphore(), + attachedMission = + if (detachedFromMissionAtUtc == null && attachedToMissionAtUtc != null + ) { + mission?.toMissionEntity( + objectMapper, + ) + } else { + null + }, + detachedMission = + if (detachedFromMissionAtUtc != null) { + mission?.toMissionEntity( + objectMapper, + ) + } else { + null + }, + ) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -178,39 +178,39 @@ data class ReportingModel( companion object { fun fromReportingEntity( - reporting: ReportingEntity, - semaphoreReference: SemaphoreModel?, - controlUnitReference: ControlUnitModel?, - missionReference: MissionModel?, + reporting: ReportingEntity, + semaphoreReference: SemaphoreModel?, + controlUnitReference: ControlUnitModel?, + missionReference: MissionModel?, ) = - ReportingModel( - id = reporting.id, - reportingId = reporting.reportingId, - sourceType = reporting.sourceType, - semaphore = semaphoreReference, - controlUnit = controlUnitReference, - sourceName = reporting.sourceName, - targetType = reporting.targetType, - vehicleType = reporting.vehicleType, - targetDetails = reporting.targetDetails, - geom = reporting.geom, - seaFront = reporting.seaFront, - description = reporting.description, - reportType = reporting.reportType, - theme = reporting.theme, - subThemes = reporting.subThemes, - actionTaken = reporting.actionTaken, - isControlRequired = reporting.isControlRequired, - hasNoUnitAvailable = reporting.hasNoUnitAvailable, - createdAt = reporting.createdAt.toInstant(), - validityTime = reporting.validityTime, - isArchived = reporting.isArchived, - isDeleted = reporting.isDeleted, - openBy = reporting.openBy, - mission = missionReference, - attachedToMissionAtUtc = reporting.attachedToMissionAtUtc?.toInstant(), - detachedFromMissionAtUtc = reporting.detachedFromMissionAtUtc?.toInstant(), - attachedEnvActionId = reporting.attachedEnvActionId, - ) + ReportingModel( + id = reporting.id, + reportingId = reporting.reportingId, + sourceType = reporting.sourceType, + semaphore = semaphoreReference, + controlUnit = controlUnitReference, + sourceName = reporting.sourceName, + targetType = reporting.targetType, + vehicleType = reporting.vehicleType, + targetDetails = reporting.targetDetails, + geom = reporting.geom, + seaFront = reporting.seaFront, + description = reporting.description, + reportType = reporting.reportType, + theme = reporting.theme, + subThemes = reporting.subThemes, + actionTaken = reporting.actionTaken, + isControlRequired = reporting.isControlRequired, + hasNoUnitAvailable = reporting.hasNoUnitAvailable, + createdAt = reporting.createdAt.toInstant(), + validityTime = reporting.validityTime, + isArchived = reporting.isArchived, + isDeleted = reporting.isDeleted, + openBy = reporting.openBy, + mission = missionReference, + attachedToMissionAtUtc = reporting.attachedToMissionAtUtc?.toInstant(), + detachedFromMissionAtUtc = reporting.detachedFromMissionAtUtc?.toInstant(), + attachedEnvActionId = reporting.attachedEnvActionId, + ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt index 681e42f2a..cf4c1f686 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt @@ -12,21 +12,21 @@ import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces. import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBMissionRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBReportingRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBSemaphoreRepository -import java.time.Instant import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.Modifying import org.springframework.orm.jpa.JpaObjectRetrievalFailureException import org.springframework.stereotype.Repository import org.springframework.transaction.annotation.Transactional +import java.time.Instant @Repository class JpaReportingRepository( - private val dbReportingRepository: IDBReportingRepository, - private val dbMissionRepository: IDBMissionRepository, - private val dbSemaphoreRepository: IDBSemaphoreRepository, - private val dbControlUnitRepository: IDBControlUnitRepository, - private val mapper: ObjectMapper, + private val dbReportingRepository: IDBReportingRepository, + private val dbMissionRepository: IDBMissionRepository, + private val dbSemaphoreRepository: IDBSemaphoreRepository, + private val dbControlUnitRepository: IDBControlUnitRepository, + private val mapper: ObjectMapper, ) : IReportingRepository { @Transactional @@ -40,26 +40,26 @@ class JpaReportingRepository( } override fun findAll( - pageable: Pageable, - reportingType: List?, - seaFronts: List?, - sourcesType: List?, - startedAfter: Instant, - startedBefore: Instant?, - status: List?, + pageable: Pageable, + reportingType: List?, + seaFronts: List?, + sourcesType: List?, + startedAfter: Instant, + startedBefore: Instant?, + status: List?, ): List { val sourcesTypeAsStringArray = sourcesType?.map { it.name } val reportingTypeAsStringArray = reportingType?.map { it.name } return dbReportingRepository.findAll( - pageable, - reportingType = convertToString(reportingTypeAsStringArray), - seaFronts = convertToString(seaFronts), - sourcesType = convertToString(sourcesTypeAsStringArray), - startedAfter = startedAfter, - startedBefore = startedBefore, - status = convertToString(status), - ) - .map { it.toReportingDTO(mapper) } + pageable, + reportingType = convertToString(reportingTypeAsStringArray), + seaFronts = convertToString(seaFronts), + sourcesType = convertToString(sourcesTypeAsStringArray), + startedAfter = startedAfter, + startedBefore = startedBefore, + status = convertToString(status), + ) + .map { it.toReportingDTO(mapper) } } override fun findByMissionId(missionId: Int): List { @@ -71,41 +71,41 @@ class JpaReportingRepository( override fun save(reporting: ReportingEntity): ReportingDTO { return try { val semaphoreReference = - if (reporting.semaphoreId != null) { - dbSemaphoreRepository.getReferenceById( - reporting.semaphoreId, - ) - } else { - null - } + if (reporting.semaphoreId != null) { + dbSemaphoreRepository.getReferenceById( + reporting.semaphoreId, + ) + } else { + null + } val controlUnitReference = - if (reporting.controlUnitId != null) { - dbControlUnitRepository.getReferenceById( - reporting.controlUnitId, - ) - } else { - null - } + if (reporting.controlUnitId != null) { + dbControlUnitRepository.getReferenceById( + reporting.controlUnitId, + ) + } else { + null + } val missionReference = - if (reporting.missionId != null) { - dbMissionRepository.getReferenceById( - reporting.missionId, - ) - } else { - null - } - val reportingModel = - ReportingModel.fromReportingEntity( - reporting = reporting, - semaphoreReference = semaphoreReference, - controlUnitReference = controlUnitReference, - missionReference = missionReference, + if (reporting.missionId != null) { + dbMissionRepository.getReferenceById( + reporting.missionId, ) + } else { + null + } + val reportingModel = + ReportingModel.fromReportingEntity( + reporting = reporting, + semaphoreReference = semaphoreReference, + controlUnitReference = controlUnitReference, + missionReference = missionReference, + ) dbReportingRepository.saveAndFlush(reportingModel).toReportingDTO(mapper) } catch (e: JpaObjectRetrievalFailureException) { throw NotFoundException( - "Invalid reference to semaphore, control unit or mission: not found in referential", - e, + "Invalid reference to semaphore, control unit or mission: not found in referential", + e, ) } catch (e: DataIntegrityViolationException) { throw NotFoundException("Invalid combination of mission and/or envAction", e) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBReportingRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBReportingRepository.kt index 6d0ecf921..9126b2523 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBReportingRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBReportingRepository.kt @@ -41,7 +41,7 @@ interface IDBReportingRepository : JpaRepository { mission_id = :missionId, attached_to_mission_at_utc = CASE WHEN mission_id is null AND id in (:reportingIds) THEN NOW() ELSE attached_to_mission_at_utc END, detached_from_mission_at_utc = CASE WHEN id not in (:reportingIds) THEN NOW() ELSE CAST(null as timestamp ) END - WHERE id in (:reportingIds) or mission_id = :missionId + WHERE id in (:reportingIds) """, nativeQuery = true, ) diff --git a/frontend/src/domain/use_cases/missions/addMission.ts b/frontend/src/domain/use_cases/missions/addMission.ts index 7efd2ac3d..af1b0a8d0 100644 --- a/frontend/src/domain/use_cases/missions/addMission.ts +++ b/frontend/src/domain/use_cases/missions/addMission.ts @@ -44,10 +44,13 @@ export const addMission = (attachedReporting?: Reporting) => async (dispatch, ge const missionsUpdated = [...missions, { isFormDirty: false, mission: missionFactory(undefined, id) }] await dispatch(multiMissionsActions.setSelectedMissions(missionsUpdated)) - if (attachedReporting) { - await dispatch(attachReportingToMissionSliceActions.setAttachedReportings([attachedReporting])) - await dispatch(attachReportingToMissionSliceActions.setAttachedReportingIds([attachedReporting?.id])) - } + + await dispatch( + attachReportingToMissionSliceActions.setAttachedReportings(attachedReporting ? [attachedReporting] : []) + ) + await dispatch( + attachReportingToMissionSliceActions.setAttachedReportingIds(attachedReporting ? [attachedReporting?.id] : []) + ) dispatch(sideWindowActions.focusAndGoTo(generatePath(sideWindowPaths.MISSION, { id }))) } diff --git a/frontend/src/domain/use_cases/missions/editMissionInLocalStore.ts b/frontend/src/domain/use_cases/missions/editMissionInLocalStore.ts index fe34781c1..1bb2fd57b 100644 --- a/frontend/src/domain/use_cases/missions/editMissionInLocalStore.ts +++ b/frontend/src/domain/use_cases/missions/editMissionInLocalStore.ts @@ -2,7 +2,6 @@ import { generatePath } from 'react-router' import { missionsAPI } from '../../../api/missionsAPI' import { attachReportingToMissionSliceActions } from '../../../features/missions/MissionForm/AttachReporting/slice' -// import * as mockMission from '../../../features/missions/MissionForm/mission.json' import { sideWindowActions } from '../../../features/SideWindow/slice' import { sideWindowPaths } from '../../entities/sideWindow' import { setToast } from '../../shared_slices/Global' @@ -37,7 +36,6 @@ export const editMissionInLocalStore = missionId => async (dispatch, getState) = // now we want to save in multiMissions state the mission we want to edit const missionToSave = response.data - // const { mission: missionToSave } = mockMission const newSelectedMissionIndex = missions.findIndex(mission => mission.mission.id === missionToSave?.id) const missionFormatted = { isFormDirty: false, diff --git a/frontend/src/domain/use_cases/missions/switchTab.ts b/frontend/src/domain/use_cases/missions/switchTab.ts index e57f43baf..eee9d86ea 100644 --- a/frontend/src/domain/use_cases/missions/switchTab.ts +++ b/frontend/src/domain/use_cases/missions/switchTab.ts @@ -1,3 +1,4 @@ +import { attachReportingToMissionSliceActions } from '../../../features/missions/MissionForm/AttachReporting/slice' import { sideWindowActions } from '../../../features/SideWindow/slice' import { getIdTyped } from '../../../utils/getIdTyped' import { getMissionPageRoute } from '../../../utils/routes' @@ -32,4 +33,18 @@ export const switchTab = path => async (dispatch, getState) => { await dispatch(multiMissionsActions.setSelectedMissions(missionsUpdated)) await dispatch(sideWindowActions.setCurrentPath(path)) + + // since we are switching to another mission, we need to update the attached reportings store + // because it's the form who listen to this store + const nextMissionIndex = missionsUpdated.findIndex(mission => mission.mission.id === id) + await dispatch( + attachReportingToMissionSliceActions.setAttachedReportings( + missionsUpdated[nextMissionIndex]?.mission?.attachedReportings || [] + ) + ) + await dispatch( + attachReportingToMissionSliceActions.setAttachedReportingIds( + missionsUpdated[nextMissionIndex]?.mission?.attachedReportingIds || [] + ) + ) } diff --git a/frontend/src/domain/use_cases/reporting/createMissionFromReporting.ts b/frontend/src/domain/use_cases/reporting/createMissionFromReporting.ts index fec762a74..b293e6c95 100644 --- a/frontend/src/domain/use_cases/reporting/createMissionFromReporting.ts +++ b/frontend/src/domain/use_cases/reporting/createMissionFromReporting.ts @@ -1,3 +1,5 @@ +import omit from 'lodash/omit' + import { reportingsAPI } from '../../../api/reportingsAPI' import { isNewReporting } from '../../../features/Reportings/utils' import { setReportingFormVisibility, setToast, VisibilityState } from '../../shared_slices/Global' @@ -12,7 +14,8 @@ export const createMissionFromReporting = (values: Reporting | Partial state.global) - - const currentfeatureId = currentFeatureOver?.getId() - const displayHoveredFeature = - typeof currentfeatureId === 'string' && currentfeatureId.startsWith(Layers.MISSION_TO_ATTACH_ON_REPORTING.code) - - return ( - - - - ) -} diff --git a/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx b/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx index 2407dde73..37c17702a 100644 --- a/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx +++ b/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx @@ -26,8 +26,7 @@ export function AttachMission({ setIsAttachNewMission }) { } const unattachMission = () => { - dispatch(attachMissionToReportingSliceActions.setMissionId(undefined)) - dispatch(attachMissionToReportingSliceActions.setAttachedMission(undefined)) + dispatch(attachMissionToReportingSliceActions.resetAttachMissionState()) } const createMission = async () => { @@ -35,6 +34,8 @@ export function AttachMission({ setIsAttachNewMission }) { handleSubmit() } + // the form listens to the redux store to update the attached mission + // because of the map interaction to attach mission useEffect(() => { if (missionId !== values.missionId) { setFieldValue('missionId', missionId) diff --git a/frontend/src/features/Reportings/ReportingForm/Form.tsx b/frontend/src/features/Reportings/ReportingForm/Form.tsx index 95162a449..9b90d48f4 100644 --- a/frontend/src/features/Reportings/ReportingForm/Form.tsx +++ b/frontend/src/features/Reportings/ReportingForm/Form.tsx @@ -82,8 +82,12 @@ export function ReportingForm({ setFieldValue('isControlRequired', reportType === ReportingTypeEnum.INFRACTION_SUSPICION) } - const changeNeedControlValue = checked => { + const changeNeedControlValue = async checked => { setFieldValue('isControlRequired', checked) + if (!checked) { + setFieldValue('hasNoUnitAvailable', false) + await dispatch(attachMissionToReportingSliceActions.resetAttachMissionState()) + } } const reduceOrExpandReporting = () => { diff --git a/frontend/src/features/Reportings/ReportingForm/index.tsx b/frontend/src/features/Reportings/ReportingForm/index.tsx index fa4826755..48c1c26c2 100644 --- a/frontend/src/features/Reportings/ReportingForm/index.tsx +++ b/frontend/src/features/Reportings/ReportingForm/index.tsx @@ -15,6 +15,8 @@ import { SideWindowBackground, FormContainer } from '../style' import { getReportingInitialValues, isNewReporting } from '../utils' export function ReportingFormWithContext({ context, totalReportings }) { + const dispatch = useAppDispatch() + const reportingFormVisibility = useAppSelector(state => state.global.reportingFormVisibility) const reportings = useAppSelector(state => state.reporting.reportings) const activeReportingId = useAppSelector(state => state.reporting.activeReportingId) @@ -22,8 +24,6 @@ export function ReportingFormWithContext({ context, totalReportings }) { activeReportingId ? state.reporting.reportings[activeReportingId]?.context : undefined ) - const dispatch = useAppDispatch() - const [isAttachNewMission, setIsAttachNewMission] = useState(false) const isReportingNew = useMemo(() => isNewReporting(activeReportingId), [activeReportingId]) @@ -38,6 +38,8 @@ export function ReportingFormWithContext({ context, totalReportings }) { if (isAttachNewMission) { await dispatch(createMissionFromReporting(values)) setIsAttachNewMission(false) + + return } dispatch(saveReporting(values, context)) } diff --git a/frontend/src/features/Reportings/hooks/useSyncFormValuesWithRedux.ts b/frontend/src/features/Reportings/hooks/useSyncFormValuesWithRedux.ts index c27cadbcd..229137602 100644 --- a/frontend/src/features/Reportings/hooks/useSyncFormValuesWithRedux.ts +++ b/frontend/src/features/Reportings/hooks/useSyncFormValuesWithRedux.ts @@ -4,28 +4,27 @@ import { useEffect, useMemo } from 'react' import { reportingActions } from '../../../domain/shared_slices/reporting' import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { attachMissionToReportingSliceActions } from '../ReportingForm/AttachMission/slice' +import { useAppSelector } from '../../../hooks/useAppSelector' import type { Reporting } from '../../../domain/entities/reporting' export const useSyncFormValuesWithRedux = () => { const { dirty, values } = useFormikContext() + const activeReportingId = useAppSelector(state => state.reporting.activeReportingId) const dispatch = useAppDispatch() const dispatchFormUpdate = useMemo(() => { const throttled = newValues => { - if (!newValues) { + if (!newValues || newValues.id !== activeReportingId) { return } dispatch(reportingActions.setReportingState(newValues)) dispatch(reportingActions.setIsDirty(newValues ? dirty : false)) - dispatch(attachMissionToReportingSliceActions.setMissionId(newValues?.missionId)) - dispatch(attachMissionToReportingSliceActions.setAttachedMission(newValues?.missionId)) } return _.throttle(throttled, 500) - }, [dispatch, dirty]) + }, [dispatch, dirty, activeReportingId]) useEffect(() => { dispatchFormUpdate(values) diff --git a/frontend/src/features/map/index.tsx b/frontend/src/features/map/index.tsx index d26075ab7..1c4e4b3d6 100644 --- a/frontend/src/features/map/index.tsx +++ b/frontend/src/features/map/index.tsx @@ -29,11 +29,11 @@ import { ShowRegulatoryMetadata } from './ShowRegulatoryMetadata' import { ReportingToAttachLayer } from '../missions/Layers/ReportingToAttach' import { HoveredReportingToAttachLayer } from '../missions/Layers/ReportingToAttach/HoveredReportingToAttachLayer' import { SelectedReportingToAttachLayer } from '../missions/Layers/ReportingToAttach/SelectedReportingToAttachLayer' -import { ReportingToAttachOverlays } from '../missions/Overlays/ReportingToAttach' +import { ReportingToAttachOverlays } from '../missions/Overlays/ReportingToAttachToMission' import { MissionToAttachLayer } from '../Reportings/Layers/MissionToAttach' import { HoveredMissionToAttachLayer } from '../Reportings/Layers/MissionToAttach/HoveredMissionToAttachLayer' import { SelectedMissionToAttachLayer } from '../Reportings/Layers/MissionToAttach/SelectedMissionToAttachLayer' -import { MissionToAttachOverlays } from '../Reportings/Overlays/MissionToAttach' +import { MissionToAttachOverlays } from '../Reportings/Overlays/MissionToAttachToReporting' export function Map() { return ( diff --git a/frontend/src/features/missions/Layers/ReportingToAttach/index.tsx b/frontend/src/features/missions/Layers/ReportingToAttach/index.tsx index e6fb45e97..a2fe33f2b 100644 --- a/frontend/src/features/missions/Layers/ReportingToAttach/index.tsx +++ b/frontend/src/features/missions/Layers/ReportingToAttach/index.tsx @@ -36,7 +36,7 @@ export function ReportingToAttachLayer({ map, mapClickEvent }: BaseMapChildrenPr const filteredReportings = reduce( reportings?.entities, (features, reporting) => { - if (reporting && reporting.geom) { + if (reporting && reporting.geom && !reporting.missionId) { features.push(getReportingZoneFeature(reporting, Layers.REPORTING_TO_ATTACH_ON_MISSION.code)) } diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index 552370b42..1a05afecb 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -54,8 +54,10 @@ export function MissionForm({ id, isAlreadyClosed, isNewMission, selectedMission const allowDeleteMission = !isNewMission && allowEditMission const allowCloseMission = !selectedMission?.isClosed || !values?.isClosed + // the form listens to the redux store to update the attached reportings + // because of the map interaction to attach reportings useEffect(() => { - if (attachedReportingIds && attachedReportingIds.length !== values?.attachedReportingIds?.length) { + if (attachedReportingIds.length !== values?.attachedReportingIds?.length) { setFieldValue('attachedReportingIds', attachedReportingIds) setFieldValue('attachedReportings', attachedReportings) } diff --git a/frontend/src/features/missions/MissionForm/mission.json b/frontend/src/features/missions/MissionForm/mission.json deleted file mode 100644 index 8de54d7b3..000000000 --- a/frontend/src/features/missions/MissionForm/mission.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "mission": { - "id": 34, - "missionTypes": ["SEA"], - "controlUnits": [ - { - "id": 16, - "administration": "Douane", - "isArchived": false, - "name": "BSN Ste Maxime", - "resources": [], - "contact": "01234567890" - }, - { - "id": 17, - "administration": "Douane", - "isArchived": false, - "name": "DF 25 Libecciu", - "resources": [], - "contact": "M. Capitaine Flame" - }, - { - "id": 18, - "administration": "Douane", - "isArchived": false, - "name": "DF 61 Port-de-Bouc", - "resources": [], - "contact": "Popeye 06789012345" - } - ], - "openBy": "STS", - "closedBy": "JAM", - "observationsCacem": "Surface different shoulder interview. Job together area probably. Of alone class capital determine machine always.", - "observationsCnsp": null, - "facade": "MEMN", - "geom": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [-0.63780295, 49.47775876], - [-0.62730058, 49.35133213], - [-0.40769229, 49.34599875], - [-0.22179846, 49.2940188], - [0.00683004, 49.33779656], - [0.07874359, 49.46756946], - [-0.22010001, 49.60456891], - [-0.54074987, 49.63043863], - [-0.63780295, 49.47775876] - ] - ] - ] - }, - "startDateTimeUtc": "2023-11-09T12:42:50.055526Z", - "endDateTimeUtc": "2023-11-10T13:42:50.055526Z", - "envActions": [ - { - "actionType": "SURVEILLANCE", - "id": "b8007c8a-5135-4bc3-816f-c69c7b75d807", - "actionStartDateTimeUtc": "2023-11-10T01:42:50.239358Z", - "actionEndDateTimeUtc": "2023-11-10T03:42:50.239358Z", - "geom": null, - "facade": null, - "department": null, - "themes": [ - { - "theme": "Police des espèces protégées et de leurs habitats (faune et flore)", - "subThemes": ["Destruction, capture, arrachage", "Atteinte aux habitats d'espèces protégées"], - "protectedSpecies": ["FLORA", "BIRDS"] - }, - { - "theme": "Police des mouillages", - "subThemes": ["Mouillage individuel", "ZMEL"], - "protectedSpecies": [] - } - ], - "observations": "RAS", - "coverMissionZone": true - }, - { - "id": "c52c6f20-e495-4b29-b3df-d7edfb67fdd7", - "actionStartDateTimeUtc": "2023-11-10T00:42:50.239358Z", - "actionEndDateTimeUtc": "2023-11-10T03:42:50.239358Z", - "geom": { - "type": "MultiPoint", - "coordinates": [[-0.48262439, 49.54216518]] - }, - "facade": null, - "department": null, - "themes": [ - { - "theme": "Police des mouillages", - "subThemes": ["Mouillage individuel", "ZMEL"], - "protectedSpecies": [] - } - ], - "observations": "RAS", - "actionNumberOfControls": 1, - "actionTargetType": "VEHICLE", - "vehicleType": "VESSEL", - "infractions": [ - { - "id": "5d5b7829-68cd-4436-8c0b-1cc8db7788a0", - "natinf": ["10038", "10231"], - "observations": "Pas d'observations", - "registrationNumber": "BALTIK", - "companyName": null, - "relevantCourt": "LOCAL_COURT", - "infractionType": "WITH_REPORT", - "formalNotice": "PENDING", - "toProcess": false, - "controlledPersonIdentity": "John Doe", - "vesselType": "COMMERCIAL", - "vesselSize": "FROM_24_TO_46m" - } - ], - "actionType": "CONTROL" - } - ], - "reportings": [ - { - "id": 5, - "reportingId": 2300005, - "sourceType": "SEMAPHORE", - "semaphoreId": 36, - "controlUnitId": null, - "sourceName": null, - "displayedSource": "Sémaphore de Fécamp", - "targetType": "COMPANY", - "vehicleType": null, - "targetDetails": [ - { - "mmsi": null, - "imo": null, - "externalReferenceNumber": null, - "vesselName": "Mr le gérant", - "operatorName": "Ma société", - "size": null - } - ], - "geom": { - "type": "MultiPoint", - "coordinates": [[-4.18759766, 47.1128127]] - }, - "seaFront": "Guadeloupe", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - "reportType": "OBSERVATION", - "theme": null, - "subThemes": null, - "actionTaken": null, - "isControlRequired": true, - "hasNoUnitAvailable": true, - "createdAt": "2023-09-25T15:17:32.847553Z", - "validityTime": 6, - "isArchived": false, - "openBy": null, - "attachedToMissionAtUtc": "2023-09-25T15:17:32.847553Z" - }, - { - "id": 4, - "reportingId": 2300004, - "sourceType": "OTHER", - "semaphoreId": null, - "controlUnitId": null, - "sourceName": "MA SUPER SOCIETE", - "displayedSource": "MA SUPER SOCIETE", - "targetType": "INDIVIDUAL", - "vehicleType": null, - "targetDetails": [ - { - "mmsi": null, - "imo": null, - "externalReferenceNumber": null, - "vesselName": null, - "operatorName": "Mr le dirigeant", - "size": null - } - ], - "geom": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [-2.81383107, 49.49805557], - [-2.63290938, 49.59886363], - [-2.80213877, 49.69953506], - [-2.91683355, 49.52263623], - [-2.81383107, 49.49805557] - ] - ] - ] - }, - "seaFront": "MED", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ultrices risus ac arcu pellentesque, et tempor justo tempor. Pellentesque sem nisl, tempor id augue non, interdum sollicitudin felis.", - "reportType": "OBSERVATION", - "theme": "Pêche à pied", - "subThemes": ["Braconnage"], - "actionTaken": null, - "isControlRequired": true, - "hasNoUnitAvailable": true, - "createdAt": "2023-09-25T13:17:32.847553Z", - "validityTime": 4, - "isArchived": false, - "openBy": null, - "attachedToMissionAtUtc": "2023-09-21T12:17:32.847553Z" - }, - { - "id": 3, - "reportingId": 2300003, - "sourceType": "CONTROL_UNIT", - "semaphoreId": null, - "controlUnitId": 1, - "sourceName": null, - "displayedSource": "Cultures marines – DDTM 40", - "targetType": "VEHICLE", - "vehicleType": "VESSEL", - "targetDetails": [ - { - "mmsi": "012314231345", - "imo": null, - "externalReferenceNumber": null, - "vesselName": "Vessel 3", - "operatorName": null, - "size": null - } - ], - "geom": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [-3.50463204, 48.93860679], - [-3.24829507, 48.95623935], - [-3.48444629, 48.99696455], - [-3.50463204, 48.93860679] - ] - ] - ] - }, - "seaFront": "NAMO", - "description": "Description 3", - "reportType": "INFRACTION_SUSPICION", - "theme": "Police des mouillages", - "subThemes": ["ZMEL"], - "actionTaken": "ACTION TAKEN", - "isControlRequired": true, - "hasNoUnitAvailable": true, - "createdAt": "2023-09-25T15:17:32.847553Z", - "validityTime": 1, - "isArchived": true, - "openBy": null, - "attachedToMissionAtUtc": "2023-06-02T11:17:32.847553Z" - } - ], - "missionSource": "MONITORENV", - "isClosed": false, - "hasMissionOrder": false, - "isUnderJdp": false - } -} diff --git a/frontend/src/features/missions/Overlays/ReportingToAttach/index.tsx b/frontend/src/features/missions/Overlays/ReportingToAttachToMission/index.tsx similarity index 100% rename from frontend/src/features/missions/Overlays/ReportingToAttach/index.tsx rename to frontend/src/features/missions/Overlays/ReportingToAttachToMission/index.tsx diff --git a/frontend/src/features/missions/Overlays/reportingToAttach/index.tsx b/frontend/src/features/missions/Overlays/reportingToAttach/index.tsx deleted file mode 100644 index 90d6f2e6a..000000000 --- a/frontend/src/features/missions/Overlays/reportingToAttach/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useState } from 'react' - -import { Layers } from '../../../../domain/entities/layers/constants' -import { useAppSelector } from '../../../../hooks/useAppSelector' -import { OverlayPositionOnCentroid } from '../../../map/overlays/OverlayPositionOnCentroid' -import { ReportingCard } from '../../../map/overlays/reportings/ReportingCard' - -import type { BaseMapChildrenProps } from '../../../map/BaseMap' - -const MARGINS = { - xLeft: 50, - xMiddle: 30, - xRight: -55, - yBottom: 50, - yMiddle: 50, - yTop: -55 -} - -export function ReportingToAttachOverlays({ currentFeatureOver, map }: BaseMapChildrenProps) { - const { displayReportingToAttachLayer } = useAppSelector(state => state.global) - - const [hoveredMargins, setHoveredMargins] = useState(MARGINS) - - const currentfeatureId = currentFeatureOver?.getId() - const displayHoveredFeature = - typeof currentfeatureId === 'string' && currentfeatureId.startsWith(Layers.REPORTING_TO_ATTACH_ON_MISSION.code) - - const updateHoveredMargins = (cardHeight: number) => { - if (MARGINS.yTop - cardHeight !== hoveredMargins.yTop) { - setHoveredMargins({ ...hoveredMargins, yTop: MARGINS.yTop - cardHeight }) - } - } - - return ( - - - - ) -} diff --git a/frontend/src/store/reducers.ts b/frontend/src/store/reducers.ts index f7675e738..21bcf9bd7 100644 --- a/frontend/src/store/reducers.ts +++ b/frontend/src/store/reducers.ts @@ -1,5 +1,3 @@ -// TODO We should move that into `/frontend/src/store` directory. - import { combineReducers } from '@reduxjs/toolkit' import { ampsAPI, ampsErrorLoggerMiddleware } from '../api/ampsAPI'