diff --git a/packages/manager-react-components/src/components/content/headers/headers.component.tsx b/packages/manager-react-components/src/components/content/headers/headers.component.tsx index 7ef38e8368f1..3e9305f01580 100644 --- a/packages/manager-react-components/src/components/content/headers/headers.component.tsx +++ b/packages/manager-react-components/src/components/content/headers/headers.component.tsx @@ -3,7 +3,7 @@ import { OdsText } from '@ovhcloud/ods-components/react'; import { Title, Subtitle } from '../../typography'; export interface HeadersProps { - title?: string; + title?: React.ReactElement | string; subtitle?: string; description?: string; headerButton?: React.ReactElement; diff --git a/packages/manager/apps/web-office/public/translations/dashboard/Messages_fr_FR.json b/packages/manager/apps/web-office/public/translations/dashboard/Messages_fr_FR.json index 090a4cf35d72..657b9e1b86e6 100644 --- a/packages/manager/apps/web-office/public/translations/dashboard/Messages_fr_FR.json +++ b/packages/manager/apps/web-office/public/translations/dashboard/Messages_fr_FR.json @@ -2,6 +2,13 @@ "title": "Dashboard page", "error_service": "No services info", "microsoft_office_dashboard_consumption": "Consommation", - "microsoft_office_dashboard_licences": "Licences", + "microsoft_office_dashboard_licenses_group": "Groupes de licences", + "microsoft_office_dashboard_licenses": "Licences", + "microsoft_office_dashboard_edit-name": "Éditer", + "microsoft_office_modal_update_headline": "Changer le nom", + "microsoft_office_modal_update_description": "Veuilliez saisir le nouveau nom que vous souhaitez donner à votre service.", + "microsoft_office_modal_update_input_label": "Nouveau nom", + "microsoft_office_modal_update_confirm": "Confirmer", + "microsoft_office_modal_update_cancel": "Annuler", "back_link": "Retour à la liste" } diff --git a/packages/manager/apps/web-office/public/translations/dashboard/users/Messages_fr_FR.json b/packages/manager/apps/web-office/public/translations/dashboard/users/Messages_fr_FR.json index 795956650d38..71db7055d169 100644 --- a/packages/manager/apps/web-office/public/translations/dashboard/users/Messages_fr_FR.json +++ b/packages/manager/apps/web-office/public/translations/dashboard/users/Messages_fr_FR.json @@ -2,7 +2,7 @@ "dashboard_users_table_firstName": "Prénom", "dashboard_users_table_lastName": " Nom", "dashboard_users_table_activationEmail": "Email d'activation", - "dashboard_users_table_licences": "Licences", + "dashboard_users_table_licenses": "Licences", "dashboard_users_table_status": "Statut", "dashboard_users_download_text": "Les programmes d'installation pour PC et MAC sont disponibles sur le site de microsoft .", "dashboard_users_download_info": "Pour smartphones et tablettes, rendez-vous directement dans votre App Store.", diff --git a/packages/manager/apps/web-office/src/api/license/api.ts b/packages/manager/apps/web-office/src/api/license/api.ts index 6c3be779716b..494797972020 100644 --- a/packages/manager/apps/web-office/src/api/license/api.ts +++ b/packages/manager/apps/web-office/src/api/license/api.ts @@ -1,12 +1,12 @@ import { v6 } from '@ovh-ux/manager-core-api'; -import { GetOfficeLicenseServiceParams } from './type'; import { getApiPath, getApiPathWithoutServiceName } from '../utils/apiPath'; +import { LicenseType } from './type'; // GET -export const getlicenseOfficeServiceQueryKey = ( - params: GetOfficeLicenseServiceParams, -) => [`get/license/office/${params.serviceName}`]; +export const getlicenseOfficeServiceQueryKey = (serviceName: string) => [ + `get/license/office/${serviceName}`, +]; export const getOfficeLicenseDetails = async (serviceName: string) => { const { data } = await v6.get(`${getApiPath(serviceName)}`); @@ -35,5 +35,12 @@ export const getOfficePrepaidLicenses = async (serviceName: string) => { // POST // PUT +export const updateOfficeLicenseDetails = async ( + serviceName: string, + licenseData: Partial, +): Promise => { + const { data } = await v6.put(`${getApiPath(serviceName)}`, licenseData); + return data; +}; // DELETE diff --git a/packages/manager/apps/web-office/src/api/parentTenant/api.ts b/packages/manager/apps/web-office/src/api/parentTenant/api.ts new file mode 100644 index 000000000000..a710ac5fb483 --- /dev/null +++ b/packages/manager/apps/web-office/src/api/parentTenant/api.ts @@ -0,0 +1,28 @@ +import { v6 } from '@ovh-ux/manager-core-api'; +import { getApiPath } from '../utils/apiPath'; +import { ParentTenantType } from './type'; + +// GET +export const getParentTenant = async ( + serviceName: string, +): Promise => { + const { data } = await v6.get( + `${getApiPath(serviceName)}parentTenant`, + ); + return data; +}; +// POST + +// PUT +export const updateParentTenant = async ( + serviceName: string, + parentTenantData: Partial, +): Promise => { + const { data } = await v6.put( + `${getApiPath(serviceName)}parentTenant`, + parentTenantData, + ); + return data; +}; + +// DELETE diff --git a/packages/manager/apps/web-office/src/api/parentTenant/key.ts b/packages/manager/apps/web-office/src/api/parentTenant/key.ts new file mode 100644 index 000000000000..13ff4c442574 --- /dev/null +++ b/packages/manager/apps/web-office/src/api/parentTenant/key.ts @@ -0,0 +1,3 @@ +export const getOfficeParentTenantQueryKey = (serviceName: string) => [ + `get/license/officePrepaid/${serviceName}/parentTenant`, +]; diff --git a/packages/manager/apps/web-office/src/api/parentTenant/type.ts b/packages/manager/apps/web-office/src/api/parentTenant/type.ts new file mode 100644 index 000000000000..1b6b20c635d8 --- /dev/null +++ b/packages/manager/apps/web-office/src/api/parentTenant/type.ts @@ -0,0 +1,15 @@ +import { UserStateEnum } from '../api.type'; + +export type ParentTenantType = { + address: string; + city: string; + creationDate: string; + displayName: string; + firstName: string; + lastName: string; + phone: string; + serviceName: string; + serviceType: string; + status: UserStateEnum; + zipCode: string; +}; diff --git a/packages/manager/apps/web-office/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/apps/web-office/src/components/Breadcrumb/Breadcrumb.tsx index 57d6eff8aa9b..8c434f387bb7 100644 --- a/packages/manager/apps/web-office/src/components/Breadcrumb/Breadcrumb.tsx +++ b/packages/manager/apps/web-office/src/components/Breadcrumb/Breadcrumb.tsx @@ -33,7 +33,7 @@ export const Breadcrumb: React.FC = () => { label: pathParts[1], href: `#/${pathParts.slice(0, 2).join('/')}`, }, - ...breadcrumbParts.map((_, index) => { + ...breadcrumbParts.map((_: any, index: number) => { const label = t( `microsoft_office_dashboard_${breadcrumbParts .slice(0, index + 1) diff --git a/packages/manager/apps/web-office/src/hooks/index.ts b/packages/manager/apps/web-office/src/hooks/index.ts index e14379ea2f19..3230a0166921 100644 --- a/packages/manager/apps/web-office/src/hooks/index.ts +++ b/packages/manager/apps/web-office/src/hooks/index.ts @@ -3,3 +3,4 @@ export * from './useOfficeLicenseDetails'; export * from './useGenerateUrl'; export * from './useOfficeServiceType'; export * from './useOfficeUsers'; +export * from './officeParentTenant/getOfficeParentTenant'; diff --git a/packages/manager/apps/web-office/src/hooks/officeParentTenant/getOfficeParentTenant.ts b/packages/manager/apps/web-office/src/hooks/officeParentTenant/getOfficeParentTenant.ts new file mode 100644 index 000000000000..0bc2e339ea52 --- /dev/null +++ b/packages/manager/apps/web-office/src/hooks/officeParentTenant/getOfficeParentTenant.ts @@ -0,0 +1,26 @@ +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'react-router-dom'; +import { useOfficeServiceType } from '../useOfficeServiceType'; +import { getParentTenant } from '@/api/parentTenant/api'; +import { + getlicenseOfficeServiceQueryKey, + getOfficeLicenseDetails, +} from '@/api/license/api'; +import { getOfficeParentTenantQueryKey } from '@/api/parentTenant/key'; + +export const getOfficeParentTenant = () => { + const { serviceName } = useParams(); + const serviceType = useOfficeServiceType(serviceName); + const isPrepaid = serviceType === 'prepaid'; + + return useQuery({ + queryKey: [ + isPrepaid + ? getOfficeParentTenantQueryKey(serviceName) + : getlicenseOfficeServiceQueryKey(serviceName), + ], + queryFn: isPrepaid + ? () => getParentTenant(serviceName) + : () => getOfficeLicenseDetails(serviceName), + }); +}; diff --git a/packages/manager/apps/web-office/src/hooks/useGenerateUrl.ts b/packages/manager/apps/web-office/src/hooks/useGenerateUrl.ts index eb36dba84aab..7855d6f1e380 100644 --- a/packages/manager/apps/web-office/src/hooks/useGenerateUrl.ts +++ b/packages/manager/apps/web-office/src/hooks/useGenerateUrl.ts @@ -1,32 +1,17 @@ import { useHref } from 'react-router-dom'; -import { useOfficeLicenseDetail } from '@/hooks'; export const UseGenerateUrl = ( baseURL: string, type: 'path' | 'href' = 'path', params?: Record, ) => { - const { data: serviceName } = useOfficeLicenseDetail(); - const URL = baseURL.replace( ':serviceName', (params?.serviceName as string) || '', ); - const queryParams = { - ...params, - ...(serviceName && { serviceNameDetail: serviceName }), - }; - - const queryString = Object.entries(queryParams) - .filter(([key]) => key !== 'serviceName') - .map(([key, value]) => `${key}=${value}`) - .join('&'); - - const fullURL = queryString ? `${URL}?${queryString}` : URL; - if (type === 'href') { - return useHref(fullURL); + return useHref(URL); } - return fullURL; + return URL; }; diff --git a/packages/manager/apps/web-office/src/hooks/useOfficeLicenseDetails.ts b/packages/manager/apps/web-office/src/hooks/useOfficeLicenseDetails.ts index 4b98d3ae2f7a..594aa96536b4 100644 --- a/packages/manager/apps/web-office/src/hooks/useOfficeLicenseDetails.ts +++ b/packages/manager/apps/web-office/src/hooks/useOfficeLicenseDetails.ts @@ -12,9 +12,9 @@ export const useOfficeLicenseDetail = ( const { serviceName: selectedServiceName } = useParams(); return useQuery({ - queryKey: getlicenseOfficeServiceQueryKey({ - serviceName: serviceName || selectedServiceName, - }), + queryKey: getlicenseOfficeServiceQueryKey( + serviceName || selectedServiceName, + ), queryFn: () => getOfficeLicenseDetails(serviceName || selectedServiceName || ''), enabled: Boolean(serviceName || selectedServiceName), diff --git a/packages/manager/apps/web-office/src/pages/dashboard/components/UpdateDisplayNameModal.component.tsx b/packages/manager/apps/web-office/src/pages/dashboard/components/UpdateDisplayNameModal.component.tsx new file mode 100644 index 000000000000..ae5c8cf7f13c --- /dev/null +++ b/packages/manager/apps/web-office/src/pages/dashboard/components/UpdateDisplayNameModal.component.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { UpdateNameModal } from '@ovh-ux/manager-react-components'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useMutation } from '@tanstack/react-query'; +import { + getOfficeParentTenant, + UseGenerateUrl, + useOfficeServiceType, +} from '@/hooks'; +import queryClient from '@/queryClient'; +import { ParentTenantType } from '@/api/parentTenant/type'; +import { updateOfficeLicenseDetails } from '@/api/license/api'; +import { updateParentTenant } from '@/api/parentTenant/api'; +import { LicenseType } from '@/api/license/type'; +import { getOfficeParentTenantQueryKey } from '@/api/parentTenant/key'; +import { getOfficeLicenseDetailsQueryKey } from '@/api/license/key'; + +export default function UpdateDisplayNameModal() { + const navigate = useNavigate(); + const { t } = useTranslation('dashboard'); + const { serviceName } = useParams(); + const serviceType = useOfficeServiceType(serviceName); + const isPrepaid = serviceType === 'prepaid'; + const { data: getData, refetch } = getOfficeParentTenant(); + + const goBackUrl = UseGenerateUrl('..', 'path'); + const closeModal = () => { + navigate(goBackUrl, { replace: true }); + }; + const { mutate: editName, isPending } = useMutation({ + mutationFn: isPrepaid + ? (parentTenantData: Partial) => + updateParentTenant(serviceName, parentTenantData) + : (licenseData: Partial) => + updateOfficeLicenseDetails(serviceName, licenseData), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: isPrepaid + ? getOfficeParentTenantQueryKey(serviceName) + : getOfficeLicenseDetailsQueryKey(serviceName), + }); + closeModal(); + refetch(); + }, + }); + const handleSaveClick = (displayName: string) => { + editName({ displayName }); + }; + + return ( + + ); +} diff --git a/packages/manager/apps/web-office/src/pages/dashboard/index.tsx b/packages/manager/apps/web-office/src/pages/dashboard/index.tsx index 5e826b783bf0..cb2bcdefaa44 100644 --- a/packages/manager/apps/web-office/src/pages/dashboard/index.tsx +++ b/packages/manager/apps/web-office/src/pages/dashboard/index.tsx @@ -1,11 +1,23 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Outlet, useParams, useResolvedPath } from 'react-router-dom'; - -import { BaseLayout } from '@ovh-ux/manager-react-components'; +import { + Outlet, + useNavigate, + useParams, + useResolvedPath, +} from 'react-router-dom'; +import { BaseLayout, UpdateNameModal } from '@ovh-ux/manager-react-components'; +import { OdsButton, OdsText } from '@ovhcloud/ods-components/react'; +import { + ODS_BUTTON_VARIANT, + ODS_ICON_NAME, + ODS_TEXT_PRESET, +} from '@ovhcloud/ods-components'; import { Breadcrumb } from '@/components/Breadcrumb/Breadcrumb'; import { urls } from '@/routes/routes.constants'; import TabsPanel from '@/components/layout-helpers/Dashboard/TabsPanel'; +import { getOfficeParentTenant, UseGenerateUrl } from '@/hooks'; +import { ParentTenantType } from '@/api/parentTenant/type'; export type DashboardTabItemProps = { name: string; @@ -21,7 +33,18 @@ export type DashboardLayoutProps = { export default function DashboardPage() { const { serviceName } = useParams(); const { t } = useTranslation('dashboard'); + const navigate = useNavigate(); + const basePath = useResolvedPath('').pathname; + const { data } = getOfficeParentTenant() as { + data?: ParentTenantType; + }; + + const hrefEditName = UseGenerateUrl('./edit-name', 'path'); + + const handleEditNamelick = () => { + navigate(hrefEditName); + }; function computePathMatchers(routes: string[]) { return routes.map( @@ -31,8 +54,8 @@ export default function DashboardPage() { const tabsList: DashboardTabItemProps[] = [ { - name: 'licence', - title: t('microsoft_office_dashboard_licences'), + name: 'license', + title: t('microsoft_office_dashboard_licenses'), to: basePath, pathMatchers: computePathMatchers([urls.license]), }, @@ -45,7 +68,26 @@ export default function DashboardPage() { ]; const header = { - title: serviceName, + title: ( + <> + + {t('microsoft_office_dashboard_licenses_group')} + +
+
{data?.displayName}
+ +
+ + {data?.serviceName} + + + ), }; return ( diff --git a/packages/manager/apps/web-office/src/pages/dashboard/users/ActionButtonUsers.component.tsx b/packages/manager/apps/web-office/src/pages/dashboard/users/ActionButtonUsers.component.tsx index 5099863e0df5..dc2a2279a009 100644 --- a/packages/manager/apps/web-office/src/pages/dashboard/users/ActionButtonUsers.component.tsx +++ b/packages/manager/apps/web-office/src/pages/dashboard/users/ActionButtonUsers.component.tsx @@ -9,11 +9,11 @@ import { LicenseType } from '@/api/license/type'; interface ActionButtonUsersProps { usersItem: UserNativeType; - licenceDetail: LicenseType; + licenseDetail: LicenseType; } const ActionButtonUsers: React.FC = ({ usersItem, - licenceDetail, + licenseDetail, }) => { const { t } = useTranslation('dashboard/users'); @@ -39,7 +39,7 @@ const ActionButtonUsers: React.FC = ({ id: 1, onclick: handlePasswordChangeClick, label: t('dashboard_users_action_user_change_password'), - urn: licenceDetail.iam.urn, + urn: licenseDetail.iam.urn, iamActions: [IAM_ACTIONS.user.edit], }, ] @@ -48,7 +48,7 @@ const ActionButtonUsers: React.FC = ({ id: 2, onclick: handleEditUserClick, label: t('dashboard_users_action_user_edit'), - urn: licenceDetail.iam.urn, + urn: licenseDetail.iam.urn, iamActions: [IAM_ACTIONS.user.edit], }, ...(!usersItem.isVirtual @@ -57,7 +57,7 @@ const ActionButtonUsers: React.FC = ({ id: 3, onclick: handleDeleteUserClick, label: t('dashboard_users_action_user_delete'), - urn: licenceDetail.iam.urn, + urn: licenseDetail.iam.urn, iamActions: [IAM_ACTIONS.user.delete], color: ODS_BUTTON_COLOR.critical, }, diff --git a/packages/manager/apps/web-office/src/pages/dashboard/users/index.tsx b/packages/manager/apps/web-office/src/pages/dashboard/users/index.tsx index f2b03c3c5926..0c8a1c4566b8 100644 --- a/packages/manager/apps/web-office/src/pages/dashboard/users/index.tsx +++ b/packages/manager/apps/web-office/src/pages/dashboard/users/index.tsx @@ -3,6 +3,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { Datagrid, DatagridColumn } from '@ovh-ux/manager-react-components'; import { ODS_ICON_NAME, ODS_TEXT_PRESET } from '@ovhcloud/ods-components'; import { OdsLink, OdsText } from '@ovhcloud/ods-components/react'; +import { Outlet } from 'react-router-dom'; import { UserNativeType } from '@/api/users/type'; import Loading from '@/components/Loading/Loading'; import { useOfficeLicenseDetail, useOfficeUsers } from '@/hooks'; @@ -15,7 +16,7 @@ export default function License() { const { data: dataUsers, isLoading: isLoadingUsers } = useOfficeUsers(); const { - data: dataLicenceDetail, + data: dataLicenseDetail, isLoading: isLoadingLicenceDetail, } = useOfficeLicenseDetail(); @@ -48,11 +49,11 @@ export default function License() { label: 'dashboard_users_table_activationEmail', }, { - id: 'licences', + id: 'licenses', cell: (item) => ( {item.licences} ), - label: 'dashboard_users_table_licences', + label: 'dashboard_users_table_licenses', }, { id: 'status', @@ -81,7 +82,7 @@ export default function License() { cell: (item) => ( ), label: '', @@ -121,6 +122,7 @@ export default function License() { className="mt-4" /> )} + ); } diff --git a/packages/manager/apps/web-office/src/pages/licences/licences.page.tsx b/packages/manager/apps/web-office/src/pages/licenses/licenses.page.tsx similarity index 100% rename from packages/manager/apps/web-office/src/pages/licences/licences.page.tsx rename to packages/manager/apps/web-office/src/pages/licenses/licenses.page.tsx diff --git a/packages/manager/apps/web-office/src/pages/licences/licences.spec.tsx b/packages/manager/apps/web-office/src/pages/licenses/licenses.spec.tsx similarity index 94% rename from packages/manager/apps/web-office/src/pages/licences/licences.spec.tsx rename to packages/manager/apps/web-office/src/pages/licenses/licenses.spec.tsx index 3f0c7b172fd1..eb26febca891 100644 --- a/packages/manager/apps/web-office/src/pages/licences/licences.spec.tsx +++ b/packages/manager/apps/web-office/src/pages/licenses/licenses.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@/utils/test.provider'; -import Licenses from './licences.page'; +import Licenses from './licenses.page'; describe('Licenses Page', () => { it('should render page with content', async () => { diff --git a/packages/manager/apps/web-office/src/queryClient.ts b/packages/manager/apps/web-office/src/queryClient.ts new file mode 100644 index 000000000000..94100b5328ad --- /dev/null +++ b/packages/manager/apps/web-office/src/queryClient.ts @@ -0,0 +1,11 @@ +import { QueryClient } from '@tanstack/react-query'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 300_000, + }, + }, +}); + +export default queryClient; diff --git a/packages/manager/apps/web-office/src/routes/routes.tsx b/packages/manager/apps/web-office/src/routes/routes.tsx index 844c79775969..4adaa49033e7 100644 --- a/packages/manager/apps/web-office/src/routes/routes.tsx +++ b/packages/manager/apps/web-office/src/routes/routes.tsx @@ -23,7 +23,7 @@ export const Routes: any = [ children: [ { path: urls.listing, - ...lazyRouteConfig(() => import('@/pages/licences/licences.page')), + ...lazyRouteConfig(() => import('@/pages/licenses/licenses.page')), handle: { tracking: { pageName: 'licenses', @@ -36,7 +36,6 @@ export const Routes: any = [ ...lazyRouteConfig(() => import('@/pages/dashboard')), children: [ { - id: 'licence', path: '', ...lazyRouteConfig(() => import('@/pages/dashboard/users')), handle: { @@ -45,9 +44,24 @@ export const Routes: any = [ pageType: PageType.dashboard, }, }, + children: [ + { + path: 'edit-name', + ...lazyRouteConfig(() => + import( + '@/pages/dashboard/components/UpdateDisplayNameModal.component' + ), + ), + handle: { + tracking: { + pageName: 'edit-name', + pageType: PageType.dashboard, + }, + }, + }, + ], }, { - id: 'consumption', path: 'consumption', ...lazyRouteConfig(() => import('@/pages/dashboard/consumption')), handle: {