diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SecurityConfig.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SecurityConfig.kt index b4302eddc1..5665679513 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SecurityConfig.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SecurityConfig.kt @@ -133,7 +133,6 @@ class SecurityConfig( } private fun validateAndProcessUser(oidcUser: OidcUser): OidcUser { - if (oidcProperties.bypassEmailDomainsFilter == "true") { logger.info("✅ OIDC is bypassing email domain checks.") return oidcUser diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaEntity.kt index dabb9e7810..f51a1fb2d2 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaEntity.kt @@ -4,18 +4,11 @@ import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.utils.WordUtils import org.apache.poi.xwpf.usermodel.XWPFDocument import org.apache.poi.xwpf.usermodel.XWPFTableCell -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.Locale data class EditableBriefVigilanceAreaEntity( val color: String, val comments: String? = null, - val endDatePeriod: ZonedDateTime? = null, - val endingOccurenceDate: String, - val frequency: String, val id: Int, - val isAtAllTimes: Boolean, override val image: String?, val imagesAttachments: List? = null, override val minimap: String?, @@ -23,9 +16,9 @@ data class EditableBriefVigilanceAreaEntity( val linkedRegulatoryAreas: String? = null, val links: List? = null, val name: String, - val startDatePeriod: ZonedDateTime? = null, val themes: String? = null, val visibility: String? = null, + val periods: List? = null, ) : DetailWithImagesRenderable { override val title = name @@ -34,12 +27,19 @@ data class EditableBriefVigilanceAreaEntity( private const val LINK_ROW_INDEX = 6 } - override fun buildDetailsRows(): List> { - val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.FRENCH) - val periodDate = "Du ${startDatePeriod?.format(formatter)} au ${endDatePeriod?.format(formatter)}" - - return listOf( - listOf("Période", if (isAtAllTimes) "En tout temps" else periodDate), + override fun buildDetailsRows(): List> = + listOf( + listOf( + "Période(s)", + periods?.joinToString("\n") { period -> + listOf( + period.getPeriodText(), + period.frequency, + period.endingOccurenceDate, + ).filter { it.isNotEmpty() }.joinToString(", ") + } + ?: "", + ), listOf("Thématique", themes ?: ""), listOf("Visibilité", visibility ?: ""), listOf("Commentaires", comments ?: ""), @@ -47,7 +47,6 @@ data class EditableBriefVigilanceAreaEntity( listOf("Amps en lien", linkedAMPs ?: ""), listOf("Liens utiles", ""), ) - } override fun customizeValueCell( rowIndex: Int, @@ -62,11 +61,14 @@ data class EditableBriefVigilanceAreaEntity( val cellRun = cell.addParagraph().createRun() cellRun.fontFamily = "Arial" cellRun.fontSize = 10 - cellRun.setText(buildDetailsRows()[0][1]) - cellRun.addBreak() - cellRun.setText(frequency) - cellRun.addBreak() - cellRun.setText(endingOccurenceDate) + val periodsText = buildDetailsRows()[0][1] + val lines = periodsText.split("\n") + lines.forEachIndexed { index, period -> + cellRun.setText(period) + if (index < lines.size - 1) { + cellRun.addBreak() + } + } } LINK_ROW_INDEX -> { diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaPeriodEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaPeriodEntity.kt new file mode 100644 index 0000000000..eef388e06c --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/dashboard/EditableBriefVigilanceAreaPeriodEntity.kt @@ -0,0 +1,22 @@ +package fr.gouv.cacem.monitorenv.domain.entities.dashboard + +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.Locale +import java.util.UUID + +data class EditableBriefVigilanceAreaPeriodEntity( + val id: UUID, + val isAtAllTimes: Boolean, + val startDatePeriod: ZonedDateTime? = null, + val endDatePeriod: ZonedDateTime? = null, + val endingOccurenceDate: String, + val frequency: String, +) { + fun getPeriodDate(): String { + val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.FRENCH) + return "Du ${startDatePeriod?.format(formatter)} au ${endDatePeriod?.format(formatter)}" + } + + fun getPeriodText(): String = if (isAtAllTimes) "En tout temps" else getPeriodDate() +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt index 1bdf951de6..055b94d353 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt @@ -8,17 +8,9 @@ import java.time.ZonedDateTime data class VigilanceAreaEntity( val id: Int? = null, val comments: String? = null, - val computedEndDate: ZonedDateTime? = null, val createdBy: String? = null, - val endDatePeriod: ZonedDateTime? = null, - val endingCondition: EndingConditionEnum? = null, - val endingOccurrenceDate: ZonedDateTime? = null, - val endingOccurrencesNumber: Int? = null, - val frequency: FrequencyEnum? = null, val geom: MultiPolygon? = null, val images: List? = listOf(), - val isAtAllTimes: Boolean, - val isArchived: Boolean, val isDeleted: Boolean, val isDraft: Boolean, val links: List? = null, @@ -27,9 +19,9 @@ data class VigilanceAreaEntity( val name: String, val seaFront: String? = null, val sources: List, - val startDatePeriod: ZonedDateTime? = null, val themes: List, val tags: List, + val periods: List, val visibility: VisibilityEnum? = null, val createdAt: ZonedDateTime?, val updatedAt: ZonedDateTime?, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaPeriodEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaPeriodEntity.kt new file mode 100644 index 0000000000..d39dacf68d --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaPeriodEntity.kt @@ -0,0 +1,17 @@ +package fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea + +import java.time.ZonedDateTime +import java.util.UUID + +data class VigilanceAreaPeriodEntity( + val id: UUID?, + val computedEndDate: ZonedDateTime?, + val endDatePeriod: ZonedDateTime?, + val endingCondition: EndingConditionEnum?, + val endingOccurrenceDate: ZonedDateTime?, + val endingOccurrencesNumber: Int?, + val frequency: FrequencyEnum?, + val isAtAllTimes: Boolean, + val isCritical: Boolean?, + val startDatePeriod: ZonedDateTime?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreas.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreas.kt deleted file mode 100644 index 67b70ff335..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreas.kt +++ /dev/null @@ -1,22 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea - -import fr.gouv.cacem.monitorenv.config.UseCase -import fr.gouv.cacem.monitorenv.domain.repositories.IVigilanceAreaRepository -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.scheduling.annotation.Scheduled - -@UseCase -class ArchiveOutdatedVigilanceAreas( - private val vigilanceAreaRepository: IVigilanceAreaRepository, -) { - private val logger: Logger = LoggerFactory.getLogger(ArchiveOutdatedVigilanceAreas::class.java) - - // At every 6 hours, after 1 minute of initial delay - @Scheduled(fixedDelay = 21600000, initialDelay = 6000) - fun execute() { - logger.info("Attempt to ARCHIVE vigilance areas") - val numberOfArchivedVigilanceAreas = vigilanceAreaRepository.archiveOutdatedVigilanceAreas() - logger.info("$numberOfArchivedVigilanceAreas vigilance areas archived") - } -} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt index f29e455962..e6f654e7b4 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt @@ -10,7 +10,6 @@ import fr.gouv.cacem.monitorenv.domain.use_cases.dashboard.SaveDashboard import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation import fr.gouv.cacem.monitorenv.domain.validators.vigilance_area.VigilanceAreaValidator import org.slf4j.LoggerFactory -import java.time.ZonedDateTime @UseCase class CreateOrUpdateVigilanceArea( @@ -29,12 +28,7 @@ class CreateOrUpdateVigilanceArea( vigilanceArea.geom?.let { nonNullGeom -> facadeAreasRepository.findFacadeFromGeometry(nonNullGeom) } - var vigilanceAreaToSave = vigilanceArea.copy(seaFront = seaFront) - if (vigilanceArea.isArchived && - (vigilanceArea.computedEndDate?.isAfter(ZonedDateTime.now()) == true || vigilanceArea.isAtAllTimes) - ) { - vigilanceAreaToSave = vigilanceAreaToSave.copy(isArchived = false) - } + val vigilanceAreaToSave = vigilanceArea.copy(seaFront = seaFront) val savedVigilanceArea = vigilanceAreaRepository.save(vigilanceAreaToSave) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt index b5c0ab5b7c..60cfd8f12b 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt @@ -48,46 +48,48 @@ class VigilanceAreaValidator : Validator { data = "Un tag ou un thème est obligatoire", ) } - if (!vigilanceArea.isAtAllTimes) { - if (vigilanceArea.startDatePeriod === null) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "La date de début est obligatoire", - ) - } - if (vigilanceArea.endDatePeriod === null) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "La date de fin est obligatoire", - ) - } - if (vigilanceArea.frequency === null) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "La fréquence est obligatoire", - ) - } - if (vigilanceArea.frequency !== FrequencyEnum.NONE && vigilanceArea.endingCondition === null) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "La condition de fin est obligatoire", - ) - } - if (vigilanceArea.endingCondition === EndingConditionEnum.END_DATE && - vigilanceArea.endingOccurrenceDate === null - ) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "La date de fin de l'occurence est obligatoire", - ) - } - if (vigilanceArea.endingCondition === EndingConditionEnum.OCCURENCES_NUMBER && - (vigilanceArea.endingOccurrencesNumber === null || vigilanceArea.endingOccurrencesNumber == 0) - ) { - throw BackendUsageException( - code = BackendUsageErrorCode.UNVALID_PROPERTY, - data = "Le nombre d'occurence est obligatoire", - ) + vigilanceArea.periods.forEach { period -> + if (!period.isAtAllTimes) { + if (period.startDatePeriod === null) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "La date de début est obligatoire", + ) + } + if (period.endDatePeriod === null) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "La date de fin est obligatoire", + ) + } + if (period.frequency === null) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "La fréquence est obligatoire", + ) + } + if (period.frequency !== FrequencyEnum.NONE && period.endingCondition === null) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "La condition de fin est obligatoire", + ) + } + if (period.endingCondition === EndingConditionEnum.END_DATE && + period.endingOccurrenceDate === null + ) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "La date de fin de l'occurence est obligatoire", + ) + } + if (period.endingCondition === EndingConditionEnum.OCCURENCES_NUMBER && + (period.endingOccurrencesNumber === null || period.endingOccurrencesNumber == 0) + ) { + throw BackendUsageException( + code = BackendUsageErrorCode.UNVALID_PROPERTY, + data = "Le nombre d'occurence est obligatoire", + ) + } } } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt index e2fd562ca1..494d1e6af2 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt @@ -1,7 +1,5 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.vigilanceArea -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum @@ -13,16 +11,8 @@ import java.time.ZonedDateTime data class VigilanceAreaDataInput( val id: Int? = null, val comments: String? = null, - val computedEndDate: ZonedDateTime? = null, val createdBy: String? = null, - val endDatePeriod: ZonedDateTime? = null, - val endingCondition: EndingConditionEnum? = null, - val endingOccurrenceDate: ZonedDateTime? = null, - val endingOccurrencesNumber: Int? = null, - val frequency: FrequencyEnum? = null, val geom: MultiPolygon? = null, - val isArchived: Boolean, - val isAtAllTimes: Boolean, val isDraft: Boolean, val images: List? = listOf(), val links: List? = null, @@ -31,28 +21,20 @@ data class VigilanceAreaDataInput( val name: String, val seaFront: String?, val sources: List, - val startDatePeriod: ZonedDateTime? = null, val themes: List = listOf(), val visibility: VisibilityEnum? = null, val createdAt: ZonedDateTime? = null, val updatedAt: ZonedDateTime? = null, val tags: List = listOf(), val validatedAt: ZonedDateTime? = null, + val periods: List = listOf(), ) { fun toVigilanceAreaEntity(): VigilanceAreaEntity = VigilanceAreaEntity( id = this.id, comments = this.comments, - computedEndDate = this.computedEndDate, createdBy = this.createdBy, - endDatePeriod = this.endDatePeriod, - endingCondition = this.endingCondition, - endingOccurrenceDate = this.endingOccurrenceDate, - endingOccurrencesNumber = this.endingOccurrencesNumber, - frequency = this.frequency, geom = this.geom, - isAtAllTimes = this.isAtAllTimes, - isArchived = this.isArchived, isDeleted = false, isDraft = this.isDraft, images = this.images?.map { image -> image.toImageEntity() }, @@ -62,12 +44,12 @@ data class VigilanceAreaDataInput( name = this.name, seaFront = this.seaFront, sources = this.sources.map { it.toVigilanceAreaSourceEntity() }, - startDatePeriod = this.startDatePeriod, themes = this.themes.map { it.toThemeEntity() }, visibility = this.visibility, createdAt = this.createdAt, updatedAt = this.updatedAt, tags = tags.map { it.toTagEntity() }, validatedAt = this.validatedAt, + periods = periods.map { it.toVigilanceAreaPeriodEntity() }, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataPeriodInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataPeriodInput.kt new file mode 100644 index 0000000000..5faef2884e --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataPeriodInput.kt @@ -0,0 +1,34 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.vigilanceArea + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity +import java.time.ZonedDateTime +import java.util.UUID + +data class VigilanceAreaDataPeriodInput( + val id: UUID?, + val computedEndDate: ZonedDateTime? = null, + val endDatePeriod: ZonedDateTime? = null, + val endingCondition: EndingConditionEnum? = null, + val endingOccurrenceDate: ZonedDateTime? = null, + val endingOccurrencesNumber: Int? = null, + val frequency: FrequencyEnum? = null, + val isAtAllTimes: Boolean, + val isCritical: Boolean?, + val startDatePeriod: ZonedDateTime? = null, +) { + fun toVigilanceAreaPeriodEntity(): VigilanceAreaPeriodEntity = + VigilanceAreaPeriodEntity( + id = this.id, + computedEndDate = this.computedEndDate, + endDatePeriod = this.endDatePeriod, + endingCondition = this.endingCondition, + endingOccurrenceDate = this.endingOccurrenceDate, + endingOccurrencesNumber = this.endingOccurrencesNumber, + frequency = this.frequency, + isAtAllTimes = this.isAtAllTimes, + isCritical = this.isCritical, + startDatePeriod = this.startDatePeriod, + ) +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaDataOutput.kt index 3d563e5bd9..a5c7beffb7 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaDataOutput.kt @@ -1,7 +1,5 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.vigilanceArea -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum @@ -15,16 +13,8 @@ import java.time.ZonedDateTime data class VigilanceAreaDataOutput( val id: Int? = null, val comments: String? = null, - val computedEndDate: ZonedDateTime? = null, val createdBy: String? = null, - val endDatePeriod: ZonedDateTime? = null, - val endingCondition: EndingConditionEnum? = null, - val endingOccurrenceDate: ZonedDateTime? = null, - val endingOccurrencesNumber: Int? = null, - val frequency: FrequencyEnum? = null, val geom: MultiPolygon? = null, - val isAtAllTimes: Boolean, - val isArchived: Boolean, val isDraft: Boolean, val images: List = mutableListOf(), val links: List? = null, @@ -33,28 +23,21 @@ data class VigilanceAreaDataOutput( val name: String? = null, val seaFront: String? = null, val sources: List, - val startDatePeriod: ZonedDateTime? = null, val themes: List? = null, val visibility: VisibilityEnum? = null, val createdAt: ZonedDateTime?, val updatedAt: ZonedDateTime?, val tags: List, val validatedAt: ZonedDateTime?, + val periods: List, ) { companion object { fun fromVigilanceArea(vigilanceArea: VigilanceAreaEntity): VigilanceAreaDataOutput = VigilanceAreaDataOutput( id = vigilanceArea.id, comments = vigilanceArea.comments, - computedEndDate = vigilanceArea.computedEndDate, createdBy = vigilanceArea.createdBy, - endDatePeriod = vigilanceArea.endDatePeriod, - endingCondition = vigilanceArea.endingCondition, - endingOccurrenceDate = vigilanceArea.endingOccurrenceDate, - endingOccurrencesNumber = vigilanceArea.endingOccurrencesNumber, - frequency = vigilanceArea.frequency, geom = vigilanceArea.geom, - isArchived = vigilanceArea.isArchived, isDraft = vigilanceArea.isDraft, images = vigilanceArea.images?.map { VigilanceAreaImageDataOutput.fromVigilanceAreaImage(it) } @@ -65,14 +48,13 @@ data class VigilanceAreaDataOutput( name = vigilanceArea.name, seaFront = vigilanceArea.seaFront, sources = vigilanceArea.sources.map { VigilanceAreaSourceOutput.fromVigilanceAreaSourceEntity(it) }, - startDatePeriod = vigilanceArea.startDatePeriod, themes = vigilanceArea.themes.map { fromThemeEntity(it) }, visibility = vigilanceArea.visibility, createdAt = vigilanceArea.createdAt, updatedAt = vigilanceArea.updatedAt, - isAtAllTimes = vigilanceArea.isAtAllTimes, tags = vigilanceArea.tags.map { fromTagEntity(it) }, validatedAt = vigilanceArea.validatedAt, + periods = vigilanceArea.periods.map { VigilanceAreaPeriodDataOutput.fromVigilanceAreaPeriod(it) }, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaPeriodDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaPeriodDataOutput.kt new file mode 100644 index 0000000000..37e0181370 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreaPeriodDataOutput.kt @@ -0,0 +1,36 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.vigilanceArea + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity +import java.time.ZonedDateTime +import java.util.UUID + +data class VigilanceAreaPeriodDataOutput( + val id: UUID?, + val computedEndDate: ZonedDateTime?, + val endDatePeriod: ZonedDateTime?, + val endingCondition: EndingConditionEnum?, + val endingOccurrenceDate: ZonedDateTime?, + val endingOccurrencesNumber: Int?, + val frequency: FrequencyEnum?, + val isAtAllTimes: Boolean, + val isCritical: Boolean?, + val startDatePeriod: ZonedDateTime?, +) { + companion object { + fun fromVigilanceAreaPeriod(vigilanceArea: VigilanceAreaPeriodEntity): VigilanceAreaPeriodDataOutput = + VigilanceAreaPeriodDataOutput( + id = vigilanceArea.id, + computedEndDate = vigilanceArea.computedEndDate, + endDatePeriod = vigilanceArea.endDatePeriod, + endingCondition = vigilanceArea.endingCondition, + endingOccurrenceDate = vigilanceArea.endingOccurrenceDate, + endingOccurrencesNumber = vigilanceArea.endingOccurrencesNumber, + frequency = vigilanceArea.frequency, + isAtAllTimes = vigilanceArea.isAtAllTimes, + isCritical = vigilanceArea.isCritical, + startDatePeriod = vigilanceArea.startDatePeriod, + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreasDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreasDataOutput.kt index 8b62f7e60b..95876943b5 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreasDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/vigilanceArea/VigilanceAreasDataOutput.kt @@ -1,7 +1,5 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.vigilanceArea -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum @@ -15,16 +13,8 @@ import java.time.ZonedDateTime data class VigilanceAreasDataOutput( val id: Int? = null, val comments: String? = null, - val computedEndDate: ZonedDateTime? = null, val createdBy: String? = null, - val endDatePeriod: ZonedDateTime? = null, - val endingCondition: EndingConditionEnum? = null, - val endingOccurrenceDate: ZonedDateTime? = null, - val endingOccurrencesNumber: Int? = null, - val frequency: FrequencyEnum? = null, val geom: MultiPolygon? = null, - val isAtAllTimes: Boolean, - val isArchived: Boolean, val isDraft: Boolean, val links: List? = null, val linkedAMPs: List? = listOf(), @@ -32,27 +22,19 @@ data class VigilanceAreasDataOutput( val name: String? = null, val seaFront: String?, val sources: List, - val startDatePeriod: ZonedDateTime? = null, val themes: List, val visibility: VisibilityEnum? = null, val tags: List, val validatedAt: ZonedDateTime?, + val periods: List, ) { companion object { fun fromVigilanceArea(vigilanceArea: VigilanceAreaEntity): VigilanceAreasDataOutput = VigilanceAreasDataOutput( id = vigilanceArea.id, comments = vigilanceArea.comments, - computedEndDate = vigilanceArea.computedEndDate, createdBy = vigilanceArea.createdBy, - endDatePeriod = vigilanceArea.endDatePeriod, - endingCondition = vigilanceArea.endingCondition, - endingOccurrenceDate = vigilanceArea.endingOccurrenceDate, - endingOccurrencesNumber = vigilanceArea.endingOccurrencesNumber, - frequency = vigilanceArea.frequency, geom = vigilanceArea.geom, - isAtAllTimes = vigilanceArea.isAtAllTimes, - isArchived = vigilanceArea.isArchived, isDraft = vigilanceArea.isDraft, links = vigilanceArea.links, linkedAMPs = vigilanceArea.linkedAMPs, @@ -60,11 +42,11 @@ data class VigilanceAreasDataOutput( name = vigilanceArea.name, seaFront = vigilanceArea.seaFront, sources = vigilanceArea.sources.map { VigilanceAreaSourceOutput.fromVigilanceAreaSourceEntity(it) }, - startDatePeriod = vigilanceArea.startDatePeriod, themes = vigilanceArea.themes.map { fromThemeEntity(it) }, visibility = vigilanceArea.visibility, tags = vigilanceArea.tags.map { fromTagEntity(it) }, validatedAt = vigilanceArea.validatedAt, + periods = vigilanceArea.periods.map { VigilanceAreaPeriodDataOutput.fromVigilanceAreaPeriod(it) }, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt index d8a35f4aeb..3ddbfda3f7 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt @@ -2,8 +2,6 @@ package fr.gouv.cacem.monitorenv.infrastructure.database.model import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum @@ -32,8 +30,6 @@ import org.hibernate.dialect.PostgreSQLEnumJdbcType import org.locationtech.jts.geom.MultiPolygon 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.time.ZonedDateTime @Entity @@ -45,20 +41,10 @@ data class VigilanceAreaModel( val id: Int? = null, @OneToMany(fetch = FetchType.LAZY, mappedBy = "vigilanceArea") val sources: List, + @OneToMany(fetch = FetchType.LAZY, mappedBy = "vigilanceArea") + val periods: List, @Column(name = "comments") val comments: String? = null, - @Column(name = "computed_end_date") val computedEndDate: Instant? = null, @Column(name = "created_by") val createdBy: String? = null, - @Column(name = "end_date_period") val endDatePeriod: Instant? = null, - @Column(name = "ending_condition", columnDefinition = "vigilance_area_ending_condition") - @Enumerated(EnumType.STRING) - @JdbcType(PostgreSQLEnumJdbcType::class) - val endingCondition: EndingConditionEnum? = null, - @Column(name = "ending_occurrence_date") val endingOccurrenceDate: Instant? = null, - @Column(name = "ending_occurrence_number") val endingOccurrencesNumber: Int? = null, - @Column(name = "frequency", columnDefinition = "vigilance_area_frequency") - @Enumerated(EnumType.STRING) - @JdbcType(PostgreSQLEnumJdbcType::class) - val frequency: FrequencyEnum? = null, @param:JsonSerialize(using = GeometrySerializer::class) @param:JsonDeserialize(contentUsing = GeometryDeserializer::class) @Column(name = "geom") @@ -71,9 +57,7 @@ data class VigilanceAreaModel( ) @OrderBy("id") var images: MutableList = mutableListOf(), - @Column(name = "is_archived", nullable = false) val isArchived: Boolean, @Column(name = "is_deleted", nullable = false) val isDeleted: Boolean, - @Column(name = "is_at_all_times", nullable = false) val isAtAllTimes: Boolean, @Column(name = "is_draft") val isDraft: Boolean, @Column(name = "links", columnDefinition = "jsonb") @Type(JsonBinaryType::class) @@ -83,7 +67,6 @@ data class VigilanceAreaModel( @Column(name = "linked_regulatory_areas", columnDefinition = "int[]") val linkedRegulatoryAreas: List? = listOf(), @Column(name = "name", nullable = false) val name: String, - @Column(name = "start_date_period") val startDatePeriod: Instant? = null, @Column(name = "sea_front") val seaFront: String? = null, @Column(name = "visibility", columnDefinition = "vigilance_area_visibility") @Enumerated(EnumType.STRING) @@ -111,16 +94,8 @@ data class VigilanceAreaModel( VigilanceAreaModel( id = vigilanceArea.id, comments = vigilanceArea.comments, - computedEndDate = vigilanceArea.computedEndDate?.toInstant(), createdBy = vigilanceArea.createdBy, - endingCondition = vigilanceArea.endingCondition, - endingOccurrenceDate = vigilanceArea.endingOccurrenceDate?.toInstant(), - endingOccurrencesNumber = vigilanceArea.endingOccurrencesNumber, - frequency = vigilanceArea.frequency, - endDatePeriod = vigilanceArea.endDatePeriod?.toInstant(), geom = vigilanceArea.geom, - isArchived = vigilanceArea.isArchived, - isAtAllTimes = vigilanceArea.isAtAllTimes, isDeleted = vigilanceArea.isDeleted, isDraft = vigilanceArea.isDraft, links = vigilanceArea.links, @@ -128,13 +103,13 @@ data class VigilanceAreaModel( linkedRegulatoryAreas = vigilanceArea.linkedRegulatoryAreas, name = vigilanceArea.name, seaFront = vigilanceArea.seaFront, - startDatePeriod = vigilanceArea.startDatePeriod?.toInstant(), themes = listOf(), visibility = vigilanceArea.visibility, createdAt = vigilanceArea.createdAt, updatedAt = vigilanceArea.updatedAt, tags = listOf(), sources = listOf(), + periods = listOf(), validatedAt = vigilanceArea.validatedAt, ) @@ -146,16 +121,8 @@ data class VigilanceAreaModel( VigilanceAreaEntity( id = id, comments = comments, - computedEndDate = computedEndDate?.atZone(UTC), createdBy = createdBy, - endingCondition = endingCondition, - endingOccurrenceDate = endingOccurrenceDate?.atZone(UTC), - endingOccurrencesNumber = endingOccurrencesNumber, - frequency = frequency, - endDatePeriod = endDatePeriod?.atZone(UTC), geom = geom, - isAtAllTimes = isAtAllTimes, - isArchived = isArchived, isDeleted = isDeleted, isDraft = isDraft, images = images.map { it.toVigilanceAreaImage() }, @@ -165,13 +132,13 @@ data class VigilanceAreaModel( name = name, seaFront = seaFront, sources = toVigilanceAreaSources(sources), - startDatePeriod = startDatePeriod?.atZone(UTC), themes = toThemeEntities(themes), visibility = visibility, createdAt = createdAt, updatedAt = updatedAt, tags = toTagEntities(tags), validatedAt = validatedAt, + periods = periods.map { it.toVigilanceAreaPeriodEntity() }, ) @PrePersist diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaPeriodModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaPeriodModel.kt new file mode 100644 index 0000000000..0c3931e93a --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaPeriodModel.kt @@ -0,0 +1,97 @@ +package fr.gouv.cacem.monitorenv.infrastructure.database.model + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import org.hibernate.Hibernate +import org.hibernate.annotations.JdbcType +import org.hibernate.dialect.PostgreSQLEnumJdbcType +import java.time.Instant +import java.time.ZoneOffset.UTC +import java.util.UUID + +@Entity +@Table(name = "vigilance_area_period") +data class VigilanceAreaPeriodModel( + @Id + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.UUID) + val id: UUID?, + @Column(name = "computed_end_date") val computedEndDate: Instant?, + @Column(name = "end_date_period") val endDatePeriod: Instant?, + @Column(name = "ending_condition", columnDefinition = "vigilance_area_ending_condition") + @Enumerated(EnumType.STRING) + @JdbcType(PostgreSQLEnumJdbcType::class) + val endingCondition: EndingConditionEnum?, + @Column(name = "ending_occurrence_date") val endingOccurrenceDate: Instant?, + @Column(name = "ending_occurrence_number") val endingOccurrencesNumber: Int?, + @Column(name = "frequency", columnDefinition = "vigilance_area_frequency") + @Enumerated(EnumType.STRING) + @JdbcType(PostgreSQLEnumJdbcType::class) + val frequency: FrequencyEnum?, + @Column(name = "is_at_all_times", nullable = false) val isAtAllTimes: Boolean, + @Column(name = "is_critical", nullable = false) val isCritical: Boolean?, + @Column(name = "start_date_period") val startDatePeriod: Instant?, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "vigilance_areas_id", nullable = false) + val vigilanceArea: VigilanceAreaModel, +) { + companion object { + fun fromVigilanceAreaPeriod( + vigilanceArea: VigilanceAreaModel, + vigilanceAreaPeriod: VigilanceAreaPeriodEntity, + ): VigilanceAreaPeriodModel { + val vigilanceAreaModel = + VigilanceAreaPeriodModel( + id = null, + vigilanceArea = vigilanceArea, + computedEndDate = vigilanceAreaPeriod.computedEndDate?.toInstant(), + endingCondition = vigilanceAreaPeriod.endingCondition, + endingOccurrenceDate = vigilanceAreaPeriod.endingOccurrenceDate?.toInstant(), + endingOccurrencesNumber = vigilanceAreaPeriod.endingOccurrencesNumber, + frequency = vigilanceAreaPeriod.frequency, + endDatePeriod = vigilanceAreaPeriod.endDatePeriod?.toInstant(), + isAtAllTimes = vigilanceAreaPeriod.isAtAllTimes, + isCritical = vigilanceAreaPeriod.isCritical, + startDatePeriod = vigilanceAreaPeriod.startDatePeriod?.toInstant(), + ) + + return vigilanceAreaModel + } + } + + fun toVigilanceAreaPeriodEntity(): VigilanceAreaPeriodEntity = + VigilanceAreaPeriodEntity( + id = id, + computedEndDate = computedEndDate?.atZone(UTC), + endingCondition = endingCondition, + endingOccurrenceDate = endingOccurrenceDate?.atZone(UTC), + endingOccurrencesNumber = endingOccurrencesNumber, + frequency = frequency, + endDatePeriod = endDatePeriod?.atZone(UTC), + isAtAllTimes = isAtAllTimes, + isCritical = isCritical, + startDatePeriod = startDatePeriod?.atZone(UTC), + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false + other as VigilanceAreaPeriodModel + + return id != null && id == other.id + } + + override fun hashCode(): Int = javaClass.hashCode() +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepository.kt index de699a0927..c0383e1238 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepository.kt @@ -5,6 +5,7 @@ import fr.gouv.cacem.monitorenv.domain.entities.themes.ThemeEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.ImageEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.SourceTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaSourceEntity import fr.gouv.cacem.monitorenv.domain.repositories.IVigilanceAreaRepository import fr.gouv.cacem.monitorenv.infrastructure.database.model.TagVigilanceAreaModel @@ -14,10 +15,13 @@ import fr.gouv.cacem.monitorenv.infrastructure.database.model.ThemeVigilanceArea import fr.gouv.cacem.monitorenv.infrastructure.database.model.ThemeVigilanceAreaModel.Companion.fromThemeEntities import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaImageModel import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaModel +import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaPeriodModel +import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaPeriodModel.Companion.fromVigilanceAreaPeriod import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaSourceModel import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBControlUnitContactRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBTagVigilanceAreaRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBThemeVigilanceAreaRepository +import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBVigilanceAreaPeriodRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBVigilanceAreaRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBVigilanceAreaSourceRepository import org.locationtech.jts.geom.Geometry @@ -29,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional class JpaVigilanceAreaRepository( private val dbVigilanceAreaRepository: IDBVigilanceAreaRepository, private val dbVigilanceAreaSourceRepository: IDBVigilanceAreaSourceRepository, + private val dbVigilanceAreaPeriodRepository: IDBVigilanceAreaPeriodRepository, private val controlUnitContactRepository: IDBControlUnitContactRepository, private val dbTagVigilanceAreaRepository: IDBTagVigilanceAreaRepository, private val dbThemeVigilanceAreaRepository: IDBThemeVigilanceAreaRepository, @@ -45,9 +50,10 @@ class JpaVigilanceAreaRepository( val savedTags = saveTags(savedVigilanceArea, vigilanceArea.tags) val savedThemes = saveThemes(savedVigilanceArea, vigilanceArea.themes) val savedSources = saveSources(savedVigilanceArea, vigilanceArea.sources) + val savedPeriods = savePeriods(savedVigilanceArea, vigilanceArea.periods) return savedVigilanceArea - .copy(tags = savedTags, themes = savedThemes, sources = savedSources) + .copy(tags = savedTags, themes = savedThemes, sources = savedSources, periods = savedPeriods) .toVigilanceAreaEntity() } @@ -78,6 +84,19 @@ class JpaVigilanceAreaRepository( return dbVigilanceAreaSourceRepository.saveAll(vigilanceAreaSourceModels) } + private fun savePeriods( + vigilanceAreaModel: VigilanceAreaModel, + periods: List, + ): List { + vigilanceAreaModel.id?.let { + dbVigilanceAreaPeriodRepository.deleteAllByVigilanceAreaId(it) + } + val vigilanceAreaPeriodModels = + periods.map { fromVigilanceAreaPeriod(vigilanceArea = vigilanceAreaModel, vigilanceAreaPeriod = it) } + + return dbVigilanceAreaPeriodRepository.saveAll(vigilanceAreaPeriodModels) + } + private fun fromVigilanceAreaSources( vigilanceAreaModel: VigilanceAreaModel, sources: List, @@ -105,6 +124,7 @@ class JpaVigilanceAreaRepository( vigilanceAreaModel, ), ) + else -> emptyList() } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBVigilanceAreaPeriodRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBVigilanceAreaPeriodRepository.kt new file mode 100644 index 0000000000..70683dac5b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/interfaces/IDBVigilanceAreaPeriodRepository.kt @@ -0,0 +1,9 @@ +package fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces + +import fr.gouv.cacem.monitorenv.infrastructure.database.model.VigilanceAreaPeriodModel +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface IDBVigilanceAreaPeriodRepository : JpaRepository { + fun deleteAllByVigilanceAreaId(vigilanceAreaId: Int) +} diff --git a/backend/src/main/resources/db/migration/internal/V0.200__create_vigilance_area_period.sql b/backend/src/main/resources/db/migration/internal/V0.200__create_vigilance_area_period.sql new file mode 100644 index 0000000000..b2ae6eb646 --- /dev/null +++ b/backend/src/main/resources/db/migration/internal/V0.200__create_vigilance_area_period.sql @@ -0,0 +1,40 @@ +CREATE TABLE VIGILANCE_AREA_PERIOD +( + ID UUID PRIMARY KEY, + VIGILANCE_AREAS_ID INT, + END_DATE_PERIOD TIMESTAMP, + ENDING_CONDITION VIGILANCE_AREA_ENDING_CONDITION, + ENDING_OCCURRENCE_DATE TIMESTAMP, + ENDING_OCCURRENCE_NUMBER INTEGER, + FREQUENCY VIGILANCE_AREA_FREQUENCY, + START_DATE_PERIOD TIMESTAMP, + COMPUTED_END_DATE TIMESTAMP, + IS_AT_ALL_TIMES BOOLEAN DEFAULT FALSE, + IS_CRITICAL BOOLEAN DEFAULT FALSE +); + +INSERT INTO VIGILANCE_AREA_PERIOD (ID, VIGILANCE_AREAS_ID, END_DATE_PERIOD, ENDING_CONDITION, ENDING_OCCURRENCE_DATE, + ENDING_OCCURRENCE_NUMBER, FREQUENCY, START_DATE_PERIOD, COMPUTED_END_DATE, + IS_AT_ALL_TIMES) +SELECT uuid_generate_v4(), + ID, + END_DATE_PERIOD, + ENDING_CONDITION, + ENDING_OCCURRENCE_DATE, + ENDING_OCCURRENCE_NUMBER, + FREQUENCY, + START_DATE_PERIOD, + COMPUTED_END_DATE, + IS_AT_ALL_TIMES +FROM VIGILANCE_AREAS; + +ALTER TABLE VIGILANCE_AREAS + DROP COLUMN END_DATE_PERIOD, + DROP COLUMN ENDING_CONDITION, + DROP COLUMN ENDING_OCCURRENCE_DATE, + DROP COLUMN ENDING_OCCURRENCE_NUMBER, + DROP COLUMN FREQUENCY, + DROP COLUMN START_DATE_PERIOD, + DROP COLUMN COMPUTED_END_DATE, + DROP COLUMN IS_AT_ALL_TIMES, + DROP COLUMN IS_ARCHIVED; \ No newline at end of file diff --git a/backend/src/main/resources/db/testdata/V666.16__insert_dummy_vigilance_area.sql b/backend/src/main/resources/db/testdata/V666.16__insert_dummy_vigilance_area.sql index 232374ac61..598d9ce85e 100644 --- a/backend/src/main/resources/db/testdata/V666.16__insert_dummy_vigilance_area.sql +++ b/backend/src/main/resources/db/testdata/V666.16__insert_dummy_vigilance_area.sql @@ -25,14 +25,10 @@ $$ /* PUBLISHED VIGILANCE AREAS */ -- period : at this moment - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (1, 'Commentaire sur la zone de vigilance', 'ABC', - today + INTERVAL '1 day', NULL, NULL, NULL, 'NONE', '0106000020E61000000100000001030000000100000021000000B7785F507915FABF5CBF7F7E61214740418F3AB4C128FABF90B809BB2A20474004908E66B530FABF784D6412E51E474030BC4D2C062DFABFC41331079D1D474038BAE144D81DFABFC4DB42355F1C474017509105C103FABF00257DD5371B474038325F1CC1DFF9BF34C24045321A4740541BD7B23AB3F9BF78E914965819474007ECD3D1E37FF9BF04AFF229B31847407F6A248CB547F9BF103B216048184740FECEA995D80CF9BFF8D0EE551C18474062A5E30590D1F8BF5819CCBD301847405A56DE162398F8BF742F6CCE8418474072BD73BBC662F8BF3874964A15194740B7645DE98733F8BFB0F559A1DC194740FDF2AE6C370CF8BF54985825D31A47401B09310B58EEF7BF6439F558EF1B474090F255A70FDBF7BF94E85D4C261D4740CEF101F51BD3F7BF48E7BA096C1E4740A2C5422FCBD6F7BFCC33430BB41F47409AC7AE16F9E5F7BFE0C69CB6F1204740BB31FF551000F8BF5CC2C3D818224740994F313F1024F8BF20E3B81D1E2347407D66B9A89650F8BF18D36A7FF7234740CA95BC89ED83F8BF6006ACA79C24474052176CCF1BBCF8BF34D38A4107254740D4B2E6C5F8F6F8BF58AA05373325474070DCAC554132F9BFD04ED3D81E254740772BB244AE6BF9BF88DCC7EECA24474060C41CA00AA1F9BFC4513CB03A2447401A1D337249D0F9BFA411C0A473234740D58EE1EE99F7F9BF18583B6E7D224740B7785F507915FABF5CBF7F7E61214740', false, '[ { @@ -40,32 +36,47 @@ $$ "linkUrl": "www.google.fr" } ]', 'Zone de vigilance 1', - today - INTERVAL '1 day', - '{"Dragage","Extraction granulats"}', 'PUBLIC', '{"12", "6"}', '{}', - today + INTERVAL '1 day'); + '{"Dragage","Extraction granulats"}', 'PUBLIC', '{"12", "6"}', '{}'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, comments, is_anonymous) - VALUES (uuid_generate_v4(), 1, 'Unité BSN Ste Maxime', 'OTHER'::vigilance_area_source_type, 'Unité de surveillance locale', true); - - INSERT INTO vigilance_areas_source (id, vigilance_areas_id, control_unit_contacts_id, type, comments, is_anonymous) - (SELECT DISTINCT uuid_generate_v4(), 1, 1, 'CONTROL_UNIT'::vigilance_area_source_type, 'On nous a appelé pour nous signaler un problème dans cette zone', false); + VALUES (uuid_generate_v4(), 1, 'Unité BSN Ste Maxime', 'OTHER'::vigilance_area_source_type, + 'Unité de surveillance locale', true); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date, + is_critical) + VALUES (uuid_generate_v4(), 1, today + INTERVAL '1 day', NULL, NULL, NULL, 'NONE', today - INTERVAL '1 day', + today + INTERVAL '1 day', true); + + INSERT INTO vigilance_areas_source (id, vigilance_areas_id, control_unit_contacts_id, type, comments, + is_anonymous) + (SELECT DISTINCT uuid_generate_v4(), + 1, + 1, + 'CONTROL_UNIT'::vigilance_area_source_type, + 'On nous a appelé pour nous signaler un problème dans cette zone', + false); -- period : within 3 months - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) + + + VALUES (2, 'Des dauphins partout', 'DEF', + '0106000020E610000001000000010300000001000000050000009E64CAC84F5DFDBF447087DF4CDA4840FAA68553DF2CFDBFE8B528CA7AD84840B2FDB77AA327FCBF30AA4C29CDD64840D9ADABB07A86FBBFE4090ED908D748409E64CAC84F5DFDBF447087DF4CDA4840', + false, NULL, 'Zone de vigilance 2', + '{"AMP","Mixte"}', + 'PRIVATE', '{}', '{"625", "425"}'); - VALUES (2, 'Des dauphins partout', 'DEF', date_within_year_and_3_months + INTERVAL '1 day', + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 2, date_within_year_and_3_months + INTERVAL '1 day', 'END_DATE', date_within_year_and_3_months + INTERVAL '6 year', NULL, - 'ALL_WEEKS', - '0106000020E610000001000000010300000001000000050000009E64CAC84F5DFDBF447087DF4CDA4840FAA68553DF2CFDBFE8B528CA7AD84840B2FDB77AA327FCBF30AA4C29CDD64840D9ADABB07A86FBBFE4090ED908D748409E64CAC84F5DFDBF447087DF4CDA4840', - false, NULL, 'Zone de vigilance 2', - date_within_year_and_3_months - three_months, '{"AMP","Mixte"}', - 'PRIVATE', '{}', '{"625", "425"}', + 'ALL_WEEKS', date_within_year_and_3_months - three_months, date_within_year_and_3_months + INTERVAL '6 year'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) @@ -73,13 +84,9 @@ $$ -- period : within this quarter - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) - VALUES (3, 'comments', 'GHI', date_within_quarter + INTERVAL '1 day', - 'OCCURENCES_NUMBER', NULL, 12, 'ALL_YEARS', + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) + VALUES (3, 'comments', 'GHI', '0106000020E61000000100000001030000000100000008000000E9F870A11E280940CC0CF854053C4540FEE4ACCF8B240940140A8203AD434540857F3863C68D084094EE39718444454008B2439CBF550840586D9AA47F564540601B5E44435E084014DFDE24936245404904E072BD1B0A404C55F4973E614540BDBC30567E890A4004B0B61F623B4540E9F870A11E280940CC0CF854053C4540', false, '[ { @@ -90,124 +97,133 @@ $$ "linkText": "lien vers arrêté réfectoral 2", "linkUrl": "www.google.fr" } - ]', 'Zone de vigilance 3', - date_within_quarter - INTERVAL '1 day', '{"PN","SAGE"}', - 'PUBLIC', '{}', '{}', - date_within_quarter + INTERVAL '11 year - 1 microsecond'); + ]', 'Zone de vigilance 3', '{"PN","SAGE"}', + 'PUBLIC', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 3, date_within_quarter + INTERVAL '1 day', + 'OCCURENCES_NUMBER', NULL, 12, 'ALL_YEARS', + date_within_quarter - INTERVAL '1 day', date_within_quarter + INTERVAL '11 year - 1 microsecond'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) - VALUES (uuid_generate_v4(), 3, 'Particulier qui était sur les lieux', 'OTHER'::vigilance_area_source_type, true); + VALUES (uuid_generate_v4(), 3, 'Particulier qui était sur les lieux', 'OTHER'::vigilance_area_source_type, + true); -- period : within this year - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (4, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc et mattis est. Integer sed scelerisque nulla, eget placerat felis. Maecenas dui dui, bibendum volutpat nisl sit amet, porttitor suscipit tellus', - 'JKL', date_within_year_not_in_quarter_nor_3_months + INTERVAL '1 day', 'END_DATE', - date_trunc('year', CURRENT_DATE) + INTERVAL '2 year - 1 microsecond', NULL, 'ALL_MONTHS', + 'JKL', '0106000020E6100000010000000103000000010000000B000000C1530E9390B809C09CCB8AE08ED247405138845383AE09C0D81EAD51ADD347408D43A734D2B309C0A0F87963B8D44740EF71916193BF09C0D461DE5673D547403F9EF361F0B809C0686F442B00D647408F77DBF258AB09C0B41BA857BED547403907773443A209C038D1EC9120D54740203FE7F454AB09C0E034792DECD447401AB051C7C0A009C0888761251DD347404075E4CAF1AF09C0C80268E341D24740C1530E9390B809C09CCB8AE08ED24740', false, NULL, 'Zone de vigilance 4', + '{"AMP","PN"}', 'PRIVATE', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 4, date_within_year_not_in_quarter_nor_3_months + INTERVAL '1 day', 'END_DATE', + date_trunc('year', CURRENT_DATE) + INTERVAL '2 year - 1 microsecond', NULL, 'ALL_MONTHS', date_within_year_not_in_quarter_nor_3_months - INTERVAL '1 day', - '{"AMP","PN"}', 'PRIVATE', '{}', '{}', date_within_year_not_in_quarter_nor_3_months + INTERVAL '1 day'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) VALUES (uuid_generate_v4(), 4, 'CACEM', 'INTERNAL'::vigilance_area_source_type, false); -- period : outer from current year - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (9, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'JKL', - date_trunc('year', CURRENT_DATE) + INTERVAL '3 year', - 'OCCURENCES_NUMBER', NULL, 16, 'ALL_WEEKS', '0106000020E61000000100000001030000000100000007000000C830399AA466F6BF9453A87E18EB45400D9F2DAA9D00F6BF04622FF852F5454096E4A71113B8F5BF6044913A01FD454037F7FB26B761F7BFB81A5FD2BEFD45406BB5ABD19B11F9BF48492DD6E6E54540B63B859AABB1F6BFD83F624C07E34540C830399AA466F6BF9453A87E18EB4540', false, NULL, 'Zone de vigilance 9', - date_trunc('year', CURRENT_DATE) + INTERVAL '2 year', '{"Extraction granulats"}', 'PUBLIC', - '{}', '{}', date_trunc('year', CURRENT_DATE) + INTERVAL '3 year'); + '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 9, date_trunc('year', CURRENT_DATE) + INTERVAL '3 year', + 'OCCURENCES_NUMBER', NULL, 16, 'ALL_WEEKS', + date_trunc('year', CURRENT_DATE) + INTERVAL '2 year', + date_trunc('year', CURRENT_DATE) + INTERVAL '3 year'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) VALUES (uuid_generate_v4(), 9, 'CACEM', 'INTERNAL'::vigilance_area_source_type, true); /* DRAFT VIGILANCE AREAS */ -- outdated - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (5, 'Proin maximus luctus urna, sit amet pellentesque diam porta ac. Praesent nisi urna, volutpat vitae consectetur et, aliquet non nisi. Sed molestie metus nec bibendum dignissim. In hac habitasse platea dictumst. Donec eu egestas nulla.', - 'ABC', today - INTERVAL '1 day', NULL, NULL, NULL, - 'NONE', + 'ABC', '0106000020E6100000010000000103000000010000000B000000ACA99227121E2140E08421C47D154540D3E6DE3F5031214070E2EDE388104540E761CC7E6A522140482E4E78E80C45404B4DD06A416921409CD57F36A00F454083F4C40A9B7E21402C1321F33E054540BACCC914244F21406CFF3FB5A0014540EF8500D4FD552140F43FE7898EFE4440B027A867692D214000C738CB7FFA44404AECC68D9AB32040585E4F4873ED4440DA4B567959602040588B590BDC064540ACA99227121E2140E08421C47D154540', - true, NULL, 'Zone de vigilance 5', today - INTERVAL '1 month', - '{"AMP","PN"}', 'PRIVATE', '{}', '{}', - today - INTERVAL '1 day'); - - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + true, NULL, 'Zone de vigilance 5', + '{"AMP","PN"}', 'PRIVATE', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 5, today - INTERVAL '1 day', NULL, NULL, NULL, + 'NONE', today - INTERVAL '1 month', today - INTERVAL '1 day'); + + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) -- period : within 3 months - VALUES (6, NULL, 'DEF', date_within_year_and_3_months + INTERVAL '1 day', 'NEVER', - NULL, - NULL, 'ALL_YEARS', + VALUES (6, NULL, 'DEF', '0106000020E61000000100000001030000000100000021000000D813E79DDB53FABF24C5A378DA31474058DBE0BC1A50FABFB8E3D82B08314740353ADF06FD44FABF5CCC00EE3D304740999ECED7EF32FABF0CDBC585832F4740B3BE98C6A41AFABF8CA28D1EE02E4740585907D20AFDF9BF3494B4015A2E4740C3D48B3145DBF9BF08A26E58F62D47403BE34624A0B6F9BFD0C1BAF8B82D47406F92692C8490F9BF88476A3FA42D4740A4418C34686AF9BFD0C1BAF8B82D47401C504727C345F9BF08A26E58F62D474086CBCB86FD23F9BF3494B4015A2E47402C663A926306F9BF8CA28D1EE02E47404786048118EEF8BF0CDBC585832F4740ABEAF3510BDCF8BF5CCC00EE3D3047408849F29BEDD0F8BFB8E3D82B083147400811ECBA2CCDF8BF24C5A378DA3147408849F29BEDD0F8BF944E1ABFAC324740ABEAF3510BDCF8BFC80CECEA763347404786048118EEF8BFEC212D38313447402C663A926306F9BF1056937FD434474086CBCB86FD23F9BF20609A7C5A3547401C504727C345F9BF5476E60ABE354740A4418C34686AF9BF94FD9358FB3547406F92692C8490F9BFFC1F900B103647403BE34624A0B6F9BF94FD9358FB354740C3D48B3145DBF9BF5476E60ABE354740585907D20AFDF9BF20609A7C5A354740B3BE98C6A41AFABF1056937FD4344740999ECED7EF32FABFEC212D3831344740353ADF06FD44FABFC80CECEA7633474058DBE0BC1A50FABF944E1ABFAC324740D813E79DDB53FABF24C5A378DA314740', true, '[ { "linkText": "lien vers arrêté réfectoral", "linkUrl": "www.google.fr" } - ]', 'Zone de vigilance 6', - date_within_year_and_3_months - three_months, - NULL, 'PUBLIC', '{}', '{}', NULL); + ]', 'Zone de vigilance 6', NULL, 'PUBLIC', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 6, date_within_year_and_3_months + INTERVAL '1 day', 'NEVER', + NULL, NULL, 'ALL_YEARS', date_within_year_and_3_months - three_months, null); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) VALUES (uuid_generate_v4(), 6, 'Unité BSN Ste Maxime', 'OTHER'::vigilance_area_source_type, true); -- period : within this quarter - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (7, 'Proin lobortis, sem quis malesuada mollis, dui orci condimentum nisl, vestibulum porttitor urna nisi non risus.', - 'GHI', date_within_quarter + INTERVAL '1 day', 'OCCURENCES_NUMBER', NULL, - 6, - 'ALL_WEEKS', + 'GHI', '0106000020E6100000010000000103000000010000000500000057F77C6FBF2D15C038E1B84BCD2E48403A42C339BDB014C038E1B84BCD2E48403A42C339BDB014C010F24763BA37484057F77C6FBF2D15C010F24763BA37484057F77C6FBF2D15C038E1B84BCD2E4840', true, NULL, 'Zone de vigilance 7', - date_within_quarter - INTERVAL '1 day', - NULL, 'PUBLIC', '{}', '{}', - date_within_quarter + INTERVAL '4 months'); + NULL, 'PUBLIC', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 7, date_within_quarter + INTERVAL '1 day', 'OCCURENCES_NUMBER', NULL, + 6, 'ALL_WEEKS', date_within_quarter - INTERVAL '1 day', date_within_quarter + INTERVAL '4 months'); INSERT INTO vigilance_areas_source(id, vigilance_areas_id, name, type, is_anonymous) VALUES (uuid_generate_v4(), 7, 'Sémaphore de Fécamp', 'OTHER'::vigilance_area_source_type, false); -- period : outer this year - INSERT INTO public.vigilance_areas(id, comments, created_by, end_date_period, ending_condition, - ending_occurrence_date, - ending_occurrence_number, frequency, geom, is_draft, links, name, - start_date_period, themes, visibility, linked_amps, linked_regulatory_areas, - computed_end_date) + INSERT INTO public.vigilance_areas(id, comments, created_by, geom, is_draft, links, name, + themes, visibility, linked_amps, linked_regulatory_areas) VALUES (8, 'Phasellus ac elit eget ex blandit varius.', 'JKL', - date_within_year_not_in_quarter_nor_3_months + INTERVAL '1 day', 'END_DATE', - '2099-12-31 23:59:59.99999', NULL, 'ALL_MONTHS', '0106000020E61000000100000001030000000100000021000000E8D31F574509F4BF300B11820653464076C82E867DD3F3BFF85436DC6D5146404A78F0B2BC90F3BFBCB1EDB718504640717A2B949343F3BFE8DD6D3B144F4640E7A8D043F9EEF2BF4CA042726A4E4640C385FD122E96F2BF844D1FE9214E4640AD77B48D9B3CF2BFB8B4776C3D4E4640C4A5F6E8B2E5F1BFBC9B88ECBB4E46404FAE8125CB94F1BFF8D6F387984F46407CD65834004DF1BF002481BCCA504640302659601411F1BF044607BC465246402EA0C32955E3F0BF102C0BE2FD534640CE18C49E84C5F0BFEC3A5645DF554640A584D30FC8B8F0BFA4A0AF5FD85746400A5A1CCA9CBDF0BFB4D611C5D5594640AC72C546D3D3F0BF40312CE3C35B46408AB982FE90FAF0BF3489C3C18F5D4640FDC473CF5830F1BF74DF93BC275F46402815B2A21973F1BFD827AB2F7C604640021377C142C0F1BF8480CC0F806146408CE4D111DD14F2BF74683A6929624640AF07A542A86DF2BF987044C071624640C515EEC73AC7F2BFB851175056624640AEE7AB6C231EF3BF882F8B25D861464023DF20300B6FF3BF2C3DF914FC604640F6B64921D6B6F3BF184C768BCA5F4640436749F5C1F2F3BF8486213D4F5E464044EDDE2B8120F4BFD8217CB3985C4640A574DEB6513EF4BFE46FEEBFB75A4640CD08CF450E4BF4BFCC58AED7BE5846406833868B3946F4BFCCD8205FC1564640C61ADD0E0330F4BF3CB381EBD2544640E8D31F574509F4BF300B118206534640', false, NULL, 'Zone de vigilance 8', - date_within_year_not_in_quarter_nor_3_months, '{"Extraction granulats","Dragage"}', - 'PRIVATE', '{}', '{}', '2099-12-31 23:59:59.99999'); - + 'PRIVATE', '{}', '{}'); + + INSERT INTO vigilance_area_period(id, vigilance_areas_id, end_date_period, ending_condition, + ending_occurrence_date, + ending_occurrence_number, frequency, start_date_period, computed_end_date) + VALUES (uuid_generate_v4(), 8, date_within_year_not_in_quarter_nor_3_months + INTERVAL '1 day', 'END_DATE', + '2099-12-31 23:59:59.99999', NULL, 'ALL_MONTHS', date_within_year_not_in_quarter_nor_3_months, + '2099-12-31 23:59:59.99999'); END $$; diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreasUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreasUTest.kt deleted file mode 100644 index 8279f7ac39..0000000000 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/ArchiveOutdatedVigilanceAreasUTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea - -import com.nhaarman.mockitokotlin2.given -import fr.gouv.cacem.monitorenv.domain.repositories.IVigilanceAreaRepository -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Mockito.mock -import org.springframework.boot.test.system.CapturedOutput -import org.springframework.boot.test.system.OutputCaptureExtension - -@ExtendWith(OutputCaptureExtension::class) -class ArchiveOutdatedVigilanceAreasUTest { - private val vigilanceAreaRepository: IVigilanceAreaRepository = mock() - private val archiveOutdatedVigilanceAreas = ArchiveOutdatedVigilanceAreas(vigilanceAreaRepository) - - @Test - fun `execute should archive vigilance areas`(log: CapturedOutput) { - // Given - given(vigilanceAreaRepository.archiveOutdatedVigilanceAreas()).willReturn(2) - - // When - archiveOutdatedVigilanceAreas.execute() - - // Then - assertThat(log.out).contains("Attempt to ARCHIVE vigilance areas") - assertThat(log.out).contains("2 vigilance areas archived") - } -} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt index f048b744f8..8f7c515caf 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt @@ -1,7 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argThat import com.nhaarman.mockitokotlin2.given import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.times @@ -16,7 +14,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.boot.test.system.CapturedOutput import org.springframework.boot.test.system.OutputCaptureExtension -import java.time.ZonedDateTime @ExtendWith(OutputCaptureExtension::class) class CreateOrUpdateVigilanceAreaUTests { @@ -43,18 +40,17 @@ class CreateOrUpdateVigilanceAreaUTests { val newVigilanceArea = VigilanceAreaEntity( comments = "Comments", - isArchived = false, isDeleted = false, isDraft = true, images = listOf(image), createdAt = null, updatedAt = null, - isAtAllTimes = false, name = "test_name", tags = emptyList(), themes = emptyList(), sources = listOf(aVigilanceAreaSource(name = "test")), validatedAt = null, + periods = emptyList(), ) val expectedVigilanceArea = newVigilanceArea.copy(id = 0) @@ -68,63 +64,4 @@ class CreateOrUpdateVigilanceAreaUTests { assertThat(log.out).contains("Attempt to CREATE or UPDATE vigilance area ${newVigilanceArea.id}") assertThat(log.out).contains("Vigilance area ${result.id} created or updated") } - - @Test - fun `execute should set archive to false then save vigilance area when it is archived and computedEndDate is in the future`() { - val isArchived = true - val computedEndDate = ZonedDateTime.now().plusHours(1) - val archivedVigilanceArea = - VigilanceAreaEntity( - id = 0, - comments = "Comments", - isArchived = isArchived, - isDeleted = false, - isDraft = true, - computedEndDate = computedEndDate, - images = listOf(), - createdAt = null, - updatedAt = null, - isAtAllTimes = false, - name = "test_name", - tags = emptyList(), - themes = emptyList(), - sources = emptyList(), - validatedAt = null, - ) - - given(vigilanceAreaRepository.save(any())).willReturn(archivedVigilanceArea) - - createOrUpdateVigilanceArea.execute(archivedVigilanceArea) - - verify(vigilanceAreaRepository, times(1)).save(argThat { vigilanceArea -> !vigilanceArea.isArchived }) - } - - @Test - fun `execute should set archive to false then save vigilance area when it is archived and is at all times`() { - val isArchived = true - val isAtAllTimes = true - val archivedVigilanceArea = - VigilanceAreaEntity( - id = 0, - comments = "Comments", - isArchived = isArchived, - isDeleted = false, - isDraft = true, - images = listOf(), - createdAt = null, - updatedAt = null, - isAtAllTimes = isAtAllTimes, - name = "test_name", - tags = emptyList(), - themes = emptyList(), - sources = emptyList(), - validatedAt = null, - ) - - given(vigilanceAreaRepository.save(any())).willReturn(archivedVigilanceArea) - - createOrUpdateVigilanceArea.execute(archivedVigilanceArea) - - verify(vigilanceAreaRepository, times(1)).save(argThat { vigilanceArea -> !vigilanceArea.isArchived }) - } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/GetVigilanceAreaByIdUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/GetVigilanceAreaByIdUTests.kt index abc83c098f..9280ec1c8c 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/GetVigilanceAreaByIdUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/GetVigilanceAreaByIdUTests.kt @@ -17,7 +17,7 @@ class GetVigilanceAreaByIdUTests { @Test fun `execute should return vigilance area entity`(log: CapturedOutput) { val vigilanceAreaId = 3 - val expectedEntity = VigilanceAreaFixture.anArchivedVigilanceAreaEntity() + val expectedEntity = VigilanceAreaFixture.aVigilanceAreaEntity() given(vigilanceAreaRepository.findById(vigilanceAreaId)).willReturn(expectedEntity) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt index 5fa3020d2f..d861ffa66a 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt @@ -7,9 +7,11 @@ import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.ImageEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum import fr.gouv.cacem.monitorenv.domain.use_cases.tags.fixtures.TagFixture.Companion.aTag import fr.gouv.cacem.monitorenv.domain.use_cases.themes.fixtures.ThemeFixture.Companion.aTheme +import fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.fixtures.VigilanceAreaPeriodFixture.Companion.aVigilanceAreaPeriodEntity import org.locationtech.jts.geom.MultiPolygon import org.locationtech.jts.io.WKTReader import java.time.ZonedDateTime @@ -37,20 +39,25 @@ class VigilanceAreaFixture { isAtAllTimes: Boolean = false, geom: MultiPolygon? = polygon, comments: String? = "Basic area comments", + periods: List = + listOf( + aVigilanceAreaPeriodEntity( + startDate = startDate, + endDate = endDate, + frequency = frequency, + endingOccurrencesNumber = endingOccurrencesNumber, + endCondition = endCondition, + endingOccurenceDate = endingOccurenceDate, + isAtAllTimes = isAtAllTimes, + ), + ), ): VigilanceAreaEntity = VigilanceAreaEntity( id = id, comments = comments, - computedEndDate = ZonedDateTime.parse("2024-01-25T00:00:00Z"), createdBy = createdBy, - endDatePeriod = endDate, - endingCondition = endCondition, - endingOccurrenceDate = endingOccurenceDate, - endingOccurrencesNumber = endingOccurrencesNumber, images = null, - frequency = frequency, geom = geom, - isArchived = false, isDeleted = false, isDraft = isDraft, links = listOf(), @@ -58,26 +65,20 @@ class VigilanceAreaFixture { linkedRegulatoryAreas = listOf(1, 2), name = "Basic Area", sources = emptyList(), - startDatePeriod = startDate, themes = themes, visibility = VisibilityEnum.PUBLIC, createdAt = null, updatedAt = null, - isAtAllTimes = isAtAllTimes, tags = tags, validatedAt = null, + periods = periods, ) fun aVigilanceAreaEntityWithImagesAndLink(): VigilanceAreaEntity = VigilanceAreaEntity( id = 2, comments = "Basic area comments", - computedEndDate = null, createdBy = "ABC", - endDatePeriod = ZonedDateTime.parse("2024-01-15T23:59:59Z"), - endingCondition = null, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, images = listOf( ImageEntity( @@ -93,9 +94,7 @@ class VigilanceAreaFixture { size = 2048, ), ), - frequency = FrequencyEnum.NONE, geom = null, - isArchived = false, isDeleted = false, isDraft = true, links = @@ -113,45 +112,13 @@ class VigilanceAreaFixture { linkedRegulatoryAreas = listOf(1, 2), name = "Basic Area", sources = emptyList(), - startDatePeriod = ZonedDateTime.parse("2024-01-15T00:00:00Z"), themes = listOf(aTheme(id = 1, name = "AMP")), visibility = VisibilityEnum.PRIVATE, createdAt = null, updatedAt = null, - isAtAllTimes = false, - tags = listOf(aTag(name = "AMP")), - validatedAt = null, - ) - - fun anArchivedVigilanceAreaEntity(): VigilanceAreaEntity = - VigilanceAreaEntity( - id = 3, - comments = "Basic area comments", - computedEndDate = null, - createdBy = "ABC", - endDatePeriod = ZonedDateTime.parse("2024-01-15T23:59:59Z"), - endingCondition = null, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, - images = listOf(), - frequency = FrequencyEnum.NONE, - geom = null, - isArchived = true, - isDeleted = false, - isDraft = true, - links = listOf(), - linkedAMPs = listOf(1, 2), - linkedRegulatoryAreas = listOf(1, 2), - name = "Basic Area", - sources = emptyList(), - startDatePeriod = ZonedDateTime.parse("2024-01-15T00:00:00Z"), - themes = listOf(aTheme(id = 1, name = "AMP")), - visibility = VisibilityEnum.PRIVATE, - createdAt = ZonedDateTime.parse("2024-01-01T00:00:00Z"), - updatedAt = ZonedDateTime.parse("2024-01-01T12:00:00Z"), - isAtAllTimes = false, tags = listOf(aTag(name = "AMP")), validatedAt = null, + periods = listOf(aVigilanceAreaPeriodEntity()), ) } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaPeriodFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaPeriodFixture.kt new file mode 100644 index 0000000000..b379c9c229 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaPeriodFixture.kt @@ -0,0 +1,33 @@ +package fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.fixtures + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity +import java.time.ZonedDateTime + +class VigilanceAreaPeriodFixture { + companion object { + fun aVigilanceAreaPeriodEntity( + startDate: ZonedDateTime? = ZonedDateTime.parse("2024-01-15T00:00:00Z"), + endDate: ZonedDateTime? = ZonedDateTime.parse("2024-01-15T23:59:59Z"), + frequency: FrequencyEnum? = FrequencyEnum.ALL_WEEKS, + endingOccurenceDate: ZonedDateTime? = null, + endCondition: EndingConditionEnum = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrencesNumber: Int? = 2, + isAtAllTimes: Boolean = false, + isCritical: Boolean = false, + ): VigilanceAreaPeriodEntity = + VigilanceAreaPeriodEntity( + id = null, + computedEndDate = ZonedDateTime.parse("2024-01-25T00:00:00Z"), + endDatePeriod = endDate, + endingCondition = endCondition, + endingOccurrenceDate = endingOccurenceDate, + endingOccurrencesNumber = endingOccurrencesNumber, + frequency = frequency, + startDatePeriod = startDate, + isAtAllTimes = isAtAllTimes, + isCritical = isCritical, + ) + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/VigilanceAreasITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/VigilanceAreasITests.kt index 4bc1d2c33b..af95ce58e2 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/VigilanceAreasITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/VigilanceAreasITests.kt @@ -9,6 +9,7 @@ import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.ImageEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.SourceTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum import fr.gouv.cacem.monitorenv.domain.use_cases.tags.fixtures.TagFixture.Companion.aTag import fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.CreateOrUpdateVigilanceArea @@ -22,6 +23,7 @@ import fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.fixtures.Vigilanc import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.tags.TagInput import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.vigilanceArea.ImageDataInput import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.vigilanceArea.VigilanceAreaDataInput +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.vigilanceArea.VigilanceAreaDataPeriodInput import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.nullValue import org.junit.jupiter.api.Test @@ -86,16 +88,25 @@ class VigilanceAreasITests { VigilanceAreaEntity( id = 1, name = "Vigilance Area 1", - isArchived = false, isDeleted = false, isDraft = false, comments = "Commentaires sur la zone de vigilance", createdBy = "ABC", - endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, - frequency = FrequencyEnum.ALL_WEEKS, - endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + periods = + listOf( + VigilanceAreaPeriodEntity( + endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrenceDate = null, + endingOccurrencesNumber = 2, + frequency = FrequencyEnum.ALL_WEEKS, + endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), + isAtAllTimes = false, + isCritical = true, + computedEndDate = null, + id = null, + ), + ), geom = polygon, images = listOf( @@ -122,12 +133,10 @@ class VigilanceAreasITests { comments = "Commentaires sur la source", ), ), - startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), themes = listOf(), visibility = VisibilityEnum.PRIVATE, createdAt = ZonedDateTime.parse(createdAt), updatedAt = ZonedDateTime.parse(updatedAt), - isAtAllTimes = false, tags = listOf( aTag( @@ -153,16 +162,10 @@ class VigilanceAreasITests { VigilanceAreaEntity( id = 2, name = "Vigilance Area 2", - isArchived = false, isDeleted = false, isDraft = true, comments = null, createdBy = "DEF", - endingCondition = EndingConditionEnum.NEVER, - endingOccurrenceDate = null, - endingOccurrencesNumber = null, - frequency = FrequencyEnum.ALL_WEEKS, - endDatePeriod = ZonedDateTime.parse("2024-12-31T23:59:59Z"), geom = polygon, images = listOf(), links = null, @@ -175,14 +178,27 @@ class VigilanceAreasITests { type = SourceTypeEnum.INTERNAL, ), ), - startDatePeriod = ZonedDateTime.parse("2024-12-01T00:00:00Z"), themes = listOf(), visibility = VisibilityEnum.PUBLIC, createdAt = ZonedDateTime.parse(createdAt), updatedAt = ZonedDateTime.parse(updatedAt), - isAtAllTimes = true, tags = listOf(), validatedAt = ZonedDateTime.parse("2025-01-01T00:00:00Z"), + periods = + listOf( + VigilanceAreaPeriodEntity( + endingCondition = EndingConditionEnum.NEVER, + endingOccurrenceDate = null, + endingOccurrencesNumber = null, + frequency = FrequencyEnum.ALL_WEEKS, + endDatePeriod = ZonedDateTime.parse("2024-12-31T23:59:59Z"), + startDatePeriod = ZonedDateTime.parse("2024-12-01T00:00:00Z"), + isAtAllTimes = true, + computedEndDate = null, + id = null, + isCritical = false, + ), + ), ) @Test @@ -201,11 +217,13 @@ class VigilanceAreasITests { .andExpect( jsonPath("$[0].comments", equalTo("Commentaires sur la zone de vigilance")), ).andExpect(jsonPath("$[0].createdBy", equalTo("ABC"))) - .andExpect(jsonPath("$[0].endingCondition", equalTo("OCCURENCES_NUMBER"))) - .andExpect(jsonPath("$[0].endingOccurrenceDate").doesNotExist()) - .andExpect(jsonPath("$[0].endingOccurrencesNumber", equalTo(2))) - .andExpect(jsonPath("$[0].frequency", equalTo("ALL_WEEKS"))) - .andExpect(jsonPath("$[0].endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$[0].periods[0].endingCondition", equalTo("OCCURENCES_NUMBER"))) + .andExpect(jsonPath("$[0].periods[0].endingOccurrenceDate").doesNotExist()) + .andExpect(jsonPath("$[0].periods[0].endingOccurrencesNumber", equalTo(2))) + .andExpect(jsonPath("$[0].periods[0].frequency", equalTo("ALL_WEEKS"))) + .andExpect(jsonPath("$[0].periods[0].endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$[0].periods[0].isAtAllTimes", equalTo(false))) + .andExpect(jsonPath("$[0].periods[0].startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$[0].geom.type", equalTo("MultiPolygon"))) .andExpect( jsonPath("$[0].links").doesNotExist(), @@ -213,21 +231,22 @@ class VigilanceAreasITests { .andExpect(jsonPath("$[0].sources[0].isAnonymous", equalTo(true))) .andExpect(jsonPath("$[0].sources[0].comments", equalTo("Commentaires sur la source"))) .andExpect(jsonPath("$[0].sources[0].type", equalTo("OTHER"))) - .andExpect(jsonPath("$[0].startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$[0].themes").isEmpty()) .andExpect(jsonPath("$[0].visibility", equalTo("PRIVATE"))) .andExpect(jsonPath("$[0].validatedAt").doesNotExist()) - .andExpect(jsonPath("$[1].isAtAllTimes", equalTo(true))) .andExpect(jsonPath("$[1].id", equalTo(2))) .andExpect(jsonPath("$[1].name", equalTo("Vigilance Area 2"))) .andExpect(jsonPath("$[1].isDraft", equalTo(true))) .andExpect(jsonPath("$[1].comments").doesNotExist()) .andExpect(jsonPath("$[1].createdBy", equalTo("DEF"))) - .andExpect(jsonPath("$[1].endingCondition", equalTo("NEVER"))) - .andExpect(jsonPath("$[1].endingOccurrenceDate").doesNotExist()) - .andExpect(jsonPath("$[1].endingOccurrencesNumber").doesNotExist()) - .andExpect(jsonPath("$[1].frequency", equalTo("ALL_WEEKS"))) - .andExpect(jsonPath("$[1].endDatePeriod", equalTo("2024-12-31T23:59:59Z"))) + .andExpect(jsonPath("$[1].periods[0].endingCondition", equalTo("NEVER"))) + .andExpect(jsonPath("$[1].periods[0].endingOccurrenceDate").doesNotExist()) + .andExpect(jsonPath("$[1].periods[0].endingOccurrencesNumber").doesNotExist()) + .andExpect(jsonPath("$[1].periods[0].frequency", equalTo("ALL_WEEKS"))) + .andExpect(jsonPath("$[1].periods[0].endDatePeriod", equalTo("2024-12-31T23:59:59Z"))) + .andExpect(jsonPath("$[1].periods[0].startDatePeriod", equalTo("2024-12-01T00:00:00Z"))) + .andExpect(jsonPath("$[1].periods[0].isAtAllTimes", equalTo(true))) + .andExpect(jsonPath("$[1].periods[0].isCritical", equalTo(false))) .andExpect(jsonPath("$[1].geom.type", equalTo("MultiPolygon"))) .andExpect( jsonPath("$[0].links").doesNotExist(), @@ -235,10 +254,8 @@ class VigilanceAreasITests { .andExpect(jsonPath("$[1].sources[0].isAnonymous", equalTo(false))) .andExpect(jsonPath("$[1].sources[0].link", equalTo("https://example.com"))) .andExpect(jsonPath("$[1].sources[0].type", equalTo("INTERNAL"))) - .andExpect(jsonPath("$[1].startDatePeriod", equalTo("2024-12-01T00:00:00Z"))) .andExpect(jsonPath("$[1].themes").isEmpty()) .andExpect(jsonPath("$[1].visibility", equalTo("PUBLIC"))) - .andExpect(jsonPath("$[1].isAtAllTimes", equalTo(true))) .andExpect(jsonPath("$[1].validatedAt", equalTo("2025-01-01T00:00:00Z"))) } @@ -256,11 +273,14 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.isDraft", equalTo(false))) .andExpect(jsonPath("$.comments", equalTo("Commentaires sur la zone de vigilance"))) .andExpect(jsonPath("$.createdBy", equalTo("ABC"))) - .andExpect(jsonPath("$.endingCondition", equalTo("OCCURENCES_NUMBER"))) - .andExpect(jsonPath("$.endingOccurrenceDate").doesNotExist()) - .andExpect(jsonPath("$.endingOccurrencesNumber", equalTo(2))) - .andExpect(jsonPath("$.frequency", equalTo("ALL_WEEKS"))) - .andExpect(jsonPath("$.endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].endingCondition", equalTo("OCCURENCES_NUMBER"))) + .andExpect(jsonPath("$.periods[0].endingOccurrenceDate").doesNotExist()) + .andExpect(jsonPath("$.periods[0].endingOccurrencesNumber", equalTo(2))) + .andExpect(jsonPath("$.periods[0].frequency", equalTo("ALL_WEEKS"))) + .andExpect(jsonPath("$.periods[0].endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].isAtAllTimes", equalTo(false))) + .andExpect(jsonPath("$.periods[0].isCritical", equalTo(true))) + .andExpect(jsonPath("$.periods[0].startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$.geom.type", equalTo("MultiPolygon"))) .andExpect( jsonPath("$[0].links").doesNotExist(), @@ -268,7 +288,6 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.sources[0].isAnonymous", equalTo(true))) .andExpect(jsonPath("$.sources[0].comments", equalTo("Commentaires sur la source"))) .andExpect(jsonPath("$.sources[0].type", equalTo("OTHER"))) - .andExpect(jsonPath("$.startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$.themes").isEmpty()) .andExpect(jsonPath("$.visibility", equalTo("PRIVATE"))) .andExpect(jsonPath("$.images[0].name", equalTo("image1.jpg"))) @@ -290,15 +309,9 @@ class VigilanceAreasITests { VigilanceAreaDataInput( id = 1, name = "Vigilance Area 1", - isArchived = false, isDraft = false, comments = "Commentaires sur la zone de vigilance", createdBy = "ABC", - endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, - frequency = FrequencyEnum.ALL_WEEKS, - endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), geom = polygon, images = listOf( @@ -326,12 +339,10 @@ class VigilanceAreasITests { comments = "Commentaires sur la source", ), ), - startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), themes = listOf(), visibility = VisibilityEnum.PRIVATE, createdAt = ZonedDateTime.parse(createdAt), updatedAt = ZonedDateTime.parse(updatedAt), - isAtAllTimes = false, tags = listOf( TagInput( @@ -351,6 +362,21 @@ class VigilanceAreasITests { ), ), ), + periods = + listOf( + VigilanceAreaDataPeriodInput( + endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrenceDate = null, + endingOccurrencesNumber = 2, + frequency = FrequencyEnum.ALL_WEEKS, + endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), + isAtAllTimes = false, + computedEndDate = null, + id = null, + isCritical = true, + ), + ), ) given(createOrUpdateVigilanceArea.execute(any())).willReturn(vigilanceArea1) @@ -368,11 +394,14 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.isDraft", equalTo(false))) .andExpect(jsonPath("$.comments", equalTo("Commentaires sur la zone de vigilance"))) .andExpect(jsonPath("$.createdBy", equalTo("ABC"))) - .andExpect(jsonPath("$.endingCondition", equalTo("OCCURENCES_NUMBER"))) - .andExpect(jsonPath("$.endingOccurrenceDate").doesNotExist()) - .andExpect(jsonPath("$.endingOccurrencesNumber", equalTo(2))) - .andExpect(jsonPath("$.frequency", equalTo("ALL_WEEKS"))) - .andExpect(jsonPath("$.endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].endingCondition", equalTo("OCCURENCES_NUMBER"))) + .andExpect(jsonPath("$.periods[0].endingOccurrenceDate").doesNotExist()) + .andExpect(jsonPath("$.periods[0].endingOccurrencesNumber", equalTo(2))) + .andExpect(jsonPath("$.periods[0].frequency", equalTo("ALL_WEEKS"))) + .andExpect(jsonPath("$.periods[0].endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) + .andExpect(jsonPath("$.periods[0].isAtAllTimes", equalTo(false))) + .andExpect(jsonPath("$.periods[0].isCritical", equalTo(true))) .andExpect(jsonPath("$.geom.type", equalTo("MultiPolygon"))) .andExpect( jsonPath("$[0].links").doesNotExist(), @@ -380,7 +409,6 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.sources[0].type", equalTo("OTHER"))) .andExpect(jsonPath("$.sources[0].comments", equalTo("Commentaires sur la source"))) .andExpect(jsonPath("$.sources[0].isAnonymous", equalTo(true))) - .andExpect(jsonPath("$.startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$.themes").isEmpty()) .andExpect(jsonPath("$.visibility", equalTo("PRIVATE"))) .andExpect(jsonPath("$.images[0].name", equalTo("image1.jpg"))) @@ -393,7 +421,6 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.images[1].content", equalTo("BAUG"))) .andExpect(jsonPath("$.createdAt", equalTo(createdAt))) .andExpect(jsonPath("$.updatedAt", equalTo(updatedAt))) - .andExpect(jsonPath("$.isAtAllTimes", equalTo(false))) .andExpect(jsonPath("$.tags[0].id", equalTo(1))) .andExpect(jsonPath("$.tags[0].name", equalTo("tag1"))) .andExpect(jsonPath("$.tags[0].startedAt", equalTo("2024-01-01T00:00:00Z"))) @@ -411,15 +438,9 @@ class VigilanceAreasITests { VigilanceAreaDataInput( id = 1, name = "Vigilance Area 1", - isArchived = false, isDraft = false, comments = "Commentaires sur la zone de vigilance", createdBy = "ABC", - endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, - frequency = FrequencyEnum.ALL_WEEKS, - endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), geom = polygon, images = emptyList(), links = null, @@ -433,13 +454,26 @@ class VigilanceAreasITests { comments = "Commentaires sur la source", ), ), - startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), themes = listOf(), visibility = VisibilityEnum.PRIVATE, createdAt = ZonedDateTime.parse(createdAt), updatedAt = ZonedDateTime.parse(updatedAt), - isAtAllTimes = false, tags = listOf(), + periods = + listOf( + VigilanceAreaDataPeriodInput( + endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrenceDate = null, + endingOccurrencesNumber = 2, + frequency = FrequencyEnum.ALL_WEEKS, + endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), + isAtAllTimes = false, + isCritical = true, + computedEndDate = null, + id = null, + ), + ), ) val updatedVigilanceArea = @@ -463,21 +497,22 @@ class VigilanceAreasITests { .andExpect(jsonPath("$.isDraft", equalTo(false))) .andExpect(jsonPath("$.comments", equalTo("Commentaires sur la zone de vigilance"))) .andExpect(jsonPath("$.createdBy", equalTo("ABC"))) - .andExpect(jsonPath("$.endingCondition", equalTo("OCCURENCES_NUMBER"))) - .andExpect(jsonPath("$.endingOccurrenceDate").doesNotExist()) - .andExpect(jsonPath("$.endingOccurrencesNumber", equalTo(2))) - .andExpect(jsonPath("$.frequency", equalTo("ALL_WEEKS"))) - .andExpect(jsonPath("$.endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].endingCondition", equalTo("OCCURENCES_NUMBER"))) + .andExpect(jsonPath("$.periods[0].endingOccurrenceDate").doesNotExist()) + .andExpect(jsonPath("$.periods[0].endingOccurrencesNumber", equalTo(2))) + .andExpect(jsonPath("$.periods[0].frequency", equalTo("ALL_WEEKS"))) + .andExpect(jsonPath("$.periods[0].endDatePeriod", equalTo("2024-08-08T23:59:59Z"))) + .andExpect(jsonPath("$.periods[0].isAtAllTimes", equalTo(false))) + .andExpect(jsonPath("$.periods[0].isCritical", equalTo(true))) + .andExpect(jsonPath("$.periods[0].startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$.geom.type", equalTo("MultiPolygon"))) .andExpect( jsonPath("$[0].links").doesNotExist(), ).andExpect(jsonPath("$.sources[0].name", equalTo("Source de la zone de vigilance"))) - .andExpect(jsonPath("$.startDatePeriod", equalTo("2024-08-18T00:00:00Z"))) .andExpect(jsonPath("$.themes").isEmpty()) .andExpect(jsonPath("$.visibility", equalTo("PRIVATE"))) .andExpect(jsonPath("$.createdAt", equalTo(createdAt))) .andExpect(jsonPath("$.updatedAt", equalTo(updatedAt))) - .andExpect(jsonPath("$.isAtAllTimes", equalTo(false))) .andExpect(jsonPath("$.images").isEmpty()) .andExpect(jsonPath("$.tags").isEmpty()) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepositoryITests.kt index 57ecf98db1..cb8333d64b 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaVigilanceAreaRepositoryITests.kt @@ -3,10 +3,16 @@ package fr.gouv.cacem.monitorenv.infrastructure.database.repositories import fr.gouv.cacem.monitorenv.config.CustomQueryCountListener import fr.gouv.cacem.monitorenv.config.DataSourceProxyBeanPostProcessor import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity -import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.* +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.ImageEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.SourceTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaPeriodEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaSourceEntity +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum import fr.gouv.cacem.monitorenv.domain.use_cases.tags.fixtures.TagFixture.Companion.aTag import fr.gouv.cacem.monitorenv.domain.use_cases.themes.fixtures.ThemeFixture.Companion.aTheme -import fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.fixtures.VigilanceAreaFixture import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -54,7 +60,6 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(vigilanceArea?.id).isEqualTo(expectedVigilanceAreaId) assertThat(vigilanceArea?.comments).isEqualTo("Commentaire sur la zone de vigilance") assertThat(vigilanceArea?.createdBy).isEqualTo("ABC") - assertThat(vigilanceArea?.endingCondition).isEqualTo(null) assertThat(vigilanceArea?.geom).isNotNull() assertThat(vigilanceArea?.isDeleted).isFalse() assertThat(vigilanceArea?.isDraft).isFalse() @@ -87,6 +92,8 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(vigilanceArea?.tags?.get(1)?.name).isEqualTo("Extraction granulats") assertThat(vigilanceArea?.tags?.get(2)?.id).isEqualTo(7) assertThat(vigilanceArea?.tags?.get(2)?.name).isEqualTo("Dragage") + assertThat(vigilanceArea?.periods).hasSize(1) + assertThat(vigilanceArea?.periods?.get(0)?.endingCondition).isNull() } @Test @@ -96,16 +103,10 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { val vigilanceArea = VigilanceAreaEntity( name = "Nouvelle zone de vigilance", - isArchived = false, isDeleted = false, isDraft = true, comments = "Commentaires sur la zone de vigilance", createdBy = "ABC", - endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, - frequency = FrequencyEnum.ALL_WEEKS, - endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), geom = null, images = listOf( @@ -131,14 +132,27 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { isAnonymous = false, ), ), - startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), visibility = VisibilityEnum.PRIVATE, createdAt = null, updatedAt = null, - isAtAllTimes = false, tags = listOf(aTag(id = 5)), themes = listOf(aTheme(id = 9)), validatedAt = ZonedDateTime.parse("2025-01-01T00:00:00Z"), + periods = + listOf( + VigilanceAreaPeriodEntity( + id = null, + computedEndDate = null, + endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrenceDate = null, + endingOccurrencesNumber = 2, + frequency = FrequencyEnum.ALL_WEEKS, + isAtAllTimes = false, + isCritical = false, + startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), + ), + ), ) // When @@ -148,22 +162,15 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(savedVigilanceArea.id).isNotZero() // id should be generated after save assertThat(savedVigilanceArea.comments).isEqualTo("Commentaires sur la zone de vigilance") assertThat(savedVigilanceArea.createdBy).isEqualTo("ABC") - assertThat(savedVigilanceArea.endDatePeriod) - .isEqualTo(ZonedDateTime.parse("2024-08-08T23:59:59Z")) - assertThat(savedVigilanceArea.endingCondition).isEqualTo(EndingConditionEnum.OCCURENCES_NUMBER) - assertThat(savedVigilanceArea.frequency).isEqualTo(FrequencyEnum.ALL_WEEKS) assertThat(savedVigilanceArea.geom).isNull() assertThat(savedVigilanceArea.isDeleted).isFalse() assertThat(savedVigilanceArea.isDraft).isTrue() assertThat(savedVigilanceArea.links).isNull() assertThat(savedVigilanceArea.sources[0].name).isEqualTo("Source de la zone de vigilance") assertThat(savedVigilanceArea.name).isEqualTo("Nouvelle zone de vigilance") - assertThat(savedVigilanceArea.startDatePeriod) - .isEqualTo(ZonedDateTime.parse("2024-08-18T00:00:00Z")) assertThat(savedVigilanceArea.visibility).isEqualTo(VisibilityEnum.PRIVATE) assertThat(savedVigilanceArea.createdAt).isNotNull() assertThat(savedVigilanceArea.updatedAt).isNull() - assertThat(savedVigilanceArea.isAtAllTimes).isFalse() assertThat(savedVigilanceArea.tags).hasSize(1) assertThat(savedVigilanceArea.tags[0].name).isEqualTo("Mouillage") assertThat(savedVigilanceArea.tags[0].id).isEqualTo(5) @@ -172,6 +179,13 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(savedVigilanceArea.themes[0].id).isEqualTo(9) assertThat(savedVigilanceArea.validatedAt) .isEqualTo(ZonedDateTime.parse("2025-01-01T00:00:00Z")) + assertThat(savedVigilanceArea.periods).hasSize(1) + assertThat(savedVigilanceArea.periods[0].isAtAllTimes).isFalse() + assertThat(savedVigilanceArea.periods[0].isCritical).isFalse() + assertThat(savedVigilanceArea.periods[0].startDatePeriod).isEqualTo(ZonedDateTime.parse("2024-08-18T00:00:00Z")) + assertThat(savedVigilanceArea.periods[0].endingCondition).isEqualTo(EndingConditionEnum.OCCURENCES_NUMBER) + assertThat(savedVigilanceArea.periods[0].frequency).isEqualTo(FrequencyEnum.ALL_WEEKS) + assertThat(savedVigilanceArea.periods[0].endDatePeriod).isEqualTo(ZonedDateTime.parse("2024-08-08T23:59:59Z")) } @Test @@ -207,6 +221,21 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { ), ), isDraft = false, + periods = + listOf( + VigilanceAreaPeriodEntity( + id = vigilanceArea.periods[0].id, + computedEndDate = null, + endDatePeriod = ZonedDateTime.parse("2024-08-08T23:59:59Z"), + endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrenceDate = null, + endingOccurrencesNumber = 2, + frequency = FrequencyEnum.ALL_WEEKS, + isAtAllTimes = false, + startDatePeriod = ZonedDateTime.parse("2024-08-18T00:00:00Z"), + isCritical = false, + ), + ), ) // When @@ -219,8 +248,6 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { .isEqualTo( "Proin maximus luctus urna, sit amet pellentesque diam porta ac. Praesent nisi urna, volutpat vitae consectetur et, aliquet non nisi. Sed molestie metus nec bibendum dignissim. In hac habitasse platea dictumst. Donec eu egestas nulla.", ) - assertThat(savedVigilanceArea.frequency).isEqualTo(FrequencyEnum.NONE) - assertThat(savedVigilanceArea.endingCondition).isNull() assertThat(savedVigilanceArea.isDeleted).isFalse() assertThat(savedVigilanceArea.isDraft).isFalse() assertThat(savedVigilanceArea.links).isNull() @@ -231,6 +258,13 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(savedVigilanceArea.themes).isEmpty() assertThat(savedVigilanceArea.visibility).isEqualTo(VisibilityEnum.PRIVATE) assertThat(savedVigilanceArea.updatedAt).isNotNull() + assertThat(savedVigilanceArea.periods).hasSize(1) + assertThat(savedVigilanceArea.periods[0].isAtAllTimes).isFalse() + assertThat(savedVigilanceArea.periods[0].isCritical).isFalse() + assertThat(savedVigilanceArea.periods[0].startDatePeriod).isEqualTo(ZonedDateTime.parse("2024-08-18T00:00:00Z")) + assertThat(savedVigilanceArea.periods[0].endingCondition).isEqualTo(EndingConditionEnum.OCCURENCES_NUMBER) + assertThat(savedVigilanceArea.periods[0].frequency).isEqualTo(FrequencyEnum.ALL_WEEKS) + assertThat(savedVigilanceArea.periods[0].endDatePeriod).isEqualTo(ZonedDateTime.parse("2024-08-08T23:59:59Z")) } @Test @@ -250,32 +284,32 @@ class JpaVigilanceAreaRepositoryITests : AbstractDBTests() { assertThat(deletedVigilanceArea?.isDeleted).isTrue() } - @Test - @Transactional - fun `archive should archive outdated vigilance areas`() { - // Given - val existingVigilanceArea = jpaVigilanceAreaRepository.findById(5) - assertThat(existingVigilanceArea?.isArchived).isEqualTo(false) - // When - jpaVigilanceAreaRepository.archiveOutdatedVigilanceAreas() - // Then - val archivedVigilanceArea = jpaVigilanceAreaRepository.findById(5) - assertThat(archivedVigilanceArea?.isArchived).isEqualTo(true) - } +// @Test +// @Transactional +// fun `archive should archive outdated vigilance areas`() { +// // Given +// val existingVigilanceArea = jpaVigilanceAreaRepository.findById(5) +// assertThat(existingVigilanceArea?.isArchived).isEqualTo(false) +// // When +// jpaVigilanceAreaRepository.archiveOutdatedVigilanceAreas() +// // Then +// val archivedVigilanceArea = jpaVigilanceAreaRepository.findById(5) +// assertThat(archivedVigilanceArea?.isArchived).isEqualTo(true) +// } - @Test - @Transactional - fun `archive should not archive limitless vigilance areas`() { - // Given - val limitlessVigilanceArea = - VigilanceAreaFixture.aVigilanceAreaEntity().copy(id = null, isAtAllTimes = true) - val savedVigilanceArea = jpaVigilanceAreaRepository.save(limitlessVigilanceArea) - // When - jpaVigilanceAreaRepository.archiveOutdatedVigilanceAreas() - // Then - val archivedVigilanceArea = jpaVigilanceAreaRepository.findById(savedVigilanceArea.id!!) - assertThat(archivedVigilanceArea?.isArchived).isEqualTo(false) - } +// @Test +// @Transactional +// fun `archive should not archive limitless vigilance areas`() { +// // Given +// val limitlessVigilanceArea = +// VigilanceAreaFixture.aVigilanceAreaEntity().copy(id = null, isAtAllTimes = true) +// val savedVigilanceArea = jpaVigilanceAreaRepository.save(limitlessVigilanceArea) +// // When +// jpaVigilanceAreaRepository.archiveOutdatedVigilanceAreas() +// // Then +// val archivedVigilanceArea = jpaVigilanceAreaRepository.findById(savedVigilanceArea.id!!) +// assertThat(archivedVigilanceArea?.isArchived).isEqualTo(false) +// } @Test fun `findAllByGeometry should return all vigilance areas that intersect the geometry `() { diff --git a/frontend/cypress/e2e/main_window/layerTree/search_zone.spec.ts b/frontend/cypress/e2e/main_window/layerTree/search_zone.spec.ts index 50ad65f4a0..486164b3c6 100644 --- a/frontend/cypress/e2e/main_window/layerTree/search_zone.spec.ts +++ b/frontend/cypress/e2e/main_window/layerTree/search_zone.spec.ts @@ -20,7 +20,7 @@ context('LayerTree > Search zone', () => { cy.getDataCy('amp-results-list-button').contains('7 résultats') cy.get('#isAmpSearchResultsVisible').should('be.checked') - cy.getDataCy('vigilance-area-results-list-button').contains('3 résultats') + cy.getDataCy('vigilance-area-results-list-button').contains('2 résultats') cy.get('#isVigilanceAreaSearchResultsVisible').should('be.checked') // reset search by zone @@ -33,7 +33,7 @@ context('LayerTree > Search zone', () => { cy.getDataCy('amp-results-list-button').contains('20 résultats') cy.get('#isAmpSearchResultsVisible').should('not.be.checked') - cy.getDataCy('vigilance-area-results-list-button').contains('5 résultats') + cy.getDataCy('vigilance-area-results-list-button').contains('4 résultats') cy.get('#isVigilanceAreaSearchResultsVisible').should('not.be.checked') }) }) diff --git a/frontend/cypress/e2e/main_window/layerTree/vigilance_area_layers.spec.ts b/frontend/cypress/e2e/main_window/layerTree/vigilance_area_layers.spec.ts index ec3b8347b5..1cb12bc57a 100644 --- a/frontend/cypress/e2e/main_window/layerTree/vigilance_area_layers.spec.ts +++ b/frontend/cypress/e2e/main_window/layerTree/vigilance_area_layers.spec.ts @@ -101,11 +101,11 @@ context('LayerTree > Vigilance Area Layers', () => { cy.getDataCy('vigilance-area-results-list-button').contains('0 résultat') }) it('Result list should be displayed by default but not checked and total should be visible', () => { - cy.getDataCy('vigilance-area-results-list-button').contains('5 résultats') + cy.getDataCy('vigilance-area-results-list-button').contains('4 résultats') cy.get('#isVigilanceAreaSearchResultsVisible').should('not.be.checked') cy.getDataCy('vigilance-area-results-list-button').click() - cy.getDataCy('vigilance-area-result-list').children().should('have.length', 5) + cy.getDataCy('vigilance-area-result-list').children().should('have.length', 4) cy.get('#isVigilanceAreaSearchResultsVisible').should('be.checked') }) }) diff --git a/frontend/cypress/e2e/main_window/vigilance_area/create_vigilance_area.spec.ts b/frontend/cypress/e2e/main_window/vigilance_area/create_vigilance_area.spec.ts index b74311aeb3..b9bea23b9c 100644 --- a/frontend/cypress/e2e/main_window/vigilance_area/create_vigilance_area.spec.ts +++ b/frontend/cypress/e2e/main_window/vigilance_area/create_vigilance_area.spec.ts @@ -8,6 +8,9 @@ import { getUtcDateInMultipleFormats } from '../../utils/getUtcDateInMultipleFor const startDate = getFutureDate(7, 'day') const endDate = getFutureDate(31, 'day') +const startDateCritical = getFutureDate(7, 'day') +const endDateCritical = getFutureDate(10, 'day') + describe('Create Vigilance Area', () => { beforeEach(() => { cy.intercept('GET', 'https://api.mapbox.com/**', FAKE_MAPBOX_RESPONSE) @@ -29,12 +32,19 @@ describe('Create Vigilance Area', () => { it('Should successfully create a vigilance area', () => { cy.fill('Nom de la zone de vigilance', 'Nouvelle zone de vigilance') - cy.fill('Période de validité', [startDate, endDate]) + cy.clickButton('Ajouter une période de vigilance simple') + cy.fill('Période de vigilance', [startDate, endDate]) - cy.getDataCy('vigilance-area-ending-condition').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-condition').should('not.exist') cy.fill('Récurrence', 'Toutes les semaines') - cy.getDataCy('vigilance-area-ending-condition').should('be.visible') + cy.getDataCy('vigilance-area-0-ending-condition').should('be.visible') cy.fill('Fin récurrence', 'Jamais') + cy.clickButton('Valider') + + cy.clickButton('Ajouter une période de vigilance critique') + cy.fill('Période critique', [startDateCritical, endDateCritical]) + cy.fill('Récurrence', 'Aucune') + cy.clickButton('Valider') cy.fill('Thématiques et sous-thématiques', ['Pêche à pied de loisir']) cy.fill('Tags et sous-tags', ['AMP']) @@ -71,7 +81,7 @@ describe('Create Vigilance Area', () => { }) cy.clickButton('Ajouter une unité') - cy.fill("Nom de l'unité", 'Cultures marines – DDTM 40') + cy.fill("Nom de l'unité", 'Cultures marines – DDTM 40', { force: true }) cy.get('label').contains('Contact 2 06 02 xx xx xx').click() cy.clickButton('Valider') @@ -101,12 +111,28 @@ describe('Create Vigilance Area', () => { const endDateDay = endDate[2] < 10 ? `0${endDate[2]}` : endDate[2] // Check the response expect(createdVigilanceArea.name).equal('Nouvelle zone de vigilance') - expect(createdVigilanceArea.startDatePeriod).equal( + expect(createdVigilanceArea.periods[0].startDatePeriod).equal( `${startDate[0]}-${startDateMonth}-${startDateDay}T00:00:00.000Z` ) - expect(createdVigilanceArea.endDatePeriod).equal(`${endDate[0]}-${endDateMonth}-${endDateDay}T23:59:59.000Z`) - expect(createdVigilanceArea.frequency).equal(VigilanceArea.Frequency.ALL_WEEKS) - expect(createdVigilanceArea.endingCondition).equal(VigilanceArea.EndingCondition.NEVER) + expect(createdVigilanceArea.periods[0].endDatePeriod).equal( + `${endDate[0]}-${endDateMonth}-${endDateDay}T23:59:59.000Z` + ) + expect(createdVigilanceArea.periods[0].isCritical).equal(false) + expect(createdVigilanceArea.periods[0].frequency).equal(VigilanceArea.Frequency.ALL_WEEKS) + expect(createdVigilanceArea.periods[0].endingCondition).equal(VigilanceArea.EndingCondition.NEVER) + const startDateMonthCritical = startDateCritical[1] < 10 ? `0${startDateCritical[1]}` : startDateCritical[1] + const startDateDayCritical = startDateCritical[2] < 10 ? `0${startDateCritical[2]}` : startDateCritical[2] + const endDateMonthCritical = endDateCritical[1] < 10 ? `0${endDateCritical[1]}` : endDateCritical[1] + const endDateDayCritical = endDateCritical[2] < 10 ? `0${endDateCritical[2]}` : endDateCritical[2] + expect(createdVigilanceArea.periods[1].startDatePeriod).equal( + `${startDateCritical[0]}-${startDateMonthCritical}-${startDateDayCritical}T00:00:00.000Z` + ) + expect(createdVigilanceArea.periods[1].endDatePeriod).equal( + `${endDateCritical[0]}-${endDateMonthCritical}-${endDateDayCritical}T23:59:59.000Z` + ) + expect(createdVigilanceArea.periods[1].isCritical).equal(true) + expect(createdVigilanceArea.periods[1].frequency).equal(VigilanceArea.Frequency.NONE) + expect(createdVigilanceArea.geom.type).equal('MultiPolygon') expect(createdVigilanceArea.themes[0].id).equal(9) expect(createdVigilanceArea.themes[0].name).equal('Pêche à pied') @@ -152,29 +178,30 @@ describe('Create Vigilance Area', () => { it('Must be able to manage frequency and display appropriate fields', () => { cy.fill('Nom de la zone de vigilance', 'Nouvelle zone de vigilance') - cy.fill('Période de validité', [startDate, endDate]) + cy.clickButton('Ajouter une période de vigilance simple') + cy.fill('Période de vigilance', [startDate, endDate]) - cy.getDataCy('vigilance-area-ending-condition').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-number').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-date').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-condition').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-number').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-date').should('not.exist') cy.fill('Récurrence', 'Aucune') - cy.getDataCy('vigilance-area-ending-condition').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-number').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-date').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-condition').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-number').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-date').should('not.exist') cy.fill('Récurrence', 'Toutes les semaines') - cy.getDataCy('vigilance-area-ending-condition').should('be.visible') + cy.getDataCy('vigilance-area-0-ending-condition').should('be.visible') cy.fill('Fin récurrence', 'Jamais') - cy.getDataCy('vigilance-area-ending-occurence-number').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-date').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-number').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-date').should('not.exist') cy.fill('Fin récurrence', 'Le…') - cy.getDataCy('vigilance-area-ending-occurence-number').should('not.exist') - cy.getDataCy('vigilance-area-ending-occurence-date').should('be.visible') + cy.getDataCy('vigilance-area-0-ending-occurence-number').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-date').should('be.visible') cy.fill('Fin récurrence', 'Après… x fois') - cy.getDataCy('vigilance-area-ending-occurence-number').should('be.visible') - cy.getDataCy('vigilance-area-ending-occurence-date').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-occurence-number').should('be.visible') + cy.getDataCy('vigilance-area-0-ending-occurence-date').should('not.exist') }) it('Should create an ongoing vigilance area and find it with period filter', () => { @@ -183,12 +210,14 @@ describe('Create Vigilance Area', () => { const { asDatePickerDateTime } = getUtcDateInMultipleFormats() const vigilanceAreaEndDate = getFutureDate(5, 'day') - cy.fill('Période de validité', [asDatePickerDateTime, vigilanceAreaEndDate]) + cy.clickButton('Ajouter une période de vigilance simple') + cy.fill('Période de vigilance', [asDatePickerDateTime, vigilanceAreaEndDate]) - cy.getDataCy('vigilance-area-ending-condition').should('not.exist') + cy.getDataCy('vigilance-area-0-ending-condition').should('not.exist') cy.fill('Récurrence', 'Toutes les semaines') - cy.getDataCy('vigilance-area-ending-condition').should('be.visible') + cy.getDataCy('vigilance-area-0-ending-condition').should('be.visible') cy.fill('Fin récurrence', 'Jamais') + cy.clickButton('Valider') cy.fill('Thématiques et sous-thématiques', ['Pêche à pied de loisir']) cy.clickOutside() @@ -219,8 +248,10 @@ describe('Create Vigilance Area', () => { const { asDatePickerDateTime } = getUtcDateInMultipleFormats() const vigilanceAreaEndDate = getFutureDate(5, 'day') - cy.fill('Période de validité', [asDatePickerDateTime, vigilanceAreaEndDate]) + cy.clickButton('Ajouter une période de vigilance simple') + cy.fill('Période de vigilance', [asDatePickerDateTime, vigilanceAreaEndDate]) cy.fill('Récurrence', 'Aucune') + cy.clickButton('Valider') // Submit the form cy.clickButton('Enregistrer') @@ -233,7 +264,7 @@ describe('Create Vigilance Area', () => { // Fill in the form fields cy.fill('Nom de la zone de vigilance', 'Nouvelle zone de vigilance infini') - cy.fill('En tout temps', true) + cy.fill('Vigilance simple en tout temps', true) // Submit the form cy.clickButton('Enregistrer') diff --git a/frontend/cypress/e2e/main_window/vigilance_area/delete_vigilance_area.spec.ts b/frontend/cypress/e2e/main_window/vigilance_area/delete_vigilance_area.spec.ts index 806cbfe20b..43417952ed 100644 --- a/frontend/cypress/e2e/main_window/vigilance_area/delete_vigilance_area.spec.ts +++ b/frontend/cypress/e2e/main_window/vigilance_area/delete_vigilance_area.spec.ts @@ -23,14 +23,18 @@ describe('Create Vigilance Area', () => { const { asDatePickerDateTime } = getUtcDateInMultipleFormats() const vigilanceAreaEndDate = getFutureDate(5, 'day') - cy.fill('Période de validité', [asDatePickerDateTime, vigilanceAreaEndDate]) + + cy.clickButton('Ajouter une période de vigilance simple') + cy.fill('Période de vigilance', [asDatePickerDateTime, vigilanceAreaEndDate]) cy.fill('Récurrence', 'Aucune') + cy.clickButton('Valider') // Submit the form cy.clickButton('Enregistrer') cy.wait('@createVigilanceArea').then(() => { cy.clickButton('Fermer la zone de vigilance') cy.clickButton('Filtrer par type de zones') + cy.fill('Période de vigilance', 'En ce moment') cy.getDataCy('vigilance-area-results-list-button').click() cy.getDataCy('vigilance-area-result-zone').contains('Ma zone de vigilance à supprimer') diff --git a/frontend/cypress/e2e/side_window/vigilance_areas_list/filters.spec.ts b/frontend/cypress/e2e/side_window/vigilance_areas_list/filters.spec.ts index 221ea4ab99..969d4dea0a 100644 --- a/frontend/cypress/e2e/side_window/vigilance_areas_list/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/vigilance_areas_list/filters.spec.ts @@ -83,6 +83,17 @@ context('Side Window > Vigilance Areas List > Filter Bar', () => { // with only PRIVATE visibility option checked cy.fill('Interne CACEM', true) cy.fill('Publique', false) + cy.getDataCy('vigilance-area-row').should('have.length', 1) + }) + + it('Should filter vigilance areas by type', () => { + cy.fill('Type de zone de vigilance', ['Aucune période de vigilance en cours']) + cy.getDataCy('vigilance-area-row').should('have.length', 1) + + cy.fill('Type de zone de vigilance', ['Période de vigilance critique en cours']) + cy.getDataCy('vigilance-area-row').should('have.length', 1) + + cy.fill('Type de zone de vigilance', ['Période de vigilance simple en cours']) cy.getDataCy('vigilance-area-row').should('have.length', 2) }) }) diff --git a/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/Layer.tsx b/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/Layer.tsx index eb8e70d942..4f0aa11c26 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/Layer.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/Layer.tsx @@ -3,6 +3,7 @@ import { dashboardActions, getOpenedPanel } from '@features/Dashboard/slice' import { Dashboard } from '@features/Dashboard/types' import { LayerLegend } from '@features/layersSelector/utils/LayerLegend.style' import { LayerSelector } from '@features/layersSelector/utils/LayerSelector.style' +import { isOutOfPeriod, isWithinPeriod } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' import { VigilanceArea } from '@features/VigilanceArea/types' import { useAppSelector } from '@hooks/useAppSelector' import { Accent, Icon, IconButton, Tag, THEME } from '@mtes-mct/monitor-ui' @@ -70,7 +71,8 @@ export function Layer({ isPinned = false, isSelected = false, vigilanceArea }: V > (({ layerId, ...props dispatch(dashboardActions.setDashboardPanel()) } + if (!vigilanceArea) { + return null + } + return ( // eslint-disable-next-line react/jsx-props-no-spreading
- {vigilanceArea?.name} + {vigilanceArea.name} - {vigilanceArea?.isDraft ? ( + {vigilanceArea.isDraft ? ( Brouillon @@ -74,21 +81,24 @@ export const Panel = forwardRef(({ layerId, ...props - - + + + + + - {vigilanceArea?.linkedRegulatoryAreas && vigilanceArea?.linkedRegulatoryAreas.length > 0 && ( + {vigilanceArea.linkedRegulatoryAreas && vigilanceArea.linkedRegulatoryAreas.length > 0 && ( )} - {vigilanceArea?.linkedAMPs && vigilanceArea?.linkedAMPs.length > 0 && ( + {vigilanceArea.linkedAMPs && vigilanceArea.linkedAMPs.length > 0 && ( )} - {vigilanceArea?.images && vigilanceArea?.images.length > 0 && ( - + {vigilanceArea.images && vigilanceArea?.images.length > 0 && ( + )} - {vigilanceArea?.links && vigilanceArea?.links.length > 0 && } - + {vigilanceArea.links && vigilanceArea.links.length > 0 && } + @@ -106,3 +116,8 @@ const Wrapper = styled.div` const StyledTitle = styled(Title)` max-width: 215px; ` + +const PlanningWrapper = styled.div` + padding: 16px; + border-bottom: 1px solid ${THEME.color.lightGray}; +` diff --git a/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx b/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx index 778306d58b..6def38971d 100644 --- a/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx +++ b/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx @@ -1,4 +1,4 @@ -import { Orientation, type ImageFront } from '@components/Form/types' +import { type ImageFront, Orientation } from '@components/Form/types' import { Dashboard } from '@features/Dashboard/types' import { getVigilanceAreaColorWithAlpha } from '@features/VigilanceArea/components/VigilanceAreaLayer/style' import { EMPTY_VALUE } from '@features/VigilanceArea/constants' @@ -93,12 +93,10 @@ export function VigilanceAreas({ {vigilanceAreas.map(vigilanceArea => { - const formattedStartPeriod = vigilanceArea.startDatePeriod - ? customDayjs(vigilanceArea.startDatePeriod).utc().format('DD/MM/YYYY') - : undefined - const formattedEndPeriod = vigilanceArea.endDatePeriod - ? customDayjs(vigilanceArea.endDatePeriod).utc().format('DD/MM/YYYY') - : undefined + const formattedStartPeriod = (startDatePeriod?: string) => + startDatePeriod ? customDayjs(startDatePeriod).utc().format('DD/MM/YYYY') : undefined + const formattedEndPeriod = (endDatePeriod?: string) => + endDatePeriod ? customDayjs(endDatePeriod).utc().format('DD/MM/YYYY') : undefined const amps = linkedAMPs.filter(amp => vigilanceArea.linkedAMPs?.includes(amp.id)) const regulatoryAreas = linkedRegulatoryAreas.filter(regulatoryArea => @@ -127,17 +125,27 @@ export function VigilanceAreas({ - Période - - - - {formattedStartPeriod ? `Du ${formattedStartPeriod} au ${formattedEndPeriod}` : EMPTY_VALUE} - - {frequencyText(vigilanceArea?.frequency)} - - {endingOccurenceText(vigilanceArea?.endingCondition, vigilanceArea?.computedEndDate)} - + Période(s) + {vigilanceArea.periods?.map(period => ( + + {period.isAtAllTimes ? ( + En tout temps + ) : ( + <> + + {formattedStartPeriod(period.startDatePeriod) + ? `Du ${formattedStartPeriod(period.startDatePeriod)} au ${formattedEndPeriod( + period.endDatePeriod + )}` + : EMPTY_VALUE} + + {frequencyText(period?.frequency)} + {endingOccurenceText(period?.endingCondition, period?.computedEndDate)} + + )} + + ))} diff --git a/frontend/src/features/Dashboard/types.ts b/frontend/src/features/Dashboard/types.ts index dc2c5861e1..797aa8f519 100644 --- a/frontend/src/features/Dashboard/types.ts +++ b/frontend/src/features/Dashboard/types.ts @@ -30,6 +30,7 @@ export namespace Dashboard { themes: ThemeFromAPI[] vigilanceAreas: VigilanceArea.VigilanceAreaFromApi[] | VigilanceArea.VigilanceAreaLayer[] } + export interface ExtractedAreaFromApi { ampIds: number[] inseeCode: string @@ -39,6 +40,7 @@ export namespace Dashboard { themes: ThemeFromAPI[] vigilanceAreaIds: number[] } + export type Dashboard = { ampIds: number[] comments?: string @@ -199,9 +201,6 @@ export namespace Dashboard { type VigilanceAreaForEditableBrief = { color: string comments?: string - endDatePeriod?: string - endingOccurenceDate: string - frequency: string id: number image?: string imagesAttachments?: ImageApi[] @@ -210,10 +209,17 @@ export namespace Dashboard { links?: Link[] minimap?: string name: string - startDatePeriod?: string + periods?: VigilanceAreaPeriodForEditableBrief[] themes?: string visibility?: string } + + type VigilanceAreaPeriodForEditableBrief = { + endDatePeriod?: string + endingOccurenceDate: string + frequency: string + } + type RecentActivityForEditableBrief = { image?: string period: string diff --git a/frontend/src/features/Dashboard/useCases/exportBrief.ts b/frontend/src/features/Dashboard/useCases/exportBrief.ts index b61fa40747..0fcb51d17e 100644 --- a/frontend/src/features/Dashboard/useCases/exportBrief.ts +++ b/frontend/src/features/Dashboard/useCases/exportBrief.ts @@ -126,19 +126,19 @@ export const exportBrief = return { color: getVigilanceAreaColorWithAlpha(vigilanceArea.name, vigilanceArea.comments), comments: vigilanceArea.comments, - endDatePeriod: vigilanceArea.endDatePeriod, - endingOccurenceDate: endingOccurenceText(vigilanceArea.endingCondition, vigilanceArea.computedEndDate), - frequency: frequencyText(vigilanceArea.frequency), id: vigilanceArea.id as number, image, imagesAttachments, - isAtAllTimes: vigilanceArea.isAtAllTimes, linkedAMPs: filteredAmps, linkedRegulatoryAreas: filteredRegulatoryAreas, links: vigilanceArea.links, minimap, name: vigilanceArea.name as string, - startDatePeriod: vigilanceArea.startDatePeriod, + periods: vigilanceArea.periods?.map(period => ({ + ...period, + endingOccurenceDate: endingOccurenceText(period.endingCondition, period.computedEndDate), + frequency: frequencyText(period.frequency) + })), themes: displayThemes(vigilanceArea.themes), visibility: VigilanceArea.VisibilityLabel[vigilanceArea?.visibility ?? VigilanceArea.VisibilityLabel.PUBLIC] } diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Form.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Form.tsx index 24eabbf232..480e1fba1e 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Form.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Form.tsx @@ -4,6 +4,7 @@ import { Tooltip } from '@components/Tooltip' import { ZonePicker } from '@components/ZonePicker' import { CancelEditDialog } from '@features/commonComponents/Modals/CancelEditModal' import { DeleteModal } from '@features/commonComponents/Modals/Delete' +import { Periods } from '@features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods' import { NEW_VIGILANCE_AREA_ID } from '@features/VigilanceArea/constants' import { vigilanceAreaActions, VigilanceAreaFormTypeOpen } from '@features/VigilanceArea/slice' import { VigilanceArea } from '@features/VigilanceArea/types' @@ -16,9 +17,6 @@ import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' import { CheckTreePicker, - type DateAsStringRange, - DateRangePicker, - FormikCheckbox, FormikMultiRadio, FormikTextarea, FormikTextInput, @@ -37,7 +35,6 @@ import styled from 'styled-components' import { AddAMPs } from './AddAMPs' import { AddRegulatoryAreas } from './AddRegulatoryAreas' import { Footer } from './Footer' -import { Frequency } from './Frequency' import { Links } from './Links' import { PhotoUploader } from './PhotoUploader' import { Sources } from './Sources' @@ -99,7 +96,7 @@ export function Form() { } const onSave = () => { - validateForm({ ...values }).then(errors => { + validateForm().then(errors => { if (isEmpty(errors)) { dispatch(saveVigilanceArea(values)) } @@ -139,11 +136,6 @@ export function Form() { dispatch(hideLayers({ keepInterestPoint: true })) } - const setPeriod = (period: DateAsStringRange | undefined) => { - setFieldValue('startDatePeriod', period ? period[0] : undefined) - setFieldValue('endDatePeriod', period ? period[1] : undefined) - } - return ( {isCancelModalOpen && @@ -180,28 +172,16 @@ export function Form() { name="name" placeholder="Nom de la zone" /> - - - - - + + - @@ -323,12 +294,6 @@ const Wrapper = styled.div` gap: 8px; ` -const DateWrapper = styled.div` - display: flex; - flex-direction: column; - gap: 8px; -` - const ThemesAndTags = styled.div` border-top: 1px solid ${p => p.theme.color.lightGray}; border-bottom: 1px solid ${p => p.theme.color.lightGray}; diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Frequency.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Frequency.tsx deleted file mode 100644 index bde0426af2..0000000000 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Frequency.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { VigilanceArea } from '@features/VigilanceArea/types' -import { FormikDatePicker, FormikNumberInput, getOptionsFromLabelledEnum, Select } from '@mtes-mct/monitor-ui' -import { useFormikContext } from 'formik' -import styled from 'styled-components' - -export function Frequency() { - const { errors, setFieldValue, values } = useFormikContext() - const frequencyOptions = getOptionsFromLabelledEnum(VigilanceArea.FrequencyLabel) - - const endingConditionOptions = getOptionsFromLabelledEnum(VigilanceArea.EndingConditionLabel) - - const updateFrequency = (nextFrequency: string | undefined) => { - setFieldValue('frequency', nextFrequency) - if (nextFrequency === VigilanceArea.Frequency.NONE) { - setFieldValue('endingCondition', undefined) - setFieldValue('endingOccurrenceDate', undefined) - setFieldValue('endingOccurrencesNumber', undefined) - } - } - - const updateEndingCondition = (nextEndingCondition: string | undefined) => { - setFieldValue('endingCondition', nextEndingCondition) - if (nextEndingCondition === VigilanceArea.EndingCondition.NEVER) { - setFieldValue('endingOccurrenceDate', undefined) - setFieldValue('endingOccurrencesNumber', undefined) - } - } - - return ( - <> - - {values.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && ( - - )} - {values.endingCondition === VigilanceArea.EndingCondition.END_DATE && ( - - )} - - )} - - ) -} - -const FrequencyContainer = styled.div` - align-items: end; - display: flex; - flex-direction: row; - gap: 8px; -` - -const StyledFormikDatePicker = styled(FormikDatePicker)` - > .Element-Fieldset__InnerBox { - > .Field-DatePicker__CalendarPicker { - > .rs-picker-popup { - left: -130px !important; - } - } - } -` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelPeriodAndThemes.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelPeriodAndThemes.tsx deleted file mode 100644 index f15f33576b..0000000000 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelPeriodAndThemes.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { EMPTY_VALUE } from '@features/VigilanceArea/constants' -import { VigilanceArea } from '@features/VigilanceArea/types' -import { endingOccurenceText, frequencyText } from '@features/VigilanceArea/utils' -import { customDayjs } from '@mtes-mct/monitor-ui' -import { displaySubTags, displayTags } from '@utils/getTagsAsOptions' -import { displaySubThemes, displayThemes } from '@utils/getThemesAsOptions' - -import { - PanelDateItem, - PanelInlineItem, - PanelInlineItemLabel, - PanelInlineItemValue, - PanelSubPart, - StyledPanelInlineItemValue -} from '../style' - -export function PanelPeriodAndThemes({ vigilanceArea }: { vigilanceArea: VigilanceArea.VigilanceArea | undefined }) { - const formattedStartPeriod = vigilanceArea?.startDatePeriod - ? customDayjs(vigilanceArea?.startDatePeriod).utc().format('DD/MM/YYYY') - : undefined - const formattedEndPeriod = vigilanceArea?.endDatePeriod - ? customDayjs(vigilanceArea?.endDatePeriod).utc().format('DD/MM/YYYY') - : undefined - - const subThemes = displaySubThemes(vigilanceArea?.themes) - const subTags = displaySubTags(vigilanceArea?.tags) - - return ( - <> - - - Période - - {vigilanceArea?.isAtAllTimes ? ( - En tout temps - ) : ( - <> - - {formattedStartPeriod ? `Du ${formattedStartPeriod} au ${formattedEndPeriod}` : EMPTY_VALUE} - - {frequencyText(vigilanceArea?.frequency)} - - {endingOccurenceText(vigilanceArea?.endingCondition, vigilanceArea?.computedEndDate)} - - - )} - - - - Thématiques - - {vigilanceArea?.themes && vigilanceArea?.themes.length > 0 - ? displayThemes(vigilanceArea?.themes) - : EMPTY_VALUE} - - - {subThemes && ( - - Sous-thématiques - - {subThemes} - - - )} - - Tags - - {vigilanceArea?.tags && vigilanceArea?.tags.length > 0 ? displayTags(vigilanceArea?.tags) : EMPTY_VALUE} - - - {subTags && ( - - Sous-tags - - {subTags} - - - )} - - - Visibilité - - {vigilanceArea?.visibility ? VigilanceArea.VisibilityLabel[vigilanceArea?.visibility] : EMPTY_VALUE} - - - - - ) -} diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelThemesAndTags.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelThemesAndTags.tsx new file mode 100644 index 0000000000..5047d2cd90 --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/PanelThemesAndTags.tsx @@ -0,0 +1,53 @@ +import { EMPTY_VALUE } from '@features/VigilanceArea/constants' +import { VigilanceArea } from '@features/VigilanceArea/types' +import { displaySubTags, displayTags } from '@utils/getTagsAsOptions' +import { displaySubThemes, displayThemes } from '@utils/getThemesAsOptions' + +import { PanelInlineItem, PanelInlineItemLabel, PanelInlineItemValue, PanelSubPart } from '../style' + +export function PanelThemesAndTags({ vigilanceArea }: { vigilanceArea: VigilanceArea.VigilanceArea | undefined }) { + const subThemes = displaySubThemes(vigilanceArea?.themes) + const subTags = displaySubTags(vigilanceArea?.tags) + + return ( + + + Thématique(s) + + {vigilanceArea?.themes && vigilanceArea?.themes.length > 0 + ? displayThemes(vigilanceArea?.themes) + : EMPTY_VALUE} + + + {subThemes && ( + + Sous-thém. + + {subThemes} + + + )} + + Tag(s) + + {vigilanceArea?.tags && vigilanceArea?.tags.length > 0 ? displayTags(vigilanceArea?.tags) : EMPTY_VALUE} + + + {subTags && ( + + Sous-tag(s) + + {subTags} + + + )} + + + Visibilité + + {vigilanceArea?.visibility ? VigilanceArea.VisibilityLabel[vigilanceArea?.visibility] : EMPTY_VALUE} + + + + ) +} diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/index.tsx index 67c307e187..e28f5684fb 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Panel/index.tsx @@ -20,7 +20,7 @@ import { PanelComments } from './PanelComments' import { PanelImages } from './PanelImages' import { PanelInternalCACEMSection } from './PanelInternalCACEMSection' import { PanelLinks } from './PanelLinks' -import { PanelPeriodAndThemes } from './PanelPeriodAndThemes' +import { PanelThemesAndTags } from './PanelThemesAndTags' import { AMPList } from '../AddAMPs/AMPList' import { RegulatoryAreas } from '../AddRegulatoryAreas/RegulatoryAreas' import { @@ -118,7 +118,7 @@ export function VigilanceAreaPanel({ vigilanceArea }: { vigilanceArea: Vigilance )} - + {values?.linkedRegulatoryAreas && values?.linkedRegulatoryAreas.length > 0 && ( diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Period.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Period.tsx new file mode 100644 index 0000000000..cf2b74a07c --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Period.tsx @@ -0,0 +1,287 @@ +import { + CheckboxWrapper, + CriticalCheckbox, + PeriodCircle +} from '@features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods' +import { PublishedVigilanceAreaPeriodSchema } from '@features/VigilanceArea/components/VigilanceAreaForm/Schema' +import { ValidateButton } from '@features/VigilanceArea/components/VigilanceAreaForm/style' +import { VigilanceArea } from '@features/VigilanceArea/types' +import { + Accent, + Button, + type DateAsStringRange, + DatePicker, + DateRangePicker, + getOptionsFromLabelledEnum, + Icon, + IconButton, + Label, + NumberInput, + Select +} from '@mtes-mct/monitor-ui' +import { getDateAsLocalizedStringVeryCompact } from '@utils/getDateAsLocalizedString' +import { omit } from 'lodash' +import { useState } from 'react' +import styled from 'styled-components' + +import type { FormikErrors } from 'formik' + +type PeriodProps = { + error: FormikErrors | undefined + index: number + initialPeriod: VigilanceArea.VigilanceAreaPeriod + onValidate: (vigilanceAreaSource: VigilanceArea.VigilanceAreaPeriod) => void + remove: (index: number) => void +} + +export function Period({ error, index, initialPeriod, onValidate, remove }: PeriodProps) { + const frequencyOptions = getOptionsFromLabelledEnum(VigilanceArea.FrequencyLabel) + const endingConditionOptions = getOptionsFromLabelledEnum(VigilanceArea.EndingConditionLabel) + const isNewlyCreatedPeriod = Object.values( + omit(initialPeriod, ['isCritical', 'id', 'frequency', 'endingCondition']) + ).every(value => value === undefined || value === false) + const [isEditing, setIsEditing] = useState(isNewlyCreatedPeriod) + const [editedPeriod, setEditedPeriod] = useState(initialPeriod) + const isValid = (value: VigilanceArea.VigilanceAreaPeriod) => PublishedVigilanceAreaPeriodSchema.isValidSync(value) + const cancel = () => { + if (isNewlyCreatedPeriod) { + remove(index) + } else { + setEditedPeriod(initialPeriod) + setIsEditing(false) + } + } + + const validate = () => { + setIsEditing(false) + onValidate(editedPeriod) + } + + const setPeriod = (period: DateAsStringRange | undefined) => { + setEditedPeriod(prevState => ({ + ...prevState, + endDatePeriod: period ? period[1] : undefined, + startDatePeriod: period ? period[0] : undefined + })) + } + + const setIsCritical = (isChecked: boolean | undefined) => { + setEditedPeriod({ ...editedPeriod, isCritical: isChecked ?? false }) + } + + const setEndingCondition = (nextEndingCondition: string | undefined) => { + setEditedPeriod({ ...editedPeriod, endingCondition: nextEndingCondition as VigilanceArea.EndingCondition }) + } + + const setEndingOccurencesNumber = (nextEndingOccurrencesNumber: number | undefined) => { + setEditedPeriod({ ...editedPeriod, endingOccurrencesNumber: nextEndingOccurrencesNumber }) + } + + const setEndingOccurenceDate = (nextEndingOccurrenceDate: string | undefined) => { + setEditedPeriod({ ...editedPeriod, endingOccurrenceDate: nextEndingOccurrenceDate }) + } + + const setFrequency = (nextFrequency: string | undefined) => { + let nextPeriod: VigilanceArea.VigilanceAreaPeriod = { + ...editedPeriod, + frequency: nextFrequency as VigilanceArea.Frequency + } + if (nextFrequency === VigilanceArea.Frequency.NONE) { + nextPeriod = { + ...nextPeriod, + endingCondition: undefined, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined + } + } + setEditedPeriod(nextPeriod) + } + + return isEditing ? ( + + + + + + + + + <> + + {editedPeriod.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && ( + + )} + {editedPeriod.endingCondition === VigilanceArea.EndingCondition.END_DATE && ( + + )} + + )} + + + + + Valider + + + + ) : ( + +
+ + + {getDateAsLocalizedStringVeryCompact(initialPeriod.startDatePeriod, true)} au{' '} + {getDateAsLocalizedStringVeryCompact(initialPeriod.endDatePeriod, true)} + +
+ + setIsEditing(true)} + title="Editer la période de la zone de vigilance" + /> + remove(index)} + title="Supprimer la période de la zone de vigilance" + /> + +
+ ) +} + +const Wrapper = styled.div<{ $isCritical: boolean | undefined }>` + background-color: ${$p => ($p.$isCritical ? $p.theme.color.maximumRed15 : $p.theme.color.gainsboro)}; + padding: 8px; + display: flex; + flex-direction: column; + gap: 8px; +` + +const PanelWrapper = styled(Wrapper)` + flex: 1; + flex-direction: row; + align-content: center; + justify-content: space-between; +` + +const PanelButtons = styled.div` + display: flex; + margin: auto 0; +` + +const Buttons = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 8px; +` + +const DateWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: baseline; + gap: 16px; +` + +const Medium = styled.span` + font-weight: 500; +` + +const FrequencyContainer = styled.div` + align-items: end; + display: flex; + flex-direction: row; + gap: 8px; +` +const StyledDatePicker = styled(DatePicker)` + > .Element-Fieldset__InnerBox { + > .Field-DatePicker__CalendarPicker { + > .rs-picker-popup { + left: -130px !important; + } + } + } +` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods.tsx new file mode 100644 index 0000000000..9cd9dd124a --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods.tsx @@ -0,0 +1,187 @@ +import { Period } from '@features/VigilanceArea/components/VigilanceAreaForm/Periods/Period' +import { getVigilanceAreaPeriodInitialValues } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' +import { VigilanceArea } from '@features/VigilanceArea/types' +import { Accent, Button, Checkbox, customDayjs, Icon, Legend } from '@mtes-mct/monitor-ui' +import { FieldArray, type FormikErrors, useFormikContext } from 'formik' +import styled from 'styled-components' + +export function Periods() { + const { errors, setFieldValue, values } = useFormikContext() + + function isArrayOfErrors( + periodErrors: string | FormikErrors[] | undefined + ): periodErrors is FormikErrors[] { + return Array.isArray(periodErrors) + } + + const { periods } = values + const setIsAtAllTimes = (isChecked: boolean | undefined, isCritical: boolean) => { + // Clean all periods if user set period to all times and critical + const filtered = isCritical ? [] : periods?.filter(period => period.isCritical !== isCritical) + + const index = periods?.findIndex(period => period.isAtAllTimes && period.isCritical === isCritical) ?? -1 + + if (isChecked && index === -1) { + const vigilanceAreaPeriod: VigilanceArea.VigilanceAreaPeriod = { + ...getVigilanceAreaPeriodInitialValues(), + isAtAllTimes: true, + isCritical + } + const newVigilanceAreaPeriods = [...(filtered ?? []), vigilanceAreaPeriod] + setFieldValue('periods', newVigilanceAreaPeriods) + } + + if (!isChecked && index !== -1) { + setFieldValue('periods', filtered) + } + } + + return ( + { + const hasSimpleIsAtAllTimesPeriod = periods?.some(period => period.isAtAllTimes && !period.isCritical) + const hasCriticalIsAtAllTimesPeriod = periods?.some(period => period.isAtAllTimes && period.isCritical) + + return ( + + + Type de période de vigilance en tout temps + + + setIsAtAllTimes(isChecked, false)} + /> + + + + setIsAtAllTimes(isChecked, true)} + /> + + + {periods + ?.filter((period: VigilanceArea.VigilanceAreaPeriod) => !period.isAtAllTimes) + .sort((a, b) => { + if (!a.startDatePeriod) { + return -1 + } + if (!b.startDatePeriod) { + return 1 + } + + return customDayjs(a.startDatePeriod).valueOf() - customDayjs(b.startDatePeriod).valueOf() + }) + .map((period: VigilanceArea.VigilanceAreaPeriod) => { + const index = periods?.indexOf(period) + + return ( + { + replace(index, vigilanceAreaPeriod) + }} + remove={remove} + /> + ) + })} + + + + ) + }} + validateOnChange={false} + /> + ) +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + border-top: 1px solid ${p => p.theme.color.lightGray}; + padding-top: 8px; +` + +export const CriticalCheckbox = styled(Checkbox)` + label { + margin-left: 14px; + } +` + +export const CheckboxWrapper = styled.div` + position: relative; +` + +export const CheckboxesWrapper = styled.fieldset` + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border: none; +` + +export const BasePeriodCircle = styled.div<{ $isCritical?: boolean }>` + height: 10px; + width: 10px; + background-color: ${p => (p.$isCritical ? 'rgba(194, 81, 65, 0.75)' : 'rgba(194, 81, 65, 0.5)')}; + border: ${p => (p.$isCritical ? `2px solid ${p.theme.color.maximumRed}` : '1px solid rgba(147, 63, 32, 0.75)')}; + border-radius: 50%; + display: inline-block; +` + +export const PeriodCircle = styled(BasePeriodCircle)` + margin-right: 6px; + position: absolute; + left: 22px; + top: 0; + transform: translateY(50%); +` + +const HiddenLegend = styled(Legend)` + width: 1px; + height: 1px; + padding: 0; + margin: 0; + overflow: hidden; + position: absolute; + left: -10000px; +` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/MonthBox.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/MonthBox.tsx index 68db383295..5fc2ff9025 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/MonthBox.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/MonthBox.tsx @@ -26,6 +26,18 @@ export function MonthBox({ dateRanges, label, monthIndex }: MonthBoxProps) { ) } + const isDayInCriticalPeriod = (dayNum: number) => { + const dayToCompare = month.set('date', dayNum) + + return dateRanges.some( + dateRange => + dateRange.isCritical && + (dayToCompare.isSame(dateRange.start, 'day') || + dayToCompare.isSame(dateRange.end, 'day') || + (dayToCompare.isAfter(dateRange.start) && dayToCompare.isBefore(dateRange.end))) + ) + } + const isStart = (dayNum: number) => { const dayToCompare = month.set('date', dayNum) @@ -46,6 +58,7 @@ export function MonthBox({ dateRanges, label, monthIndex }: MonthBoxProps) { {days.map(day => ( } - {isCurrentMonth && } + {isCurrentMonth && ( + + + + )} ) @@ -63,6 +80,12 @@ const Wrapper = styled.div` position: relative; ` +const IconWrapper = styled.div` + display: flex; + width: 100%; + justify-content: center; +` + const Label = styled.span<{ $isBold: boolean }>` ${p => p.$isBold && `font-weight: 700; color:${p.theme.color.charcoal}`} ` @@ -77,17 +100,16 @@ const Box = styled.div<{ $dayInMonth: number }>` margin-top: 4px; ` -const DayBox = styled.div<{ $isEnd: boolean; $isHighlighted: boolean; $isStart: boolean }>` +const DayBox = styled.div<{ $isCritical: boolean; $isEnd: boolean; $isHighlighted: boolean; $isStart: boolean }>` width: 100%; height: 100%; - ${({ $isHighlighted }) => + ${({ $isCritical, $isHighlighted, theme }) => $isHighlighted && - `background-color: #C2514180; - border-top: 1px solid #933F20; - border-bottom: 1px solid #933F20;`}; - background-color: ${({ $isHighlighted }) => ($isHighlighted ? '#C2514180' : `white`)}; - ${p => p.$isStart && 'border-left: 1px solid #933F20;'} - ${p => p.$isEnd && 'border-right: 1px solid #933F20;'} + `background-color: ${$isCritical ? '#C25141BF' : '#C2514180'}; + border-top: ${$isCritical ? `2px solid ${theme.color.maximumRed}` : '1px solid #933F20'}; + border-bottom: ${$isCritical ? `2px solid ${theme.color.maximumRed}` : '1px solid #933F20'};`}; + ${p => p.$isStart && `border-left: ${p.$isCritical ? `2px solid ${p.theme.color.maximumRed}` : '1px solid #933F20'}`} + ${p => p.$isEnd && `border-right: ${p.$isCritical ? `2px solid ${p.theme.color.maximumRed}` : '1px solid #933F20'}`} ` const BackgroundBox = styled.div` @@ -99,8 +121,6 @@ const BackgroundBox = styled.div` ` const StyledIcon = styled(Icon.FilledArrow)` - position: absolute; - bottom: -4px; - left: 50%; - transform: translateX(-50%) rotate(-90deg); + margin: 0 auto; + transform: rotate(-90deg); ` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/PlanningBody.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/PlanningBody.tsx index e8351bc46e..0419010f0a 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/PlanningBody.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/PlanningBody.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/destructuring-assignment */ import { Bold } from '@components/style' +import { Tooltip } from '@components/Tooltip' import { Rectangle } from '@features/layersSelector/utils/LayerLegend.style' -import { computeOccurenceWithinCurrentYear } from '@features/VigilanceArea/components/VigilanceAreaForm/Planning/utils' +import { + computeOccurenceWithinCurrentYear, + type DateRange +} from '@features/VigilanceArea/components/VigilanceAreaForm/Planning/utils' import { VigilanceArea } from '@features/VigilanceArea/types' +import { computeVigilanceAreaPeriod, endingOccurenceText, frequencyText } from '@features/VigilanceArea/utils' import { Icon, Size, THEME } from '@mtes-mct/monitor-ui' -import { getDateAsLocalizedStringVeryCompact } from '@utils/getDateAsLocalizedString' import { useMemo, useState } from 'react' import styled from 'styled-components' @@ -17,42 +21,102 @@ type PlanningBodyProps = { export function PlanningBody({ vigilanceArea }: PlanningBodyProps) { const [isSummaryOpen, setIsSummaryOpen] = useState(false) + const [isCriticalSummaryOpen, setIsCriticalSummaryOpen] = useState(false) const occurences = useMemo( - () => (vigilanceArea ? computeOccurenceWithinCurrentYear(vigilanceArea) : []), + () => + (vigilanceArea.periods ?? []) + .reduce((acc: DateRange[][], period) => { + acc.push(computeOccurenceWithinCurrentYear(period)) + + return acc + }, []) + .flat(), [vigilanceArea] ) + const hasSimplePeriods = useMemo(() => occurences.some(occ => !occ.isCritical), [occurences]) + const hasCriticalPeriods = useMemo(() => occurences.some(occ => occ.isCritical), [occurences]) return ( <> - {!vigilanceArea.isAtAllTimes && !vigilanceArea.startDatePeriod ? ( + {vigilanceArea.periods && vigilanceArea.periods.length === 0 ? ( Aucune période de vigilance définie ) : ( -
{ - if (event.target) { - const elt = event.target as HTMLDetailsElement - setIsSummaryOpen(elt.open) - } - }} - open={isSummaryOpen} - > - - - Vigilance simple - - - - {occurences.map(({ end, start }, index) => ( - // eslint-disable-next-line react/no-array-index-key -
  • - Du {getDateAsLocalizedStringVeryCompact(start.toISOString(), true)} au{' '} - {getDateAsLocalizedStringVeryCompact(end.toISOString(), true)} -
  • - ))} -
    -
    + + {hasSimplePeriods && ( +
    { + if (event.target) { + const elt = event.target as HTMLDetailsElement + setIsSummaryOpen(elt.open) + } + }} + open={isSummaryOpen} + > + + + Vigilance simple + + + + {vigilanceArea.periods + ?.filter(period => !period.isCritical) + .map(period => ( + + {computeVigilanceAreaPeriod(period, false)} + {period?.frequency && period.frequency !== VigilanceArea.Frequency.NONE && ( + + {[ + frequencyText(period.frequency, false), + endingOccurenceText(period?.endingCondition, period?.computedEndDate, false) + ] + .filter(value => !!value) + .join(', ')} + + )} + + ))} + +
    + )} + {hasCriticalPeriods && ( +
    { + if (event.target) { + const elt = event.target as HTMLDetailsElement + setIsCriticalSummaryOpen(elt.open) + } + }} + open={isCriticalSummaryOpen} + > + + + Vigilance critique + + + + {vigilanceArea.periods + ?.filter(occ => occ.isCritical) + .map(period => ( + + {computeVigilanceAreaPeriod(period, false)} + {period?.frequency && period.frequency !== VigilanceArea.Frequency.NONE && ( + + {[ + frequencyText(period?.frequency, false), + endingOccurenceText(period?.endingCondition, period?.computedEndDate, false) + ] + .filter(value => !!value) + .join(', ')} + + )} + + ))} + +
    + )} +
    )} La zone sera visible sur la cartographie et la liste uniquement lors ses périodes de vigilance @@ -63,6 +127,12 @@ export function PlanningBody({ vigilanceArea }: PlanningBodyProps) { ) } +const DetailsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; +` const Details = styled.details` cursor: pointer; ` @@ -75,7 +145,7 @@ const Summary = styled.summary` ` const Periods = styled.section` - margin-top: 16px; + margin-top: 8px; ` const PeriodList = styled.ol` @@ -87,6 +157,12 @@ const PeriodList = styled.ol` flex-wrap: wrap; ` +const PeriodItem = styled.li` + display: flex; + align-items: center; + gap: 8px; +` + const PeriodDescription = styled.p` font-style: italic; font-weight: 400; diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/index.tsx index cf4c59657c..acad0bef44 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/index.tsx @@ -37,12 +37,12 @@ export function Planning({ occurences }: PlanningProps) { const PlanningWrapper = styled.ol` display: grid; grid-template-columns: repeat(6, 1fr); - gap: 10px 4px; + column-gap: 10px; font-size: 11px; color: ${p => p.theme.color.slateGray}; - padding-bottom: 10px; + padding-bottom: 16px; border-bottom: 1px solid ${p => p.theme.color.lightGray}; > li { diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.test.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.test.ts index 44e4c6ab6f..85ab98d0b0 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.test.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.test.ts @@ -8,67 +8,87 @@ describe('computeOccurenceWithinCurrentYear should return all occurences that ma jest.useFakeTimers().setSystemTime(new Date('2024-05-13T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - isArchived: false, - isAtAllTimes: true, isDraft: false, name: undefined, + periods: [ + { + isAtAllTimes: true + } + ], seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) jest.useRealTimers() expect(occurenceDates).toEqual([ - { end: customDayjs('2024-12-31T23:59:59.999Z').utc(), start: customDayjs('2024-01-01T00:00:00.000Z').utc() } + { + end: customDayjs('2024-12-31T23:59:59.999Z').utc(), + isCritical: undefined, + start: customDayjs('2024-01-01T00:00:00.000Z').utc() + } ]) }) it('it should return empty when there is no starting date', () => { const vigilanceArea: VigilanceArea.VigilanceArea = { - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, + periods: [ + { + isAtAllTimes: false + } + ], seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) expect(occurenceDates).toEqual([]) }) it('it should return startDatePeriod and endDatePeriod when frequency is NONE', () => { + jest.useFakeTimers().setSystemTime(new Date('2025-01-01T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - endDatePeriod: '2025-04-19T00:00:00.000Z', - frequency: VigilanceArea.Frequency.NONE, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, - seaFront: undefined, - startDatePeriod: '2025-01-01T00:00:00.000Z' + periods: [ + { + endDatePeriod: '2025-04-19T00:00:00.000Z', + frequency: VigilanceArea.Frequency.NONE, + isAtAllTimes: false, + startDatePeriod: '2025-01-01T00:00:00.000Z' + } + ], + seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) + jest.useRealTimers() expect(occurenceDates).toEqual([ { - end: customDayjs(vigilanceArea.endDatePeriod).utc(), - start: customDayjs(vigilanceArea.startDatePeriod).utc() + end: customDayjs('2025-04-19T00:00:00.000Z').utc(), + isCritical: undefined, + start: customDayjs('2025-01-01T00:00:00.000Z').utc() } ]) }) it('it should return date range of the current year when frequency is ALL_YEARS', () => { jest.useFakeTimers().setSystemTime(new Date('2025-01-01T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - endDatePeriod: '2023-04-19T00:00:00.000Z', - endingCondition: VigilanceArea.EndingCondition.NEVER, - frequency: VigilanceArea.Frequency.ALL_YEARS, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, - seaFront: undefined, - startDatePeriod: '2023-01-01T00:00:00.000Z' + periods: [ + { + endDatePeriod: '2023-04-19T00:00:00.000Z', + endingCondition: VigilanceArea.EndingCondition.NEVER, + frequency: VigilanceArea.Frequency.ALL_YEARS, + isAtAllTimes: false, + startDatePeriod: '2023-01-01T00:00:00.000Z' + } + ], + seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) jest.useRealTimers() expect(occurenceDates).toEqual([ { end: customDayjs('2025-04-19T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-01T00:00:00.000Z').utc() } ]) @@ -76,42 +96,51 @@ describe('computeOccurenceWithinCurrentYear should return all occurences that ma it('it should return date range of the current year when frequency is ALL_WEEKS with occurences number', () => { jest.useFakeTimers().setSystemTime(new Date('2025-01-01T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - endDatePeriod: '2023-11-13T00:00:00.000Z', - endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, - endingOccurrencesNumber: 66, - frequency: VigilanceArea.Frequency.ALL_WEEKS, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, - seaFront: undefined, - startDatePeriod: '2023-11-12T00:00:00.000Z' + periods: [ + { + endDatePeriod: '2023-11-13T00:00:00.000Z', + endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, + endingOccurrencesNumber: 66, + frequency: VigilanceArea.Frequency.ALL_WEEKS, + isAtAllTimes: false, + startDatePeriod: '2023-11-12T00:00:00.000Z' + } + ], + seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) jest.useRealTimers() expect(occurenceDates).toEqual([ { end: customDayjs('2025-01-06T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-05T00:00:00.000Z').utc() }, { end: customDayjs('2025-01-13T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-12T00:00:00.000Z').utc() }, { end: customDayjs('2025-01-20T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-19T00:00:00.000Z').utc() }, { end: customDayjs('2025-01-27T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-26T00:00:00.000Z').utc() }, { end: customDayjs('2025-02-03T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-02-02T00:00:00.000Z').utc() }, { end: customDayjs('2025-02-10T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-02-09T00:00:00.000Z').utc() } ]) @@ -120,17 +149,20 @@ describe('computeOccurenceWithinCurrentYear should return all occurences that ma it('it should return empty when frequency is ALL_WEEKS out of the current year', () => { jest.useFakeTimers().setSystemTime(new Date('2025-01-01T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - endDatePeriod: '2023-11-13T00:00:00.000Z', - endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, - frequency: VigilanceArea.Frequency.NONE, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, - seaFront: undefined, - startDatePeriod: '2023-11-12T00:00:00.000Z' + periods: [ + { + endDatePeriod: '2023-11-13T00:00:00.000Z', + endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, + frequency: VigilanceArea.Frequency.NONE, + isAtAllTimes: false, + startDatePeriod: '2023-11-12T00:00:00.000Z' + } + ], + seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) jest.useRealTimers() expect(occurenceDates).toEqual([]) }) @@ -138,22 +170,26 @@ describe('computeOccurenceWithinCurrentYear should return all occurences that ma it('it should return date range until the given date when frequency is ALL_WEEKS and ending is a date', () => { jest.useFakeTimers().setSystemTime(new Date('2025-01-01T12:00:00.000Z')) const vigilanceArea: VigilanceArea.VigilanceArea = { - endDatePeriod: '2023-11-13T00:00:00.000Z', - endingCondition: VigilanceArea.EndingCondition.END_DATE, - endingOccurrenceDate: '2025-01-10T12:00:00.000Z', - frequency: VigilanceArea.Frequency.ALL_WEEKS, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: undefined, - seaFront: undefined, - startDatePeriod: '2023-11-12T00:00:00.000Z' + periods: [ + { + endDatePeriod: '2023-11-13T00:00:00.000Z', + endingCondition: VigilanceArea.EndingCondition.END_DATE, + endingOccurrenceDate: '2025-01-10T12:00:00.000Z', + frequency: VigilanceArea.Frequency.ALL_WEEKS, + isAtAllTimes: false, + startDatePeriod: '2023-11-12T00:00:00.000Z' + } + ], + seaFront: undefined } - const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea) + const occurenceDates = computeOccurenceWithinCurrentYear(vigilanceArea.periods![0]!) jest.useRealTimers() expect(occurenceDates).toEqual([ { end: customDayjs('2025-01-06T00:00:00.000Z').utc(), + isCritical: undefined, start: customDayjs('2025-01-05T00:00:00.000Z').utc() } ]) diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.ts index 7ebfbb44a6..8b1acd80bd 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Planning/utils.ts @@ -5,35 +5,36 @@ import type { Dayjs } from 'dayjs' export type DateRange = { end: Dayjs + isCritical: boolean | undefined start: Dayjs } -export function computeOccurenceWithinCurrentYear(area: VigilanceArea.VigilanceArea): DateRange[] { +export function computeOccurenceWithinCurrentYear(period: VigilanceArea.VigilanceAreaPeriod): DateRange[] { const now = customDayjs().utc() const startOfYear = now.startOf('year') const endOfYear = now.endOf('year') // Cas 1: Toute l'année - if (area.isAtAllTimes) { - return [{ end: endOfYear, start: startOfYear }] + if (period.isAtAllTimes) { + return [{ end: endOfYear, isCritical: period.isCritical, start: startOfYear }] } // Cas 2: Pas de date de début - if (!area.startDatePeriod) { + if (!period.startDatePeriod) { return [] } - const startDate = customDayjs(area.startDatePeriod).utc() - const endDate = customDayjs(area.endDatePeriod).utc() + const startDate = customDayjs(period.startDatePeriod).utc() + const endDate = customDayjs(period.endDatePeriod).utc() // Cas 3: Pas de récurrence - if (area.frequency === VigilanceArea.Frequency.NONE) { + if (period.frequency === VigilanceArea.Frequency.NONE) { // Vérifier si la période intersecte avec l'année courante if (endDate.isBefore(startOfYear) || startDate.isAfter(endOfYear)) { return [] } - return [{ end: endDate.utc(), start: startDate.utc() }] + return [{ end: endDate.utc(), isCritical: period.isCritical, start: startDate.utc() }] } // Cas 4: Récurrences (week/month/year) @@ -42,13 +43,13 @@ export function computeOccurenceWithinCurrentYear(area: VigilanceArea.VigilanceA // Limites const maxOccurrences = - area.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && area.endingOccurrencesNumber - ? area.endingOccurrencesNumber + period.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && period.endingOccurrencesNumber + ? period.endingOccurrencesNumber : Infinity const endingDate = - area.endingCondition === VigilanceArea.EndingCondition.END_DATE && area.endingOccurrenceDate - ? customDayjs(area.endingOccurrenceDate).utc() + period.endingCondition === VigilanceArea.EndingCondition.END_DATE && period.endingOccurrenceDate + ? customDayjs(period.endingOccurrenceDate).utc() : null // Trouver la première occurrence dans l'année @@ -80,7 +81,11 @@ export function computeOccurenceWithinCurrentYear(area: VigilanceArea.VigilanceA const clippedEnd = occEnd.isAfter(endOfYear) ? endOfYear : occEnd if (clippedEnd.isAfter(startOfYear) || clippedEnd.isSame(startOfYear)) { - results.push({ end: clippedEnd.clone().utc(), start: clippedStart.clone().utc() }) + results.push({ + end: clippedEnd.clone().utc(), + isCritical: period.isCritical, + start: clippedStart.clone().utc() + }) } if (unit === 'year') { @@ -95,19 +100,20 @@ export function computeOccurenceWithinCurrentYear(area: VigilanceArea.VigilanceA return results } - if (area.frequency === VigilanceArea.Frequency.ALL_WEEKS) { + if (period.frequency === VigilanceArea.Frequency.ALL_WEEKS) { return generateRecurring('week') } - if (area.frequency === VigilanceArea.Frequency.ALL_MONTHS) { + if (period.frequency === VigilanceArea.Frequency.ALL_MONTHS) { return generateRecurring('month') } - if (area.frequency === VigilanceArea.Frequency.ALL_YEARS) { + if (period.frequency === VigilanceArea.Frequency.ALL_YEARS) { return generateRecurring('year') } return [ { end: endDate.isAfter(endOfYear) ? endOfYear : endDate, + isCritical: period.isCritical, start: startDate } ] diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Schema.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Schema.ts index 40bd770b01..b047e78363 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Schema.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Schema.ts @@ -24,49 +24,9 @@ export const VigilanceAreaSourceSchema: Yup.Schema !!(value?.name || value?.email || value?.phone || (value?.controlUnitContacts?.length ?? 0) > 0) ) -export const DraftSchema: Yup.Schema< - Omit -> = Yup.object() - .shape({ - comments: Yup.string().optional(), - computedEndDate: Yup.string().nullable(), - createdBy: Yup.string() - .min(3, 'Minimum 3 lettres pour le trigramme') - .max(3, 'Maximum 3 lettres pour le trigramme') - .optional(), - endDatePeriod: Yup.string().optional(), - endingCondition: Yup.mixed().optional(), - endingOccurrenceDate: Yup.string().optional(), - endingOccurrencesNumber: Yup.number().optional(), - frequency: Yup.mixed().optional(), - geom: Yup.mixed().optional(), - id: Yup.number().optional(), - images: Yup.array().optional(), - isArchived: Yup.boolean().required(), - isAtAllTimes: Yup.boolean().required(), - isDraft: Yup.boolean().required(), - linkedAMPs: Yup.array().optional(), - linkedRegulatoryAreas: Yup.array().optional(), - links: Yup.array().optional(), - name: Yup.string().required(), - sources: Yup.array().of(VigilanceAreaSourceSchema).ensure(), - startDatePeriod: Yup.string().optional(), - tags: Yup.array().ensure().optional(), - themes: Yup.array().ensure().optional(), - visibility: Yup.mixed().optional() - }) - .required() - -export const PublishedSchema: Yup.Schema< - Omit -> = Yup.object() - .shape({ - comments: Yup.string().required(), - computedEndDate: Yup.string().nullable(), - createdBy: Yup.string() - .min(3, 'Minimum 3 lettres pour le trigramme') - .max(3, 'Maximum 3 lettres pour le trigramme') - .required(), +export const PublishedVigilanceAreaPeriodSchema: Yup.Schema> = + Yup.object().shape({ + computedEndDate: Yup.string().optional(), endDatePeriod: Yup.string().when('isAtAllTimes', { is: false, otherwise: schema => schema.nullable(), @@ -101,6 +61,59 @@ export const PublishedSchema: Yup.Schema< otherwise: schema => schema.nullable(), then: schema => schema.nullable().required('Requis') }), + isAtAllTimes: Yup.boolean().required(), + startDatePeriod: Yup.string().when('isAtAllTimes', { + is: false, + otherwise: schema => schema.nullable(), + then: schema => schema.nullable().required('Requis') + }) + }) + +export const DraftVigilanceAreaPeriodSchema: Yup.Schema> = + Yup.object().shape({ + computedEndDate: Yup.string().optional(), + endDatePeriod: Yup.string().optional(), + endingCondition: Yup.mixed().optional(), + endingOccurrenceDate: Yup.string().optional(), + endingOccurrencesNumber: Yup.number().optional(), + frequency: Yup.mixed().optional(), + isAtAllTimes: Yup.boolean().required(), + startDatePeriod: Yup.string().optional() + }) + +export const DraftSchema: Yup.Schema> = + Yup.object() + .shape({ + comments: Yup.string().optional(), + createdBy: Yup.string() + .min(3, 'Minimum 3 lettres pour le trigramme') + .max(3, 'Maximum 3 lettres pour le trigramme') + .optional(), + geom: Yup.mixed().optional(), + id: Yup.number().optional(), + images: Yup.array().optional(), + isDraft: Yup.boolean().required(), + linkedAMPs: Yup.array().optional(), + linkedRegulatoryAreas: Yup.array().optional(), + links: Yup.array().optional(), + name: Yup.string().required(), + periods: Yup.array().ensure().of(DraftVigilanceAreaPeriodSchema).optional(), + sources: Yup.array().ensure().of(VigilanceAreaSourceSchema).ensure(), + tags: Yup.array().ensure().optional(), + themes: Yup.array().ensure().optional(), + visibility: Yup.mixed().optional() + }) + .required() + +export const PublishedSchema: Yup.Schema< + Omit +> = Yup.object() + .shape({ + comments: Yup.string().required(), + createdBy: Yup.string() + .min(3, 'Minimum 3 lettres pour le trigramme') + .max(3, 'Maximum 3 lettres pour le trigramme') + .required(), geom: Yup.mixed() .test({ message: 'Veuillez définir une zone de vigilance', @@ -110,19 +123,13 @@ export const PublishedSchema: Yup.Schema< .required(), id: Yup.number().optional(), images: Yup.array().optional(), - isArchived: Yup.boolean().required(), - isAtAllTimes: Yup.boolean().required(), isDraft: Yup.boolean().required(), linkedAMPs: Yup.array().optional(), linkedRegulatoryAreas: Yup.array().optional(), links: Yup.array().optional(), name: Yup.string().required(), - sources: Yup.array().of(VigilanceAreaSourceSchema).optional(), - startDatePeriod: Yup.string().when('isAtAllTimes', { - is: false, - otherwise: schema => schema.nullable(), - then: schema => schema.nullable().required('Requis') - }), + periods: Yup.array().ensure().of(PublishedVigilanceAreaPeriodSchema).optional(), + sources: Yup.array().ensure().of(VigilanceAreaSourceSchema).optional(), tags: Yup.array() .ensure() .test('required-if-no-themes', 'Renseignez au moins un thème ou un tag', (tags, context) => { @@ -130,7 +137,6 @@ export const PublishedSchema: Yup.Schema< return (tags && tags.length > 0) || (themes && themes.length > 0) }), - themes: Yup.array() .ensure() .test('required-if-no-tags', 'Renseignez au moins un thème ou un tag', (themes, context) => { diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Sources/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Sources/index.tsx index 3aded70e73..402f0aca40 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Sources/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/Sources/index.tsx @@ -28,7 +28,7 @@ export function Sources() { return ( { @@ -42,14 +42,30 @@ export function Sources() { @@ -61,7 +77,7 @@ export function Sources() { return ( { @@ -75,7 +91,14 @@ export function Sources() { diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/index.tsx index 413a21382d..bf3487ef8d 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/index.tsx @@ -21,7 +21,7 @@ import { Form } from './Form' import { VigilanceAreaPanel } from './Panel' import { VigilanceAreaSchema } from './Schema' import { Header, SubHeaderContainer, Title, TitleContainer } from './style' -import { getVigilanceAreaInitialValues } from './utils' +import { getVigilanceAreaInitialValues, isOutOfPeriod, isWithinPeriod } from './utils' type VigilanceAreaFormProps = { isOpen: boolean @@ -91,7 +91,10 @@ export function VigilanceAreaForm({ isOpen, isReadOnly = false, vigilanceAreaId
    { return { comments: undefined, - computedEndDate: undefined, createdBy: undefined, - endDatePeriod: undefined, - endingCondition: undefined, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: undefined, geom: undefined, images: [], - isArchived: false, - isAtAllTimes: false, isDraft: true, linkedAMPs: [], linkedRegulatoryAreas: [], links: [], name: undefined, + periods: [], seaFront: undefined, sources: [], - startDatePeriod: undefined, tags: [], themes: [], validatedAt: undefined, @@ -30,8 +26,44 @@ export function getVigilanceAreaInitialValues(): Omit { const SchemaToValidate = againstDraftSchema ? DraftSchema : PublishedSchema return SchemaToValidate.isValidSync(vigilanceArea, { abortEarly: false }) } + +export function isWithinPeriod( + periods: VigilanceArea.VigilanceAreaPeriod[] | undefined, + isCritical: boolean | undefined +) { + return !!periods?.some(period => { + const dateRanges = computeOccurenceWithinCurrentYear(period) + + return dateRanges.some( + dateRange => dateRange.isCritical === isCritical && customDayjs.utc().isBetween(dateRange.start, dateRange.end) + ) + }) +} + +export function isOutOfPeriod(periods: VigilanceArea.VigilanceAreaPeriod[] | undefined) { + return !!periods?.every(period => { + const dateRanges = computeOccurenceWithinCurrentYear(period) + + return dateRanges.every(dateRange => !customDayjs.utc().isBetween(dateRange.start, dateRange.end)) + }) +} diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/style.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/style.ts index 5cd4b487ec..794628b039 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/style.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/style.ts @@ -1,14 +1,25 @@ +import { isOutOfPeriod, isWithinPeriod } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' +import { VigilanceArea } from '@features/VigilanceArea/types' import { THEME } from '@mtes-mct/monitor-ui' +import { getColorWithAlpha, stringToColorInGroup } from '@utils/utils' import { Fill, Stroke, Style } from 'ol/style' import { Layers } from '../../../../domain/entities/layers/constants' -import { getColorWithAlpha, stringToColorInGroup } from '../../../../utils/utils' -const getStyle = (color: string, isSelected: boolean | undefined, asMinimap: boolean, isFilled: boolean = true) => { +const getStyle = ( + color: string, + isSelected: boolean | undefined, + asMinimap: boolean, + isFilled: boolean = true, + isWithinCriticalPeriod: boolean = false +) => { const strokeColor = () => { if (asMinimap) { return getColorWithAlpha(THEME.color.charcoal, 1) } + if (isWithinCriticalPeriod) { + return getColorWithAlpha(THEME.color.maximumRed, 1) + } return isSelected ? getColorWithAlpha('#FF4433', 1) : getColorWithAlpha(THEME.color.rufous, 1) } @@ -19,7 +30,7 @@ const getStyle = (color: string, isSelected: boolean | undefined, asMinimap: boo }), stroke: new Stroke({ color: strokeColor(), - width: isSelected || asMinimap ? 3 : 1 + width: isSelected || asMinimap || isWithinCriticalPeriod ? 3 : 1 }) }) } @@ -27,9 +38,9 @@ const getStyle = (color: string, isSelected: boolean | undefined, asMinimap: boo export const getVigilanceAreaColorWithAlpha = ( name: string | null = '', comments: string | null = '', - isArchived = false + withoutPeriod = false ) => { - if (isArchived) { + if (withoutPeriod) { return THEME.color.white } @@ -37,9 +48,17 @@ export const getVigilanceAreaColorWithAlpha = ( } export const getVigilanceAreaLayerStyle = feature => { - const isArchived = feature.get('isArchived') + const periods = feature.get('periods') as Array | undefined + const isWithinCriticalPeriod = isWithinPeriod(periods, true) - const colorWithAlpha = getVigilanceAreaColorWithAlpha(feature.get('name'), feature.get('comments'), isArchived) + const isInformative = isOutOfPeriod(periods) + const colorWithAlpha = getVigilanceAreaColorWithAlpha(feature.get('name'), feature.get('comments'), isInformative) - return getStyle(colorWithAlpha, feature.get('isSelected'), feature.get('asMinimap'), feature.get('isFilled')) + return getStyle( + colorWithAlpha, + feature.get('isSelected'), + feature.get('asMinimap'), + feature.get('isFilled'), + isWithinCriticalPeriod + ) } diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper.ts index ec6370aebf..f448c3f05a 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper.ts @@ -20,9 +20,7 @@ export const getVigilanceAreaZoneFeature = ( }) const area = geometry && getArea(geometry) - const feature = new Feature({ - geometry - }) + const feature = new Feature({ geometry }) const isolatedLayerIsVigilanceArea = isolatedLayer?.type.includes('VIGILANCE_AREA') ?? false const isLayerFilled = isolatedLayer diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaTypeFilter.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaTypeFilter.tsx new file mode 100644 index 0000000000..560394713a --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaTypeFilter.tsx @@ -0,0 +1,44 @@ +import { OptionValue } from '@features/Reportings/Filters/style' +import { vigilanceAreaFiltersActions } from '@features/VigilanceArea/components/VigilanceAreasList/Filters/slice' +import { useAppDispatch } from '@hooks/useAppDispatch' +import { useAppSelector } from '@hooks/useAppSelector' +import { CheckPicker, type Option } from '@mtes-mct/monitor-ui' +import React from 'react' + +import { VigilanceArea } from '../types' + +export function VigilanceAreaTypeFilter({ style }: { style?: React.CSSProperties }) { + const dispatch = useAppDispatch() + const vigilanceAreaTypeOptions = Object.entries(VigilanceArea.VigilanceAreaFilterTypeLabel).map(([value, label]) => ({ + label, + value + })) as Option[] + + const filteredVigilanceAreaType = useAppSelector(state => state.vigilanceAreaFilters.type) + + const handleSetFilteredVigilanceType = ( + nextVigilanceAreaType: VigilanceArea.VigilanceAreaFilterType[] | undefined + ) => { + dispatch(vigilanceAreaFiltersActions.updateFilters({ key: 'type', value: nextVigilanceAreaType ?? [] })) + } + + return ( + + filteredVigilanceAreaType && ( + {`Type de zone de vigilance (${filteredVigilanceAreaType.length})`} + ) + } + style={style} + value={filteredVigilanceAreaType} + /> + ) +} diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodCell.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodCell.tsx new file mode 100644 index 0000000000..fa7068ffe3 --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodCell.tsx @@ -0,0 +1,30 @@ +import { Tooltip } from '@components/Tooltip' +import { StyledPeriodCircle } from '@features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodsCell' +import { VigilanceArea } from '@features/VigilanceArea/types' +import { computeVigilanceAreaPeriod, endingOccurenceText, frequencyText } from '@features/VigilanceArea/utils' +import { Icon } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +export function PeriodCell({ period }: { period: VigilanceArea.VigilanceAreaPeriod | undefined }) { + return ( + + {!period?.isCritical && } + {period?.isCritical && } + {computeVigilanceAreaPeriod(period, false)} + {period?.frequency && period.frequency !== VigilanceArea.Frequency.NONE && ( + + {[ + frequencyText(period?.frequency, false), + endingOccurenceText(period?.endingCondition, period?.computedEndDate, false) + ].join(', ')} + + )} + + ) +} + +const StyledCell = styled.span` + display: flex; + align-items: center; + gap: 8px; +` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodsCell.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodsCell.tsx new file mode 100644 index 0000000000..7fe37e2c1f --- /dev/null +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodsCell.tsx @@ -0,0 +1,50 @@ +import { Tooltip } from '@components/Tooltip' +import { BasePeriodCircle } from '@features/VigilanceArea/components/VigilanceAreaForm/Periods/Periods' +import { PeriodCell } from '@features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodCell' +import { VigilanceArea } from '@features/VigilanceArea/types' +import styled from 'styled-components' + +export function PeriodsCell({ periods }: { periods: VigilanceArea.VigilanceAreaPeriod[] | undefined }) { + if (!periods || periods.length === 0) { + return - + } + const hasSimplePeriod = periods.some(period => !period.isCritical) + const hasCriticalPeriod = periods.some(period => period.isCritical) + + return ( + + {periods.length > 1 && ( + +
    + {hasSimplePeriod && } + {hasCriticalPeriod && } +
    + Périodes multiples{' '} + + {periods.map(period => ( + + ))} + +
    + )} + {periods.length === 1 && } +
    + ) +} + +const StyledCell = styled.span` + display: flex; + align-items: center; + gap: 8px; +` + +export const StyledPeriodCircle = styled(BasePeriodCircle)` + margin-right: 4px; +` + +const StyledTooltip = styled(Tooltip)` + display: flex; + flex-direction: column; + justify-content: center; + gap: 8px; +` diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Columns/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Columns/index.tsx index 8a2b28aad5..49d0825964 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Columns/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Columns/index.tsx @@ -1,9 +1,7 @@ -import { DateCell } from '@components/Table/DateCell' import { StyledSkeletonRow } from '@features/commonComponents/Skeleton' -import { VigilanceArea } from '@features/VigilanceArea/types' +import { PeriodsCell } from '@features/VigilanceArea/components/VigilanceAreasList/Cells/PeriodsCell' import { EditCell } from '../Cells/EditCell' -import { FrequencyCell } from '../Cells/FrequencyCell' import { HighlightCell } from '../Cells/HighlightCell' import { LocalizeCell } from '../Cells/LocalizeCell' import { StatusCell } from '../Cells/StatusCell' @@ -11,62 +9,30 @@ import { TagsCell } from '../Cells/TagsCell' import { ValidationDateCell } from '../Cells/ValidationDateCell' import { VisibilityCell } from '../Cells/VisibilityCell' -import type { Row } from '@tanstack/react-table' - export const Columns = (legacyFirefoxOffset: number = 0, isFetching: boolean = false) => [ - { - accessorFn: row => row.startDatePeriod, - cell: info => (isFetching ? : ), - enableSorting: true, - header: () => 'Début', - id: 'startDatePeriod', - size: 90 + legacyFirefoxOffset - }, - { - accessorFn: row => row.endDatePeriod, - cell: info => (isFetching ? : ), - enableSorting: true, - header: () => 'Fin', - id: 'endDatePeriod', - size: 90 + legacyFirefoxOffset - }, - { - accessorFn: row => row.frequency, - cell: info => (isFetching ? : ), - enableSorting: true, - header: () => 'Récurrence', - id: 'frequency', - size: 106 + legacyFirefoxOffset, - sortingFn: (rowA: Row, rowB: Row) => { - const labelA = VigilanceArea.FrequencyLabelForList[rowA.original.frequency] ?? '' - const labelB = VigilanceArea.FrequencyLabelForList[rowB.original.frequency] ?? '' - - return labelA.localeCompare(labelB) - } - }, - { - accessorFn: row => row.validatedAt, - cell: info => (isFetching ? : ), - enableSorting: true, - header: () => 'Validée le', - id: 'validatedAt', - size: 96 + legacyFirefoxOffset - }, { accessorFn: row => row.name, cell: info => (isFetching ? : ), enableSorting: true, header: () => 'Nom de la zone', id: 'name', - size: 270 + legacyFirefoxOffset + size: 250 + legacyFirefoxOffset + }, + { + accessorFn: row => row.periods, + cell: info => (isFetching ? : ), + enableSorting: true, + header: () => 'Période(s) de vigilance', + id: 'periods', + size: 250 + legacyFirefoxOffset }, { accessorFn: row => row.tags, cell: info => (isFetching ? : ), enableSorting: true, header: () => 'Tags', - id: 'themes', - size: 260 + legacyFirefoxOffset + id: 'tags', + size: 230 + legacyFirefoxOffset }, { accessorFn: row => row.comments, @@ -74,7 +40,7 @@ export const Columns = (legacyFirefoxOffset: number = 0, isFetching: boolean = f enableSorting: true, header: () => 'Commentaire', id: 'comments', - size: 310 + legacyFirefoxOffset + size: 330 + legacyFirefoxOffset }, { accessorFn: row => row.seaFront, @@ -85,11 +51,11 @@ export const Columns = (legacyFirefoxOffset: number = 0, isFetching: boolean = f size: 100 + legacyFirefoxOffset }, { - accessorFn: row => row.visibility, - cell: info => (isFetching ? : ), + accessorFn: row => row.validatedAt, + cell: info => (isFetching ? : ), enableSorting: true, - header: () => 'Visibilité', - id: 'visibility', + header: () => 'Validée le', + id: 'validatedAt', size: 100 + legacyFirefoxOffset }, { @@ -108,6 +74,14 @@ export const Columns = (legacyFirefoxOffset: number = 0, isFetching: boolean = f id: 'createdBy', size: 97 + legacyFirefoxOffset }, + { + accessorFn: row => row.visibility, + cell: info => (isFetching ? : ), + enableSorting: true, + header: () => 'Visibilité', + id: 'visibility', + size: 105 + legacyFirefoxOffset + }, { accessorFn: row => row.geom, cell: ({ row }) => diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/index.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/index.tsx index 7830ce21ed..62632b84c9 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/index.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/index.tsx @@ -13,6 +13,7 @@ import { setSearchExtent, setShouldFilterSearchOnMapExtent } from '@features/layersSelector/search/slice' +import { VigilanceAreaTypeFilter } from '@features/VigilanceArea/components/VigilanceAreaTypeFilter' import { VigilanceArea } from '@features/VigilanceArea/types' import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' @@ -140,6 +141,7 @@ export function VigilanceAreasFilters() { + diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/slice.ts b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/slice.ts index c94a8de177..7426fae2e7 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/slice.ts +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreasList/Filters/slice.ts @@ -26,8 +26,10 @@ export type VigilanceAreaSliceState = { seaFronts: string[] specificPeriod: DateAsStringRange | undefined status: VigilanceArea.Status[] + type: VigilanceArea.VigilanceAreaFilterType[] visibility: VigilanceArea.Visibility[] } + export const INITIAL_STATE: VigilanceAreaSliceState = { createdBy: [], nbOfFiltersSetted: 0, @@ -35,8 +37,10 @@ export const INITIAL_STATE: VigilanceAreaSliceState = { seaFronts: [], specificPeriod: undefined, status: [VigilanceArea.Status.DRAFT, VigilanceArea.Status.PUBLISHED], + type: [], visibility: [VigilanceArea.Visibility.PUBLIC, VigilanceArea.Visibility.PRIVATE] } + export const vigilanceAreaFiltersSlice = createSlice({ initialState: INITIAL_STATE, name: 'vigilanceAreaFilters', diff --git a/frontend/src/features/VigilanceArea/hooks/useGetFilteredVigilanceAreasQuery.ts b/frontend/src/features/VigilanceArea/hooks/useGetFilteredVigilanceAreasQuery.ts index 9c4f1326b7..6aede689fb 100644 --- a/frontend/src/features/VigilanceArea/hooks/useGetFilteredVigilanceAreasQuery.ts +++ b/frontend/src/features/VigilanceArea/hooks/useGetFilteredVigilanceAreasQuery.ts @@ -1,6 +1,7 @@ import { useGetVigilanceAreasQuery } from '@api/vigilanceAreasAPI' import { getFilterVigilanceAreasPerPeriod } from '@features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod' import { getIntersectingLayers } from '@features/layersSelector/utils/getIntersectingLayerIds' +import { isVigilanceAreaPartOfType } from '@features/VigilanceArea/useCases/filters/isVigilanceAreaPartOfType' import { useAppSelector } from '@hooks/useAppSelector' import { CustomSearch } from '@mtes-mct/monitor-ui' import { useMemo } from 'react' @@ -17,7 +18,7 @@ import { isVigilanceAreaPartOfVisibility } from '../useCases/filters/isVigilance export const useGetFilteredVigilanceAreasQuery = () => { const isSuperUser = useAppSelector(state => state.account.isSuperUser) - const { createdBy, period, seaFronts, specificPeriod, status, visibility } = useAppSelector( + const { createdBy, period, seaFronts, specificPeriod, status, type, visibility } = useAppSelector( state => state.vigilanceAreaFilters ) @@ -43,7 +44,8 @@ export const useGetFilteredVigilanceAreasQuery = () => { isVigilanceAreaPartOfStatus(vigilanceArea, isSuperUser ? status : [VigilanceArea.Status.PUBLISHED]) && isVigilanceAreaPartOfTag(vigilanceArea, filteredRegulatoryTags) && isVigilanceAreaPartOfTheme(vigilanceArea, filteredRegulatoryThemes) && - isVigilanceAreaPartOfVisibility(vigilanceArea, visibility) + isVigilanceAreaPartOfVisibility(vigilanceArea, visibility) && + isVigilanceAreaPartOfType(vigilanceArea, type) ), [ vigilanceAreas, @@ -53,13 +55,14 @@ export const useGetFilteredVigilanceAreasQuery = () => { status, filteredRegulatoryTags, filteredRegulatoryThemes, - visibility + visibility, + type ] ) const vigilanceAreasByPeriod = useMemo( - () => getFilterVigilanceAreasPerPeriod(tempVigilanceAreas, period, specificPeriod, isSuperUser), - [tempVigilanceAreas, period, specificPeriod, isSuperUser] + () => getFilterVigilanceAreasPerPeriod(tempVigilanceAreas, period, specificPeriod, type, isSuperUser), + [tempVigilanceAreas, period, specificPeriod, type, isSuperUser] ) const filteredVigilanceAreas = useMemo(() => { diff --git a/frontend/src/features/VigilanceArea/types.ts b/frontend/src/features/VigilanceArea/types.ts index 6d42ab9962..e5b86d0992 100644 --- a/frontend/src/features/VigilanceArea/types.ts +++ b/frontend/src/features/VigilanceArea/types.ts @@ -9,27 +9,19 @@ import type { GeoJSON } from 'domain/types/GeoJSON' export namespace VigilanceArea { export interface VigilanceArea { comments?: string - computedEndDate?: string createdAt?: string createdBy?: string - endDatePeriod?: string - endingCondition?: EndingCondition - endingOccurrenceDate?: string - endingOccurrencesNumber?: number - frequency?: Frequency geom?: GeoJSON.MultiPolygon id?: number images?: ImageApi[] - isArchived: boolean - isAtAllTimes: boolean isDraft: boolean linkedAMPs?: number[] linkedRegulatoryAreas?: number[] links?: Link[] name: string | undefined + periods?: VigilanceAreaPeriod[] seaFront: string | undefined sources?: VigilanceAreaSource[] - startDatePeriod?: string tags?: TagFromAPI[] themes?: ThemeFromAPI[] updatedAt?: string @@ -39,27 +31,19 @@ export namespace VigilanceArea { export interface VigilanceAreaFromApi { comments?: string - computedEndDate?: string createdAt?: string createdBy?: string - endDatePeriod?: string - endingCondition?: EndingCondition - endingOccurrenceDate?: string - endingOccurrencesNumber?: number - frequency: Frequency | undefined geom?: GeoJSON.MultiPolygon id: number images?: ImageApi[] - isArchived: boolean - isAtAllTimes: boolean isDraft: boolean linkedAMPs?: number[] linkedRegulatoryAreas?: number[] links?: Link[] name: string + periods?: VigilanceAreaPeriod[] seaFront: string | undefined sources?: VigilanceAreaSource[] - startDatePeriod?: string tags?: TagFromAPI[] themes?: ThemeFromAPI[] updatedAt?: string @@ -79,6 +63,19 @@ export namespace VigilanceArea { type: VigilanceAreaSourceType } + export interface VigilanceAreaPeriod { + computedEndDate?: string + endDatePeriod?: string + endingCondition?: EndingCondition + endingOccurrenceDate?: string + endingOccurrencesNumber?: number + frequency?: Frequency + id?: string + isAtAllTimes: boolean + isCritical?: boolean + startDatePeriod?: string + } + export enum Frequency { NONE = 'NONE', ALL_WEEKS = 'ALL_WEEKS', @@ -147,12 +144,17 @@ export namespace VigilanceArea { SPECIFIC_PERIOD = 'Période spécifique' } - export type VigilanceAreaFilterPeriodType = - | 'AT_THE_MOMENT' - | 'NEXT_THREE_MONTHS' - | 'CURRENT_QUARTER' - | 'CURRENT_YEAR' - | 'SPECIFIC_PERIOD' + export enum VigilanceAreaFilterType { + SIMPLE = 'SIMPLE', + CRITICAL = 'CRITICAL', + INFORMATIVE = 'INFORMATIVE' + } + + export enum VigilanceAreaFilterTypeLabel { + SIMPLE = 'Période de vigilance simple en cours', + CRITICAL = 'Période de vigilance critique en cours', + INFORMATIVE = 'Aucune période de vigilance en cours' + } export type VigilanceAreaProperties = Omit & { id: number @@ -162,8 +164,6 @@ export namespace VigilanceArea { export type VigilanceAreaLayer = VigilanceArea.VigilanceAreaFromApi & { bbox: number[] } - export type StatusType = 'DRAFT' | 'PUBLISHED' - export enum Status { DRAFT = 'DRAFT', PUBLISHED = 'PUBLISHED' @@ -179,10 +179,4 @@ export namespace VigilanceArea { OTHER = 'OTHER', INTERNAL = 'INTERNAL' } - - export enum VigilanceAreaSourceTypeLabel { - CONTROL_UNIT = 'Unité', - OTHER = 'Autre', - INTERNAL = 'Interne CACEM' - } } diff --git a/frontend/src/features/VigilanceArea/useCases/filters/isVigilanceAreaPartOfType.ts b/frontend/src/features/VigilanceArea/useCases/filters/isVigilanceAreaPartOfType.ts new file mode 100644 index 0000000000..983003a693 --- /dev/null +++ b/frontend/src/features/VigilanceArea/useCases/filters/isVigilanceAreaPartOfType.ts @@ -0,0 +1,32 @@ +import { isOutOfPeriod, isWithinPeriod } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' +import { VigilanceArea } from '@features/VigilanceArea/types' + +export function getFilterInformativeVigilanceArea( + typeFilter: VigilanceArea.VigilanceAreaFilterType[] | undefined, + vigilanceArea: VigilanceArea.VigilanceArea +) { + return ( + (typeFilter?.includes(VigilanceArea.VigilanceAreaFilterType.INFORMATIVE) && + (vigilanceArea.periods ?? []).length === 0) === true + ) +} + +export function isVigilanceAreaPartOfType( + vigilanceArea: VigilanceArea.VigilanceArea, + typeFilter?: VigilanceArea.VigilanceAreaFilterType[] +): boolean { + if (!typeFilter || typeFilter.length === 0) { + return true + } + + const filterSimpleVigilanceArea = + typeFilter.includes(VigilanceArea.VigilanceAreaFilterType.SIMPLE) && isWithinPeriod(vigilanceArea.periods, false) + + const filterInformativeVigilanceArea = + typeFilter.includes(VigilanceArea.VigilanceAreaFilterType.INFORMATIVE) && isOutOfPeriod(vigilanceArea.periods) + + const filterCriticalVigilanceArea = + typeFilter.includes(VigilanceArea.VigilanceAreaFilterType.CRITICAL) && isWithinPeriod(vigilanceArea.periods, true) + + return filterSimpleVigilanceArea || filterCriticalVigilanceArea || filterInformativeVigilanceArea +} diff --git a/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts b/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts index 0a43ce5bb0..2eb42d50c7 100644 --- a/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts +++ b/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts @@ -16,11 +16,15 @@ export const saveVigilanceArea = ? vigilanceAreasAPI.endpoints.createVigilanceArea : vigilanceAreasAPI.endpoints.updateVigilanceArea - const realEndDate = computeRealEndDate(values) - const computedEndDate = realEndDate ?? undefined + const periodsWithComputedEndDate: VigilanceArea.VigilanceAreaPeriod[] | undefined = values.periods?.map(period => { + const realEndDate = computeRealEndDate(period) + const computedEndDate = realEndDate ?? undefined + return { ...period, computedEndDate } + }) + const vigilanceAreaToSave = { ...values, periods: periodsWithComputedEndDate } try { - const response = await dispatch(vigilanceAreaEnpoint.initiate({ ...values, computedEndDate })) + const response = await dispatch(vigilanceAreaEnpoint.initiate(vigilanceAreaToSave)) if ('data' in response) { const vigilanceAreaResponse = response.data as VigilanceArea.VigilanceArea @@ -79,31 +83,28 @@ export const saveVigilanceArea = } } -const computeRealEndDate = (vigilanceArea: VigilanceArea.VigilanceArea): string | undefined => { - let currentOccurrence = customDayjs(vigilanceArea.startDatePeriod) +const computeRealEndDate = (period: VigilanceArea.VigilanceAreaPeriod): string | undefined => { + let currentOccurrence = customDayjs(period.startDatePeriod) - const endDate = vigilanceArea.endDatePeriod ? customDayjs(vigilanceArea.endDatePeriod) : undefined + const endDate = period.endDatePeriod ? customDayjs(period.endDatePeriod) : undefined const vigilanceAreaDurationInDays = - vigilanceArea.startDatePeriod && vigilanceArea.endDatePeriod - ? customDayjs(vigilanceArea.endDatePeriod).diff(vigilanceArea.startDatePeriod, 'days') + period.startDatePeriod && period.endDatePeriod + ? customDayjs(period.endDatePeriod).diff(period.startDatePeriod, 'days') : 0 - if (vigilanceArea.endingCondition === VigilanceArea.EndingCondition.NEVER) { + if (period.endingCondition === VigilanceArea.EndingCondition.NEVER) { return undefined } - if (vigilanceArea.endingCondition === VigilanceArea.EndingCondition.END_DATE && vigilanceArea.endingOccurrenceDate) { - const endingDate = customDayjs(vigilanceArea.endingOccurrenceDate) + if (period.endingCondition === VigilanceArea.EndingCondition.END_DATE && period.endingOccurrenceDate) { + const endingDate = customDayjs(period.endingOccurrenceDate) return endingDate.isAfter(endDate) ? endingDate.toISOString() : endDate?.toISOString() } - if ( - vigilanceArea.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && - vigilanceArea.endingOccurrencesNumber - ) { - for (let i = 1; i < vigilanceArea.endingOccurrencesNumber; i += 1) { - switch (vigilanceArea.frequency) { + if (period.endingCondition === VigilanceArea.EndingCondition.OCCURENCES_NUMBER && period.endingOccurrencesNumber) { + for (let i = 1; i < period.endingOccurrencesNumber; i += 1) { + switch (period.frequency) { case VigilanceArea.Frequency.ALL_WEEKS: currentOccurrence = currentOccurrence.add(7, 'days') break @@ -114,7 +115,7 @@ const computeRealEndDate = (vigilanceArea: VigilanceArea.VigilanceArea): string currentOccurrence = currentOccurrence.add(1, 'year') break case VigilanceArea.Frequency.NONE: - currentOccurrence = customDayjs(vigilanceArea.endDatePeriod) + currentOccurrence = customDayjs(period.endDatePeriod) break default: return undefined // No recurrence diff --git a/frontend/src/features/VigilanceArea/utils.ts b/frontend/src/features/VigilanceArea/utils.ts index d53f9a9b8c..a5213f1159 100644 --- a/frontend/src/features/VigilanceArea/utils.ts +++ b/frontend/src/features/VigilanceArea/utils.ts @@ -50,3 +50,24 @@ export const frequencyText = (frequency?: VigilanceArea.Frequency, capitalize = } const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1) + +export function computeVigilanceAreaPeriod( + period: VigilanceArea.VigilanceAreaPeriod | undefined, + withReccurenceText = true +) { + if (period?.isAtAllTimes) { + return 'En tout temps' + } + if (period?.startDatePeriod) { + return `${[ + `${period?.startDatePeriod ? `Du ${customDayjs(period?.startDatePeriod).utc().format('DD/MM/YYYY')}` : ''} + ${period?.endDatePeriod ? `au ${customDayjs(period?.endDatePeriod).utc().format('DD/MM/YYYY')}` : ''}`, + withReccurenceText ? frequencyText(period?.frequency, false) : '', + withReccurenceText ? endingOccurenceText(period?.endingCondition, period?.computedEndDate, false) : '' + ] + .filter(Boolean) + .join(', ')}` + } + + return '' +} diff --git a/frontend/src/features/layersSelector/myVigilanceAreas/MyVigilanceAreaLayerZone.tsx b/frontend/src/features/layersSelector/myVigilanceAreas/MyVigilanceAreaLayerZone.tsx index e57a95a2ea..4313f5ad77 100644 --- a/frontend/src/features/layersSelector/myVigilanceAreas/MyVigilanceAreaLayerZone.tsx +++ b/frontend/src/features/layersSelector/myVigilanceAreas/MyVigilanceAreaLayerZone.tsx @@ -1,5 +1,6 @@ import { useGetVigilanceAreasQuery } from '@api/vigilanceAreasAPI' import { StyledTransparentButton } from '@components/style' +import { isOutOfPeriod, isWithinPeriod } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' import { vigilanceAreaActions } from '@features/VigilanceArea/slice' import { useAppSelector } from '@hooks/useAppSelector' import { Accent, Icon, IconButton, OPENLAYERS_PROJECTION, Size, THEME, WSG84_PROJECTION } from '@mtes-mct/monitor-ui' @@ -76,7 +77,8 @@ export function MyVigilanceAreaLayerZone({ + computeVigilanceAreaPeriod(period) + ) + : [] - const isArchived = (item.properties as VigilanceArea.VigilanceAreaProperties)?.isArchived ?? false + const { periods } = item.properties as VigilanceArea.VigilanceAreaProperties const isIsolatedLayerFilled = isolatedLayer?.id === id && isolatedLayer?.isFilled @@ -220,7 +205,8 @@ export function OverlayContent({ items }: OverlayContentProps) { > - {items.length === 1 && isVigilanceArea && {vigilanceAreaPeriod}} + {items.length === 1 && + isVigilanceArea && + vigilanceAreaPeriod?.map(period => {period})} ) })} @@ -344,6 +332,7 @@ const Period = styled.span` const StyledIconButton = styled(IconButton)` padding: 6px; + > span { > svg { height: 18px; diff --git a/frontend/src/features/layersSelector/search/LayerFilters.tsx b/frontend/src/features/layersSelector/search/LayerFilters.tsx index e812288cb4..33b9f84f1a 100644 --- a/frontend/src/features/layersSelector/search/LayerFilters.tsx +++ b/frontend/src/features/layersSelector/search/LayerFilters.tsx @@ -8,6 +8,7 @@ import { INITIAL_STATE, vigilanceAreaFiltersActions } from '@features/VigilanceArea/components/VigilanceAreasList/Filters/slice' +import { VigilanceAreaTypeFilter } from '@features/VigilanceArea/components/VigilanceAreaTypeFilter' import { getIsLinkingAMPToVigilanceArea, getIsLinkingRegulatoryToVigilanceArea, @@ -169,6 +170,11 @@ export function LayerFilters() { onChange={updateDateRangeFilter} /> )} + + + + Ce champ est utilisé uniquement comme critère de recherche pour les zones de vigilance. + {(filteredRegulatoryTags.length > 0 || filteredAmpTypes?.length > 0 || diff --git a/frontend/src/features/layersSelector/search/ResultsList/VigilanceAreaLayer/index.tsx b/frontend/src/features/layersSelector/search/ResultsList/VigilanceAreaLayer/index.tsx index d7bdb1ebd7..91f7555841 100644 --- a/frontend/src/features/layersSelector/search/ResultsList/VigilanceAreaLayer/index.tsx +++ b/frontend/src/features/layersSelector/search/ResultsList/VigilanceAreaLayer/index.tsx @@ -1,4 +1,5 @@ import { StyledTransparentButton } from '@components/style' +import { isOutOfPeriod, isWithinPeriod } from '@features/VigilanceArea/components/VigilanceAreaForm/utils' import { vigilanceAreaActions } from '@features/VigilanceArea/slice' import { useTracking } from '@hooks/useTracking' import { Accent, Icon, IconButton, OPENLAYERS_PROJECTION, THEME, WSG84_PROJECTION } from '@mtes-mct/monitor-ui' @@ -97,7 +98,8 @@ export function VigilanceAreaLayer({ layer, searchedText }: RegulatoryLayerProps > + return ( + + ) case MonitorEnvLayers.LOCALIZED_AREAS: return default: @@ -46,11 +54,11 @@ export function LayerLegend({ } } -export const Rectangle = styled.div<{ $size: Size; $vectorLayerColor?: string }>` +export const Rectangle = styled.div<{ $border?: string; $size: Size; $vectorLayerColor?: string }>` width: ${p => (p.$size === Size.SMALL ? '14px' : '16px')}; height: ${p => (p.$size === Size.SMALL ? '14px' : '16px')}; background: ${p => p.$vectorLayerColor ?? p.theme.color.gainsboro}; - border: 1px solid ${p => p.theme.color.slateGray}; + border: ${p => p.$border ?? `1px solid ${p.theme.color.slateGray}`}; display: inline-block; flex-shrink: 0; ` diff --git a/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.test.ts b/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.test.ts index ac7ec6948f..f43e642816 100644 --- a/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.test.ts +++ b/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.test.ts @@ -6,163 +6,193 @@ import { getFilterVigilanceAreasPerPeriod } from './getFilteredVigilanceAreasPer describe('filterVigilanceAreas', () => { const todayMin2Days = { - computedEndDate: undefined, createdAt: undefined, - endDatePeriod: `${customDayjs().subtract(11, 'day').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.NEVER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_WEEKS, id: 1, - isArchived: false, isAtAllTimes: false, isDraft: false, name: 'todayMin2Days', + periods: [ + { + computedEndDate: undefined, + endDatePeriod: `${customDayjs().subtract(11, 'day').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.NEVER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_WEEKS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().subtract(12, 'day').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().subtract(12, 'day').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const today = { - computedEndDate: `${customDayjs().add(1, 'year').format('YYYY-MM-DD')} 23:59:59.99999`, createdAt: undefined, - endDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.END_DATE, - endingOccurrenceDate: `${customDayjs().add(1, 'year').format('YYYY-MM-DD')} 23:59:59.00000`, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_MONTHS, id: 2, - isArchived: false, isAtAllTimes: false, isDraft: false, name: 'Today', + periods: [ + { + computedEndDate: `${customDayjs().add(1, 'year').format('YYYY-MM-DD')} 23:59:59.99999`, + endDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.END_DATE, + endingOccurrenceDate: `${customDayjs().add(1, 'year').format('YYYY-MM-DD')} 23:59:59.00000`, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_MONTHS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const quarter = { - computedEndDate: `${customDayjs().endOf('quarter').add(3, 'weeks').format('YYYY-MM-DD')} 23:59:59.99999`, createdAt: undefined, - endDatePeriod: `${customDayjs().endOf('quarter').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: 3, - frequency: VigilanceArea.Frequency.ALL_WEEKS, id: 3, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'Quarter', + periods: [ + { + computedEndDate: `${customDayjs().endOf('quarter').add(3, 'weeks').format('YYYY-MM-DD')} 23:59:59.99999`, + endDatePeriod: `${customDayjs().endOf('quarter').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: 3, + frequency: VigilanceArea.Frequency.ALL_WEEKS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().startOf('quarter').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().startOf('quarter').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const outsideFilteredDate = { - computedEndDate: `${customDayjs().add(2, 'year').add(4, 'days').format('YYYY-MM-DD')} 23:59:59.99999`, createdAt: undefined, - endDatePeriod: `${customDayjs() - .add(1, 'year') - .add(4, 'months') - .add(7, 'days') - .format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: 36, - frequency: VigilanceArea.Frequency.ALL_WEEKS, id: 4, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'OutsideFilteredDate', + periods: [ + { + computedEndDate: `${customDayjs().add(2, 'year').add(4, 'days').format('YYYY-MM-DD')} 23:59:59.99999`, + endDatePeriod: `${customDayjs() + .add(1, 'year') + .add(4, 'months') + .add(7, 'days') + .format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.OCCURENCES_NUMBER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: 36, + frequency: VigilanceArea.Frequency.ALL_WEEKS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().add(1, 'year').add(4, 'months').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().add(1, 'year').add(4, 'months').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const year = { - computedEndDate: undefined, createdAt: undefined, - endDatePeriod: `${customDayjs().add(24, 'days').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.NEVER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_YEARS, id: 5, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'Year', + periods: [ + { + endDatePeriod: `${customDayjs().add(24, 'days').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.NEVER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_YEARS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().subtract(3, 'days').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().subtract(3, 'days').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const allYear = { - computedEndDate: undefined, createdAt: undefined, - endDatePeriod: `${customDayjs('12/31/2024').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.NEVER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_YEARS, + id: 6, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'allYear', + periods: [ + { + computedEndDate: undefined, + endDatePeriod: `${customDayjs('12/31/2024').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.NEVER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_YEARS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs('01/01/2024').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs('01/01/2024').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const infinite = { - computedEndDate: undefined, createdAt: undefined, - endDatePeriod: undefined, - endingCondition: undefined, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: undefined, id: 7, - isArchived: false, - isAtAllTimes: true, isDraft: false, name: 'Infinite', + periods: [ + { + computedEndDate: undefined, + endDatePeriod: undefined, + endingCondition: undefined, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined, + frequency: undefined, + isAtAllTimes: true, + startDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const last3Months = { - computedEndDate: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.99999`, createdAt: undefined, - endDatePeriod: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.END_DATE, - endingOccurrenceDate: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.00000`, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_MONTHS, id: 8, - isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'Last 3 months', + periods: [ + { + computedEndDate: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.99999`, + endDatePeriod: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.END_DATE, + endingOccurrenceDate: `${customDayjs().subtract(1, 'month').format('YYYY-MM-DD')} 23:59:59.00000`, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_MONTHS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().subtract(3, 'month').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().subtract(3, 'month').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } const last12Months = { - computedEndDate: `${customDayjs().subtract(10, 'months').format('YYYY-MM-DD')} 23:59:59.99999`, createdAt: undefined, - endDatePeriod: `${customDayjs().subtract(10, 'months').format('YYYY-MM-DD')} 23:59:59.99999`, - endingCondition: VigilanceArea.EndingCondition.END_DATE, - endingOccurrenceDate: `${customDayjs().subtract(11, 'months').format('YYYY-MM-DD')} 23:59:59.00000`, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_MONTHS, id: 9, - isArchived: false, isAtAllTimes: false, isDraft: false, name: 'Last 12 months', + periods: [ + { + computedEndDate: `${customDayjs().subtract(10, 'months').format('YYYY-MM-DD')} 23:59:59.99999`, + endDatePeriod: `${customDayjs().subtract(10, 'months').format('YYYY-MM-DD')} 23:59:59.99999`, + endingCondition: VigilanceArea.EndingCondition.END_DATE, + endingOccurrenceDate: `${customDayjs().subtract(11, 'months').format('YYYY-MM-DD')} 23:59:59.00000`, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_MONTHS, + isAtAllTimes: false, + startDatePeriod: `${customDayjs().subtract(12, 'months').format('YYYY-MM-DD')} 00:00:00.00000` + } + ], seaFront: 'MED', - startDatePeriod: `${customDayjs().subtract(12, 'months').format('YYYY-MM-DD')} 00:00:00.00000`, updatedAt: undefined } @@ -175,12 +205,12 @@ describe('filterVigilanceAreas', () => { it('filters areas within current quarter', () => { const result = getFilterVigilanceAreasPerPeriod(areas, VigilanceArea.VigilanceAreaFilterPeriod.CURRENT_QUARTER) - expect(result).toEqual([todayMin2Days, today, quarter, year, allYear, infinite, last3Months]) + expect(result).toEqual([todayMin2Days, today, quarter, year, allYear, infinite]) }) it('filters areas within current year', () => { const result = getFilterVigilanceAreasPerPeriod(areas, VigilanceArea.VigilanceAreaFilterPeriod.CURRENT_YEAR) - expect(result).toEqual([todayMin2Days, today, quarter, year, allYear, infinite, last3Months, last12Months]) + expect(result).toEqual([todayMin2Days, today, quarter, year, allYear, infinite]) }) it('filters areas within next three months', () => { @@ -199,20 +229,24 @@ describe('filterVigilanceAreas', () => { }) it('filters areas with vigilance area one complete year', () => { const vigilanceAreaOneCompleteYear = { - computedEndDate: undefined, createdAt: undefined, - endDatePeriod: '2024-12-31 23:59:59.99999', - endingCondition: VigilanceArea.EndingCondition.NEVER, - endingOccurrenceDate: undefined, - endingOccurrencesNumber: undefined, - frequency: VigilanceArea.Frequency.ALL_YEARS, id: 10, isArchived: false, - isAtAllTimes: false, isDraft: false, name: 'Today', + periods: [ + { + computedEndDate: undefined, + endDatePeriod: '2024-12-31 23:59:59.99999', + endingCondition: VigilanceArea.EndingCondition.NEVER, + endingOccurrenceDate: undefined, + endingOccurrencesNumber: undefined, + frequency: VigilanceArea.Frequency.ALL_YEARS, + isAtAllTimes: false, + startDatePeriod: '2024-01-01 00:00:00.00000' + } + ], seaFront: 'MED', - startDatePeriod: '2024-01-01 00:00:00.00000', updatedAt: undefined } const result = getFilterVigilanceAreasPerPeriod( diff --git a/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.ts b/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.ts index dc6f1791f5..c0a18e6328 100644 --- a/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.ts +++ b/frontend/src/features/layersSelector/utils/getFilteredVigilanceAreasPerPeriod.ts @@ -1,4 +1,5 @@ import { VigilanceArea } from '@features/VigilanceArea/types' +import { getFilterInformativeVigilanceArea } from '@features/VigilanceArea/useCases/filters/isVigilanceAreaPartOfType' import { customDayjs } from '@mtes-mct/monitor-ui' import type { Dayjs } from 'dayjs' @@ -98,6 +99,7 @@ export const getFilterVigilanceAreasPerPeriod = ( vigilanceAreas: (VigilanceArea.VigilanceAreaLayer | VigilanceArea.VigilanceAreaFromApi)[], periodFilter: VigilanceArea.VigilanceAreaFilterPeriod | undefined, vigilanceAreaSpecificPeriodFilter?: string[], + vigilanceAreaTypeFilter?: VigilanceArea.VigilanceAreaFilterType[], isSuperUser: boolean = true ): VigilanceArea.VigilanceAreaLayer[] => { const { endDate: endDateFilter, startDate: startDateFilter } = calculatePeriodBounds( @@ -110,48 +112,56 @@ export const getFilterVigilanceAreasPerPeriod = ( } return Object.values((vigilanceAreas as Array) ?? []).filter(vigilanceArea => { - if (!isSuperUser && (vigilanceArea.isDraft || vigilanceArea.visibility === VigilanceArea.Visibility.PRIVATE)) { - return false - } - if (vigilanceArea.isAtAllTimes) { - return true - } - - if (!vigilanceArea || !vigilanceArea.startDatePeriod || !vigilanceArea.endDatePeriod) { + if ( + !vigilanceArea || + (!isSuperUser && (vigilanceArea.isDraft || vigilanceArea.visibility === VigilanceArea.Visibility.PRIVATE)) + ) { return false } - - const startDate = customDayjs(vigilanceArea.startDatePeriod).utc() - const endDate = customDayjs(vigilanceArea.endDatePeriod).utc() - - // in case there is no end of recurrence (because endingCondition is NEVER) we set a default end date to the end of the period filter - const loopStopDate = vigilanceArea.computedEndDate - ? customDayjs(vigilanceArea.computedEndDate) - : customDayjs(endDate).add(5, 'year') - - if (vigilanceArea.frequency === VigilanceArea.Frequency.NONE) { - return isMatchForSingleOccurrence(startDate, endDate, startDateFilter, endDateFilter) - } - if ( - !!startDateFilter && - !!endDateFilter && - (startDateFilter?.isBetween(startDate, endDate) || endDateFilter.isBetween(startDate, endDate)) + getFilterInformativeVigilanceArea(vigilanceAreaTypeFilter, vigilanceArea) || + vigilanceArea.periods?.some(period => period.isAtAllTimes) ) { return true } - if (vigilanceArea.frequency) { - return isMatchForRecurringOccurrence( - startDate, - endDate, - startDateFilter, - endDateFilter, - vigilanceArea.frequency, - loopStopDate - ) + if (vigilanceArea.periods?.every(period => !period.startDatePeriod || !period.endDatePeriod)) { + return false } - return false + return vigilanceArea.periods?.some(period => { + const startDate = customDayjs(period.startDatePeriod).utc() + const endDate = customDayjs(period.endDatePeriod).utc() + + // in case there is no end of recurrence (because endingCondition is NEVER) we set a default end date to the end of the period filter + const loopStopDate = period.computedEndDate + ? customDayjs(period.computedEndDate) + : customDayjs(endDate).add(5, 'year') + + if (period.frequency === VigilanceArea.Frequency.NONE) { + return isMatchForSingleOccurrence(startDate, endDate, startDateFilter, endDateFilter) + } + + if ( + !!startDateFilter && + !!endDateFilter && + (startDateFilter?.isBetween(startDate, endDate) || endDateFilter.isBetween(startDate, endDate)) + ) { + return true + } + + if (period.frequency) { + return isMatchForRecurringOccurrence( + startDate, + endDate, + startDateFilter, + endDateFilter, + period.frequency, + loopStopDate + ) + } + + return false + }) }) }