diff --git a/src/client/components/features/ServicesJeunes/ServicesJeunes.module.scss b/src/client/components/features/ServicesJeunes/ServicesJeunes.module.scss index bd14c6b94a..88cb897a4d 100644 --- a/src/client/components/features/ServicesJeunes/ServicesJeunes.module.scss +++ b/src/client/components/features/ServicesJeunes/ServicesJeunes.module.scss @@ -5,10 +5,6 @@ } .container { - @include utilities.media(medium) { - padding-top: 2rem; - } - & div > ul > li { flex-grow: 1; flex-basis: 100%; diff --git a/src/client/components/features/ServicesJeunes/ServicesJeunes.tsx b/src/client/components/features/ServicesJeunes/ServicesJeunes.tsx index 748e17e898..ca189f5239 100644 --- a/src/client/components/features/ServicesJeunes/ServicesJeunes.tsx +++ b/src/client/components/features/ServicesJeunes/ServicesJeunes.tsx @@ -24,13 +24,16 @@ export function ServicesJeunes(props: ServicesJeunesProps) { return ( <> - + { props.cardList.length > 0 ? ( + + ) + :

Aucun service jeune disponible. Veuillez modifier votre sélection.

}
); diff --git a/src/client/components/useRouter.mock.ts b/src/client/components/useRouter.mock.ts index cf8485db3d..2987f26c7e 100644 --- a/src/client/components/useRouter.mock.ts +++ b/src/client/components/useRouter.mock.ts @@ -17,7 +17,10 @@ interface MockUseRouter { isReady?: boolean } -export function mockUseRouter({ asPath = '', pathname = '', query = {}, route = '', prefetch = jest.fn(), push = jest.fn(), reload = jest.fn(), replace = jest.fn(), back = jest.fn(), isReady = true }: MockUseRouter) { +export function mockUseRouter({ asPath = '', pathname = '', query = {}, + route = '', prefetch = jest.fn(), push = jest.fn(), + reload = jest.fn(), replace = jest.fn(), back = jest.fn(), + isReady = true }: MockUseRouter) { useRouter.mockImplementation(() => ({ asPath, back, diff --git a/src/pages/services-jeunes/index.module.scss b/src/pages/services-jeunes/index.module.scss index 6d86c203c4..a12dacf24b 100644 --- a/src/pages/services-jeunes/index.module.scss +++ b/src/pages/services-jeunes/index.module.scss @@ -1,8 +1,21 @@ @use "@styles/utilities-deprecated"; +@use "@styles/utilities"; .section { padding-inline: 1rem; margin-bottom: 3rem; + + @include utilities.media(medium) { + padding-inline: 5rem; + } +} + +.selectCategorie { + margin-bottom: 1rem; +} + +.etiquetteList { + margin-bottom: 2rem; } .cartesActualitesList { diff --git a/src/pages/services-jeunes/index.page.test.tsx b/src/pages/services-jeunes/index.page.test.tsx index f8f684ccef..0a6a6d16aa 100644 --- a/src/pages/services-jeunes/index.page.test.tsx +++ b/src/pages/services-jeunes/index.page.test.tsx @@ -13,6 +13,7 @@ import { aManualAnalyticsService } from '~/client/services/analytics/analytics.s import ServicesJeunePage, { getStaticProps } from '~/pages/services-jeunes/index.page'; import { createFailure, createSuccess } from '~/server/errors/either'; import { ErreurMetier } from '~/server/errors/erreurMetier.types'; +import { ServiceJeune } from '~/server/services-jeunes/domain/servicesJeunes'; import { aServiceJeune, aServiceJeuneList } from '~/server/services-jeunes/domain/servicesJeunes.fixture'; import { dependencies } from '~/server/start'; @@ -30,11 +31,19 @@ jest.mock('~/server/start', () => ({ }, })); +jest.mock('next/navigation', () => ({ + usePathname: jest.fn().mockReturnValue('/services-jeunes'), + useSearchParams: () => ({ + getAll: jest.fn().mockReturnValue([]), + }), +})); + describe('Page Services Jeunes', () => { beforeEach(() => { mockSmallScreen(); mockUseRouter({}); mockScrollIntoView(); + }); afterEach(() => { jest.clearAllMocks(); @@ -131,83 +140,220 @@ describe('Page Services Jeunes', () => { }); describe('Si des services jeunes sont récupérés', () => { - it('affiche au maximum 6 services initialement', () => { - // Given - const serviceJeuneList = [ - aServiceJeune({ titre: 'service 1' }), - aServiceJeune({ titre: 'service 2' }), - aServiceJeune({ titre: 'service 3' }), - aServiceJeune({ titre: 'service 4' }), - aServiceJeune({ titre: 'service 5' }), - aServiceJeune({ titre: 'service 6' }), - aServiceJeune({ titre: 'service 7' }), - ]; - const analyticsService = aManualAnalyticsService(); - - // When - render( - - - , - ); - - // Then - const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); - const servicesJeunesList = within(mesuresJeunesSection).getAllByRole('listitem'); - expect(servicesJeunesList.length).toBe(6); + describe('Sélection des filtres', () => { + describe('aucun filtre n’est renseigné', () => { + it('affiche l’ensemble des types de services', () => { + // Given + const serviceJeuneList = [ + aServiceJeune({ categorie: ServiceJeune.Categorie.ACCOMPAGNEMENT, titre: 'service 1' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.AIDES_FINANCIERES, titre: 'service 2' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENGAGEMENT, titre: 'service 3' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENTREE_VIE_PROFESSIONELLE, titre: 'service 4' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.LOGEMENT, titre: 'service 5' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ORIENTATION_FORMATION, titre: 'service 6' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const servicesJeunesList = within(mesuresJeunesSection).getAllByRole('list')[1]; + const servicesJeunesResultats = within(servicesJeunesList).getAllByRole('listitem'); + expect(servicesJeunesResultats.length).toBe(6); + }); + }); + describe('au moins un filtre est renseigné', () => { + beforeEach(() => { + mockUseRouter({ query : { filtre : ['Accompagnement', 'Logement', 'Engagement'] } }); + }); + it('affiche la liste des filtres dans des étiquettes', () => { + // Given + const serviceJeuneList = [ + aServiceJeune({ categorie: ServiceJeune.Categorie.ACCOMPAGNEMENT, titre: 'service 1' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.AIDES_FINANCIERES, titre: 'service 2' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENGAGEMENT, titre: 'service 3' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENTREE_VIE_PROFESSIONELLE, titre: 'service 4' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.LOGEMENT, titre: 'service 5' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ORIENTATION_FORMATION, titre: 'service 6' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const servicesJeunesList = within(mesuresJeunesSection).getAllByRole('list')[0]; + const servicesJeunesEtiquettes = within(servicesJeunesList).getAllByRole('listitem'); + expect(servicesJeunesEtiquettes.length).toBe(3); + }); + it('supprime le filtre au clic sur son étiquette', async () => { + // Given + const routerPush = jest.fn(); + mockUseRouter({ push: routerPush, query: { filtre : ['Accompagnement', 'Logement', 'Engagement'] } }); + const user = userEvent.setup(); + + const serviceJeuneList = [ + aServiceJeune({ categorie: ServiceJeune.Categorie.ACCOMPAGNEMENT, titre: 'service 1' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.AIDES_FINANCIERES, titre: 'service 2' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + const mesuresJeunesSectionBefore = screen.getByRole('region', { name: 'les services jeunes' }); + const servicesJeunesListBefore = within(mesuresJeunesSectionBefore).getAllByRole('list')[0]; + const servicesJeunesEtiquettesBefore = within(servicesJeunesListBefore).getAllByRole('listitem'); + expect(servicesJeunesEtiquettesBefore.length).toBe(3); + + const resetButton = within(servicesJeunesEtiquettesBefore[0]).getByRole('button'); + await user.click(resetButton); + + // Then + expect(routerPush).toHaveBeenCalledTimes(1); + expect(routerPush).toHaveBeenCalledWith(expect.stringContaining('filtre=Logement')); + expect(routerPush).toHaveBeenCalledWith(expect.stringContaining('filtre=Engagement')); + }); + it('affiche les services des catégories filtrées', () => { + const serviceJeuneList = [ + aServiceJeune({ categorie: ServiceJeune.Categorie.ACCOMPAGNEMENT, titre: 'service 1' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.AIDES_FINANCIERES, titre: 'service 2' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENGAGEMENT, titre: 'service 3' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ENTREE_VIE_PROFESSIONELLE, titre: 'service 4' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.LOGEMENT, titre: 'service 5' }), + aServiceJeune({ categorie: ServiceJeune.Categorie.ORIENTATION_FORMATION, titre: 'service 6' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const servicesJeunesList = within(mesuresJeunesSection).getAllByRole('list')[1]; + const servicesJeunesEtiquettes = within(servicesJeunesList).getAllByRole('listitem'); + expect(servicesJeunesEtiquettes.length).toBe(3); + }); + }); }); - it('affiche un bouton voir plus quand il y a plus de 6 services', () => { + describe('Liste de résultats', () => { + it('affiche un message d’erreur quand aucun service n’est disponible', () => { + // Given + mockUseRouter({ query: { filtre : ['Accompagnement'] } }); + const serviceJeuneList = [ + aServiceJeune({ categorie: ServiceJeune.Categorie.AIDES_FINANCIERES, titre: 'service 2' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const messageErreur = within(mesuresJeunesSection).getByText('Aucun service jeune disponible. Veuillez modifier votre sélection.'); + expect(messageErreur).toBeVisible(); + }); + it('affiche au maximum 6 services initialement', () => { // Given - const serviceJeuneList = [ - aServiceJeune({ titre: 'service 1' }), - aServiceJeune({ titre: 'service 2' }), - aServiceJeune({ titre: 'service 3' }), - aServiceJeune({ titre: 'service 4' }), - aServiceJeune({ titre: 'service 5' }), - aServiceJeune({ titre: 'service 6' }), - aServiceJeune({ titre: 'service 7' }), - ]; - const analyticsService = aManualAnalyticsService(); - - // When - render( - - - , - ); - - // Then - const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); - const voirPlusDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir plus de services conçus pour les jeunes' }); - expect(voirPlusDeServicesJeunesBouton).toBeVisible(); - }); - it('affiche un bouton voir moins quand plus de 6 services jeunes sont visibles', async () => { + const serviceJeuneList = [ + aServiceJeune({ titre: 'service 1' }), + aServiceJeune({ titre: 'service 2' }), + aServiceJeune({ titre: 'service 3' }), + aServiceJeune({ titre: 'service 4' }), + aServiceJeune({ titre: 'service 5' }), + aServiceJeune({ titre: 'service 6' }), + aServiceJeune({ titre: 'service 7' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const servicesJeunesList = within(mesuresJeunesSection).getAllByRole('list')[1]; + const servicesJeunesResultats = within(servicesJeunesList).getAllByRole('listitem'); + expect(servicesJeunesResultats.length).toBe(6); + }); + it('affiche un bouton voir plus quand il y a plus de 6 services', () => { // Given - const serviceJeuneList = [ - aServiceJeune({ titre: 'service 1' }), - aServiceJeune({ titre: 'service 2' }), - aServiceJeune({ titre: 'service 3' }), - aServiceJeune({ titre: 'service 4' }), - aServiceJeune({ titre: 'service 5' }), - aServiceJeune({ titre: 'service 6' }), - aServiceJeune({ titre: 'service 7' }), - ]; - const analyticsService = aManualAnalyticsService(); - - render( - - - , - ); - const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); - const voirPlusDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir plus de services conçus pour les jeunes' }); - - // When - await userEvent.click(voirPlusDeServicesJeunesBouton); - - // Then - const voirMoinsDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir moins de services conçus pour les jeunes' }); - expect(voirMoinsDeServicesJeunesBouton).toBeVisible(); + const serviceJeuneList = [ + aServiceJeune({ titre: 'service 1' }), + aServiceJeune({ titre: 'service 2' }), + aServiceJeune({ titre: 'service 3' }), + aServiceJeune({ titre: 'service 4' }), + aServiceJeune({ titre: 'service 5' }), + aServiceJeune({ titre: 'service 6' }), + aServiceJeune({ titre: 'service 7' }), + ]; + const analyticsService = aManualAnalyticsService(); + + // When + render( + + + , + ); + + // Then + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const voirPlusDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir plus de services conçus pour les jeunes' }); + expect(voirPlusDeServicesJeunesBouton).toBeVisible(); + }); + it('affiche un bouton voir moins quand plus de 6 services jeunes sont visibles', async () => { + // Given + const serviceJeuneList = [ + aServiceJeune({ titre: 'service 1' }), + aServiceJeune({ titre: 'service 2' }), + aServiceJeune({ titre: 'service 3' }), + aServiceJeune({ titre: 'service 4' }), + aServiceJeune({ titre: 'service 5' }), + aServiceJeune({ titre: 'service 6' }), + aServiceJeune({ titre: 'service 7' }), + ]; + const analyticsService = aManualAnalyticsService(); + + render( + + + , + ); + const mesuresJeunesSection = screen.getByRole('region', { name: 'les services jeunes' }); + const voirPlusDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir plus de services conçus pour les jeunes' }); + + // When + await userEvent.click(voirPlusDeServicesJeunesBouton); + + // Then + const voirMoinsDeServicesJeunesBouton = within(mesuresJeunesSection).getByRole('button', { name: 'Voir moins de services conçus pour les jeunes' }); + expect(voirMoinsDeServicesJeunesBouton).toBeVisible(); + }); }); }); }); diff --git a/src/pages/services-jeunes/index.page.tsx b/src/pages/services-jeunes/index.page.tsx index 3bad971f3b..1057ddd8e0 100644 --- a/src/pages/services-jeunes/index.page.tsx +++ b/src/pages/services-jeunes/index.page.tsx @@ -1,10 +1,17 @@ import { GetStaticPropsResult } from 'next'; -import React from 'react'; +import { usePathname } from 'next/navigation'; +import { useRouter } from 'next/router'; +import React, { useCallback } from 'react'; import { ServicesJeunes } from '~/client/components/features/ServicesJeunes/ServicesJeunes'; import { Head } from '~/client/components/head/Head'; +import { Champ } from '~/client/components/ui/Form/Champ/Champ'; +import { SelectMultiple } from '~/client/components/ui/Form/Select/SelectMultiple'; import { LightHero, LightHeroPrimaryText, LightHeroSecondaryText } from '~/client/components/ui/Hero/LightHero'; +import { Icon } from '~/client/components/ui/Icon/Icon'; +import { TagList } from '~/client/components/ui/Tag/TagList'; import useAnalytics from '~/client/hooks/useAnalytics'; +import { getArrayQueryParam } from '~/client/utils/queryParams.utils'; import analytics from '~/pages/espace-jeune/index.analytics'; import { isFailure } from '~/server/errors/either'; import { ServiceJeune } from '~/server/services-jeunes/domain/servicesJeunes'; @@ -19,6 +26,30 @@ interface ServicesJeunePageProps { export default function ServicesJeunesPage({ serviceJeuneList }: ServicesJeunePageProps) { useAnalytics(analytics); + const router = useRouter(); + const pathname = usePathname(); + const filtreList = getArrayQueryParam(router.query.filtre) ?? []; + + const toggleOption = useCallback( + (option: HTMLElement) => { + if(filtreList.includes(option.dataset.value!) && option.getAttribute('aria-selected') === 'true'){ + return filtreList.filter((e) => e !== option.dataset.value!).map((e) => 'filtre=' + e).join('&'); + } else if (!filtreList.includes(option.dataset.value!) && option.getAttribute('aria-selected') === 'false') { + return filtreList.map((e) => 'filtre=' + e).join('&').concat('&filtre=' + option.dataset.value!); + } + return ''; + }, + [filtreList], + ); + + function removeFilterAndGenerateQueryString(filtre: string) { + return filtreList.filter((e) => e !== filtre).map((e) => 'filtre=' + e).join('&'); + } + + const servicesJeunesVisibles = filtreList.length > 0 ? serviceJeuneList.filter((service) => { + return filtreList.includes(service.categorie!); + }) : serviceJeuneList; + return ( <>
- + + + Types de service à destination des jeunes + Sélectionnez votre/vos choix + + { + router.push(pathname + '?' + toggleOption(option)); + }} + optionsAriaLabel={'Toto'}> + {Object.values(ServiceJeune.Categorie).map((option, index) => + {option}, + )} + + + + { + return ( + + ); + }, + )} /> +
+ );