From 5740a5f7fca2eefc47d038adc7040ebecd312b8d Mon Sep 17 00:00:00 2001 From: Dustin Kroger Date: Tue, 14 Oct 2025 20:24:21 +0200 Subject: [PATCH] feat(communication): add settings listing page ref: #MANAGER-18774 Signed-off-by: Dustin Kroger --- .../translations/settings/Messages_fr_FR.json | 20 ++ .../routingStatus/RoutingStatus.component.tsx | 14 ++ .../src/data/api/notification.ts | 1 + .../communication/src/data/api/routing.ts | 8 + .../useNotificationRouting.tsx | 16 ++ .../apps/communication/src/data/index.ts | 2 + .../communication/src/data/types/index.ts | 1 + .../src/data/types/routing.type.ts | 26 +++ .../src/pages/contacts/Contacts.page.tsx | 12 -- .../src/pages/settings/Settings.page.tsx | 177 ++++++++++++++++++ .../src/pages/settings/index.tsx | 7 - .../src/pages/settings/settings.constants.ts | 25 +++ .../apps/communication/src/routes/routes.tsx | 2 +- 13 files changed, 291 insertions(+), 20 deletions(-) create mode 100644 packages/manager/apps/communication/public/translations/settings/Messages_fr_FR.json create mode 100644 packages/manager/apps/communication/src/components/routingStatus/RoutingStatus.component.tsx create mode 100644 packages/manager/apps/communication/src/data/api/routing.ts create mode 100644 packages/manager/apps/communication/src/data/hooks/useNotificationRouting/useNotificationRouting.tsx create mode 100644 packages/manager/apps/communication/src/data/types/routing.type.ts create mode 100644 packages/manager/apps/communication/src/pages/settings/Settings.page.tsx delete mode 100644 packages/manager/apps/communication/src/pages/settings/index.tsx create mode 100644 packages/manager/apps/communication/src/pages/settings/settings.constants.ts diff --git a/packages/manager/apps/communication/public/translations/settings/Messages_fr_FR.json b/packages/manager/apps/communication/public/translations/settings/Messages_fr_FR.json new file mode 100644 index 000000000000..75b0a73047d6 --- /dev/null +++ b/packages/manager/apps/communication/public/translations/settings/Messages_fr_FR.json @@ -0,0 +1,20 @@ +{ + "description": "Définissez vos règles de diffusion pour choisir quels contacts recevront les communications, en fonction des catégories et priorités que vous sélectionnez.", + "table_column_name": "Nom", + "table_column_status": "Statut", + "add_routing_button": "Créer une règle", + "status_enabled": "Activé", + "status_disabled": "Désactivé", + "actions_menu_label": "Actions", + "table_action_deactivate": "Désactiver", + "add_routing_error_message": "Une erreur est survenue lors de la création de la règle.", + "add_routing_success_message": "La règle a été créée avec succès.", + "edit_routing_error_message": "Une erreur est survenue lors de la modification de la règle.", + "edit_routing_success_message": "La règle a été modifiée avec succès.", + "delete_routing_error_message": "Une erreur est survenue lors de la suppression de la règle.", + "delete_routing_success_message": "La règle a été supprimée avec succès.", + "add_routing_modal_title": "Créer une règle", + "edit_routing_modal_title": "Modifier la règle", + "delete_routing_modal_title": "Supprimer la règle", + "delete_routing_modal_info": "Êtes-vous sûr de vouloir supprimer la règle {{ routingName }} ?" +} diff --git a/packages/manager/apps/communication/src/components/routingStatus/RoutingStatus.component.tsx b/packages/manager/apps/communication/src/components/routingStatus/RoutingStatus.component.tsx new file mode 100644 index 000000000000..e7e51e8f00e8 --- /dev/null +++ b/packages/manager/apps/communication/src/components/routingStatus/RoutingStatus.component.tsx @@ -0,0 +1,14 @@ +import { NAMESPACES } from '@ovh-ux/manager-common-translations'; +import { ODS_BADGE_COLOR } from '@ovhcloud/ods-components'; +import { OdsBadge } from '@ovhcloud/ods-components/react'; +import { useTranslation } from 'react-i18next'; + +export default function RoutingStatusChip({ active }: { active: boolean }) { + const { t } = useTranslation(NAMESPACES.STATUS); + return ( + + ); +} diff --git a/packages/manager/apps/communication/src/data/api/notification.ts b/packages/manager/apps/communication/src/data/api/notification.ts index a0cf4dd39861..a469fecec653 100644 --- a/packages/manager/apps/communication/src/data/api/notification.ts +++ b/packages/manager/apps/communication/src/data/api/notification.ts @@ -1,5 +1,6 @@ import apiClient from '@ovh-ux/manager-core-api'; import { NotificationReference } from '../types/reference.type'; +import { Notification } from '../types/notification.type'; export const getNotificationReference = async (): Promise => { const { data } = await apiClient.v2.get('/notification/reference'); diff --git a/packages/manager/apps/communication/src/data/api/routing.ts b/packages/manager/apps/communication/src/data/api/routing.ts new file mode 100644 index 000000000000..a9f9d4563b12 --- /dev/null +++ b/packages/manager/apps/communication/src/data/api/routing.ts @@ -0,0 +1,8 @@ +import apiClient from '@ovh-ux/manager-core-api'; +import { NotificationRouting } from '../types/routing.type'; + +export const getNotificationRoutingQueryKey = () => ['/notification/routing']; +export const getNotificationRouting = async (): Promise => { + const { data } = await apiClient.v2.get('/notification/routing'); + return data; +}; diff --git a/packages/manager/apps/communication/src/data/hooks/useNotificationRouting/useNotificationRouting.tsx b/packages/manager/apps/communication/src/data/hooks/useNotificationRouting/useNotificationRouting.tsx new file mode 100644 index 000000000000..076025a7ca6e --- /dev/null +++ b/packages/manager/apps/communication/src/data/hooks/useNotificationRouting/useNotificationRouting.tsx @@ -0,0 +1,16 @@ +import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import { ApiError } from '@ovh-ux/manager-core-api'; +import { NotificationRouting } from '@/data/types/routing.type'; +import { + getNotificationRouting, + getNotificationRoutingQueryKey, +} from '@/data/api/routing'; + +export const useNotificationRouting = (): UseQueryResult< + NotificationRouting[], + ApiError +> => + useQuery({ + queryKey: getNotificationRoutingQueryKey(), + queryFn: () => getNotificationRouting(), + }); diff --git a/packages/manager/apps/communication/src/data/index.ts b/packages/manager/apps/communication/src/data/index.ts index ea648d95524b..c0bc4f9fddc2 100644 --- a/packages/manager/apps/communication/src/data/index.ts +++ b/packages/manager/apps/communication/src/data/index.ts @@ -3,11 +3,13 @@ import { useNotificationReference, useNotificationHistory, } from './hooks/useNotification/useNotification'; +import { useNotificationRouting } from './hooks/useNotificationRouting/useNotificationRouting'; import { useAccountUrn } from './hooks/useAccountUrn/useAccountUrn'; export { useNotification, useNotificationReference, useNotificationHistory, + useNotificationRouting, useAccountUrn, }; diff --git a/packages/manager/apps/communication/src/data/types/index.ts b/packages/manager/apps/communication/src/data/types/index.ts index ea7e8ec2e8e5..ab9af7e9b511 100644 --- a/packages/manager/apps/communication/src/data/types/index.ts +++ b/packages/manager/apps/communication/src/data/types/index.ts @@ -1,3 +1,4 @@ export * from './notification.type'; export * from './reference.type'; +export * from './routing.type'; export * from './iam-resource.type'; diff --git a/packages/manager/apps/communication/src/data/types/routing.type.ts b/packages/manager/apps/communication/src/data/types/routing.type.ts new file mode 100644 index 000000000000..8f5d257259a7 --- /dev/null +++ b/packages/manager/apps/communication/src/data/types/routing.type.ts @@ -0,0 +1,26 @@ +import { ContactMeanType } from './contact-mean.type'; + +export type NotificationRoutingContactMean = { + id: string; + email: string; + type: ContactMeanType; +}; + +export type NotificationRoutingCondition = { + category: string[]; + priority: string[]; +}; + +export type NotificationRoutingRule = { + continue: boolean; + condition: NotificationRoutingCondition; + contactMeans: NotificationRoutingContactMean[]; +}; + +export type NotificationRouting = { + active: boolean; + createdAt: string; + id: string; + name: string; + rules: NotificationRoutingRule[]; +}; diff --git a/packages/manager/apps/communication/src/pages/contacts/Contacts.page.tsx b/packages/manager/apps/communication/src/pages/contacts/Contacts.page.tsx index cacc561fb7db..6856e7803d16 100644 --- a/packages/manager/apps/communication/src/pages/contacts/Contacts.page.tsx +++ b/packages/manager/apps/communication/src/pages/contacts/Contacts.page.tsx @@ -21,7 +21,6 @@ import ContactMeanStatusChip from '@/components/contactMeanStatus/contactMeanSta import { useAccountUrn } from '@/data'; import { urls } from '@/routes/routes.constant'; import { - useDeleteContactMean, useChangeContactMeanStatus, useRestartValidationContactMean, } from '@/data/hooks/useContactMean/useContactMean'; @@ -35,17 +34,6 @@ function ContactMeanActionMenu({ contactMean }: { contactMean: ContactMean }) { const { t } = useTranslation(['contacts', NAMESPACES.ACTIONS, 'common']); const { addSuccess, addError, clearNotifications } = useNotifications(); const { data: accountUrn } = useAccountUrn(); - const { mutate: deleteContactMean } = useDeleteContactMean({ - id: contactMean.id, - onSuccess: () => { - clearNotifications(); - addSuccess(t('delete_contact_success_message')); - }, - onError: () => { - clearNotifications(); - addError(t('delete_contact_error_message')); - }, - }); const { mutate: disableContactMean } = useChangeContactMeanStatus({ contactMeanId: contactMean.id, onSuccess: () => { diff --git a/packages/manager/apps/communication/src/pages/settings/Settings.page.tsx b/packages/manager/apps/communication/src/pages/settings/Settings.page.tsx new file mode 100644 index 000000000000..390e4b047ee3 --- /dev/null +++ b/packages/manager/apps/communication/src/pages/settings/Settings.page.tsx @@ -0,0 +1,177 @@ +import { useTranslation } from 'react-i18next'; +import { OdsMessage, OdsText } from '@ovhcloud/ods-components/react'; +import { + ActionMenu, + ActionMenuItem, + Datagrid, + DatagridColumn, + DataGridTextCell, + ManagerButton, + Notifications, + useResourcesIcebergV2, +} from '@ovh-ux/manager-react-components'; +import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components'; +import { useMemo } from 'react'; +import { Outlet } from 'react-router-dom'; +import { NAMESPACES } from '@ovh-ux/manager-common-translations'; +import { useAuthorization } from '@/hooks'; +import { NotificationRouting } from '@/data/types/routing.type'; +import { useAccountUrn } from '@/data'; +import { getNotificationRoutingQueryKey } from '@/data/api/routing'; +import { + NotificationRoutingActions, + displayActionMenuItem, +} from './settings.constants'; +import RoutingStatusChip from '@/components/routingStatus/RoutingStatus.component'; + +function RoutingActionMenu({ routing }: { routing: NotificationRouting }) { + const { t } = useTranslation(['settings', NAMESPACES.ACTIONS, 'common']); + // const { addSuccess, addError, clearNotifications } = useNotifications(); + const { data: accountUrn } = useAccountUrn(); + + const items = useMemo( + () => + [ + displayActionMenuItem(routing, NotificationRoutingActions.ENABLE) && { + id: 1, + label: t('activate', { ns: NAMESPACES.ACTIONS }), + onClick: () => {}, + iamActions: ['account:apiovh:notification/routing/edit'], + urn: accountUrn, + }, + displayActionMenuItem(routing, NotificationRoutingActions.DISABLE) && { + id: 2, + label: t('table_action_deactivate'), + onClick: () => {}, + iamActions: ['account:apiovh:notification/routing/edit'], + urn: accountUrn, + }, + displayActionMenuItem(routing, NotificationRoutingActions.EDIT) && { + id: 3, + label: t('modify', { ns: NAMESPACES.ACTIONS }), + onClick: () => {}, + iamActions: ['account:apiovh:notification/routing/edit'], + urn: accountUrn, + }, + displayActionMenuItem(routing, NotificationRoutingActions.DELETE) && { + id: 4, + label: t('delete', { ns: NAMESPACES.ACTIONS }), + onClick: () => {}, + iamActions: ['account:apiovh:notification/routing/delete'], + urn: accountUrn, + }, + ].filter(Boolean) as ActionMenuItem[], + [t, routing], + ); + + return ( + + ); +} + +function SettingsPage() { + const { t } = useTranslation(['settings', 'common']); + const { data: accountUrn } = useAccountUrn(); + const { isAuthorized, isLoading: isLoadingAuthorization } = useAuthorization([ + 'account:apiovh:notification/routing/get', + ]); + + const columns: DatagridColumn[] = useMemo( + () => [ + { + id: 'name', + label: t('table_column_name'), + isSortable: false, + cell: ({ name }) => ( + {name} + ), + }, + { + id: 'status', + label: t('table_column_status'), + isSortable: false, + size: 50, + cell: ({ active }) => ( + + + + ), + }, + { + id: 'actions', + label: '', + size: 50, + isSortable: false, + cell: (routing) => ( +
+ +
+ ), + } + ], + [t], + ); + + const { + flattenData, + isLoading: isLoadingRouting, + hasNextPage, + fetchNextPage, + } = useResourcesIcebergV2({ + columns, + route: '/notification/routing', + queryKey: getNotificationRoutingQueryKey(), + enabled: isAuthorized, + }); + + const isLoading = isLoadingRouting || isLoadingAuthorization; + + return ( +
+ + {t('description')} + + + {!isLoading && !isAuthorized && ( + + {t('common:iam_display_content_message', { ns: 'common' })} + + )} + + + + {}} + /> + } + totalItems={flattenData?.length || 0} + hasNextPage={hasNextPage} + onFetchNextPage={fetchNextPage} + manualSorting={true} + /> + + +
+ ); +} + +export default SettingsPage; diff --git a/packages/manager/apps/communication/src/pages/settings/index.tsx b/packages/manager/apps/communication/src/pages/settings/index.tsx deleted file mode 100644 index aeee2aa231c4..000000000000 --- a/packages/manager/apps/communication/src/pages/settings/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -function SettingsTab() { - return
tbd
; -} - -export default SettingsTab; diff --git a/packages/manager/apps/communication/src/pages/settings/settings.constants.ts b/packages/manager/apps/communication/src/pages/settings/settings.constants.ts new file mode 100644 index 000000000000..25b4bfb03ba9 --- /dev/null +++ b/packages/manager/apps/communication/src/pages/settings/settings.constants.ts @@ -0,0 +1,25 @@ +import { NotificationRouting } from '@/data/types/routing.type'; + +export enum NotificationRoutingActions { + EDIT = 'EDIT', + DELETE = 'DELETE', + ENABLE = 'ENABLE', + DISABLE = 'DISABLE', +} + +export const displayActionMenuItem = ( + routing: NotificationRouting, + action: NotificationRoutingActions, +): boolean => { + switch (action) { + case NotificationRoutingActions.EDIT: + case NotificationRoutingActions.DISABLE: + return routing.active; + case NotificationRoutingActions.ENABLE: + return !routing.active; + case NotificationRoutingActions.DELETE: + return true; + default: + return false; + } +}; diff --git a/packages/manager/apps/communication/src/routes/routes.tsx b/packages/manager/apps/communication/src/routes/routes.tsx index f5b4fcbe2f29..fd70d875dff4 100644 --- a/packages/manager/apps/communication/src/routes/routes.tsx +++ b/packages/manager/apps/communication/src/routes/routes.tsx @@ -24,7 +24,7 @@ const CommunicationsPage = lazy(() => const CommunicationsDetailPage = lazy(() => import('@/pages/communications/detail/CommunicationsDetail.page'), ); -const SettingsPage = lazy(() => import('@/pages/settings')); +const SettingsPage = lazy(() => import('@/pages/settings/Settings.page')); export default (