Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PC-31316)[PRO] feat: enable multiprovider set-up in venue settings #13883

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class VenueProviderResponse(BaseModel):
isDuo: bool | None
isFromAllocineProvider: bool
lastSyncDate: datetime | None
dateCreated: datetime
price: float | None
provider: ProviderResponse
quantity: int | None
Expand Down
2 changes: 2 additions & 0 deletions api/tests/routes/pro/get_venue_providers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def test_get_list_with_valid_venue_id(self, client):
venue__managingOfferer=user_offerer.offerer,
provider=titelive_things_provider,
lastSyncDate=datetime(2021, 8, 16),
dateCreated=datetime(2021, 8, 15),
)

auth_request = client.with_session_auth(email=user_offerer.user.email)
Expand All @@ -35,6 +36,7 @@ def test_get_list_with_valid_venue_id(self, client):
assert response.json["venue_providers"][0].get("id") == venue_provider.id
assert response.json["venue_providers"][0].get("venueId") == venue_provider.venue.id
assert response.json["venue_providers"][0].get("lastSyncDate") == "2021-08-16T00:00:00Z"
assert response.json["venue_providers"][0].get("dateCreated") == "2021-08-15T00:00:00Z"

@pytest.mark.usefixtures("db_session")
def test_get_list_that_include_allocine_with_valid_venue_id(self, client):
Expand Down
1 change: 1 addition & 0 deletions api/tests/routes/pro/post_venue_provider_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def test_when_no_regression_on_format(
# Then
assert response.status_code == 201
assert set(response.json.keys()) == {
"dateCreated",
"id",
"isActive",
"isDuo",
Expand Down
1 change: 1 addition & 0 deletions pro/src/apiClient/v1/models/VenueProviderResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/* eslint-disable */
import type { ProviderResponse } from './ProviderResponse';
export type VenueProviderResponse = {
dateCreated: string;
id: number;
isActive: boolean;
isDuo?: boolean | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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 = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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%;
}
Original file line number Diff line number Diff line change
@@ -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[]
Expand All @@ -16,27 +18,31 @@ export const OffersSynchronization = ({
venueProviders,
}: OffersSynchronization) => {
return (
<FormLayout mediumWidthActions>
<FormLayout>
<FormLayout.Section
title="Gestion des synchronisations"
description="Vous pouvez synchroniser votre lieu avec un logiciel tiers afin de faciliter la gestion de vos offres et de vos réservations."
>
<FormLayout.Row
className={cn(style['venue-providers'], 'form-layout-actions')}
>
{venueProviders.map((venueProvider) => (
<VenueProviderCard
key={venueProvider.id}
venueProvider={venueProvider}
venue={venue}
venueDepartmentCode={venue.departementCode}
offererId={venue.managingOfferer.id}
/>
))}
</FormLayout.Row>
<FormLayout.Row>
{venueProviders.length > 0 ? (
<ul>
{venueProviders.map((venueProvider) => (
<VenueProviderItem
key={venueProvider.id}
venueProvider={venueProvider}
venue={venue}
venueDepartmentCode={venue.departementCode}
offererId={venue.managingOfferer.id}
/>
))}
</ul>
) : (
<AddVenueProviderButton venue={venue} />
)}
<AddVenueProviderButton
venue={venue}
linkedProvidersIds={venueProviders.map(
({ provider }) => provider.id
)}
/>
</FormLayout.Row>
</FormLayout.Section>
</FormLayout>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react'
import { useSWRConfig } from 'swr'

import { api } from 'apiClient/api'
import { getHumanReadableApiError } from 'apiClient/helpers'
import {
GetVenueResponseModel,
PostVenueProviderBody,
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 { FormValuesProps } from '../AllocineProviderForm/AllocineProviderForm'

import { AllocineProviderFormDialog } from './AllocineProviderFormDialog'
import style from './ProviderActionButton.module.scss'

export interface AllocineProviderEditProps {
venueProvider: VenueProviderResponse
venue: GetVenueResponseModel
offererId: number
}

export const AllocineProviderEdit = ({
venueProvider,
venue,
offererId,
}: AllocineProviderEditProps): JSX.Element => {
const notification = useNotification()
const { mutate } = useSWRConfig()

const editVenueProvider = async (
payload: PostVenueProviderBody
): Promise<boolean> => {
try {
await api.updateVenueProvider(payload)
await mutate([GET_VENUE_PROVIDERS_QUERY_KEY, venue.id])
notification.success(
"Les modifications ont bien été importées et s'appliqueront aux nouvelles séances créées."
)
return true
} catch (error) {
notification.error(getHumanReadableApiError(error))
return false
}
}

const onConfirmDialog = async (
payload: PostVenueProviderBody
): Promise<boolean> => {
const isSuccess = await editVenueProvider({
...payload,
isActive: venueProvider.isActive,
})

return isSuccess
}

const initialValues: FormValuesProps = {
price: venueProvider.price ? venueProvider.price : '',
quantity: venueProvider.quantity ? Number(venueProvider.quantity) : '',
isDuo: venueProvider.isDuo ?? false,
}

return (
<DialogBuilder
trigger={
<Button variant={ButtonVariant.TERNARY}>
<SvgIcon
src={fullEditIcon}
alt=""
className={style['provider-action-icon']}
/>
Paramétrer
</Button>
}
>
<AllocineProviderFormDialog
initialValues={initialValues}
onConfirm={onConfirmDialog}
providerId={venueProvider.provider.id}
venueId={venueProvider.venueId}
offererId={offererId}
/>
</DialogBuilder>
)
}

This file was deleted.

Loading
Loading