From a8257aace13d957cea44ebe899c8b43dbb8733e5 Mon Sep 17 00:00:00 2001 From: Thibault Coudray Date: Thu, 29 Aug 2024 16:59:33 +0200 Subject: [PATCH] (PC-31316)[API] feat: add `VenueProviderCard` and implement multi-provider logic --- .../AddVenueProviderButton.tsx | 33 +++- .../OffersSynchronization.module.scss | 13 ++ .../OffersSynchronization.tsx | 40 +++-- ...arameters.tsx => AllocineProviderEdit.tsx} | 82 ++++----- .../AllocineProviderParameters.module.scss | 29 ---- ...rParameters.tsx => CinemaProviderEdit.tsx} | 62 ++++--- .../CinemaProviderParameters.module.scss | 26 --- .../DeleteVenueProviderButton.tsx | 2 +- .../ProviderActionButton.module.scss | 6 + .../ToggleVenueProviderStatusButton.tsx | 2 +- .../VenueProviderCard.module.scss | 112 +++++++++++++ .../VenueProviderList/VenueProviderCard.tsx | 121 ++++++++++++++ .../VenueProviderItem.module.scss | 86 ---------- .../VenueProviderList/VenueProviderItem.tsx | 106 ------------ .../__specs__/AllocineProviderEdit.spec.tsx | 74 ++++++++ .../__specs__/CinemaProviderEdit.spec.tsx | 74 ++++++++ .../__specs__/VenueProviderCard.spec.tsx | 158 ++++++++++++++++++ .../__specs__/AddVenueProviderButton.spec.tsx | 92 ++++++++++ .../VenueSettingsFormScreen.spec.tsx | 79 ++++++++- 19 files changed, 830 insertions(+), 367 deletions(-) create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.module.scss rename pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/{AllocineProviderParameters.tsx => AllocineProviderEdit.tsx} (51%) delete mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.module.scss rename pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/{CinemaProviderParameters.tsx => CinemaProviderEdit.tsx} (60%) delete mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.module.scss create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ProviderActionButton.module.scss create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.module.scss create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.tsx delete mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.module.scss delete mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.tsx create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/AllocineProviderEdit.spec.tsx create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/CinemaProviderEdit.spec.tsx create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/VenueProviderCard.spec.tsx create mode 100644 pro/src/pages/VenueSettings/VenueProvidersManager/__specs__/AddVenueProviderButton.spec.tsx diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/AddVenueProviderButton.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/AddVenueProviderButton.tsx index 0f403c29814..47e55e25a32 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/AddVenueProviderButton.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/AddVenueProviderButton.tsx @@ -20,12 +20,14 @@ import { sortByLabel } from 'utils/strings' import { DEFAULT_PROVIDER_OPTION } from './utils/_constants' import { VenueProviderForm } from './VenueProviderForm' -interface AddVenueProviderButtonProps { +export interface AddVenueProviderButtonProps { venue: GetVenueResponseModel + linkedProvidersIds: number[] } export const AddVenueProviderButton = ({ venue, + linkedProvidersIds, }: AddVenueProviderButtonProps) => { const { mutate } = useSWRConfig() @@ -50,10 +52,31 @@ export const AddVenueProviderButton = ({ ) const providersOptions = sortByLabel( - providers.map((item) => ({ - value: item['id'].toString(), - label: item['name'], - })) + // 1. Filter out providers that are already linked to the venue + // 2. Format providers + providers.reduce( + ( + filteredProvidersOptions: { value: string; label: string }[], + provider + ) => { + const isAlreadyLinkedToVenue = !!linkedProvidersIds.find( + (providerId) => provider.id === providerId + ) + + if (!isAlreadyLinkedToVenue) { + return [ + ...filteredProvidersOptions, + { + value: provider['id'].toString(), + label: provider['name'], + }, + ] + } + + return filteredProvidersOptions + }, + [] + ) ) const setCreationMode = () => { diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.module.scss new file mode 100644 index 00000000000..72fc8bdd7d2 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.module.scss @@ -0,0 +1,13 @@ +@use "styles/mixins/_fonts.scss" as fonts; +@use "styles/mixins/_rem.scss" as rem; +@use "styles/variables/_size.scss" as size; + +.venue-providers { + display: flex; + flex-flow: row wrap; + width: 100%; +} + +.venue-providers-section { + max-width: 100%; +} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.tsx index 267bea5ca05..b04261f1926 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/OffersSynchronization.tsx @@ -1,10 +1,12 @@ +import cn from 'classnames' import React from 'react' import { VenueProviderResponse, GetVenueResponseModel } from 'apiClient/v1' import { FormLayout } from 'components/FormLayout/FormLayout' import { AddVenueProviderButton } from './AddVenueProviderButton' -import { VenueProviderItem } from './VenueProviderList/VenueProviderItem' +import style from './OffersSynchronization.module.scss' +import { VenueProviderCard } from './VenueProviderList/VenueProviderCard' interface OffersSynchronization { venueProviders: VenueProviderResponse[] @@ -16,27 +18,31 @@ export const OffersSynchronization = ({ venueProviders, }: OffersSynchronization) => { return ( - + + + {venueProviders.map((venueProvider) => ( + + ))} + - {venueProviders.length > 0 ? ( -
    - {venueProviders.map((venueProvider) => ( - - ))} -
- ) : ( - - )} + provider.id + )} + />
diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderEdit.tsx similarity index 51% rename from pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.tsx rename to pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderEdit.tsx index 289fd53bb4d..be9c4a7b570 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderEdit.tsx @@ -10,26 +10,28 @@ import { } from 'apiClient/v1' import { GET_VENUE_PROVIDERS_QUERY_KEY } from 'config/swrQueryKeys' import { useNotification } from 'hooks/useNotification' +import fullEditIcon from 'icons/full-edit.svg' import { Button } from 'ui-kit/Button/Button' import { ButtonVariant } from 'ui-kit/Button/types' import { DialogBuilder } from 'ui-kit/DialogBuilder/DialogBuilder' +import { SvgIcon } from 'ui-kit/SvgIcon/SvgIcon' import { FormValuesProps } from '../AllocineProviderForm/AllocineProviderForm' import { AllocineProviderFormDialog } from './AllocineProviderFormDialog' -import style from './AllocineProviderParameters.module.scss' +import style from './ProviderActionButton.module.scss' -interface AllocineProviderParametersProps { +export interface AllocineProviderEditProps { venueProvider: VenueProviderResponse venue: GetVenueResponseModel offererId: number } -export const AllocineProviderParameters = ({ +export const AllocineProviderEdit = ({ venueProvider, venue, offererId, -}: AllocineProviderParametersProps): JSX.Element => { +}: AllocineProviderEditProps): JSX.Element => { const [isOpenedFormDialog, setIsOpenedFormDialog] = useState(false) const notification = useNotification() const { mutate } = useSWRConfig() @@ -69,55 +71,27 @@ export const AllocineProviderParameters = ({ } return ( -
-

Paramètres des offres synchronisées

-
-
- Prix de vente/place :{' '} - - {venueProvider.price - ? `${new Intl.NumberFormat('fr-FR', { - style: 'currency', - currency: 'EUR', - }).format(venueProvider.price)}` - : ''} - -
-
- Nombre de places/séance :{' '} - - {`${ - typeof venueProvider.quantity === 'number' - ? venueProvider.quantity - : 'Illimité' - }`} - -
-
- Accepter les offres DUO :{' '} - {`${venueProvider.isDuo ? 'Oui' : 'Non'} `} -
-
- - Modifier les paramètres - - } - > - - -
+ + + Paramétrer + + } + > + + ) } diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.module.scss deleted file mode 100644 index a1159af0dea..00000000000 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/AllocineProviderParameters.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "styles/mixins/_fonts.scss" as fonts; -@use "styles/mixins/_rem.scss" as rem; - -.allocine-provider-parameters-container { - width: 100%; - margin-top: rem.torem(16px); - - .title { - @include fonts.title4; - } - - .parameters-list { - color: var(--color-grey-dark); - - .parameter-item { - margin-top: rem.torem(8px); - } - - span { - color: var(--color-black); - - @include fonts.body-important; - } - } - - .edit-parameters-btn { - margin-top: rem.torem(16px); - } -} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderEdit.tsx similarity index 60% rename from pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.tsx rename to pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderEdit.tsx index db9acc695a2..42e51d31c78 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderEdit.tsx @@ -5,25 +5,27 @@ import { api } from 'apiClient/api' import { GetVenueResponseModel, VenueProviderResponse } from 'apiClient/v1' import { GET_VENUE_PROVIDERS_QUERY_KEY } from 'config/swrQueryKeys' import { useNotification } from 'hooks/useNotification' +import fullEditIcon from 'icons/full-edit.svg' import { Button } from 'ui-kit/Button/Button' import { ButtonVariant } from 'ui-kit/Button/types' import { DialogBuilder } from 'ui-kit/DialogBuilder/DialogBuilder' +import { SvgIcon } from 'ui-kit/SvgIcon/SvgIcon' import { CinemaProviderFormDialog } from './CinemaProviderFormDialog' -import style from './CinemaProviderParameters.module.scss' +import style from './ProviderActionButton.module.scss' import { CinemaProviderParametersValues } from './types' -interface CinemaProviderParametersProps { +export interface CinemaProviderEditProps { venueProvider: VenueProviderResponse venue: GetVenueResponseModel offererId: number } -export const CinemaProviderParameters = ({ +export const CinemaProviderEdit = ({ venueProvider, venue, offererId, -}: CinemaProviderParametersProps): JSX.Element => { +}: CinemaProviderEditProps): JSX.Element => { const [isOpenedFormDialog, setIsOpenedFormDialog] = useState(false) const notification = useNotification() const { mutate } = useSWRConfig() @@ -62,35 +64,27 @@ export const CinemaProviderParameters = ({ } return ( -
- cc -

Paramètres des offres synchronisées

-
-
- Accepter les offres DUO :{' '} - {`${venueProvider.isDuo ? 'Oui' : 'Non'} `} -
-
- - Modifier les paramètres - - } - > - - -
+ + + Paramétrer + + } + > + + ) } diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.module.scss deleted file mode 100644 index a03be77a6f1..00000000000 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/CinemaProviderParameters.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -@use "styles/mixins/_fonts.scss" as fonts; -@use "styles/mixins/_rem.scss" as rem; - -.cinema-provider-parameters { - width: 100%; - margin-top: rem.torem(16px); - - .title { - @include fonts.title4; - } - - .cinema-provider-parameters-list { - color: var(--color-grey-dark); - margin-top: rem.torem(8px); - - span { - color: var(--color-black); - - @include fonts.body-important; - } - } - - .edit-parameters-btn { - margin-top: rem.torem(16px); - } -} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/DeleteVenueProviderButton.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/DeleteVenueProviderButton.tsx index 1ed7b68ce24..66b80c9aad5 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/DeleteVenueProviderButton.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/DeleteVenueProviderButton.tsx @@ -11,7 +11,7 @@ import { ButtonVariant } from 'ui-kit/Button/types' import { SvgIcon } from 'ui-kit/SvgIcon/SvgIcon' import { DeleteVenueProviderDialog } from './DeleteVenueProviderDialog' -import style from './VenueProviderItem.module.scss' +import style from './ProviderActionButton.module.scss' interface DeleteVenueProviderButtonProps { venueProviderId: number diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ProviderActionButton.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ProviderActionButton.module.scss new file mode 100644 index 00000000000..c48c982f837 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ProviderActionButton.module.scss @@ -0,0 +1,6 @@ +@use "styles/mixins/_rem.scss" as rem; + +.provider-action-icon { + width: rem.torem(20px); + margin-right: rem.torem(8px); +} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ToggleVenueProviderStatusButton.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ToggleVenueProviderStatusButton.tsx index 63678e81b89..b3a47d007d8 100644 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ToggleVenueProviderStatusButton.tsx +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/ToggleVenueProviderStatusButton.tsx @@ -11,8 +11,8 @@ import { Button } from 'ui-kit/Button/Button' import { ButtonVariant } from 'ui-kit/Button/types' import { SvgIcon } from 'ui-kit/SvgIcon/SvgIcon' +import style from './ProviderActionButton.module.scss' import { ToggleVenueProviderStatusDialog } from './ToggleVenueProviderStatusDialog' -import style from './VenueProviderItem.module.scss' interface ToggleVenueProviderStatusButtonProps { venueProvider: VenueProviderResponse diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.module.scss new file mode 100644 index 00000000000..6a5567caf93 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.module.scss @@ -0,0 +1,112 @@ +@use "styles/mixins/_fonts.scss" as fonts; +@use "styles/mixins/_rem.scss" as rem; +@use "styles/variables/_size.scss" as size; + +.venue-provider-card { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + border-radius: rem.torem(10px); + border: 1px solid var(--color-grey-medium); + padding: rem.torem(16px) rem.torem(24px) rem.torem(8px); + margin-bottom: rem.torem(16px); + width: 100%; + + @media (max-width: size.$tablet) { + gap: rem.torem(20px); + } + + @media (min-width: size.$tablet) { + margin-bottom: rem.torem(24px); + margin-right: rem.torem(16px); + width: rem.torem(520px); + height: rem.torem(150px); + } +} + +.provider-info-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + + @media (min-width: size.$tablet) { + flex-direction: row; + align-items: center; + } +} + +.provider-logo { + height: rem.torem(40px); + margin-right: rem.torem(16px); +} + +.provider-name { + @include fonts.title4; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.venue-provider-info-container { + @include fonts.caption; +} + +.venue-provider-account-info { + margin-bottom: rem.torem(8px); +} + +.provider-actions-container { + display: flex; + flex-direction: column; + align-items: flex-start; + + @media (max-width: size.$tablet) { + gap: rem.torem(12px); + } + + @media (min-width: size.$tablet) { + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + } +} + +.provider-actions-delete-and-inactivate { + gap: rem.torem(8px); + display: flex; + flex-direction: column; + align-items: flex-start; + + @media (max-width: size.$tablet) { + gap: rem.torem(12px); + } + + @media (min-width: size.$tablet) { + flex-direction: row; + align-items: center; + } +} + +.venue-id-at-offer-provider-container { + display: flex; + flex-direction: column; + color: var(--color-grey-dark); + margin-top: rem.torem(8px); + gap: rem.torem(8px); +} + +.last-synchronisation { + &::before { + background-color: var(--color-valid); + border-radius: 50%; + content: ""; + display: inline-block; + height: rem.torem(8px); + margin-right: rem.torem(8px); + width: rem.torem(8px); + } +} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.tsx new file mode 100644 index 00000000000..79621b890a3 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderCard.tsx @@ -0,0 +1,121 @@ +import React from 'react' + +import { GetVenueResponseModel, VenueProviderResponse } from 'apiClient/v1' +import { getProviderInfo } from 'core/Providers/utils/getProviderInfo' +import { + isAllocineProvider, + isCinemaProvider, +} from 'core/Providers/utils/utils' +import { formatLocalTimeDateString } from 'utils/timezone' + +import { AllocineProviderEdit } from './AllocineProviderEdit' +import { CinemaProviderEdit } from './CinemaProviderEdit' +import { DeleteVenueProviderButton } from './DeleteVenueProviderButton' +import { ToggleVenueProviderStatusButton } from './ToggleVenueProviderStatusButton' +import style from './VenueProviderCard.module.scss' + +export interface VenueProviderCardProps { + venueProvider: VenueProviderResponse + venue: GetVenueResponseModel + venueDepartmentCode?: string | null + offererId: number +} + +export const VenueProviderCard = ({ + venueProvider, + venue, + venueDepartmentCode, + offererId, +}: VenueProviderCardProps): JSX.Element => { + const { lastSyncDate, provider, venueIdAtOfferProvider, dateCreated } = + venueProvider + + const providerInfo = getProviderInfo(provider.name) + + const isPublicAPIProvider = venueProvider.provider.hasOffererProvider + + return ( +
+
+ {providerInfo && providerInfo.logo && ( + {providerInfo.name} + )} +
+ {providerInfo?.name ?? provider.name} +
+
+ {isPublicAPIProvider ? ( +
+
+ Date d'ajout : + +   + {formatLocalTimeDateString( + dateCreated, + venueDepartmentCode, + 'dd/MM/yyyy à HH:mm' + )} + +
+
+ ) : ( +
+
+ Compte : {venueIdAtOfferProvider} +
+ {lastSyncDate ? ( +
+ Dernière synchronisation : + +   + {formatLocalTimeDateString( + lastSyncDate, + venueDepartmentCode, + 'dd/MM/yyyy à HH:mm' + )} + +
+ ) : ( +
+ Importation en cours. Cette étape peut durer plusieurs dizaines de + minutes. +
+ )} +
+ )} +
+
+ {isAllocineProvider(venueProvider.provider) && ( + + )} + + {isCinemaProvider(venueProvider.provider) && ( + + )} +
+
+ + +
+
+
+ ) +} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.module.scss b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.module.scss deleted file mode 100644 index 34e38539da3..00000000000 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.module.scss +++ /dev/null @@ -1,86 +0,0 @@ -@use "styles/mixins/_fonts.scss" as fonts; -@use "styles/mixins/_rem.scss" as rem; -@use "styles/variables/_size.scss" as size; - -.venue-provider-row { - align-items: normal; - display: flex; - flex-direction: column; - width: 100%; - max-width: rem.torem(900px); -} - -.venue-provider-item-info { - display: flex; - flex-direction: column; - width: 100%; - max-width: rem.torem(900px); -} - -.provider-name-container { - @include fonts.caption; - - display: flex; - flex-direction: column; - gap: rem.torem(16px); - background-color: var(--color-grey-light); - border-radius: rem.torem(6px); - padding: rem.torem(12px) rem.torem(20px); - - @media (min-width: size.$tablet) { - flex-direction: row; - justify-content: space-between; - align-items: center; - } -} - -.provider-name { - @include fonts.title4; -} - -.provider-actions { - display: flex; - flex-direction: column; - gap: rem.torem(8px); - align-items: flex-start; - - @media (min-width: size.$tablet) { - flex-direction: row; - align-items: center; - } -} - -.provider-action-icon { - width: rem.torem(20px); - margin-right: rem.torem(8px); -} - -.venue-id-at-offer-provider-container { - display: flex; - flex-direction: column; - color: var(--color-grey-dark); - margin-top: rem.torem(8px); - gap: rem.torem(8px); -} - -.venue-id-at-offer-provider-message { - color: var(--color-black); - - @include fonts.body-important; -} - -.last-synchronisation { - color: var(--color-grey-dark); - - @include fonts.caption; - - &::before { - background-color: var(--color-valid); - border-radius: 50%; - content: ""; - display: inline-block; - height: rem.torem(8px); - margin-right: rem.torem(8px); - width: rem.torem(8px); - } -} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.tsx deleted file mode 100644 index cc0b64db92f..00000000000 --- a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/VenueProviderItem.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { GetVenueResponseModel, VenueProviderResponse } from 'apiClient/v1' -import { getProviderInfo } from 'core/Providers/utils/getProviderInfo' -import { - isAllocineProvider, - isCinemaProvider, -} from 'core/Providers/utils/utils' -import { formatLocalTimeDateString } from 'utils/timezone' - -import { AllocineProviderParameters } from './AllocineProviderParameters' -import { CinemaProviderParameters } from './CinemaProviderParameters' -import { DeleteVenueProviderButton } from './DeleteVenueProviderButton' -import { ToggleVenueProviderStatusButton } from './ToggleVenueProviderStatusButton' -import style from './VenueProviderItem.module.scss' - -interface VenueProviderItemV2Props { - venueProvider: VenueProviderResponse - venue: GetVenueResponseModel - venueDepartmentCode?: string | null - offererId: number -} - -export const VenueProviderItem = ({ - venueProvider, - venue, - venueDepartmentCode, - offererId, -}: VenueProviderItemV2Props): JSX.Element => { - const { lastSyncDate, provider, venueIdAtOfferProvider } = venueProvider - - const providerInfo = getProviderInfo(provider.name) - - return ( -
  • -
    -
    -
    - {providerInfo?.name ?? provider.name} -
    - -
    - - - -
    -
    - - {!venueProvider.provider.hasOffererProvider ? ( - !lastSyncDate ? ( -
    -
    - Compte : {venueIdAtOfferProvider} -
    - - - Importation en cours. Cette étape peut durer plusieurs dizaines - de minutes. Vous pouvez fermer votre navigateur et revenir plus - tard. - -
    - ) : ( -
    -
    - Dernière synchronisation : - - -   - {formatLocalTimeDateString( - lastSyncDate, - venueDepartmentCode, - 'dd/MM/yyyy à HH:mm' - )} - -
    - -
    - Compte : {venueIdAtOfferProvider} -
    -
    - ) - ) : null} -
    - - {isAllocineProvider(venueProvider.provider) && ( - - )} - - {isCinemaProvider(venueProvider.provider) && ( - - )} -
  • - ) -} diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/AllocineProviderEdit.spec.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/AllocineProviderEdit.spec.tsx new file mode 100644 index 00000000000..bde76c54a75 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/AllocineProviderEdit.spec.tsx @@ -0,0 +1,74 @@ +import { screen, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' + +import { api } from 'apiClient/api' +import { defaultGetVenue } from 'utils/collectiveApiFactories' +import { defaultVenueProvider } from 'utils/individualApiFactories' +import { renderWithProviders } from 'utils/renderWithProviders' + +import { + AllocineProviderEdit, + AllocineProviderEditProps, +} from '../AllocineProviderEdit' + +const renderAllocineProviderEdit = async (props: AllocineProviderEditProps) => { + renderWithProviders() + + await waitFor(() => screen.getByText('Paramétrer')) +} + +describe('AllocineProviderEdit', () => { + let props: AllocineProviderEditProps + const offererId = 3 + + beforeEach(() => { + props = { + offererId: offererId, + venue: defaultGetVenue, + venueProvider: defaultVenueProvider, + } + + vi.spyOn(api, 'updateVenueProvider').mockResolvedValue(defaultVenueProvider) + }) + + it('should display functional edit button', async () => { + await renderAllocineProviderEdit(props) + + const editButton = screen.getByRole('button', { name: 'Paramétrer' }) + expect(editButton).toBeInTheDocument() + + await userEvent.click(editButton) + expect( + screen.getByText('Modifier les paramètres de mes offres') + ).toBeInTheDocument() + expect( + screen.getByText('Accepter les réservations duo') + ).toBeInTheDocument() + + const isDuoCheckbox = screen.getByLabelText(/Accepter les réservations duo/) + expect(isDuoCheckbox).toBeInTheDocument() + expect(isDuoCheckbox).toBeChecked() + expect(screen.getByText('Prix de vente/place *')).toBeInTheDocument() + }) + + it('should update venue on submit', async () => { + props.venueProvider = { + ...props.venueProvider, + quantity: 2, + price: 2, + } + await renderAllocineProviderEdit(props) + + const editButton = screen.getByRole('button', { name: 'Paramétrer' }) + + expect(editButton).toBeInTheDocument() + await userEvent.click(editButton) + const editValidationButton = screen.getByRole('button', { + name: 'Modifier', + }) + + expect(editValidationButton).toBeInTheDocument() + await userEvent.click(editValidationButton) + expect(api.updateVenueProvider).toHaveBeenCalledTimes(1) + }) +}) diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/CinemaProviderEdit.spec.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/CinemaProviderEdit.spec.tsx new file mode 100644 index 00000000000..0145c516b17 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/CinemaProviderEdit.spec.tsx @@ -0,0 +1,74 @@ +import { screen, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' + +import { api } from 'apiClient/api' +import { defaultGetVenue } from 'utils/collectiveApiFactories' +import { defaultVenueProvider } from 'utils/individualApiFactories' +import { renderWithProviders } from 'utils/renderWithProviders' + +import { + CinemaProviderEdit, + CinemaProviderEditProps, +} from '../CinemaProviderEdit' + +const renderCinemaProviderEdit = async (props: CinemaProviderEditProps) => { + renderWithProviders() + + await waitFor(() => screen.getByText('Paramétrer')) +} + +describe('CinemaProviderEdit', () => { + let props: CinemaProviderEditProps + const offererId = 3 + + beforeEach(() => { + props = { + offererId: offererId, + venue: defaultGetVenue, + venueProvider: defaultVenueProvider, + } + vi.spyOn(api, 'updateVenueProvider').mockResolvedValue(defaultVenueProvider) + }) + + it('should display functional edit button', async () => { + await renderCinemaProviderEdit(props) + + const editButton = screen.getByRole('button', { name: 'Paramétrer' }) + expect(editButton).toBeInTheDocument() + + await userEvent.click(editButton) + expect( + screen.getByText('Modifier les paramètres de mes offres') + ).toBeInTheDocument() + expect( + screen.getByText('Accepter les réservations duo') + ).toBeInTheDocument() + + const isDuoCheckbox = screen.getByLabelText(/Accepter les réservations duo/) + expect(isDuoCheckbox).toBeInTheDocument() + expect(isDuoCheckbox).toBeChecked() + }) + + it('should update venue on submit', async () => { + await renderCinemaProviderEdit(props) + + const editButton = screen.getByRole('button', { name: 'Paramétrer' }) + expect(editButton).toBeInTheDocument() + + await userEvent.click(editButton) + expect( + screen.getByText('Modifier les paramètres de mes offres') + ).toBeInTheDocument() + expect( + screen.getByText('Accepter les réservations duo') + ).toBeInTheDocument() + + const isDuoCheckbox = screen.getByLabelText(/Accepter les réservations duo/) + expect(isDuoCheckbox).toBeInTheDocument() + expect(isDuoCheckbox).toBeChecked() + + const submitButton = screen.getByRole('button', { name: 'Modifier' }) + await userEvent.click(submitButton) + expect(api.updateVenueProvider).toHaveBeenCalledTimes(1) + }) +}) diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/VenueProviderCard.spec.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/VenueProviderCard.spec.tsx new file mode 100644 index 00000000000..9942fcc50f7 --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/VenueProviderList/__specs__/VenueProviderCard.spec.tsx @@ -0,0 +1,158 @@ +import { screen, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' + +import { defaultGetVenue } from 'utils/collectiveApiFactories' +import { defaultVenueProvider } from 'utils/individualApiFactories' +import { renderWithProviders } from 'utils/renderWithProviders' + +import { VenueProviderCard, VenueProviderCardProps } from '../VenueProviderCard' + +const renderVenueProviderCard = async (props: VenueProviderCardProps) => { + renderWithProviders() + + await waitFor(() => screen.getByText('Supprimer')) +} + +describe('VenueProviderCard', () => { + let props: VenueProviderCardProps + const offererId = 3 + + beforeEach(() => { + props = { + offererId: offererId, + venue: defaultGetVenue, + venueProvider: defaultVenueProvider, + } + }) + + describe('integration provider with on going sync', () => { + beforeEach(() => { + props.venueProvider.isActive = true + }) + + it('should display cinema provider info', async () => { + await renderVenueProviderCard(props) + + const providerNameDiv = screen.getByText('Ciné Office') + const providerLogo = screen.getByRole('img', { name: 'Ciné Office' }) + + expect(providerNameDiv).toBeInTheDocument() + expect(providerLogo).toBeInTheDocument() + }) + + it('should display edit button', async () => { + await renderVenueProviderCard(props) + + const cinemaProviderEditButton = screen.getByRole('button', { + name: 'Paramétrer', + }) + expect(cinemaProviderEditButton).toBeInTheDocument() + + await userEvent.click(cinemaProviderEditButton) + expect( + screen.getByText('Modifier les paramètres de mes offres') + ).toBeInTheDocument() + expect( + screen.getByText('Accepter les réservations duo') + ).toBeInTheDocument() + }) + + it('should display delete and pause buttons', async () => { + await renderVenueProviderCard(props) + + const cinemaProviderDeleteButton = screen.getByRole('button', { + name: 'Supprimer', + }) + const cinemaProviderPauseButton = screen.getByRole('button', { + name: 'Mettre en pause la synchronisation Mettre en pause', + }) + expect(cinemaProviderDeleteButton).toBeInTheDocument() + expect(cinemaProviderPauseButton).toBeInTheDocument() + }) + + it('should display on going sync info', async () => { + await renderVenueProviderCard(props) + + const onGoingSyncDiv = screen.getByText( + 'Importation en cours. Cette étape peut durer plusieurs dizaines de minutes.' + ) + const account = screen.getByText('Compte :') + const venueIdAtOfferProviderInfo = screen.getByText( + `${props.venueProvider.venueIdAtOfferProvider}` + ) + expect(onGoingSyncDiv).toBeInTheDocument() + expect(account).toBeInTheDocument() + expect(venueIdAtOfferProviderInfo).toBeInTheDocument() + }) + }) + + describe('synched integration provider', () => { + beforeEach(() => { + props.venueProvider.lastSyncDate = '2021-08-16T00:00:00Z' + }) + + it('should display sync info', async () => { + await renderVenueProviderCard(props) + + const syncDiv = screen.getByText('Dernière synchronisation :') + const lastSyncDate = screen.getByText('16/08/2021 à 02:00') + const account = screen.getByText('Compte :') + const venueIdAtOfferProviderInfo = screen.getByText( + `${props.venueProvider.venueIdAtOfferProvider}` + ) + expect(syncDiv).toBeInTheDocument() + expect(lastSyncDate).toBeInTheDocument() + expect(account).toBeInTheDocument() + expect(venueIdAtOfferProviderInfo).toBeInTheDocument() + }) + }) + + describe('Allociné provider', () => { + beforeEach(() => { + props.venueProvider.provider.name = 'Allociné' + }) + + it('should display allociné spécific info', async () => { + await renderVenueProviderCard(props) + + const providerNameDiv = screen.getByText('Allociné') + const providerLogo = screen.getByRole('img', { name: 'Allociné' }) + + expect(providerNameDiv).toBeInTheDocument() + expect(providerLogo).toBeInTheDocument() + }) + + it('should display edit button', async () => { + await renderVenueProviderCard(props) + + const allocineEditButton = screen.getByRole('button', { + name: 'Paramétrer', + }) + expect(allocineEditButton).toBeInTheDocument() + await userEvent.click(allocineEditButton) + expect( + screen.getByText('Modifier les paramètres de mes offres') + ).toBeInTheDocument() + expect( + screen.getByText('Accepter les réservations duo') + ).toBeInTheDocument() + expect(screen.getByText('Prix de vente/place *')).toBeInTheDocument() + }) + }) + + describe('public API provider', () => { + beforeEach(() => { + props.venueProvider.provider.hasOffererProvider = true + }) + + it('should display creation date info', async () => { + await renderVenueProviderCard(props) + + const creationDate = screen.getByText('15/08/2021 à 02:00') + const creationDateText = screen.getByText("Date d'ajout :") + + expect(creationDate).toBeInTheDocument() + expect(creationDateText).toBeInTheDocument() + }) + }) +}) diff --git a/pro/src/pages/VenueSettings/VenueProvidersManager/__specs__/AddVenueProviderButton.spec.tsx b/pro/src/pages/VenueSettings/VenueProvidersManager/__specs__/AddVenueProviderButton.spec.tsx new file mode 100644 index 00000000000..4468218fa0c --- /dev/null +++ b/pro/src/pages/VenueSettings/VenueProvidersManager/__specs__/AddVenueProviderButton.spec.tsx @@ -0,0 +1,92 @@ +import { screen, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' + +import { api } from 'apiClient/api' +import { defaultGetVenue } from 'utils/collectiveApiFactories' +import { renderWithProviders } from 'utils/renderWithProviders' + +import { + AddVenueProviderButton, + AddVenueProviderButtonProps, +} from '../AddVenueProviderButton' + +const renderAddVenueProviderButton = async ( + props: AddVenueProviderButtonProps +) => { + renderWithProviders() + await waitFor(() => { + screen.getByText('Sélectionner un logiciel') + }) +} + +describe('AddVenueProviderButton', () => { + let props: AddVenueProviderButtonProps + + beforeEach(() => { + props = { + venue: defaultGetVenue, + linkedProvidersIds: [], + } + + vi.spyOn(api, 'getProvidersByVenue').mockResolvedValue([ + { + name: 'Ciné Office', + id: 12, + hasOffererProvider: false, + isActive: true, + }, + { + name: 'Allociné', + id: 13, + hasOffererProvider: false, + isActive: true, + }, + { + name: 'Ticket Buster', + id: 14, + hasOffererProvider: true, + isActive: true, + }, + ]) + }) + + it('should display the add button', async () => { + await renderAddVenueProviderButton(props) + + const addVenueProviderButton = screen.getByText('Sélectionner un logiciel') + expect(addVenueProviderButton).toBeInTheDocument() + }) + + it('should display the providers on click', async () => { + await renderAddVenueProviderButton(props) + + const addVenueProviderButton = screen.getByText('Sélectionner un logiciel') + expect(addVenueProviderButton).toBeInTheDocument() + await userEvent.click(addVenueProviderButton) + const options = screen.getAllByRole('option') + expect(options.length).toBe(4) + + expect(screen.getByRole('option', { name: 'Allociné' })).toBeInTheDocument() + expect( + screen.getByRole('option', { name: 'Ticket Buster' }) + ).toBeInTheDocument() + expect( + screen.getByRole('option', { name: 'Ciné Office' }) + ).toBeInTheDocument() + }) + + it('should hide linked providers', async () => { + await renderAddVenueProviderButton({ + ...props, + linkedProvidersIds: [12, 14], + }) + + const addVenueProviderButton = screen.getByText('Sélectionner un logiciel') + expect(addVenueProviderButton).toBeInTheDocument() + await userEvent.click(addVenueProviderButton) + const options = screen.getAllByRole('option') + expect(options.length).toBe(2) + + expect(screen.getByRole('option', { name: 'Allociné' })).toBeInTheDocument() + }) +}) diff --git a/pro/src/pages/VenueSettings/__specs__/VenueSettingsFormScreen.spec.tsx b/pro/src/pages/VenueSettings/__specs__/VenueSettingsFormScreen.spec.tsx index a00c52060ee..80d3041974d 100644 --- a/pro/src/pages/VenueSettings/__specs__/VenueSettingsFormScreen.spec.tsx +++ b/pro/src/pages/VenueSettings/__specs__/VenueSettingsFormScreen.spec.tsx @@ -1,9 +1,13 @@ import { screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' +import { api } from 'apiClient/api' import { VenueTypeResponseModel } from 'apiClient/v1' import { defaultGetVenue } from 'utils/collectiveApiFactories' -import { defaultGetOffererResponseModel } from 'utils/individualApiFactories' +import { + defaultGetOffererResponseModel, + defaultVenueProvider, +} from 'utils/individualApiFactories' import { renderWithProviders } from 'utils/renderWithProviders' import { sharedCurrentUserFactory } from 'utils/storeFactories' @@ -14,7 +18,7 @@ const venueTypes: VenueTypeResponseModel[] = [ { id: 'SCIENTIFIC_CULTURE', label: 'Culture scientifique' }, ] -const renderForm = () => { +const renderForm = async () => { renderWithProviders( { { label: 'Lieu de spectacle', value: 'show' }, { label: 'Lieu de pratique', value: 'practice' }, ]} - venueProviders={[]} + venueProviders={[ + defaultVenueProvider, + { + id: 2, + isActive: true, + isFromAllocineProvider: true, + lastSyncDate: undefined, + venueId: 2, + dateCreated: '2021-08-15T00:00:00Z', + venueIdAtOfferProvider: 'allocine_id_1', + provider: { + name: 'Allociné', + id: 13, + hasOffererProvider: false, + isActive: true, + }, + quantity: 0, + isDuo: true, + price: 0, + }, + ]} venue={defaultGetVenue} initialValues={{ addressAutocomplete: '123 Rue Principale, Ville Exemple', @@ -55,19 +79,58 @@ const renderForm = () => { user: sharedCurrentUserFactory(), } ) + + await waitFor(() => { + screen.getByText('Paramètres généraux') + }) } describe('VenueSettingsFormScreen', () => { + beforeEach(() => { + vi.spyOn(api, 'getProvidersByVenue').mockResolvedValue([ + { + name: 'Ciné Office', + id: 12, + hasOffererProvider: false, + isActive: true, + }, + { + name: 'Allociné', + id: 13, + hasOffererProvider: false, + isActive: true, + }, + { + name: 'Ticket Buster', + id: 14, + hasOffererProvider: true, + isActive: true, + }, + ]) + }) + it('should display the route leaving guard when leaving without saving', async () => { - renderForm() + await renderForm() await userEvent.type(screen.getByLabelText('Nom public'), 'test') await userEvent.click(screen.getByText('Annuler')) - await waitFor(() => { - expect( - screen.getByText('Les informations non enregistrées seront perdues') - ).toBeInTheDocument() + expect( + screen.getByText('Les informations non enregistrées seront perdues') + ).toBeInTheDocument() + }) + + it('should display the venue provider cards & add provider button', async () => { + await renderForm() + + const cineOfficeCard = screen.getByText('Ciné Office') + const allocineCard = screen.getByText('Allociné') + const addProviderButton = screen.getByRole('button', { + name: 'Sélectionner un logiciel', }) + + expect(cineOfficeCard).toBeInTheDocument() + expect(allocineCard).toBeInTheDocument() + expect(addProviderButton).toBeInTheDocument() }) })