From 98aab5008a017dd1b45dd0d8db70faae1333caa9 Mon Sep 17 00:00:00 2001 From: gmeigniez-pass <139768952+gmeigniez-pass@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:02:30 +0100 Subject: [PATCH 1/2] (PC-34043)[PRO] feat: Let users create a digital offer on a physical venue. --- .../commons/__specs__/utils.spec.tsx | 100 ++++++---------- .../IndividualOfferDetails/commons/utils.ts | 54 ++------- .../components/DetailsForm/DetailsForm.tsx | 32 +++-- .../IndividualOfferDetailsScreen.spec.tsx | 37 +++++- .../IndividualOfferDetailsScreen.tsx | 25 ++-- .../UsefulInformationForm.tsx | 2 +- .../__specs__/getFilteredVenueList.spec.ts | 112 ------------------ .../commons/getFilteredVenueList.ts | 39 ------ 8 files changed, 112 insertions(+), 289 deletions(-) delete mode 100644 pro/src/pages/IndividualOffer/commons/__specs__/getFilteredVenueList.spec.ts delete mode 100644 pro/src/pages/IndividualOffer/commons/getFilteredVenueList.ts diff --git a/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/__specs__/utils.spec.tsx b/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/__specs__/utils.spec.tsx index b6e6791c7d7..3f0586ef258 100644 --- a/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/__specs__/utils.spec.tsx +++ b/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/__specs__/utils.spec.tsx @@ -12,10 +12,9 @@ import { buildShowSubTypeOptions, completeSubcategoryConditionalFields, buildSubcategoryOptions, - buildVenueOptions, + formatVenuesOptions, deSerializeDurationMinutes, serializeExtraData, - setDefaultInitialValues, setDefaultInitialValuesFromOffer, setFormReadOnlyFields, serializeDetailsPostData, @@ -150,69 +149,46 @@ describe('buildSubcategoryFields', () => { }) }) -describe('buildVenueOptions', () => { - it('should build venues options', () => { - expect( - buildVenueOptions( - [venueListItemFactory({}), venueListItemFactory({})], - false, - false - ) - ).toStrictEqual([ - { - label: 'Sélectionner le partenaire', - value: '', - }, - { - label: 'Le nom du lieu 1', - value: '1', - }, - { - label: 'Le nom du lieu 2', - value: '2', - }, - ]) +describe('formatVenuesOptions', () => { + it('should format venues as options', () => { + const formattedVenuesOptions = formatVenuesOptions( + [ + venueListItemFactory({ isVirtual: false, id: 10 }), + venueListItemFactory({ isVirtual: false, id: 3 }), + ], + false + ) + expect(formattedVenuesOptions).toEqual( + expect.arrayContaining([expect.objectContaining({ value: '10' })]) + ) + + expect(formattedVenuesOptions).toEqual( + expect.arrayContaining([expect.objectContaining({ value: '3' })]) + ) }) -}) -describe('setDefaultInitialValues', () => { - it('should set default initial values', () => { - expect( - setDefaultInitialValues({ - filteredVenues: [venueListItemFactory({}), venueListItemFactory({})], - areSuggestedSubcategoriesUsed: false, - }) - ).toStrictEqual({ - author: '', - categoryId: '', - description: '', - durationMinutes: '', - ean: '', - gtl_id: '', - name: '', - performer: '', - showSubType: '', - showType: '', - speaker: '', - stageDirector: '', - subcategoryConditionalFields: [], - suggestedSubcategory: '', - subcategoryId: '', - venueId: '', - visa: '', - productId: '', - callId: '', - }) + it('should exclude digital venues if a physcal venue is available', () => { + const formattedVenuesOptions = formatVenuesOptions( + [ + venueListItemFactory({ isVirtual: true, id: 10 }), + venueListItemFactory({ isVirtual: false, id: 3 }), + ], + false + ) + expect(formattedVenuesOptions).not.toEqual( + expect.arrayContaining([expect.objectContaining({ value: '10' })]) + ) - expect( - setDefaultInitialValues({ - filteredVenues: [venueListItemFactory({ id: 666 })], - areSuggestedSubcategoriesUsed: false, - }) - ).toStrictEqual( - expect.objectContaining({ - venueId: '666', - }) + expect(formattedVenuesOptions).toEqual( + expect.arrayContaining([expect.objectContaining({ value: '3' })]) + ) + + const formattedDigitalVenuesOptions = formatVenuesOptions( + [venueListItemFactory({ isVirtual: true, id: 10 })], + true + ) + expect(formattedDigitalVenuesOptions).toEqual( + expect.arrayContaining([expect.objectContaining({ value: '10' })]) ) }) }) diff --git a/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/utils.ts b/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/utils.ts index 58c551d5c90..dcb1b0365cb 100644 --- a/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/utils.ts +++ b/pro/src/pages/IndividualOffer/IndividualOfferDetails/commons/utils.ts @@ -179,58 +179,22 @@ export const onSubcategoryChange = async ({ }) } -export const buildVenueOptions = ( +export const formatVenuesOptions = ( venues: VenueListItemResponseModel[], - areSuggestedCategoriesEnabled: boolean, - isOfferAddressEnabled: boolean + isOnline: boolean ) => { - let venueOptions = venues - .filter((venue) => !areSuggestedCategoriesEnabled || !venue.isVirtual) + // We want to display the virtual venues only if there are no physical venues available + // We also want to prevent selecting a virtual venue for a physical offer form + const hasAtLeastOnePhysicalVenue = venues.some((v) => !v.isVirtual) + return venues + .filter((venue) => + hasAtLeastOnePhysicalVenue || !isOnline ? !venue.isVirtual : true + ) .map((venue) => ({ value: venue.id.toString(), label: computeVenueDisplayName(venue), })) .sort((a, b) => a.label.localeCompare(b.label, 'fr')) - if (venueOptions.length > 1) { - venueOptions = [ - { - value: '', - label: isOfferAddressEnabled - ? 'Sélectionner la structure' - : 'Sélectionner le partenaire', - }, - ...venueOptions, - ] - } - - return venueOptions -} - -type SetDefaultInitialValuesProps = { - filteredVenues: VenueListItemResponseModel[] - areSuggestedSubcategoriesUsed: boolean -} - -export function setDefaultInitialValues({ - filteredVenues, - areSuggestedSubcategoriesUsed, -}: SetDefaultInitialValuesProps): DetailsFormValues { - let venueId = '' - - const venues = areSuggestedSubcategoriesUsed - ? filteredVenues.filter((v) => !v.isVirtual) - : filteredVenues - - if (venues.length === 1) { - venueId = String(venues[0].id) - } else if (venues.length === 0 && filteredVenues.length > 0) { - venueId = String(filteredVenues[0].id) - } - - return { - ...DEFAULT_DETAILS_FORM_VALUES, - venueId, - } } type SetDefaultInitialValuesFromOfferProps = { diff --git a/pro/src/pages/IndividualOffer/IndividualOfferDetails/components/DetailsForm/DetailsForm.tsx b/pro/src/pages/IndividualOffer/IndividualOfferDetails/components/DetailsForm/DetailsForm.tsx index 681d904c888..3002cbcf0e6 100644 --- a/pro/src/pages/IndividualOffer/IndividualOfferDetails/components/DetailsForm/DetailsForm.tsx +++ b/pro/src/pages/IndividualOffer/IndividualOfferDetails/components/DetailsForm/DetailsForm.tsx @@ -7,7 +7,6 @@ import { CategoryResponseModel, SubcategoryResponseModel, SuggestedSubcategoriesResponseModel, - VenueListItemResponseModel, } from 'apiClient/v1' import { useIndividualOfferContext } from 'commons/context/IndividualOfferContext/IndividualOfferContext' import { CATEGORY_STATUS } from 'commons/core/Offers/constants' @@ -19,10 +18,7 @@ import { OnImageUploadArgs } from 'components/ImageUploader/components/ButtonIma import fullMoreIcon from 'icons/full-more.svg' import { DEFAULT_DETAILS_FORM_VALUES } from 'pages/IndividualOffer/IndividualOfferDetails/commons/constants' import { DetailsFormValues } from 'pages/IndividualOffer/IndividualOfferDetails/commons/types' -import { - buildVenueOptions, - isSubCategoryCD, -} from 'pages/IndividualOffer/IndividualOfferDetails/commons/utils' +import { isSubCategoryCD } from 'pages/IndividualOffer/IndividualOfferDetails/commons/utils' import { Callout } from 'ui-kit/Callout/Callout' import { CalloutVariant } from 'ui-kit/Callout/types' import { Select } from 'ui-kit/form/Select/Select' @@ -38,7 +34,7 @@ const DEBOUNCE_TIME_BEFORE_REQUEST = 400 type DetailsFormProps = { isEanSearchDisplayed: boolean isProductBased: boolean - filteredVenues: VenueListItemResponseModel[] + venuesOptions: { label: string; value: string }[] filteredCategories: CategoryResponseModel[] filteredSubcategories: SubcategoryResponseModel[] readonlyFields: string[] @@ -50,7 +46,7 @@ type DetailsFormProps = { export const DetailsForm = ({ isEanSearchDisplayed, isProductBased, - filteredVenues, + venuesOptions, filteredCategories, filteredSubcategories, readonlyFields: readOnlyFields, @@ -71,12 +67,6 @@ export const DetailsForm = ({ const { offer, subCategories } = useIndividualOfferContext() const offerAddressEnabled = useActiveFeature('WIP_ENABLE_OFFER_ADDRESS') - const venueOptions = buildVenueOptions( - filteredVenues, - areSuggestedSubcategoriesUsed, - offerAddressEnabled - ) - async function getSuggestedSubcategories() { if (!areSuggestedSubcategoriesUsed && !offer) { return @@ -131,14 +121,14 @@ export const DetailsForm = ({ subcategoryId !== DEFAULT_DETAILS_FORM_VALUES.subcategoryId const showAddVenueBanner = - !areSuggestedSubcategoriesUsed && venueOptions.length === 0 + !areSuggestedSubcategoriesUsed && venuesOptions.length === 0 const isSuggestedSubcategoryDisplayed = areSuggestedSubcategoriesUsed && !offer && !isProductBased const showOtherAddVenueBanner = areSuggestedSubcategoriesUsed && - venueOptions.length === 0 && + venuesOptions.length === 0 && subcategory?.onlineOfflinePlatform === CATEGORY_STATUS.OFFLINE return ( @@ -166,12 +156,18 @@ export const DetailsForm = ({ )} {!showAddVenueBanner && ( <> - {venueOptions.length > 1 && ( + {venuesOptions.length > 1 && (