From 3e51dd8e14dad090e641692de85230e0c5f2cec7 Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Fri, 6 Oct 2023 10:42:52 +0200 Subject: [PATCH 01/12] Add Brave in supported browsers --- frontend/src/utils/isBrowserSupported.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/isBrowserSupported.ts b/frontend/src/utils/isBrowserSupported.ts index f614a2326..41ff83859 100644 --- a/frontend/src/utils/isBrowserSupported.ts +++ b/frontend/src/utils/isBrowserSupported.ts @@ -1,12 +1,13 @@ import { browserName, browserVersion } from 'react-device-detect' import { toast } from 'react-toastify' +// TODO Share that in MUI. export function isBrowserSupported(): boolean { const browserVersionAsNumber = Number(browserVersion) switch (browserName) { - case 'Internet Explorer': - return false + case 'Brave': + return browserVersionAsNumber >= 112 case 'Edge': return browserVersionAsNumber >= 79 @@ -20,12 +21,15 @@ export function isBrowserSupported(): boolean { case 'Firefox': return browserVersionAsNumber >= 62 - case 'Safari': - return browserVersionAsNumber >= 12 + case 'Internet Explorer': + return false case 'Opera': return browserVersionAsNumber >= 56 + case 'Safari': + return browserVersionAsNumber >= 12 + default: toast.error(`Navigateur inconnu: "${browserName} v${browserVersion}"`) From 65a718727e4ba8f9a9e252fd639cdef4921dbcfa Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Fri, 6 Oct 2023 11:11:07 +0200 Subject: [PATCH 02/12] Remove ghost package-lock file --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c01a5f041..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "monitorenv", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 49d756afbc6e0cf3f8af780c01d4fc77ad6c3876 Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Mon, 9 Oct 2023 10:51:03 +0200 Subject: [PATCH 03/12] Add control unit contact & resource deletion --- .../controlUnit/DeleteControlUnitContact.kt | 11 ++ .../controlUnit/DeleteControlUnitResource.kt | 11 ++ .../ApiControlUnitContactsController.kt | 12 ++ .../ApiControlUnitResourcesController.kt | 12 ++ .../back_office/administration_form.spec.ts | 4 +- .../administration_list/row_actions.spec.ts | 6 +- .../e2e/back_office/control_unit_form.spec.ts | 4 +- .../control_unit_list/row_actions.spec.ts | 3 +- frontend/src/api/controlUnitContactsAPI.ts | 9 ++ frontend/src/api/controlUnitResourcesAPI.ts | 20 +++ frontend/src/components/ConfirmationModal.tsx | 87 +++++++++++ frontend/src/components/Dialog.tsx | 82 ++++++++++ .../src/domain/shared_slices/BackOffice.ts | 71 --------- frontend/src/domain/shared_slices/Global.ts | 3 +- .../shared_slices/{index.js => index.ts} | 24 +-- .../backOffice/handleModalConfirmation.ts | 35 ----- .../src/domain/use_cases/backOffice/types.ts | 6 - .../controlUnit/deleteControlUnit.ts | 30 ---- .../AdministrationForm}/constants.tsx | 6 +- .../components/AdministrationForm}/index.tsx | 12 +- .../components/AdministrationForm}/types.ts | 2 +- .../AdministrationTable}/FilterBar.tsx | 8 +- .../AdministrationTable}/TabMenu.tsx | 22 +-- .../AdministrationTable}/constants.tsx | 6 +- .../components/AdministrationTable}/index.tsx | 12 +- .../components/AdministrationTable}/slice.ts | 17 +-- .../components/AdministrationTable}/types.ts | 0 .../components/AdministrationTable}/utils.tsx | 31 ++-- .../useCases}/archiveAdministration.ts | 25 ++- .../useCases}/deleteAdministration.ts | 25 ++- .../BackOfficeConfirmationModal.tsx | 27 ++++ .../components/BackOfficeDialog.tsx | 22 +++ .../{TabBar => BackOfficeTabBar}/Tab.tsx | 0 .../{TabBar => BackOfficeTabBar}/index.tsx | 4 +- .../components/ConfirmationModal.tsx | 36 ----- .../features/BackOffice/components/Dialog.tsx | 26 ---- frontend/src/features/BackOffice/slice.ts | 50 ++++++ frontend/src/features/BackOffice/types.ts | 20 +++ .../useCases/handleModalConfirmation.ts | 39 +++++ .../components/BaseForm}/constants.ts | 0 .../components/BaseForm}/index.tsx | 14 +- .../components/BaseForm}/types.ts | 2 +- .../components/BaseForm}/utils.ts | 2 +- .../components/BaseTable}/FilterBar.tsx | 8 +- .../components/BaseTable}/constants.tsx | 6 +- .../components/BaseTable}/index.tsx | 10 +- .../components/BaseTable}/slice.ts | 14 +- .../components/BaseTable}/types.ts | 0 .../components/BaseTable}/utils.ts | 2 +- .../ControlUnitDialog}/AreaNote.tsx | 0 .../ControlUnitContactList/Form.tsx | 112 ++++++++++++++ .../FormikNameSelect.tsx | 49 ++++++ .../ControlUnitContactList/Item.tsx | 16 +- .../ControlUnitContactList/constants.ts | 25 +++ .../ControlUnitContactList/index.tsx | 42 +++-- .../ControlUnitContactList/types.ts | 8 + .../ControlUnitResourceList/Form.tsx | 144 ++++++++++++++++++ .../ControlUnitResourceList/Item.tsx | 71 +++++++++ .../ControlUnitResourceList/constants.ts | 8 + .../ControlUnitResourceList/index.tsx | 47 ++++-- .../ControlUnitResourceList/types.ts | 6 + .../components/ControlUnitDialog}/index.tsx | 16 +- .../ControlUnitDialog}/shared/Section.tsx | 1 - .../components/ControlUnitDialog}/slice.ts | 12 +- .../components/ControlUnitForm}/constants.tsx | 2 +- .../components/ControlUnitForm}/index.tsx | 16 +- .../components/ControlUnitForm}/types.ts | 2 +- .../ControlUnitListDialog}/FilterBar.tsx | 20 +-- .../ControlUnitListDialog}/Item.tsx | 10 +- .../ControlUnitListDialog}/index.tsx | 8 +- .../ControlUnitListDialog}/slice.ts | 14 +- .../ControlUnitListDialog}/types.ts | 0 .../ControlUnitListDialog}/utils.ts | 2 +- .../ControlUnitTable}/FilterBar.tsx | 12 +- .../components/ControlUnitTable}/TabMenu.tsx | 22 +-- .../ControlUnitTable}/constants.tsx | 6 +- .../components/ControlUnitTable}/index.tsx | 12 +- .../components/ControlUnitTable}/slice.ts | 17 +-- .../components/ControlUnitTable}/types.ts | 0 .../components/ControlUnitTable}/utils.tsx | 34 +++-- .../usesCases}/archiveControlUnit.ts | 12 +- .../usesCases/deleteControlUnit.ts | 45 ++++++ .../usesCases/deleteControlUnitContact.ts | 36 +++++ .../usesCases/deleteControlUnitResource.ts | 28 ++++ .../ControlUnitContactList/Form.tsx | 52 ------- .../ControlUnitContactList/constants.ts | 8 - .../ControlUnitContactList/types.ts | 4 - .../ControlUnitResourceList/Form.tsx | 73 --------- .../ControlUnitResourceList/Item.tsx | 59 ------- .../ControlUnitResourceList/types.ts | 4 - .../MainWindowConfirmationModal.tsx | 27 ++++ .../components/MainWindowDialog.tsx | 30 ++++ .../RightMenu}/ControlUnitListButton.tsx | 14 +- frontend/src/features/MainWindow/slice.ts | 56 +++++++ frontend/src/features/MainWindow/types.ts | 17 +++ .../useCases/handleModalConfirmation.ts | 29 ++++ frontend/src/pages/BackOfficePage.tsx | 40 +++-- frontend/src/pages/HomePage.tsx | 12 +- frontend/src/{store.ts => store/index.ts} | 6 +- 99 files changed, 1480 insertions(+), 684 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitContact.kt create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitResource.kt create mode 100644 frontend/src/components/ConfirmationModal.tsx create mode 100644 frontend/src/components/Dialog.tsx delete mode 100644 frontend/src/domain/shared_slices/BackOffice.ts rename frontend/src/domain/shared_slices/{index.js => index.ts} (78%) delete mode 100644 frontend/src/domain/use_cases/backOffice/handleModalConfirmation.ts delete mode 100644 frontend/src/domain/use_cases/backOffice/types.ts delete mode 100644 frontend/src/domain/use_cases/controlUnit/deleteControlUnit.ts rename frontend/src/features/{Administrations/BackOfficeAdministrationForm => Administration/components/AdministrationForm}/constants.tsx (87%) rename frontend/src/features/{Administrations/BackOfficeAdministrationForm => Administration/components/AdministrationForm}/index.tsx (92%) rename frontend/src/features/{Administrations/BackOfficeAdministrationForm => Administration/components/AdministrationForm}/types.ts (66%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/FilterBar.tsx (72%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/TabMenu.tsx (53%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/constants.tsx (85%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/index.tsx (84%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/slice.ts (58%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/types.ts (100%) rename frontend/src/features/{Administrations/BackOfficeAdministrationList => Administration/components/AdministrationTable}/utils.tsx (75%) rename frontend/src/{domain/use_cases/administration => features/Administration/useCases}/archiveAdministration.ts (50%) rename frontend/src/{domain/use_cases/administration => features/Administration/useCases}/deleteAdministration.ts (50%) create mode 100644 frontend/src/features/BackOffice/components/BackOfficeConfirmationModal.tsx create mode 100644 frontend/src/features/BackOffice/components/BackOfficeDialog.tsx rename frontend/src/features/BackOffice/components/{TabBar => BackOfficeTabBar}/Tab.tsx (100%) rename frontend/src/features/BackOffice/components/{TabBar => BackOfficeTabBar}/index.tsx (59%) delete mode 100644 frontend/src/features/BackOffice/components/ConfirmationModal.tsx delete mode 100644 frontend/src/features/BackOffice/components/Dialog.tsx create mode 100644 frontend/src/features/BackOffice/slice.ts create mode 100644 frontend/src/features/BackOffice/types.ts create mode 100644 frontend/src/features/BackOffice/useCases/handleModalConfirmation.ts rename frontend/src/features/{Bases/BackOfficeBaseForm => Base/components/BaseForm}/constants.ts (100%) rename frontend/src/features/{Bases/BackOfficeBaseForm => Base/components/BaseForm}/index.tsx (88%) rename frontend/src/features/{Bases/BackOfficeBaseForm => Base/components/BaseForm}/types.ts (68%) rename frontend/src/features/{Bases/BackOfficeBaseForm => Base/components/BaseForm}/utils.ts (64%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/FilterBar.tsx (73%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/constants.tsx (86%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/index.tsx (84%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/slice.ts (62%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/types.ts (100%) rename frontend/src/features/{Bases/BackOfficeBaseList => Base/components/BaseTable}/utils.ts (90%) rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/AreaNote.tsx (100%) create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/ControlUnitContactList/Item.tsx (72%) create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/constants.ts rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/ControlUnitContactList/index.tsx (56%) create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/types.ts create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Form.tsx create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/Item.tsx rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/ControlUnitResourceList/constants.ts (50%) rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/ControlUnitResourceList/index.tsx (53%) create mode 100644 frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitResourceList/types.ts rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/index.tsx (78%) rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/shared/Section.tsx (95%) rename frontend/src/features/{ControlUnits/MapControlUnitDialog => ControlUnit/components/ControlUnitDialog}/slice.ts (54%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitForm => ControlUnit/components/ControlUnitForm}/constants.tsx (94%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitForm => ControlUnit/components/ControlUnitForm}/index.tsx (91%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitForm => ControlUnit/components/ControlUnitForm}/types.ts (67%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/FilterBar.tsx (78%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/Item.tsx (82%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/index.tsx (86%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/slice.ts (59%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/types.ts (100%) rename frontend/src/features/{ControlUnits/MapControlUnitListDialog => ControlUnit/components/ControlUnitListDialog}/utils.ts (97%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/FilterBar.tsx (76%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/TabMenu.tsx (52%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/constants.tsx (88%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/index.tsx (84%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/slice.ts (61%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/types.ts (100%) rename frontend/src/features/{ControlUnits/BackOfficeControlUnitList => ControlUnit/components/ControlUnitTable}/utils.tsx (75%) rename frontend/src/{domain/use_cases/controlUnit => features/ControlUnit/usesCases}/archiveControlUnit.ts (58%) create mode 100644 frontend/src/features/ControlUnit/usesCases/deleteControlUnit.ts create mode 100644 frontend/src/features/ControlUnit/usesCases/deleteControlUnitContact.ts create mode 100644 frontend/src/features/ControlUnit/usesCases/deleteControlUnitResource.ts delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitContactList/Form.tsx delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitContactList/constants.ts delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitContactList/types.ts delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitResourceList/Form.tsx delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitResourceList/Item.tsx delete mode 100644 frontend/src/features/ControlUnits/MapControlUnitDialog/ControlUnitResourceList/types.ts create mode 100644 frontend/src/features/MainWindow/components/MainWindowConfirmationModal.tsx create mode 100644 frontend/src/features/MainWindow/components/MainWindowDialog.tsx rename frontend/src/features/{MapRightMenu => MainWindow/components/RightMenu}/ControlUnitListButton.tsx (60%) create mode 100644 frontend/src/features/MainWindow/slice.ts create mode 100644 frontend/src/features/MainWindow/types.ts create mode 100644 frontend/src/features/MainWindow/useCases/handleModalConfirmation.ts rename frontend/src/{store.ts => store/index.ts} (88%) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitContact.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitContact.kt new file mode 100644 index 000000000..29f39ab49 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitContact.kt @@ -0,0 +1,11 @@ +package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit + +import fr.gouv.cacem.monitorenv.config.UseCase +import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitContactRepository + +@UseCase +class DeleteControlUnitContact(private val controlUnitContactRepository: IControlUnitContactRepository) { + fun execute(controlUnitContactId: Int) { + return controlUnitContactRepository.deleteById(controlUnitContactId) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitResource.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitResource.kt new file mode 100644 index 000000000..02d5ea929 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/DeleteControlUnitResource.kt @@ -0,0 +1,11 @@ +package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit + +import fr.gouv.cacem.monitorenv.config.UseCase +import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitResourceRepository + +@UseCase +class DeleteControlUnitResource(private val controlUnitResourceRepository: IControlUnitResourceRepository) { + fun execute(controlUnitResourceId: Int) { + return controlUnitResourceRepository.deleteById(controlUnitResourceId) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitContactsController.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitContactsController.kt index 73b6f8eff..95c29fd16 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitContactsController.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitContactsController.kt @@ -1,6 +1,7 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.CreateOrUpdateControlUnitContact +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.DeleteControlUnitContact import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContactById import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitContacts import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitContactDataInput @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.* @Tag(name = "Control Unit Contacts") class ApiControlUnitContactsController( private val createOrUpdateControlUnitContact: CreateOrUpdateControlUnitContact, + private val deleteControlUnitContact: DeleteControlUnitContact, private val getControlUnitContacts: GetControlUnitContacts, private val getControlUnitContactById: GetControlUnitContactById, ) { @@ -32,6 +34,16 @@ class ApiControlUnitContactsController( return ControlUnitContactDataOutput.fromControlUnitContact(createdControlUnitContact) } + @DeleteMapping("/{controlUnitContactId}") + @Operation(summary = "Delete a control unit contact") + fun delete( + @PathParam("Control unit contact ID") + @PathVariable(name = "controlUnitContactId") + controlUnitContactId: Int, + ) { + deleteControlUnitContact.execute(controlUnitContactId) + } + @GetMapping("/{controlUnitContactId}") @Operation(summary = "Get a control unit contact by its ID") fun get( diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitResourcesController.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitResourcesController.kt index 277187fe1..aa23d52be 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitResourcesController.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiControlUnitResourcesController.kt @@ -1,6 +1,7 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.CreateOrUpdateControlUnitResource +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.DeleteControlUnitResource import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitResourceById import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.GetControlUnitResources import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateControlUnitResourceDataInput @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.* @Tag(name = "Control Unit Resources") class ApiControlUnitResourcesController( private val createOrUpdateControlUnitResource: CreateOrUpdateControlUnitResource, + private val deleteControlUnitResource: DeleteControlUnitResource, private val getControlUnitResources: GetControlUnitResources, private val getControlUnitResourceById: GetControlUnitResourceById, ) { @@ -33,6 +35,16 @@ class ApiControlUnitResourcesController( return ControlUnitResourceDataOutput.fromControlUnitResource(createdControlUnitResource) } + @DeleteMapping("/{controlUnitResourceId}") + @Operation(summary = "Delete a control unit resource") + fun delete( + @PathParam("Control unit resource ID") + @PathVariable(name = "controlUnitResourceId") + controlUnitResourceId: Int, + ) { + deleteControlUnitResource.execute(controlUnitResourceId) + } + @GetMapping("/{controlUnitResourceId}") @Operation(summary = "Get a control unit resource by its ID") fun get( diff --git a/frontend/cypress/e2e/back_office/administration_form.spec.ts b/frontend/cypress/e2e/back_office/administration_form.spec.ts index 8f8bed997..3e695193e 100644 --- a/frontend/cypress/e2e/back_office/administration_form.spec.ts +++ b/frontend/cypress/e2e/back_office/administration_form.spec.ts @@ -59,7 +59,7 @@ context('Back Office > Administration Form', () => { cy.intercept('POST', `/api/v1/administrations/2007/archive`).as('archiveAdministration') cy.getTableRowById(2007).clickButton('Archiver cette administration') - cy.clickButton('Confirmer') + cy.clickButton('Archiver') cy.wait('@archiveAdministration') @@ -73,7 +73,7 @@ context('Back Office > Administration Form', () => { cy.intercept('DELETE', `/api/v1/administrations/2007`).as('deleteAdministration') cy.getTableRowById(2007).clickButton('Supprimer cette administration') - cy.clickButton('Confirmer') + cy.clickButton('Supprimer') cy.wait('@deleteAdministration') diff --git a/frontend/cypress/e2e/back_office/administration_list/row_actions.spec.ts b/frontend/cypress/e2e/back_office/administration_list/row_actions.spec.ts index 91b5af156..07576b1f2 100644 --- a/frontend/cypress/e2e/back_office/administration_list/row_actions.spec.ts +++ b/frontend/cypress/e2e/back_office/administration_list/row_actions.spec.ts @@ -12,21 +12,23 @@ context('Back Office > Administration List > Row Actions', () => { cy.intercept('POST', `/api/v1/administrations/1005/archive`).as('archiveAdministration') cy.getTableRowById(1005).clickButton('Archiver cette administration') - cy.clickButton('Confirmer') + cy.clickButton('Archiver') cy.wait('@archiveAdministration') cy.get('.Component-Dialog').should('be.visible') + cy.contains('Archivage impossible').should('be.visible') }) it('Should show a dialog when trying to delete an administration linked to some control units', () => { cy.intercept('DELETE', `/api/v1/administrations/1005`).as('deleteAdministration') cy.getTableRowById(1005).clickButton('Supprimer cette administration') - cy.clickButton('Confirmer') + cy.clickButton('Supprimer') cy.wait('@deleteAdministration') cy.get('.Component-Dialog').should('be.visible') + cy.contains('Suppression impossible').should('be.visible') }) }) diff --git a/frontend/cypress/e2e/back_office/control_unit_form.spec.ts b/frontend/cypress/e2e/back_office/control_unit_form.spec.ts index 22648d3cd..ae06ede2e 100644 --- a/frontend/cypress/e2e/back_office/control_unit_form.spec.ts +++ b/frontend/cypress/e2e/back_office/control_unit_form.spec.ts @@ -77,7 +77,7 @@ context('Back Office > Control Unit Form', () => { cy.intercept('POST', `/api/v2/control_units/10033/archive`).as('archiveControlUnit') cy.getTableRowById(10033).clickButton('Archiver cette unité de contrôle') - cy.clickButton('Confirmer') + cy.clickButton('Archiver') cy.wait('@archiveControlUnit') @@ -91,7 +91,7 @@ context('Back Office > Control Unit Form', () => { cy.intercept('DELETE', `/api/v2/control_units/10033`).as('deleteControlUnit') cy.getTableRowById(10033).clickButton('Supprimer cette unité de contrôle') - cy.clickButton('Confirmer') + cy.clickButton('Supprimer') cy.wait('@deleteControlUnit') diff --git a/frontend/cypress/e2e/back_office/control_unit_list/row_actions.spec.ts b/frontend/cypress/e2e/back_office/control_unit_list/row_actions.spec.ts index b34ee7c52..af7b5463d 100644 --- a/frontend/cypress/e2e/back_office/control_unit_list/row_actions.spec.ts +++ b/frontend/cypress/e2e/back_office/control_unit_list/row_actions.spec.ts @@ -12,10 +12,11 @@ context('Back Office > Control Unit List > Row Actions', () => { cy.intercept('DELETE', `/api/v2/control_units/10000`).as('deleteControlUnit') cy.getTableRowById(10000).clickButton('Supprimer cette unité de contrôle') - cy.clickButton('Confirmer') + cy.clickButton('Supprimer') cy.wait('@deleteControlUnit') cy.get('.Component-Dialog').should('be.visible') + cy.contains('Suppression impossible').should('be.visible') }) }) diff --git a/frontend/src/api/controlUnitContactsAPI.ts b/frontend/src/api/controlUnitContactsAPI.ts index 23517f66e..00b2888de 100644 --- a/frontend/src/api/controlUnitContactsAPI.ts +++ b/frontend/src/api/controlUnitContactsAPI.ts @@ -17,6 +17,14 @@ export const controlUnitContactsAPI = monitorenvPublicApi.injectEndpoints({ }) }), + deleteControlUnitContact: builder.mutation({ + invalidatesTags: () => [{ type: 'ControlUnits' }], + query: controlUnitContactId => ({ + method: 'DELETE', + url: `/v1/control_unit_contacts/${controlUnitContactId}` + }) + }), + getControlUnitContact: builder.query({ providesTags: () => [{ type: 'ControlUnits' }], query: controlUnitContactId => `/v1/control_unit_contacts/${controlUnitContactId}`, @@ -42,6 +50,7 @@ export const controlUnitContactsAPI = monitorenvPublicApi.injectEndpoints({ export const { useCreateControlUnitContactMutation, + useDeleteControlUnitContactMutation, useGetControlUnitContactQuery, useGetControlUnitContactsQuery, useUpdateControlUnitContactMutation diff --git a/frontend/src/api/controlUnitResourcesAPI.ts b/frontend/src/api/controlUnitResourcesAPI.ts index 1cf6bb76f..767531a42 100644 --- a/frontend/src/api/controlUnitResourcesAPI.ts +++ b/frontend/src/api/controlUnitResourcesAPI.ts @@ -1,8 +1,12 @@ import { monitorenvPublicApi } from './api' +import { ApiErrorCode } from './types' import { FrontendApiError } from '../libs/FrontendApiError' +import { newUserError } from '../libs/UserError' import type { ControlUnit } from '../domain/entities/controlUnit' +const DELETE_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE = + "Ce moyen est rattaché à des missions. Veuillez l'en détacher avant de la supprimer." const GET_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE = "Nous n'avons pas pu récupérer cette resource." const GET_CONTROL_UNIT_RESOURCES_ERROR_MESSAGE = "Nous n'avons pas pu récupérer la liste des resources." @@ -17,6 +21,21 @@ export const controlUnitResourcesAPI = monitorenvPublicApi.injectEndpoints({ }) }), + deleteControlUnitResource: builder.mutation({ + invalidatesTags: () => [{ type: 'Bases' }, { type: 'ControlUnits' }], + query: controlUnitResourceId => ({ + method: 'DELETE', + url: `/v1/control_unit_resources/${controlUnitResourceId}` + }), + transformErrorResponse: response => { + if (response.data.type === ApiErrorCode.FOREIGN_KEY_CONSTRAINT) { + return newUserError(DELETE_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE) + } + + return new FrontendApiError(DELETE_CONTROL_UNIT_RESOURCE_ERROR_MESSAGE, response) + } + }), + getControlUnitResource: builder.query({ providesTags: () => [{ type: 'ControlUnits' }], query: controlUnitResourceId => `/v1/control_unit_resources/${controlUnitResourceId}`, @@ -42,6 +61,7 @@ export const controlUnitResourcesAPI = monitorenvPublicApi.injectEndpoints({ export const { useCreateControlUnitResourceMutation, + useDeleteControlUnitResourceMutation, useGetControlUnitResourceQuery, useGetControlUnitResourcesQuery, useUpdateControlUnitResourceMutation diff --git a/frontend/src/components/ConfirmationModal.tsx b/frontend/src/components/ConfirmationModal.tsx new file mode 100644 index 000000000..d2b89aa3f --- /dev/null +++ b/frontend/src/components/ConfirmationModal.tsx @@ -0,0 +1,87 @@ +import { Accent, Button, Dialog, Icon } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +import type { Promisable } from 'type-fest' + +export type ConfirmationModalProps = { + color?: string + confirmationButtonLabel: string + iconName?: keyof typeof Icon + message: string + onCancel: () => Promisable + onConfirm: () => Promisable + title: string +} +export function ConfirmationModal({ + color, + confirmationButtonLabel, + iconName, + message, + onCancel, + onConfirm, + title +}: ConfirmationModalProps) { + const SelectedIcon = iconName ? Icon[iconName] : null + + return ( + + {title} + + {SelectedIcon && ( + + + + )} + {message} + + + + + + + ) +} + +// TODO Allow direct `width` prop control in MUI. +// This is a mess. I wonder if we should add inner classes in MUI. +const StyledDialog = styled(Dialog)` + > div:last-child { + max-width: 440px; + min-width: 440px; + + /* Dialog.Body */ + > div:nth-child(2) { + padding-top: 24px; + } + + /* Dialog.Action */ + > div:last-child { + padding: 24px 0 32px; + + > .Element-Button { + width: 136px; + + :not(:first-child) { + margin-left: 8px; + } + } + } + } +` + +const Picto = styled.div` + display: flex; + justify-content: center; + margin-bottom: 8px; +` + +/* TODO Replace the `> p` forcing the `!important`. */ +const Message = styled.p<{ + $color?: string +}>` + ${p => p.$color && `color: ${p.$color} !important;`} + font-size: 16px; + font-weight: bold; +` diff --git a/frontend/src/components/Dialog.tsx b/frontend/src/components/Dialog.tsx new file mode 100644 index 000000000..c7188e5e1 --- /dev/null +++ b/frontend/src/components/Dialog.tsx @@ -0,0 +1,82 @@ +import { Button, Dialog as MuiDialog, Icon } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +import type { Promisable } from 'type-fest' + +export type DialogProps = { + color?: string + iconName?: keyof typeof Icon + message: string + onClose: () => Promisable + title: string + titleBackgroundColor?: string +} +export function Dialog({ color, iconName, message, onClose, title, titleBackgroundColor }: DialogProps) { + const SelectedIcon = iconName ? Icon[iconName] : null + + return ( + + {title} + + {SelectedIcon && ( + + + + )} + {message} + + + + + + ) +} + +// TODO Allow direct `width` prop control in MUI. +// This is a mess. I wonder if we should add inner classes in MUI. +const StyledDialog = styled(MuiDialog)<{ + $titleBackgroundColor: string | undefined +}>` + > div:last-child { + max-width: 440px; + min-width: 440px; + + /* Dialog.Title */ + > h4 { + ${p => p.$titleBackgroundColor && `background-color: ${p.$titleBackgroundColor};`} + } + + /* Dialog.Body */ + > div:nth-child(2) { + padding-top: 24px; + } + + /* Dialog.Action */ + > div:last-child { + padding: 24px 0 32px; + + > .Element-Button { + width: 136px; + + :not(:first-child) { + margin-left: 8px; + } + } + } + } +` + +const Picto = styled.div` + display: flex; + justify-content: center; + margin-bottom: 8px; +` + +/* TODO Replace the `> p` forcing the `!important`. */ +const Message = styled.p<{ + $color?: string +}>` + ${p => p.$color && `color: ${p.$color} !important;`} + font-size: 16px; + font-weight: bold; +` diff --git a/frontend/src/domain/shared_slices/BackOffice.ts b/frontend/src/domain/shared_slices/BackOffice.ts deleted file mode 100644 index 969c82775..000000000 --- a/frontend/src/domain/shared_slices/BackOffice.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { createSlice, type PayloadAction } from '@reduxjs/toolkit' - -import type { BackOfficeConfirmationModalActionType } from '../use_cases/backOffice/types' - -type BackOfficeState = { - confirmationModalActionId: number - confirmationModalActionType: BackOfficeConfirmationModalActionType | undefined - confirmationModalMessage: string | undefined - - dialogMessage: string | undefined - - isConfirmationModalOpen: boolean - isDialogOpen: boolean -} -const INITIAL_STATE: BackOfficeState = { - confirmationModalActionId: -1, - confirmationModalActionType: undefined, - confirmationModalMessage: undefined, - - dialogMessage: undefined, - - isConfirmationModalOpen: false, - isDialogOpen: false -} - -const backOfficeSlice = createSlice({ - initialState: INITIAL_STATE, - name: 'backOffice', - reducers: { - closeConfirmationModal(state) { - state.confirmationModalActionId = -1 - state.confirmationModalActionType = undefined - state.confirmationModalMessage = undefined - state.isConfirmationModalOpen = false - }, - - closeDialog(state) { - state.dialogMessage = undefined - state.isDialogOpen = false - }, - - openConfirmationModal( - state, - action: PayloadAction<{ - actionId: number - actionType: BackOfficeConfirmationModalActionType - message: string - }> - ) { - state.confirmationModalActionId = action.payload.actionId - state.confirmationModalActionType = action.payload.actionType - state.confirmationModalMessage = action.payload.message - state.isConfirmationModalOpen = true - }, - - openDialog( - state, - action: PayloadAction<{ - message: string - }> - ) { - state.dialogMessage = action.payload.message - state.isDialogOpen = true - } - } -}) - -export const { closeConfirmationModal, openConfirmationModal } = backOfficeSlice.actions - -export const backOfficeActions = backOfficeSlice.actions -export const backOfficeReducer = backOfficeSlice.reducer diff --git a/frontend/src/domain/shared_slices/Global.ts b/frontend/src/domain/shared_slices/Global.ts index 3ffc8d31d..ddab01d44 100644 --- a/frontend/src/domain/shared_slices/Global.ts +++ b/frontend/src/domain/shared_slices/Global.ts @@ -1,4 +1,4 @@ -// TODO It may be a good thing to either call this slice 'mainWindowSlice' (or something with "map"?) since it targets the main window. +// TODO This slice should disappear in favor of `features/MainWindow/slice.ts`. import { createSlice, type PayloadAction } from '@reduxjs/toolkit' @@ -95,6 +95,7 @@ const initialState: GlobalStateType = { // state entry for other children components whom visibility is already handled by parent components isLayersSidebarVisible: false, + // TODO Use `MainWindowDialog` or `MainWindowConfirmationModal`. isControlUnitDialogVisible: false, isControlUnitListDialogVisible: false, diff --git a/frontend/src/domain/shared_slices/index.js b/frontend/src/domain/shared_slices/index.ts similarity index 78% rename from frontend/src/domain/shared_slices/index.js rename to frontend/src/domain/shared_slices/index.ts index 5bc4c4b83..d3d960fb1 100644 --- a/frontend/src/domain/shared_slices/index.js +++ b/frontend/src/domain/shared_slices/index.ts @@ -3,7 +3,6 @@ import { combineReducers } from '@reduxjs/toolkit' import { administrativeSlicePersistedReducer } from './Administrative' -import { backOfficeReducer } from './BackOffice' import { drawReducer } from './Draw' import { globalReducer } from './Global' import { interestPointSlicePersistedReducer } from './InterestPoint' @@ -28,12 +27,14 @@ import { missionsAPI } from '../../api/missionsAPI' import { regulatoryLayersAPI } from '../../api/regulatoryLayersAPI' import { reportingsAPI } from '../../api/reportingsAPI' import { semaphoresAPI } from '../../api/semaphoresAPI' -import { backOfficeAdministrationListPersistedReducer } from '../../features/Administrations/BackOfficeAdministrationList/slice' -import { backOfficeBaseListPersistedReducer } from '../../features/Bases/BackOfficeBaseList/slice' -import { backOfficeControlUnitListPersistedReducer } from '../../features/ControlUnits/BackOfficeControlUnitList/slice' -import { mapControlUnitDialogReducer } from '../../features/ControlUnits/MapControlUnitDialog/slice' -import { mapControlUnitListDialogPersistedReducer } from '../../features/ControlUnits/MapControlUnitListDialog/slice' +import { administrationTablePersistedReducer } from '../../features/Administration/components/AdministrationTable/slice' +import { backOfficeReducer } from '../../features/BackOffice/slice' +import { baseTablePersistedReducer } from '../../features/Base/components/BaseTable/slice' +import { controlUnitDialogReducer } from '../../features/ControlUnit/components/ControlUnitDialog/slice' +import { controlUnitListDialogPersistedReducer } from '../../features/ControlUnit/components/ControlUnitListDialog/slice' +import { controlUnitTablePersistedReducer } from '../../features/ControlUnit/components/ControlUnitTable/slice' import { layerSearchSliceReducer } from '../../features/layersSelector/search/LayerSearch.slice' +import { mainWindowReducer } from '../../features/MainWindow/slice' import { sideWindowReducer } from '../../features/SideWindow/slice' // TODO Maybe add a specifc store for the backoffice? @@ -44,16 +45,17 @@ export const homeReducers = combineReducers({ administrative: administrativeSlicePersistedReducer, backOffice: backOfficeReducer, - backOfficeAdministrationList: backOfficeAdministrationListPersistedReducer, - backOfficeBaseList: backOfficeBaseListPersistedReducer, - backOfficeControlUnitList: backOfficeControlUnitListPersistedReducer, + backOfficeAdministrationList: administrationTablePersistedReducer, + backOfficeBaseList: baseTablePersistedReducer, + backOfficeControlUnitList: controlUnitTablePersistedReducer, draw: drawReducer, global: globalReducer, interestPoint: interestPointSlicePersistedReducer, layerSearch: layerSearchSliceReducer, + mainWindow: mainWindowReducer, map: mapSliceReducer, - mapControlUnitDialog: mapControlUnitDialogReducer, - mapControlUnitListDialog: mapControlUnitListDialogPersistedReducer, + mapControlUnitDialog: controlUnitDialogReducer, + mapControlUnitListDialog: controlUnitListDialogPersistedReducer, measurement: measurementSlicePersistedReducer, missionFilters: missionFiltersPersistedReducer, missionState: missionStateSliceReducer, diff --git a/frontend/src/domain/use_cases/backOffice/handleModalConfirmation.ts b/frontend/src/domain/use_cases/backOffice/handleModalConfirmation.ts deleted file mode 100644 index 7736badf6..000000000 --- a/frontend/src/domain/use_cases/backOffice/handleModalConfirmation.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BackOfficeConfirmationModalActionType } from './types' -import { closeConfirmationModal } from '../../shared_slices/BackOffice' -import { archiveAdministration } from '../administration/archiveAdministration' -import { deleteAdministration } from '../administration/deleteAdministration' -import { archiveControlUnit } from '../controlUnit/archiveControlUnit' -import { deleteControlUnit } from '../controlUnit/deleteControlUnit' - -import type { AppThunk } from '../../../store' - -export const handleModalConfirmation = (): AppThunk => async (dispatch, getState) => { - const { confirmationModalActionType } = getState().backOffice - - switch (confirmationModalActionType) { - case BackOfficeConfirmationModalActionType.ARCHIVE_ADMINISTRATION: - await dispatch(archiveAdministration()) - break - - case BackOfficeConfirmationModalActionType.ARCHIVE_CONTROL_UNIT: - await dispatch(archiveControlUnit()) - break - - case BackOfficeConfirmationModalActionType.DELETE_ADMINISTRATION: - await dispatch(deleteAdministration()) - break - - case BackOfficeConfirmationModalActionType.DELETE_CONTROL_UNIT: - await dispatch(deleteControlUnit()) - break - - default: - break - } - - dispatch(closeConfirmationModal()) -} diff --git a/frontend/src/domain/use_cases/backOffice/types.ts b/frontend/src/domain/use_cases/backOffice/types.ts deleted file mode 100644 index 13a2e6eed..000000000 --- a/frontend/src/domain/use_cases/backOffice/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum BackOfficeConfirmationModalActionType { - 'ARCHIVE_ADMINISTRATION' = 'ARCHIVE_ADMINISTRATION', - 'ARCHIVE_CONTROL_UNIT' = 'ARCHIVE_CONTROL_UNIT', - 'DELETE_ADMINISTRATION' = 'DELETE_ADMINISTRATION', - 'DELETE_CONTROL_UNIT' = 'DELETE_CONTROL_UNIT' -} diff --git a/frontend/src/domain/use_cases/controlUnit/deleteControlUnit.ts b/frontend/src/domain/use_cases/controlUnit/deleteControlUnit.ts deleted file mode 100644 index 2e75bad86..000000000 --- a/frontend/src/domain/use_cases/controlUnit/deleteControlUnit.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { logSoftError } from '@mtes-mct/monitor-ui' - -import { controlUnitsAPI } from '../../../api/controlUnitsAPI' -import { isUserError } from '../../../libs/UserError' -import { backOfficeActions } from '../../shared_slices/BackOffice' - -import type { AppThunk } from '../../../store' - -export const deleteControlUnit = (): AppThunk> => async (dispatch, getState) => { - const controlUnitId = getState().backOffice.confirmationModalActionId - - try { - const { error } = await dispatch(controlUnitsAPI.endpoints.deleteControlUnit.initiate(controlUnitId) as any) - if (error) { - throw error - } - } catch (err) { - if (isUserError(err)) { - dispatch(backOfficeActions.openDialog({ message: err.userMessage })) - - return - } - - logSoftError({ - message: `An error happened while deleting a control unit (ID=${controlUnitId}").`, - originalError: err, - userMessage: "Une erreur est survenue pendant la suppression de l'unité de contrôle." - }) - } -} diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationForm/constants.tsx b/frontend/src/features/Administration/components/AdministrationForm/constants.tsx similarity index 87% rename from frontend/src/features/Administrations/BackOfficeAdministrationForm/constants.tsx rename to frontend/src/features/Administration/components/AdministrationForm/constants.tsx index 19c846da0..76100c36d 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationForm/constants.tsx +++ b/frontend/src/features/Administration/components/AdministrationForm/constants.tsx @@ -1,11 +1,11 @@ import { Icon, Size } from '@mtes-mct/monitor-ui' import { object, string } from 'yup' -import { NavIconButton } from '../../../ui/NavIconButton' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { NavIconButton } from '../../../../ui/NavIconButton' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' import type { AdministrationFormValues } from './types' -import type { ControlUnit } from '../../../domain/entities/controlUnit' +import type { ControlUnit } from '../../../../domain/entities/controlUnit' import type { ColumnDef } from '@tanstack/react-table' export const ADMINISTRATION_FORM_SCHEMA = object({ diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationForm/index.tsx b/frontend/src/features/Administration/components/AdministrationForm/index.tsx similarity index 92% rename from frontend/src/features/Administrations/BackOfficeAdministrationForm/index.tsx rename to frontend/src/features/Administration/components/AdministrationForm/index.tsx index efc5f4014..32ede500d 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationForm/index.tsx +++ b/frontend/src/features/Administration/components/AdministrationForm/index.tsx @@ -6,15 +6,15 @@ import { useNavigate, useParams } from 'react-router' import styled from 'styled-components' import { ADMINISTRATION_FORM_SCHEMA, CONTROL_UNIT_TABLE_COLUMNS, INITIAL_ADMINISTRATION_FORM_VALUES } from './constants' -import { administrationsAPI, useGetAdministrationQuery } from '../../../api/administrationsAPI' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { FrontendError } from '../../../libs/FrontendError' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { administrationsAPI, useGetAdministrationQuery } from '../../../../api/administrationsAPI' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { FrontendError } from '../../../../libs/FrontendError' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' import type { AdministrationFormValues } from './types' -import type { Administration } from '../../../domain/entities/administration' +import type { Administration } from '../../../../domain/entities/administration' -export function BackOfficeAdministrationForm() { +export function AdministrationForm() { const { administrationId } = useParams() if (!administrationId) { throw new FrontendError('`administrationId` is undefined.') diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationForm/types.ts b/frontend/src/features/Administration/components/AdministrationForm/types.ts similarity index 66% rename from frontend/src/features/Administrations/BackOfficeAdministrationForm/types.ts rename to frontend/src/features/Administration/components/AdministrationForm/types.ts index 82195d762..9c30d25a1 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationForm/types.ts +++ b/frontend/src/features/Administration/components/AdministrationForm/types.ts @@ -1,4 +1,4 @@ -import type { Administration } from '../../../domain/entities/administration' +import type { Administration } from '../../../../domain/entities/administration' import type { UndefineExceptArrays } from '@mtes-mct/monitor-ui' export type AdministrationFormValues = UndefineExceptArrays diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/FilterBar.tsx b/frontend/src/features/Administration/components/AdministrationTable/FilterBar.tsx similarity index 72% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/FilterBar.tsx rename to frontend/src/features/Administration/components/AdministrationTable/FilterBar.tsx index 79c2ac714..2fe6e05cd 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/FilterBar.tsx +++ b/frontend/src/features/Administration/components/AdministrationTable/FilterBar.tsx @@ -2,9 +2,9 @@ import { Icon, TextInput } from '@mtes-mct/monitor-ui' import { useCallback } from 'react' import styled from 'styled-components' -import { backOfficeAdministrationListActions } from './slice' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' +import { administrationTableActions } from './slice' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../../hooks/useAppSelector' export function FilterBar() { const dispatch = useAppDispatch() @@ -12,7 +12,7 @@ export function FilterBar() { const updateQuery = useCallback( (nextValue: string | undefined) => { - dispatch(backOfficeAdministrationListActions.setFilter({ key: 'query', value: nextValue })) + dispatch(administrationTableActions.setFilter({ key: 'query', value: nextValue })) }, [dispatch] ) diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/TabMenu.tsx b/frontend/src/features/Administration/components/AdministrationTable/TabMenu.tsx similarity index 53% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/TabMenu.tsx rename to frontend/src/features/Administration/components/AdministrationTable/TabMenu.tsx index 9a8d146e9..2c77a406d 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/TabMenu.tsx +++ b/frontend/src/features/Administration/components/AdministrationTable/TabMenu.tsx @@ -1,30 +1,30 @@ -import { backOfficeAdministrationListActions } from './slice' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' -import { TabBar } from '../../BackOffice/components/TabBar' +import { administrationTableActions } from './slice' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../../hooks/useAppSelector' +import { BackOfficeTabBar } from '../../../BackOffice/components/BackOfficeTabBar' export function TabMenu() { const dispatch = useAppDispatch() const backOfficeAdministrationList = useAppSelector(store => store.backOfficeAdministrationList) const filterArchivedAdministrations = (isArchived: boolean) => { - dispatch(backOfficeAdministrationListActions.setFilter({ key: 'isArchived', value: isArchived })) + dispatch(administrationTableActions.setFilter({ key: 'isArchived', value: isArchived })) } return ( - - + filterArchivedAdministrations(false)} > Administrations actives - - + filterArchivedAdministrations(true)} > Administrations archivées - - + + ) } diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/constants.tsx b/frontend/src/features/Administration/components/AdministrationTable/constants.tsx similarity index 85% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/constants.tsx rename to frontend/src/features/Administration/components/AdministrationTable/constants.tsx index 7dfbd9cd6..53f73520c 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/constants.tsx +++ b/frontend/src/features/Administration/components/AdministrationTable/constants.tsx @@ -1,9 +1,9 @@ import { Icon, Size } from '@mtes-mct/monitor-ui' -import { NavIconButton } from '../../../ui/NavIconButton' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { NavIconButton } from '../../../../ui/NavIconButton' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' -import type { Administration } from '../../../domain/entities/administration' +import type { Administration } from '../../../../domain/entities/administration' import type { ColumnDef } from '@tanstack/react-table' export const ADMINISTRATION_TABLE_COLUMNS: Array> = [ diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/index.tsx b/frontend/src/features/Administration/components/AdministrationTable/index.tsx similarity index 84% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/index.tsx rename to frontend/src/features/Administration/components/AdministrationTable/index.tsx index 11dd14741..58b951e48 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/index.tsx +++ b/frontend/src/features/Administration/components/AdministrationTable/index.tsx @@ -5,13 +5,13 @@ import styled from 'styled-components' import { FilterBar } from './FilterBar' import { TabMenu } from './TabMenu' import { getAdministrationTableColumns, getFilters } from './utils' -import { useGetAdministrationsQuery } from '../../../api/administrationsAPI' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' -import { NavButton } from '../../../ui/NavButton' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { useGetAdministrationsQuery } from '../../../../api/administrationsAPI' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../../hooks/useAppSelector' +import { NavButton } from '../../../../ui/NavButton' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' -export function BackOfficeAdministrationList() { +export function AdministrationTable() { const backOfficeAdministrationList = useAppSelector(store => store.backOfficeAdministrationList) const dispatch = useAppDispatch() const { data: administrations } = useGetAdministrationsQuery() diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/slice.ts b/frontend/src/features/Administration/components/AdministrationTable/slice.ts similarity index 58% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/slice.ts rename to frontend/src/features/Administration/components/AdministrationTable/slice.ts index 734045163..532027f9c 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/slice.ts +++ b/frontend/src/features/Administration/components/AdministrationTable/slice.ts @@ -5,11 +5,11 @@ import storage from 'redux-persist/lib/storage' import type { FiltersState } from './types' -export type BackOfficeAdministrationListState = { +interface AdministrationTableState { filtersState: FiltersState } -const INITIAL_STATE: BackOfficeAdministrationListState = { +const INITIAL_STATE: AdministrationTableState = { filtersState: { isArchived: false, query: undefined @@ -17,13 +17,13 @@ const INITIAL_STATE: BackOfficeAdministrationListState = { } const persistConfig = { - key: 'backOfficeAdministrationList', + key: 'administrationList', storage } -const backOfficeAdministrationList = createSlice({ +const AdministrationTableSlice = createSlice({ initialState: INITIAL_STATE, - name: 'backOfficeAdministrationList', + name: 'administrationList', reducers: { setFilter( state, @@ -37,9 +37,6 @@ const backOfficeAdministrationList = createSlice({ } }) -export const backOfficeAdministrationListActions = backOfficeAdministrationList.actions +export const administrationTableActions = AdministrationTableSlice.actions -export const backOfficeAdministrationListPersistedReducer = persistReducer( - persistConfig, - backOfficeAdministrationList.reducer -) +export const administrationTablePersistedReducer = persistReducer(persistConfig, AdministrationTableSlice.reducer) diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/types.ts b/frontend/src/features/Administration/components/AdministrationTable/types.ts similarity index 100% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/types.ts rename to frontend/src/features/Administration/components/AdministrationTable/types.ts diff --git a/frontend/src/features/Administrations/BackOfficeAdministrationList/utils.tsx b/frontend/src/features/Administration/components/AdministrationTable/utils.tsx similarity index 75% rename from frontend/src/features/Administrations/BackOfficeAdministrationList/utils.tsx rename to frontend/src/features/Administration/components/AdministrationTable/utils.tsx index fac67768e..09c1b43d6 100644 --- a/frontend/src/features/Administrations/BackOfficeAdministrationList/utils.tsx +++ b/frontend/src/features/Administration/components/AdministrationTable/utils.tsx @@ -1,22 +1,29 @@ import { CustomSearch, IconButton, type Filter, Icon, Size } from '@mtes-mct/monitor-ui' import { ADMINISTRATION_TABLE_COLUMNS } from './constants' -import { openConfirmationModal } from '../../../domain/shared_slices/BackOffice' -import { BackOfficeConfirmationModalActionType } from '../../../domain/use_cases/backOffice/types' +import { backOfficeActions } from '../../../BackOffice/slice' +import { BackOfficeConfirmationModalActionType } from '../../../BackOffice/types' import type { FiltersState } from './types' -import type { Administration } from '../../../domain/entities/administration' -import type { AppDispatch } from '../../../store' +import type { Administration } from '../../../../domain/entities/administration' +import type { AppDispatch } from '../../../../store' import type { CellContext, ColumnDef } from '@tanstack/react-table' function archiveAdministration(info: CellContext, dispatch: AppDispatch) { const administration = info.getValue() dispatch( - openConfirmationModal({ - actionId: administration.id, + backOfficeActions.openConfirmationModal({ actionType: BackOfficeConfirmationModalActionType.ARCHIVE_ADMINISTRATION, - message: `Confirmez-vous l'archivage de l'administration "${administration.name}" ? Elle n'apparaîtra plus dans MonitorEnv, elle ne sera plus utilisée que pour les statistiques.` + entityId: administration.id, + modalProps: { + confirmationButtonLabel: 'Archiver', + message: [ + `Êtes-vous sûr de vouloir archiver l'administration "${administration.name}" ?`, + `Elle n'apparaîtra plus dans MonitorEnv, elle ne sera plus utilisée que pour les statistiques.` + ].join(' '), + title: `Archivage de l'administration` + } }) ) } @@ -25,10 +32,14 @@ function deleteAdministration(info: CellContext() dispatch( - openConfirmationModal({ - actionId: administration.id, + backOfficeActions.openConfirmationModal({ actionType: BackOfficeConfirmationModalActionType.DELETE_ADMINISTRATION, - message: `Confirmez-vous la suppression de l'administration "${administration.name}" ?` + entityId: administration.id, + modalProps: { + confirmationButtonLabel: 'Supprimer', + message: `Êtes-vous sûr de vouloir supprimer l'administration "${administration.name}" ?`, + title: `Suppression de l'administration` + } }) ) } diff --git a/frontend/src/domain/use_cases/administration/archiveAdministration.ts b/frontend/src/features/Administration/useCases/archiveAdministration.ts similarity index 50% rename from frontend/src/domain/use_cases/administration/archiveAdministration.ts rename to frontend/src/features/Administration/useCases/archiveAdministration.ts index 4ad571a0f..7f456b2ce 100644 --- a/frontend/src/domain/use_cases/administration/archiveAdministration.ts +++ b/frontend/src/features/Administration/useCases/archiveAdministration.ts @@ -1,30 +1,43 @@ -import { logSoftError } from '@mtes-mct/monitor-ui' +import { THEME, logSoftError } from '@mtes-mct/monitor-ui' import { administrationsAPI } from '../../../api/administrationsAPI' +import { FrontendError } from '../../../libs/FrontendError' import { isUserError } from '../../../libs/UserError' -import { backOfficeActions } from '../../shared_slices/BackOffice' +import { backOfficeActions } from '../../BackOffice/slice' import type { AppThunk } from '../../../store' export const archiveAdministration = (): AppThunk> => async (dispatch, getState) => { - const administrationId = getState().backOffice.confirmationModalActionId + const { confirmationModal } = getState().backOffice + if (!confirmationModal) { + throw new FrontendError('`confirmationModal` is indefined.') + } try { const { error } = await dispatch( - administrationsAPI.endpoints.archiveAdministration.initiate(administrationId) as any + administrationsAPI.endpoints.archiveAdministration.initiate(confirmationModal.entityId) as any ) if (error) { throw error } } catch (err) { if (isUserError(err)) { - dispatch(backOfficeActions.openDialog({ message: err.userMessage })) + dispatch( + backOfficeActions.openDialog({ + dialogProps: { + color: THEME.color.maximumRed, + message: err.userMessage, + title: `Archivage impossible`, + titleBackgroundColor: THEME.color.maximumRed + } + }) + ) return } logSoftError({ - message: `An error happened while archiving an administration (ID=${administrationId}").`, + message: `An error happened while archiving an administration (ID=${confirmationModal.entityId}").`, originalError: err, userMessage: "Une erreur est survenue pendant l'archivage de l'administration." }) diff --git a/frontend/src/domain/use_cases/administration/deleteAdministration.ts b/frontend/src/features/Administration/useCases/deleteAdministration.ts similarity index 50% rename from frontend/src/domain/use_cases/administration/deleteAdministration.ts rename to frontend/src/features/Administration/useCases/deleteAdministration.ts index 9198c015b..53a975676 100644 --- a/frontend/src/domain/use_cases/administration/deleteAdministration.ts +++ b/frontend/src/features/Administration/useCases/deleteAdministration.ts @@ -1,30 +1,43 @@ -import { logSoftError } from '@mtes-mct/monitor-ui' +import { THEME, logSoftError } from '@mtes-mct/monitor-ui' import { administrationsAPI } from '../../../api/administrationsAPI' +import { FrontendError } from '../../../libs/FrontendError' import { isUserError } from '../../../libs/UserError' -import { backOfficeActions } from '../../shared_slices/BackOffice' +import { backOfficeActions } from '../../BackOffice/slice' import type { AppThunk } from '../../../store' export const deleteAdministration = (): AppThunk> => async (dispatch, getState) => { - const administrationId = getState().backOffice.confirmationModalActionId + const { confirmationModal } = getState().backOffice + if (!confirmationModal) { + throw new FrontendError('`confirmationModal` is indefined.') + } try { const { error } = await dispatch( - administrationsAPI.endpoints.deleteAdministration.initiate(administrationId) as any + administrationsAPI.endpoints.deleteAdministration.initiate(confirmationModal.entityId) as any ) if (error) { throw error } } catch (err) { if (isUserError(err)) { - dispatch(backOfficeActions.openDialog({ message: err.userMessage })) + dispatch( + backOfficeActions.openDialog({ + dialogProps: { + color: THEME.color.maximumRed, + message: err.userMessage, + title: `Suppression impossible`, + titleBackgroundColor: THEME.color.maximumRed + } + }) + ) return } logSoftError({ - message: `An error happened while deleting an administration (ID=${administrationId}").`, + message: `An error happened while deleting an administration (ID=${confirmationModal.entityId}").`, originalError: err, userMessage: "Une erreur est survenue pendant la suppression de l'administration." }) diff --git a/frontend/src/features/BackOffice/components/BackOfficeConfirmationModal.tsx b/frontend/src/features/BackOffice/components/BackOfficeConfirmationModal.tsx new file mode 100644 index 000000000..c96365363 --- /dev/null +++ b/frontend/src/features/BackOffice/components/BackOfficeConfirmationModal.tsx @@ -0,0 +1,27 @@ +import { useCallback } from 'react' + +import { ConfirmationModal } from '../../../components/ConfirmationModal' +import { useAppDispatch } from '../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../hooks/useAppSelector' +import { FrontendError } from '../../../libs/FrontendError' +import { backOfficeActions } from '../slice' +import { handleModalConfirmation } from '../useCases/handleModalConfirmation' + +export function BackOfficeConfirmationModal() { + const dispatch = useAppDispatch() + const { confirmationModal } = useAppSelector(store => store.backOffice) + if (!confirmationModal) { + throw new FrontendError('`confirmationModal` is undefined.') + } + + const close = useCallback(() => { + dispatch(backOfficeActions.closeConfirmationModal()) + }, [dispatch]) + + const confirm = useCallback(() => { + dispatch(handleModalConfirmation()) + }, [dispatch]) + + // eslint-disable-next-line react/jsx-props-no-spreading + return +} diff --git a/frontend/src/features/BackOffice/components/BackOfficeDialog.tsx b/frontend/src/features/BackOffice/components/BackOfficeDialog.tsx new file mode 100644 index 000000000..851f03fb0 --- /dev/null +++ b/frontend/src/features/BackOffice/components/BackOfficeDialog.tsx @@ -0,0 +1,22 @@ +import { useCallback } from 'react' + +import { Dialog } from '../../../components/Dialog' +import { useAppDispatch } from '../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../hooks/useAppSelector' +import { FrontendError } from '../../../libs/FrontendError' +import { backOfficeActions } from '../slice' + +export function BackOfficeDialog() { + const dispatch = useAppDispatch() + const { dialog } = useAppSelector(store => store.backOffice) + if (!dialog) { + throw new FrontendError('`dialog` is undefined.') + } + + const close = useCallback(() => { + dispatch(backOfficeActions.closeDialog()) + }, [dispatch]) + + // eslint-disable-next-line react/jsx-props-no-spreading + return +} diff --git a/frontend/src/features/BackOffice/components/TabBar/Tab.tsx b/frontend/src/features/BackOffice/components/BackOfficeTabBar/Tab.tsx similarity index 100% rename from frontend/src/features/BackOffice/components/TabBar/Tab.tsx rename to frontend/src/features/BackOffice/components/BackOfficeTabBar/Tab.tsx diff --git a/frontend/src/features/BackOffice/components/TabBar/index.tsx b/frontend/src/features/BackOffice/components/BackOfficeTabBar/index.tsx similarity index 59% rename from frontend/src/features/BackOffice/components/TabBar/index.tsx rename to frontend/src/features/BackOffice/components/BackOfficeTabBar/index.tsx index 5acbbf14e..0661cc81f 100644 --- a/frontend/src/features/BackOffice/components/TabBar/index.tsx +++ b/frontend/src/features/BackOffice/components/BackOfficeTabBar/index.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components' import { Tab } from './Tab' -const BareTabBar = styled.div` +const BareBackOfficeTabBar = styled.div` display: flex; width: 100%; @@ -11,6 +11,6 @@ const BareTabBar = styled.div` } ` -export const TabBar = Object.assign(BareTabBar, { +export const BackOfficeTabBar = Object.assign(BareBackOfficeTabBar, { Tab }) diff --git a/frontend/src/features/BackOffice/components/ConfirmationModal.tsx b/frontend/src/features/BackOffice/components/ConfirmationModal.tsx deleted file mode 100644 index 6d501def7..000000000 --- a/frontend/src/features/BackOffice/components/ConfirmationModal.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Accent, Button, Dialog } from '@mtes-mct/monitor-ui' -import { useCallback } from 'react' - -import { backOfficeActions } from '../../../domain/shared_slices/BackOffice' -import { handleModalConfirmation } from '../../../domain/use_cases/backOffice/handleModalConfirmation' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' - -export function ConfirmationModal() { - const dispatch = useAppDispatch() - const backOffice = useAppSelector(store => store.backOffice) - - const cancel = useCallback(() => { - dispatch(backOfficeActions.closeConfirmationModal()) - }, [dispatch]) - - const confirm = useCallback(() => { - dispatch(handleModalConfirmation()) - }, [dispatch]) - - return ( - - -

{backOffice.confirmationModalMessage}

-
- - - - -
- ) -} diff --git a/frontend/src/features/BackOffice/components/Dialog.tsx b/frontend/src/features/BackOffice/components/Dialog.tsx deleted file mode 100644 index 6a6eb32f1..000000000 --- a/frontend/src/features/BackOffice/components/Dialog.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Button, Dialog as MonitorUiDialog } from '@mtes-mct/monitor-ui' -import { useCallback } from 'react' - -import { backOfficeActions } from '../../../domain/shared_slices/BackOffice' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' - -export function Dialog() { - const dispatch = useAppDispatch() - const backOffice = useAppSelector(store => store.backOffice) - - const close = useCallback(() => { - dispatch(backOfficeActions.closeDialog()) - }, [dispatch]) - - return ( - - -

{backOffice.dialogMessage}

-
- - - -
- ) -} diff --git a/frontend/src/features/BackOffice/slice.ts b/frontend/src/features/BackOffice/slice.ts new file mode 100644 index 000000000..941f9185a --- /dev/null +++ b/frontend/src/features/BackOffice/slice.ts @@ -0,0 +1,50 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit' + +import type { ConfirmationModalState, DialogState } from './types' + +interface BackOfficeState { + /** Shared Confirmation Modal */ + confirmationModal: ConfirmationModalState | undefined + + /** Shared Dialog */ + dialog: DialogState | undefined + + isConfirmationModalOpen: boolean + isDialogOpen: boolean +} +const INITIAL_STATE: BackOfficeState = { + confirmationModal: undefined, + dialog: undefined, + + isConfirmationModalOpen: false, + isDialogOpen: false +} + +const backOfficeSlice = createSlice({ + initialState: INITIAL_STATE, + name: 'backOffice', + reducers: { + closeConfirmationModal(state) { + state.confirmationModal = undefined + state.isConfirmationModalOpen = false + }, + + closeDialog(state) { + state.dialog = undefined + state.isDialogOpen = false + }, + + openConfirmationModal(state, action: PayloadAction) { + state.confirmationModal = action.payload + state.isConfirmationModalOpen = true + }, + + openDialog(state, action: PayloadAction) { + state.dialog = action.payload + state.isDialogOpen = true + } + } +}) + +export const backOfficeActions = backOfficeSlice.actions +export const backOfficeReducer = backOfficeSlice.reducer diff --git a/frontend/src/features/BackOffice/types.ts b/frontend/src/features/BackOffice/types.ts new file mode 100644 index 000000000..27e62bf87 --- /dev/null +++ b/frontend/src/features/BackOffice/types.ts @@ -0,0 +1,20 @@ +import type { ConfirmationModalProps } from '../../components/ConfirmationModal' +import type { DialogProps } from '../../components/Dialog' + +export type ConfirmationModalState = { + actionType: BackOfficeConfirmationModalActionType + /** ID of the targeted entity. */ + entityId: number + modalProps: Omit +} + +export type DialogState = { + dialogProps: Omit +} + +export enum BackOfficeConfirmationModalActionType { + 'ARCHIVE_ADMINISTRATION' = 'ARCHIVE_ADMINISTRATION', + 'ARCHIVE_CONTROL_UNIT' = 'ARCHIVE_CONTROL_UNIT', + 'DELETE_ADMINISTRATION' = 'DELETE_ADMINISTRATION', + 'DELETE_CONTROL_UNIT' = 'DELETE_CONTROL_UNIT' +} diff --git a/frontend/src/features/BackOffice/useCases/handleModalConfirmation.ts b/frontend/src/features/BackOffice/useCases/handleModalConfirmation.ts new file mode 100644 index 000000000..b5c7deea2 --- /dev/null +++ b/frontend/src/features/BackOffice/useCases/handleModalConfirmation.ts @@ -0,0 +1,39 @@ +import { FrontendError } from '../../../libs/FrontendError' +import { archiveAdministration } from '../../Administration/useCases/archiveAdministration' +import { deleteAdministration } from '../../Administration/useCases/deleteAdministration' +import { archiveControlUnit } from '../../ControlUnit/usesCases/archiveControlUnit' +import { deleteControlUnit } from '../../ControlUnit/usesCases/deleteControlUnit' +import { backOfficeActions } from '../slice' +import { BackOfficeConfirmationModalActionType } from '../types' + +import type { AppThunk } from '../../../store' + +export const handleModalConfirmation = (): AppThunk => async (dispatch, getState) => { + const { confirmationModal } = getState().backOffice + if (!confirmationModal) { + throw new FrontendError('`confirmationModal` is indefined.') + } + + switch (confirmationModal.actionType) { + case BackOfficeConfirmationModalActionType.ARCHIVE_ADMINISTRATION: + await dispatch(archiveAdministration()) + break + + case BackOfficeConfirmationModalActionType.ARCHIVE_CONTROL_UNIT: + await dispatch(archiveControlUnit()) + break + + case BackOfficeConfirmationModalActionType.DELETE_ADMINISTRATION: + await dispatch(deleteAdministration()) + break + + case BackOfficeConfirmationModalActionType.DELETE_CONTROL_UNIT: + await dispatch(deleteControlUnit()) + break + + default: + break + } + + dispatch(backOfficeActions.closeConfirmationModal()) +} diff --git a/frontend/src/features/Bases/BackOfficeBaseForm/constants.ts b/frontend/src/features/Base/components/BaseForm/constants.ts similarity index 100% rename from frontend/src/features/Bases/BackOfficeBaseForm/constants.ts rename to frontend/src/features/Base/components/BaseForm/constants.ts diff --git a/frontend/src/features/Bases/BackOfficeBaseForm/index.tsx b/frontend/src/features/Base/components/BaseForm/index.tsx similarity index 88% rename from frontend/src/features/Bases/BackOfficeBaseForm/index.tsx rename to frontend/src/features/Base/components/BaseForm/index.tsx index 7e76e8c59..02f7586dc 100644 --- a/frontend/src/features/Bases/BackOfficeBaseForm/index.tsx +++ b/frontend/src/features/Base/components/BaseForm/index.tsx @@ -7,16 +7,16 @@ import styled from 'styled-components' import { INITIAL_BASE_FORM_VALUES, BASE_FORM_SCHEMA } from './constants' import { isBase } from './utils' -import { basesAPI, useGetBaseQuery } from '../../../api/basesAPI' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { FrontendError } from '../../../libs/FrontendError' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' -import { CONTROL_UNIT_RESOURCE_TABLE_COLUMNS } from '../../ControlUnits/BackOfficeControlUnitForm/constants' +import { basesAPI, useGetBaseQuery } from '../../../../api/basesAPI' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { FrontendError } from '../../../../libs/FrontendError' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' +import { CONTROL_UNIT_RESOURCE_TABLE_COLUMNS } from '../../../ControlUnit/components/ControlUnitForm/constants' import type { BaseFormValues } from './types' -import type { Base } from '../../../domain/entities/base' +import type { Base } from '../../../../domain/entities/base' -export function BackOfficeBaseForm() { +export function BaseForm() { const { baseId } = useParams() if (!baseId) { throw new FrontendError('`baseId` is undefined.') diff --git a/frontend/src/features/Bases/BackOfficeBaseForm/types.ts b/frontend/src/features/Base/components/BaseForm/types.ts similarity index 68% rename from frontend/src/features/Bases/BackOfficeBaseForm/types.ts rename to frontend/src/features/Base/components/BaseForm/types.ts index 75c3086c3..8b8ebb239 100644 --- a/frontend/src/features/Bases/BackOfficeBaseForm/types.ts +++ b/frontend/src/features/Base/components/BaseForm/types.ts @@ -1,4 +1,4 @@ -import type { Base } from '../../../domain/entities/base' +import type { Base } from '../../../../domain/entities/base' import type { UndefineExceptArrays } from '@mtes-mct/monitor-ui' export type BaseFormValues = UndefineExceptArrays diff --git a/frontend/src/features/Bases/BackOfficeBaseForm/utils.ts b/frontend/src/features/Base/components/BaseForm/utils.ts similarity index 64% rename from frontend/src/features/Bases/BackOfficeBaseForm/utils.ts rename to frontend/src/features/Base/components/BaseForm/utils.ts index 4367f4fa7..2c9314b2c 100644 --- a/frontend/src/features/Bases/BackOfficeBaseForm/utils.ts +++ b/frontend/src/features/Base/components/BaseForm/utils.ts @@ -1,4 +1,4 @@ -import type { Base } from '../../../domain/entities/base' +import type { Base } from '../../../../domain/entities/base' export function isBase(baseData: Base.BaseData): baseData is Base.Base { return baseData.id !== undefined diff --git a/frontend/src/features/Bases/BackOfficeBaseList/FilterBar.tsx b/frontend/src/features/Base/components/BaseTable/FilterBar.tsx similarity index 73% rename from frontend/src/features/Bases/BackOfficeBaseList/FilterBar.tsx rename to frontend/src/features/Base/components/BaseTable/FilterBar.tsx index e5cbe189e..cc6679cd2 100644 --- a/frontend/src/features/Bases/BackOfficeBaseList/FilterBar.tsx +++ b/frontend/src/features/Base/components/BaseTable/FilterBar.tsx @@ -2,9 +2,9 @@ import { Icon, TextInput } from '@mtes-mct/monitor-ui' import { useCallback } from 'react' import styled from 'styled-components' -import { backOfficeBaseListActions } from './slice' -import { useAppDispatch } from '../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../hooks/useAppSelector' +import { baseTableActions } from './slice' +import { useAppDispatch } from '../../../../hooks/useAppDispatch' +import { useAppSelector } from '../../../../hooks/useAppSelector' export function FilterBar() { const dispatch = useAppDispatch() @@ -12,7 +12,7 @@ export function FilterBar() { const updateQuery = useCallback( (nextValue: string | undefined) => { - dispatch(backOfficeBaseListActions.setFilter({ key: 'query', value: nextValue })) + dispatch(baseTableActions.setFilter({ key: 'query', value: nextValue })) }, [dispatch] ) diff --git a/frontend/src/features/Bases/BackOfficeBaseList/constants.tsx b/frontend/src/features/Base/components/BaseTable/constants.tsx similarity index 86% rename from frontend/src/features/Bases/BackOfficeBaseList/constants.tsx rename to frontend/src/features/Base/components/BaseTable/constants.tsx index d58a1b3f5..ae7cc862f 100644 --- a/frontend/src/features/Bases/BackOfficeBaseList/constants.tsx +++ b/frontend/src/features/Base/components/BaseTable/constants.tsx @@ -1,9 +1,9 @@ import { Icon, Size } from '@mtes-mct/monitor-ui' -import { NavIconButton } from '../../../ui/NavIconButton' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { NavIconButton } from '../../../../ui/NavIconButton' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' -import type { Base } from '../../../domain/entities/base' +import type { Base } from '../../../../domain/entities/base' import type { ColumnDef } from '@tanstack/react-table' export const BASE_TABLE_COLUMNS: Array> = [ diff --git a/frontend/src/features/Bases/BackOfficeBaseList/index.tsx b/frontend/src/features/Base/components/BaseTable/index.tsx similarity index 84% rename from frontend/src/features/Bases/BackOfficeBaseList/index.tsx rename to frontend/src/features/Base/components/BaseTable/index.tsx index 2c23943f0..c8420fed1 100644 --- a/frontend/src/features/Bases/BackOfficeBaseList/index.tsx +++ b/frontend/src/features/Base/components/BaseTable/index.tsx @@ -5,12 +5,12 @@ import styled from 'styled-components' import { BASE_TABLE_COLUMNS } from './constants' import { FilterBar } from './FilterBar' import { getFilters } from './utils' -import { useGetBasesQuery } from '../../../api/basesAPI' -import { useAppSelector } from '../../../hooks/useAppSelector' -import { NavButton } from '../../../ui/NavButton' -import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../BackOfficeMenu/constants' +import { useGetBasesQuery } from '../../../../api/basesAPI' +import { useAppSelector } from '../../../../hooks/useAppSelector' +import { NavButton } from '../../../../ui/NavButton' +import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants' -export function BackOfficeBaseList() { +export function BaseTable() { const backOfficeBaseList = useAppSelector(store => store.backOfficeBaseList) const { data: bases } = useGetBasesQuery() diff --git a/frontend/src/features/Bases/BackOfficeBaseList/slice.ts b/frontend/src/features/Base/components/BaseTable/slice.ts similarity index 62% rename from frontend/src/features/Bases/BackOfficeBaseList/slice.ts rename to frontend/src/features/Base/components/BaseTable/slice.ts index 5bb7d556f..a858b2535 100644 --- a/frontend/src/features/Bases/BackOfficeBaseList/slice.ts +++ b/frontend/src/features/Base/components/BaseTable/slice.ts @@ -5,22 +5,22 @@ import storage from 'redux-persist/lib/storage' import type { FiltersState } from './types' -export type BackOfficeBaseListState = { +interface BaseTableState { filtersState: FiltersState } -const INITIAL_STATE: BackOfficeBaseListState = { +const INITIAL_STATE: BaseTableState = { filtersState: {} } const persistConfig = { - key: 'backOfficeBaseList', + key: 'baseTable', storage } -const backOfficeBaseList = createSlice({ +const baseTableSlice = createSlice({ initialState: INITIAL_STATE, - name: 'backOfficeBaseList', + name: 'baseTable', reducers: { setFilter( state, @@ -34,6 +34,6 @@ const backOfficeBaseList = createSlice({ } }) -export const backOfficeBaseListActions = backOfficeBaseList.actions +export const baseTableActions = baseTableSlice.actions -export const backOfficeBaseListPersistedReducer = persistReducer(persistConfig, backOfficeBaseList.reducer) +export const baseTablePersistedReducer = persistReducer(persistConfig, baseTableSlice.reducer) diff --git a/frontend/src/features/Bases/BackOfficeBaseList/types.ts b/frontend/src/features/Base/components/BaseTable/types.ts similarity index 100% rename from frontend/src/features/Bases/BackOfficeBaseList/types.ts rename to frontend/src/features/Base/components/BaseTable/types.ts diff --git a/frontend/src/features/Bases/BackOfficeBaseList/utils.ts b/frontend/src/features/Base/components/BaseTable/utils.ts similarity index 90% rename from frontend/src/features/Bases/BackOfficeBaseList/utils.ts rename to frontend/src/features/Base/components/BaseTable/utils.ts index 0f2dac9d1..43ff6533d 100644 --- a/frontend/src/features/Bases/BackOfficeBaseList/utils.ts +++ b/frontend/src/features/Base/components/BaseTable/utils.ts @@ -1,7 +1,7 @@ import { CustomSearch, type Filter } from '@mtes-mct/monitor-ui' import type { FiltersState } from './types' -import type { Base } from '../../../domain/entities/base' +import type { Base } from '../../../../domain/entities/base' export function getFilters(data: Base.Base[], filtersState: FiltersState): Filter[] { const customSearch = new CustomSearch(data, ['name'], { diff --git a/frontend/src/features/ControlUnits/MapControlUnitDialog/AreaNote.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx similarity index 100% rename from frontend/src/features/ControlUnits/MapControlUnitDialog/AreaNote.tsx rename to frontend/src/features/ControlUnit/components/ControlUnitDialog/AreaNote.tsx diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx new file mode 100644 index 000000000..69080f9d4 --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/Form.tsx @@ -0,0 +1,112 @@ +import { Accent, Button, FormikTextInput, Icon, IconButton, THEME, useKey } from '@mtes-mct/monitor-ui' +import { Formik } from 'formik' +import { useCallback } from 'react' +import styled from 'styled-components' + +import { CONTROL_UNIT_CONTACT_FORM_SCHEMA } from './constants' +import { FormikNameSelect } from './FormikNameSelect' +import { useAppDispatch } from '../../../../../hooks/useAppDispatch' +import { mainWindowActions } from '../../../../MainWindow/slice' +import { MainWindowConfirmationModalActionType } from '../../../../MainWindow/types' + +import type { ControlUnitContactFormValues } from './types' + +export type FormProps = { + initialValues: ControlUnitContactFormValues + isNew: boolean + onCancel: () => void + onSubmit: (controlUnitContactFormValues: ControlUnitContactFormValues) => void +} +export function Form({ initialValues, isNew, onCancel, onSubmit }: FormProps) { + const dispatch = useAppDispatch() + const key = useKey([initialValues]) + + const askForDeletionConfirmation = useCallback(async () => { + if (!initialValues.id) { + return + } + + dispatch( + mainWindowActions.openConfirmationModal({ + actionType: MainWindowConfirmationModalActionType.DELETE_CONTROL_UNIT_CONTACT, + entityId: initialValues.id, + modalProps: { + color: THEME.color.maximumRed, + confirmationButtonLabel: 'Supprimer', + iconName: 'Delete', + message: `Êtes-vous sûr de vouloir supprimer ce contact ?`, + title: `Suppression du contact` + } + }) + ) + }, [initialValues.id, dispatch]) + + return ( + + {({ handleSubmit }) => ( + <> + Ajouter un contact + + + + + + +
+ + +
+ {!isNew && ( + + )} +
+
+ + )} +
+ ) +} + +const Title = styled.p` + background-color: ${p => p.theme.color.gainsboro}; + margin: 16px 0 2px; + padding: 8px 16px; + /* TODO This should be the default height everywhere to have a consistent and exact height of 18px. */ + /* Monitor UI provides that value: https://github.com/MTES-MCT/monitor-ui/blob/main/src/GlobalStyle.ts#L76. */ + line-height: 1.3846; +` + +const StyledForm = styled.form` + background-color: ${p => p.theme.color.gainsboro}; + padding: 16px; + + > div:not(:first-child) { + margin-top: 16px; + } +` + +const ActionBar = styled.div` + display: flex; + justify-content: space-between; + + > div:first-child { + > .Element-Button:last-child { + margin-left: 8px; + } + } +` diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx new file mode 100644 index 000000000..bbbe18bca --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/ControlUnitContactList/FormikNameSelect.tsx @@ -0,0 +1,49 @@ +import { FormikTextInput, Select, getOptionsFromLabelledEnum } from '@mtes-mct/monitor-ui' +import { useField } from 'formik' +import { useCallback, useState } from 'react' + +import { ControlUnit } from '../../../../../domain/entities/controlUnit' + +export function FormikNameSelect() { + const [isCustomName, setIsCustomName] = useState(false) + + const [field, meta, helpers] = useField('name') + + const namesAsOptions = [ + ...getOptionsFromLabelledEnum(ControlUnit.ControlUnitContactName), + { + label: 'Ajouter un nom personnalisé', + value: 'SWITCH_TO_CUSTOM_NAME' + } + ] + + const handleChange = useCallback( + (nextName: string | undefined) => { + if (nextName === 'SWITCH_TO_CUSTOM_NAME') { + setIsCustomName(true) + + return + } + + helpers.setValue(nextName) + }, + + // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering + // eslint-disable-next-line react-hooks/exhaustive-deps + [setIsCustomName] + ) + + return isCustomName ? ( + + ) : ( + any } -export function ControlUnitContactList({ controlUnit }: ControlUnitContactListProps) { +export function ControlUnitContactList({ controlUnit, onSubmit }: ControlUnitContactListProps) { const [createControlUnitContact] = useCreateControlUnitContactMutation() const [updateControlUnitContact] = useUpdateControlUnitContactMutation() @@ -59,7 +59,12 @@ export function ControlUnitContactList({ controlUnit }: ControlUnitContactListPr
Contacts - + {controlUnitContacts.map(controlUnitContact => ( { dispatch( @@ -41,6 +42,17 @@ export function ControlUnitDialog() { ) } + if (!controlUnit) { + return ( + + + Chargement en cours... + + + + ) + } + return ( @@ -51,9 +63,9 @@ export function ControlUnitDialog() { - + - + diff --git a/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx b/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx new file mode 100644 index 000000000..d77573028 --- /dev/null +++ b/frontend/src/features/ControlUnit/components/ControlUnitDialog/shared/TextareaForm.tsx @@ -0,0 +1,91 @@ +import { Accent, Button, Label, Textarea, type TextareaProps } from '@mtes-mct/monitor-ui' +import { useCallback, useState, type FormEvent, type ChangeEvent } from 'react' +import styled from 'styled-components' + +import type { ControlUnit } from '../../../../../domain/entities/controlUnit' +import type { Promisable } from 'type-fest' + +type TextareaFormProps = Omit & { + controlUnit: ControlUnit.ControlUnit + name: 'areaNote' | 'termsNote' + onSubmit: (nextControlUnit: ControlUnit.ControlUnit) => Promisable +} +export function TextareaForm({ controlUnit, isLabelHidden, label, name, onSubmit, ...props }: TextareaFormProps) { + const [isEditing, setIsEditing] = useState(false) + const [value, setValue] = useState(controlUnit[name]) + + const moveCursorToEnd = useCallback((event: ChangeEvent) => { + event.target.setSelectionRange(event.target.value.length, event.target.value.length) + }, []) + + const updateControlUnit = (event: FormEvent) => { + event.preventDefault() + + const nextControlUnit = { + ...controlUnit, + [name]: value + } + + onSubmit(nextControlUnit) + + setIsEditing(false) + } + + const toggleIsEditing = useCallback(() => { + setIsEditing(!isEditing) + }, [isEditing]) + + if (isEditing) { + return ( +
+