From 847861bec2b772ec8f7fa426e6ffc79ec86a44e8 Mon Sep 17 00:00:00 2001 From: Victor Zeinstra Date: Wed, 4 Dec 2024 12:29:22 +0100 Subject: [PATCH] feat: module trouver sa cc (#6267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: dsfr * fix: dsfr * fix: dsfr * fix: tests * fix: build * fix: build * fix: build * feat(dsfr): ajout du footer (#6079) * feat: add code * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: dernier titi * fix: autoclick * fix: bug * feat(css): ajout de la lib `panda-css` (zero-runtime) (#6085) * fix: pandacss * fix: build * fix: build * feat(dsfr): ajout de la page stats (#6090) * fix: pandacss * fix: build * fix: build * fix(stats): add page * fix: stats * fix: tests * fix: tests * Update packages/code-du-travail-frontend/src/modules/mentions-legales/index.tsx Co-authored-by: Martial Maillot * fix(recherche): remonter les pré-qualifiés dans la recherche (#6082) * chore(release): version 4.151.1 * fix(csp): remove reporting on sentry (#6092) * fix: config * fix: config * fix: config * empty * fix: config * empty * Update packages/code-du-travail-frontend/src/modules/mentions-legales/index.tsx Co-authored-by: Martial Maillot * Update packages/code-du-travail-frontend/src/modules/mentions-legales/index.tsx Co-authored-by: Martial Maillot * fix: config * chore(dsfr): mise à jour de la version DSFR * fix(dsfr): ignore les exceptions d'hydratation * feat(tests): ajout du module de testing (#6096) * fix: tests * fix: tests * fix: command * fix: merge date * fix: tests * fix: tests * fix: tests * fix: tests * fix(dsfr): ignore les exceptions d'hydratation * fix: readme * feat(DSFR): migration de la page article du code du travail (#6099) * feat: 6093 dsfr page politique de confidentialit (#6094) * feat: implémentation page politique confidentialité dsfr * chore: clean * fix: iframe dark mode * chore: refacto + e2e test * feat: convert a to Link * chore: review * chore: rename test --------- Co-authored-by: victor * feat(dsfr): ajout des liens d'évitement (#6120) * feat(dsfr): migration de la page plan du site (#6097) * feat(dsfr): mise à jour des snapshots * feat(dsfr): mise à jour des snapshots * chore(dsfr): mise à jour de la lib DSFR * feat(dsfr): ajout du composant "Avez-vous trouvé une réponse à votre question" (#6121) * fix: merge date * fix: satisfaction * fix: retours preavis * fix: retours preavis * fix: tests * move feedback component to the page articleCodeDuTravail.tsx * clean-up css * fix: feedback * fix: feedback * fix: tests * fix: tests * feat(dsfr): création du composant "Besoin de plus d'information" (#6135) * fix: ui * fix: composant --------- Co-authored-by: carolineBda Co-authored-by: Martial Maillot * fix: retours * fix: tests * feat: nouvelle API pour les articles du code du travail (#6132) Co-authored-by: carolineBda * fix(spec) : fix de la spec article-code-du-travail.spec.ts * fix(dsfr): ajout de la config pour supprimer le `insafe-inline` des `scripts` dans les `csp` (#6151) * feat(dsfr): ajout de matomo pour tracker les events (#6157) * fix: matomo * fix: matomo --------- Co-authored-by: Martial Maillot * fix(feedback): ajout d'une logique de caractères restants (#6156) * fix: tests * feat: limiter à 500 caractères la saisie * feat: limiter à 500 caractères la saisie --------- Co-authored-by: Martial Maillot * feat(dsfr): ajout du nouveau logo (#6159) * feat(dsfr): ajout des pages d'erreurs (404 + 500) (#6146) * fix: pages * fix: 404 * fix: tests * fix: tests * fix: tests * fix: lint * test error * fix errors * Fix spec * add button to test error page * feat: force error 500 for testing purpose * feat: revert errors --------- Co-authored-by: carolineBda Co-authored-by: Martial Maillot * feat: séparation des anciennes API et des nouvelles (#6183) * fix(article code du travail): retrait du tag Code du travail (#6182) * feat(config): correction de `husky`, ajout de `prettier` pour le formattage et de `lint-staged` pour runner le formattage au `precommit` (#6192) * fix: prettier * fix: prettier * fix: readme * fix: readme * fix: bug * fix: prettier * fix: prettier * fix: tests * fix: tests * fix: prettier * fix: prettier * fix: branch * merge dev * feat: implémentation simulateur brut net * chore: fix ts * chore: snap * chore: clean * chore: clean * chore: clean * chore: review * feat: update responsive * Revert "Merge branch 'dev' into simulateur-brut-net-dsfr" This reverts commit 07489287af016ae667dfc30c55e93a3783d4b211, reversing changes made to cd15bc7ee0bbe31de5978c9adcda83165707941d. * chore: clean * feat: chore clean * chore: clean * chore: remove undesired files * chore: remove undesired files * chore: clean * feat: implementation page convention collective * feat: update button link * fix: meta description share * feat: switch simu brut net couleur par defaut * feat: ajout de l'icone * feat: update svg * fix: ts * fix: bug load * chore: review * fix: mobile display * chore: review * chore: review * chore: review * feat: implémentation trouver cc * feat: implementation trouver CC * chore: common style * feat: add error alert entreprise search * feat: disable card * feat: ajout TU + fix * chore: clean * fix: e2e tests * chore: clean * fix: widget * fix: error * feat: info bulle * fix: widget * fix: home fetch agreement * feat: add postmessage searchCC * fix: home cc * feat: change contenu lié search cc * fix: related items * fix: message erreur lors de la recherche * fix: error message * fix: relatedItems * chore: add TU * refactor: move files to folders * fix: build * chore: review * chore: clean * fix: ajout des canonicals * fix: input info * fix: canonicals * feat: ajout d'un spinner * fix: tests * chore: review * feat: add spinner to enterprise search * fix: load on select item autocomplete * fix: bug widget selection entreprise * fix: disabled rule * chore: review * chore: review * chore: review * chore: review * chore: review * chore: review * chore: review * fix: garder la recherche entrerprise au click precedent * chore: review * chore: review * fix: TU + some styles * fix: e2e * refactor * fix: TU * fix: TU * chore: review * chore: review * chore: review * chore: review postalcode * chore: review * chore: review * fix: cypress test * chore: add widgetMode TU * chore: fix test * fix: cypress test * fix: cypress test * chore: cy * chore: fix cy * refactor * refactor * chore: review * chore: clean * chore: review * fix: largeur champs recherche entreprise * feat: ajout d'un message d'erreur pour les codes naf * feat: update design * chore: clean * chore: design review * chore: review * chore: test noindex * chore: nbsp * chore: review * chore: review * chore: fix test e2e --------- Co-authored-by: maxgfr <25312957+maxgfr@users.noreply.github.com> Co-authored-by: Caroline <4971715+carolineBda@users.noreply.github.com> Co-authored-by: Martial Maillot Co-authored-by: Social Groovy Bot <45039513+SocialGroovyBot@users.noreply.github.com> Co-authored-by: victor Co-authored-by: carolineBda --- package.json | 1 + .../app/convention-collective/page.tsx | 16 +- .../convention-collective/convention/page.tsx | 45 +++ .../entreprise/[slug]/page.tsx | 58 ++++ .../convention-collective/entreprise/page.tsx | 47 +++ .../app/outils/convention-collective/page.tsx | 47 +++ .../entreprise/[slug]/page.tsx | 46 +++ .../widgets/convention-collective/page.tsx | 42 +++ .../integration-convention-collective.spec.ts | 36 +- .../light/outils/preavis-retraite.spec.ts | 12 +- .../outils/trouver-sa-cc-je-la-saisis.spec.ts | 18 +- ...trouver-sa-cc-recherche-entreprise.spec.ts | 39 ++- .../light/widgets/trouver-sa-cc.spec.ts | 6 + .../cypress/support/commands.ts | 47 +++ .../cypress/support/index.ts | 1 + .../pages/outils/[slug].tsx | 2 - .../assets/img/convention-collective.png | Bin 36864 -> 52798 bytes .../__mocks__/fetchEnterprises.mock.ts | 2 +- .../__tests__/fetchEnterprises.es.test.ts | 10 +- .../enterprises/service/fetchEnterprises.ts | 20 +- .../src/api/modules/idcc/controller.ts | 7 +- .../src/api/modules/idcc/queries.ts | 4 +- .../src/api/modules/idcc/service.ts | 4 +- .../modules/Location/LocationSearchInput.tsx | 55 +++ .../__tests__/LocationSearchInput.test.tsx | 65 ++++ .../src/modules/Location/__tests__/ui.ts | 7 + .../src/modules/Location/searchCities.ts | 36 ++ .../common/Autocomplete/Autocomplete.tsx | 228 +++++++++++++ .../src/modules/common/Autocomplete/index.ts | 1 + .../src/modules/common/Spinner.svg | 1 + .../AgreementSearch/AgreementSearch.tsx | 38 +++ .../AgreementSearch/AgreementSearchInput.tsx | 123 +++++++ .../AgreementSearch/AgreementSearchIntro.tsx | 78 +++++ .../AgreementSearch/index.ts | 3 + .../{ => Agreements}/Agreements.tsx | 2 +- .../{ => Agreements}/AgreementsGlossaire.tsx | 0 .../{ => Agreements}/AgreementsIntro.tsx | 25 +- .../{ => Agreements}/AgreementsSection.tsx | 2 +- .../convention-collective/Agreements/index.ts | 4 + .../__tests__/AgreementSearchByName.test.tsx | 84 +++++ .../__tests__/queries.es.test.ts | 14 +- .../__tests__/search.test.ts | 24 ++ .../convention-collective/__tests__/ui.ts | 68 ++++ .../agreementRelatedItems.ts | 25 ++ .../modules/convention-collective/error.tsx | 10 + .../modules/convention-collective/index.ts | 6 +- .../modules/convention-collective/queries.ts | 63 +++- .../modules/convention-collective/search.ts | 52 +++ .../modules/convention-collective/style.ts | 17 + .../src/modules/documents/type.ts | 1 + .../EnterpriseAgreementSearch.tsx | 38 +++ .../EnterpriseAgreementSearchInput.tsx | 319 ++++++++++++++++++ .../EnterpriseAgreementSelection.tsx | 146 ++++++++ .../__tests__/AgreementSelection.test.tsx | 125 +++++++ .../EnterpriseAgreementSearch.test.tsx | 168 +++++++++ .../EnterpriseAgreementSearch/__tests__/ui.ts | 57 ++++ .../EnterpriseAgreementSearch/index.ts | 3 + .../EnterpriseAgreementSearch/store.ts | 11 + .../enterprise/__tests__/queries.test.ts | 35 ++ .../src/modules/enterprise/index.ts | 2 + .../src/modules/enterprise/queries.ts | 62 ++++ .../src/modules/enterprise/types.ts | 31 ++ .../src/modules/home/queries.ts | 23 +- .../FindAgreementBlock.tsx | 40 +++ .../FindAgreementLayout.tsx | 31 ++ .../FindAgreementWidgetLayout.tsx | 18 + .../outils/convention-collective/index.ts | 3 + .../src/modules/outils/index.ts | 1 + .../src/modules/plan-du-site/queries.ts | 10 +- .../EnterpriseSearch/Location/Search.tsx | 3 +- yarn.lock | 1 + 71 files changed, 2554 insertions(+), 115 deletions(-) create mode 100644 packages/code-du-travail-frontend/app/outils/convention-collective/convention/page.tsx create mode 100644 packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/[slug]/page.tsx create mode 100644 packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/page.tsx create mode 100644 packages/code-du-travail-frontend/app/outils/convention-collective/page.tsx create mode 100644 packages/code-du-travail-frontend/app/widgets/convention-collective/entreprise/[slug]/page.tsx create mode 100644 packages/code-du-travail-frontend/app/widgets/convention-collective/page.tsx create mode 100644 packages/code-du-travail-frontend/cypress/integration/light/widgets/trouver-sa-cc.spec.ts create mode 100644 packages/code-du-travail-frontend/src/modules/Location/LocationSearchInput.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/Location/__tests__/LocationSearchInput.test.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/Location/__tests__/ui.ts create mode 100644 packages/code-du-travail-frontend/src/modules/Location/searchCities.ts create mode 100644 packages/code-du-travail-frontend/src/modules/common/Autocomplete/Autocomplete.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/common/Autocomplete/index.ts create mode 100644 packages/code-du-travail-frontend/src/modules/common/Spinner.svg create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearch.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchInput.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchIntro.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/index.ts rename packages/code-du-travail-frontend/src/modules/convention-collective/{ => Agreements}/Agreements.tsx (95%) rename packages/code-du-travail-frontend/src/modules/convention-collective/{ => Agreements}/AgreementsGlossaire.tsx (100%) rename packages/code-du-travail-frontend/src/modules/convention-collective/{ => Agreements}/AgreementsIntro.tsx (80%) rename packages/code-du-travail-frontend/src/modules/convention-collective/{ => Agreements}/AgreementsSection.tsx (92%) create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/index.ts create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/AgreementSearchByName.test.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/search.test.ts create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/ui.ts create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/agreementRelatedItems.ts create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/error.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/search.ts create mode 100644 packages/code-du-travail-frontend/src/modules/convention-collective/style.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearch.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearchInput.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSelection.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/AgreementSelection.test.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/EnterpriseAgreementSearch.test.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/ui.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/index.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/store.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/__tests__/queries.test.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/index.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/queries.ts create mode 100644 packages/code-du-travail-frontend/src/modules/enterprise/types.ts create mode 100644 packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementBlock.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementLayout.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementWidgetLayout.tsx create mode 100644 packages/code-du-travail-frontend/src/modules/outils/convention-collective/index.ts diff --git a/package.json b/package.json index d2ef94823a..c44de6f42e 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "url": "https://github.com/SocialGouv/code-du-travail-numerique/issues" }, "devDependencies": { + "cypress-iframe": "^1.0.1", "husky": "^9.1.6", "lerna": "8.1.8" }, diff --git a/packages/code-du-travail-frontend/app/convention-collective/page.tsx b/packages/code-du-travail-frontend/app/convention-collective/page.tsx index a9d8124ebb..8583ff86e0 100644 --- a/packages/code-du-travail-frontend/app/convention-collective/page.tsx +++ b/packages/code-du-travail-frontend/app/convention-collective/page.tsx @@ -3,24 +3,30 @@ import { Agreements, AgreementsPerLetter, } from "../../src/modules/convention-collective/Agreements"; -import { fetchAgreements } from "../../src/modules/convention-collective"; +import { fetchAllAgreements } from "../../src/modules/convention-collective"; import { generateDefaultMetadata } from "../../src/modules/common/metas"; +import { notFound } from "next/navigation"; +import { SITE_URL } from "../../src/config"; export const metadata = generateDefaultMetadata({ title: "Votre convention collective", description: "Retrouvez les questions/réponses fréquentes organisées par thème pour votre convention collective", path: "/convention-collective", + overrideCanonical: `{${SITE_URL}/outils/convention-collective}`, }); const removeAccents = (text: string) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); async function AgreementPage() { - const agreements = await fetchAgreements( - ["slug", "shortTitle"], - "shortTitle" - ); + const agreements = await fetchAllAgreements({ + fields: ["slug", "shortTitle"], + sortBy: "shortTitle", + }); + if (!agreements.length) { + return notFound(); + } const firstLettersAgreements = agreements.reduce( (agreementPerletter, agreement) => { const { shortTitle } = agreement; diff --git a/packages/code-du-travail-frontend/app/outils/convention-collective/convention/page.tsx b/packages/code-du-travail-frontend/app/outils/convention-collective/convention/page.tsx new file mode 100644 index 0000000000..3968d5086f --- /dev/null +++ b/packages/code-du-travail-frontend/app/outils/convention-collective/convention/page.tsx @@ -0,0 +1,45 @@ +import { DsfrLayout } from "../../../../src/modules/layout"; +import { DocumentElasticResult } from "../../../../src/modules/documents"; +import { fetchTool, FindAgreementLayout } from "../../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../../src/modules/common/metas"; +import { ElasticTool } from "../../../../src/modules/outils/type"; +import { AgreementSearch } from "../../../../src/modules/convention-collective"; +import { agreementRelatedItems } from "../../../../src/modules/convention-collective/agreementRelatedItems"; +import { SITE_URL } from "../../../../src/config"; + +export async function generateMetadata() { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `${SITE_URL}/outils/convention-collective/convention`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function FindAgreementByNamePage() { + const tool = await getTool(); + return ( + + + + + + ); +} + +const getTool = async () => { + const tool = await fetchTool("convention-collective"); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default FindAgreementByNamePage; diff --git a/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/[slug]/page.tsx b/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/[slug]/page.tsx new file mode 100644 index 0000000000..98552d574e --- /dev/null +++ b/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/[slug]/page.tsx @@ -0,0 +1,58 @@ +import { DsfrLayout } from "../../../../../src/modules/layout"; +import { DocumentElasticResult } from "../../../../../src/modules/documents"; +import { + fetchTool, + FindAgreementLayout, +} from "../../../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../../../src/modules/common/metas"; +import { ElasticTool } from "../../../../../src/modules/outils/type"; +import { EnterpriseAgreementSelection } from "../../../../../src/modules/enterprise"; +import { searchEnterprises } from "../../../../../src/modules/enterprise/queries"; +import { agreementRelatedItems } from "../../../../../src/modules/convention-collective/agreementRelatedItems"; +import { SITE_URL } from "../../../../../src/config"; + +export async function generateMetadata({ params }) { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `${SITE_URL}/outils/convention-collective/selection/${params.slug}`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function AgreementSelectionPage({ params }) { + const [enterprise] = await searchEnterprises({ + query: params.slug, + }); + if (!enterprise) { + return notFound(); + } + const tool = await getTool(); + return ( + + + + + + + ); +} + +const getTool = async () => { + const tool: DocumentElasticResult = await fetchTool( + "convention-collective" + ); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default AgreementSelectionPage; diff --git a/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/page.tsx b/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/page.tsx new file mode 100644 index 0000000000..b1f74658ce --- /dev/null +++ b/packages/code-du-travail-frontend/app/outils/convention-collective/entreprise/page.tsx @@ -0,0 +1,47 @@ +import { DsfrLayout } from "../../../../src/modules/layout"; +import { DocumentElasticResult } from "../../../../src/modules/documents"; +import { fetchTool, FindAgreementLayout } from "../../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../../src/modules/common/metas"; +import { ElasticTool } from "../../../../src/modules/outils/type"; +import { EnterpriseAgreementSearch } from "../../../../src/modules/enterprise"; +import { agreementRelatedItems } from "../../../../src/modules/convention-collective/agreementRelatedItems"; +import { SITE_URL } from "../../../../src/config"; + +export async function generateMetadata() { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `${SITE_URL}/outils/convention-collective/entreprise`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function FindAgreementByEnterprisePage() { + const tool = await getTool(); + return ( + + + + + + ); +} + +const getTool = async () => { + const tool: DocumentElasticResult = await fetchTool( + "convention-collective" + ); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default FindAgreementByEnterprisePage; diff --git a/packages/code-du-travail-frontend/app/outils/convention-collective/page.tsx b/packages/code-du-travail-frontend/app/outils/convention-collective/page.tsx new file mode 100644 index 0000000000..0d7b3d5c33 --- /dev/null +++ b/packages/code-du-travail-frontend/app/outils/convention-collective/page.tsx @@ -0,0 +1,47 @@ +import { DsfrLayout } from "../../../src/modules/layout"; +import { DocumentElasticResult } from "../../../src/modules/documents"; +import { fetchTool, FindAgreementLayout } from "../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../src/modules/common/metas"; +import { ElasticTool } from "../../../src/modules/outils/type"; +import { AgreementSearchIntro } from "../../../src/modules/convention-collective"; +import { agreementRelatedItems } from "../../../src/modules/convention-collective/agreementRelatedItems"; +import { SITE_URL } from "../../../src/config"; + +export async function generateMetadata() { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `/outils/convention-collective`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function FindAgreementPage() { + const tool = await getTool(); + return ( + + + + + + ); +} + +const getTool = async () => { + const tool: DocumentElasticResult = await fetchTool( + "convention-collective" + ); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default FindAgreementPage; diff --git a/packages/code-du-travail-frontend/app/widgets/convention-collective/entreprise/[slug]/page.tsx b/packages/code-du-travail-frontend/app/widgets/convention-collective/entreprise/[slug]/page.tsx new file mode 100644 index 0000000000..a4127a5afb --- /dev/null +++ b/packages/code-du-travail-frontend/app/widgets/convention-collective/entreprise/[slug]/page.tsx @@ -0,0 +1,46 @@ +import { DocumentElasticResult } from "../../../../../src/modules/documents"; +import { + fetchTool, + FindAgreementWidgetLayout, +} from "../../../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../../../src/modules/common/metas"; +import { ElasticTool } from "../../../../../src/modules/outils/type"; +import { EnterpriseAgreementSelection } from "../../../../../src/modules/enterprise"; +import { searchEnterprises } from "../../../../../src/modules/enterprise/queries"; +import { SITE_URL } from "../../../../../src/config"; + +export async function generateMetadata({ params }) { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `${SITE_URL}/widgets/convention-collective/selection/${params.slug}`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function AgreementSelectionPage({ params }) { + const [enterprise] = await searchEnterprises({ + query: params.slug, + }); + return ( + + + + ); +} + +const getTool = async () => { + const tool: DocumentElasticResult = await fetchTool( + "convention-collective" + ); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default AgreementSelectionPage; diff --git a/packages/code-du-travail-frontend/app/widgets/convention-collective/page.tsx b/packages/code-du-travail-frontend/app/widgets/convention-collective/page.tsx new file mode 100644 index 0000000000..789735a7f6 --- /dev/null +++ b/packages/code-du-travail-frontend/app/widgets/convention-collective/page.tsx @@ -0,0 +1,42 @@ +import { DocumentElasticResult } from "../../../src/modules/documents"; +import { + fetchTool, + FindAgreementWidgetLayout, +} from "../../../src/modules/outils"; +import { notFound } from "next/navigation"; +import { generateDefaultMetadata } from "../../../src/modules/common/metas"; +import { ElasticTool } from "../../../src/modules/outils/type"; +import { EnterpriseAgreementSearch } from "../../../src/modules/enterprise"; +import { SITE_URL } from "../../../src/config"; + +export async function generateMetadata() { + const { title, description } = await getTool(); + + return generateDefaultMetadata({ + title: `Simulateur - ${title}`, + description: description, + path: `${SITE_URL}/widgets/convention-collective`, + overrideCanonical: `${SITE_URL}/outils/convention-collective`, + }); +} + +async function FindAgreementByEnterprisePage() { + return ( + + + + ); +} + +const getTool = async () => { + const tool: DocumentElasticResult = await fetchTool( + "convention-collective" + ); + + if (!tool) { + return notFound(); + } + return tool; +}; + +export default FindAgreementByEnterprisePage; diff --git a/packages/code-du-travail-frontend/cypress/integration/light/integration-convention-collective.spec.ts b/packages/code-du-travail-frontend/cypress/integration/light/integration-convention-collective.spec.ts index d1077d806b..a08468d4b5 100644 --- a/packages/code-du-travail-frontend/cypress/integration/light/integration-convention-collective.spec.ts +++ b/packages/code-du-travail-frontend/cypress/integration/light/integration-convention-collective.spec.ts @@ -1,11 +1,3 @@ -Cypress.Commands.add("getIframe" as any, () => { - return cy - .get("iframe") - .its("0.contentDocument.body") - .should("not.be.empty") - .then(cy.wrap); -}); - describe("Pages integration convention collective", () => { it("should display iframe convention collective", () => { cy.visit("/integration/convention-collective", { @@ -14,17 +6,25 @@ describe("Pages integration convention collective", () => { }, }); - // @ts-ignore - cy.getIframe().as("iframe"); + cy.frameLoaded({ url: "/widgets/convention-collective" }); + + cy.iframe() + .findByRole("heading", { level: 1 }) + .should("have.text", "Trouver sa convention collective") + .click(); + + cy.iframe() + // @ts-ignore + .findByLabel("Nom de votre entreprise ou numéro Siren/Siret") + .as("inputSiret"); + + cy.get("@inputSiret").type("carrefour", { force: true }); - cy.get("@iframe").contains("Trouver sa convention collective"); - cy.get("@iframe").find("#enterprise-search").as("entreprise-search"); - cy.get("@entreprise-search").type("carrefour"); - cy.get("@iframe").find("button[type=submit]").as("button-submit"); - cy.get("@button-submit").click(); - cy.get("@iframe").contains("CARREFOUR HYPERMARCHES").as("entreprise"); - cy.get("@entreprise").click(); - cy.get("@iframe").contains("Conventions collectives").as("cc"); + cy.iframe().find("button[type=submit]").contains("Rechercher").click(); + cy.iframe().contains("CARREFOUR HYPERMARCHES").click(); + cy.iframe() + .contains("Commerce de détail et de gros à prédominance alimentaire") + .as("cc"); cy.get("@cc").click(); cy.get("@postMessage") .should("have.been.calledOnce") diff --git a/packages/code-du-travail-frontend/cypress/integration/light/outils/preavis-retraite.spec.ts b/packages/code-du-travail-frontend/cypress/integration/light/outils/preavis-retraite.spec.ts index 088239df30..4cafa99c6f 100644 --- a/packages/code-du-travail-frontend/cypress/integration/light/outils/preavis-retraite.spec.ts +++ b/packages/code-du-travail-frontend/cypress/integration/light/outils/preavis-retraite.spec.ts @@ -181,14 +181,16 @@ describe("Outil - Préavis de retraite", () => { cy.contains("Cliquez sur Suivant pour poursuivre la simulation."); cy.get('[aria-label="Fermer"]').click(); cy.get("#enterprise-search").clear(); - cy.get("#enterprise-search").type("boulangerie", { delay: 0 }); - cy.get("#enterprise-search-address").type("7927"); + cy.get("#enterprise-search").type("boursorama", { delay: 0 }); + cy.get("#enterprise-search-address").type("9210"); cy.get("#enterprise-search-address").type("0{downArrow}{enter}", { delay: 3000, force: true, }); cy.get('button[type="submit"]').last().click(); - cy.contains("PHILIPPE PETIT").click(); + cy.contains( + "BOURSORAMA (BOURSORAMA - BOURSORAMA BANQUE - BOURSOBANK)" + ).click(); cy.contains( "Une convention collective a été trouvée pour cette entreprise" ); @@ -209,10 +211,10 @@ describe("Outil - Préavis de retraite", () => { cy.contains("2 mois"); cy.contains("Durée prévue par le code du travail (durée légale) : 2 mois"); cy.contains( - "Durée prévue par la convention collective (durée conventionnelle) : 6 mois" + "Durée prévue par la convention collective (durée conventionnelle) : pas de préavis" ); cy.contains( - "La durée à appliquer pour le salarié est donc la durée légale, celle-ci étant plus courte que la durée prévue par la convention collective." + "En l’absence de durée prévue par la convention collective, la durée de préavis à appliquer pour le salarié est donc la durée légale." ); }); }); diff --git a/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-je-la-saisis.spec.ts b/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-je-la-saisis.spec.ts index fbd8d4b75d..1c3b3e46a7 100644 --- a/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-je-la-saisis.spec.ts +++ b/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-je-la-saisis.spec.ts @@ -1,11 +1,19 @@ describe("Outil - Trouver sa convention collective", () => { it("Recherche de convention collective je la saisis", () => { cy.visit("/outils/convention-collective"); + cy.checkNoIndex(false); cy.checkCanonical("/outils/convention-collective"); - cy.get("h1").should("have.text", "Trouver sa convention collective"); - cy.contains("Je la saisis").click(); + cy.findByRole("heading", { level: 1 }) + .should("have.text", "Trouver sa convention collective") + .click(); + cy.contains("Je connais ma convention collective je la saisis").click(); cy.checkCanonical("/outils/convention-collective"); - cy.get("#agreement-search").type("boulangerie"); + // @ts-ignore + cy.selectByLabel( + "Nom de la convention collective ou son numéro d’identification IDCC (4 chiffres)" + ) + .as("inputIdcc") + .type("boulangerie"); cy.get('ul[role="listbox"] li').should( "contain", "Boulangerie-pâtisserie (entreprises artisanales)" @@ -14,8 +22,8 @@ describe("Outil - Trouver sa convention collective", () => { "contain", "Activités industrielles de boulangerie et pâtisserie" ); - cy.get("#agreement-search").clear(); - cy.get("#agreement-search").type("2247"); + cy.get("@inputIdcc").clear(); + cy.get("@inputIdcc").type("2247"); cy.get('ul[role="listbox"] li').should( "contain", "Entreprises de courtage d'assurances et/ou de réassurances" diff --git a/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-recherche-entreprise.spec.ts b/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-recherche-entreprise.spec.ts index 93241da597..9e13f16d2f 100644 --- a/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-recherche-entreprise.spec.ts +++ b/packages/code-du-travail-frontend/cypress/integration/light/outils/trouver-sa-cc-recherche-entreprise.spec.ts @@ -1,25 +1,34 @@ describe("Outil - Trouver sa convention collective", () => { it("Recherche de convention collective par entreprise", () => { cy.visit("/outils/convention-collective"); - cy.get("h1").should("have.text", "Trouver sa convention collective"); - cy.contains("Je la recherche").click(); + cy.checkNoIndex(false); + cy.findByRole("heading", { level: 1 }) + .should("have.text", "Trouver sa convention collective") + .click(); + cy.contains( + "Je cherche mon entreprise pour trouver ma convention collective" + ).click(); cy.checkCanonical("/outils/convention-collective"); - cy.get("#enterprise-search").type("82129756100010", { delay: 0 }); - cy.get("#enterprise-search-address").type("7501"); - cy.get("#enterprise-search-address").type("8{downArrow}{enter}", { + // @ts-ignore + cy.selectByLabel("Nom de votre entreprise ou numéro Siren/Siret").type( + "82129756100010", + { delay: 0 } + ); + // @ts-ignore + cy.selectByLabel("Code postal ou Ville (optionnel)") + .as("locationInput") + .type("7501"); + cy.get("@locationInput").type("8{downArrow}{enter}", { delay: 2000, force: true, }); cy.get('button[type="submit"]').last().click(); cy.contains("BOUILLON PIGALLE").click(); - cy.get("p").should( - "contain", - "1 convention collective trouvée pour « BOUILLON PIGALLE, 22 BOULEVARD DE CLICHY 75018 PARIS »" - ); + cy.contains("1 convention collective trouvée pour :"); cy.contains("Précédent").click(); - cy.get("#enterprise-search") + cy.selectByLabel("Nom de votre entreprise ou numéro Siren/Siret") .clear() .type("C") .type("A") @@ -37,14 +46,12 @@ describe("Outil - Trouver sa convention collective", () => { .type("Q") .type("U") .type("E"); - cy.get("#enterprise-search-address").clear(); + // @ts-ignore + cy.selectByLabel("Code postal ou Ville (optionnel)").clear(); cy.get('button[type="submit"]').last().click(); cy.contains("CARREFOUR BANQUE").click(); - cy.get("p").should( - "contain", - "2 conventions collectives trouvées pour « CARREFOUR BANQUE, 37 AVENUE D'ESSOMES 02400 CHATEAU-THIERRY »" - ); - cy.contains("Banque IDCC2120") + cy.contains("2 conventions collectives trouvées pour :"); + cy.contains("Banque") .should("have.prop", "href") .and( "equal", diff --git a/packages/code-du-travail-frontend/cypress/integration/light/widgets/trouver-sa-cc.spec.ts b/packages/code-du-travail-frontend/cypress/integration/light/widgets/trouver-sa-cc.spec.ts new file mode 100644 index 0000000000..a339c1084c --- /dev/null +++ b/packages/code-du-travail-frontend/cypress/integration/light/widgets/trouver-sa-cc.spec.ts @@ -0,0 +1,6 @@ +describe("Outil - Trouver sa convention collective", () => { + it("Recherche de convention collective je la saisis", () => { + cy.visit("/widgets/convention-collective"); + cy.checkNoIndex(true); + }); +}); diff --git a/packages/code-du-travail-frontend/cypress/support/commands.ts b/packages/code-du-travail-frontend/cypress/support/commands.ts index e3f3b0a00a..a7203a0628 100644 --- a/packages/code-du-travail-frontend/cypress/support/commands.ts +++ b/packages/code-du-travail-frontend/cypress/support/commands.ts @@ -1,7 +1,54 @@ import "@testing-library/cypress/add-commands"; +import "cypress-iframe"; + +declare global { + namespace Cypress { + interface Chainable { + selectByLabel: Chainable; + findByLabel: Chainable; + } + } +} Cypress.Commands.add("checkCanonical", (path) => { cy.get("head > link[rel='canonical']") .should("have.prop", "href") .and("equal", `${Cypress.config().baseUrl}${path}`); }); + +Cypress.Commands.add("checkNoIndex", (noIndex) => { + if (!noIndex) { + cy.get("head > meta[name='robots']").should("not.exist"); + } else { + cy.get("head > meta[name='robots']") + .should("have.prop", "content") + .and("equal", `noindex,nofollow`); + } +}); + +Cypress.Commands.add("selectByLabel", (labelText) => { + cy.contains("label", labelText) + .invoke("attr", "for") + .then((id) => { + cy.get(`#${CSS.escape(id)}`); + }); +}); + +Cypress.Commands.add( + "findByLabel", + { + prevSubject: true, + }, + (subject, labelText) => { + if (subject) { + cy.wrap(subject) + .contains("label", labelText) + .invoke("attr", "for") + .then((id) => { + if (id) { + cy.wrap(subject).find(`#${CSS.escape(id)}`); + } + }); + } + } +); diff --git a/packages/code-du-travail-frontend/cypress/support/index.ts b/packages/code-du-travail-frontend/cypress/support/index.ts index 720f641bdd..4f8b1482ea 100644 --- a/packages/code-du-travail-frontend/cypress/support/index.ts +++ b/packages/code-du-travail-frontend/cypress/support/index.ts @@ -31,6 +31,7 @@ declare global { * @example cy.checkCanonical("/contribution/quelle-peut-etre-la-duree-maximale-dun-cdd") */ checkCanonical(value: string): Chainable; + checkNoIndex(noIndex: boolean): Chainable; } } } diff --git a/packages/code-du-travail-frontend/pages/outils/[slug].tsx b/packages/code-du-travail-frontend/pages/outils/[slug].tsx index 18e9ed2fd9..f5a777811f 100644 --- a/packages/code-du-travail-frontend/pages/outils/[slug].tsx +++ b/packages/code-du-travail-frontend/pages/outils/[slug].tsx @@ -12,7 +12,6 @@ import { RelatedItems } from "../../src/common/RelatedItems"; import { Share } from "../../src/common/Share"; import { Layout } from "../../src/layout/Layout"; import { - AgreementSearch, CalculateurIndemniteLicenciement, CalculateurRuptureConventionnelle, DismissalProcess, @@ -30,7 +29,6 @@ import { import { Tool } from "@socialgouv/cdtn-types"; const toolsBySlug = { - "convention-collective": AgreementSearch, "heures-recherche-emploi": HeuresRechercheEmploi, "indemnite-licenciement": CalculateurIndemniteLicenciement, "indemnite-precarite": SimulateurIndemnitePrecarite, diff --git a/packages/code-du-travail-frontend/public/static/assets/img/convention-collective.png b/packages/code-du-travail-frontend/public/static/assets/img/convention-collective.png index a0f76200a8d7fab1e3c19b8563e11d0bf52f5879..00f9d4e9c4cbf66ca27d530e3585be79f39e5ce8 100644 GIT binary patch literal 52798 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 alTc!wTpE2^{nFkZnYIhyE>o|LT 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 aAN=onA9rWL^iKysj`4K$b6Mw<&;$U>k}FpL literal 36864 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
4DQc+xN}K(Br$d7F2K^~l)4NaAL4Pa21suO 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 { { id: "9999", idcc: 9999, - shortTitle: "Convention collective 9999", - title: "Convention collective 9999", + shortTitle: "___Sans convention collective___", + title: "___Sans convention collective___", + }, + { + id: "9998", + idcc: 9998, + shortTitle: "___Convention non encore en vigueur___", + title: "___Convention non encore en vigueur___", }, ], etablissements: 169, diff --git a/packages/code-du-travail-frontend/src/api/modules/enterprises/service/fetchEnterprises.ts b/packages/code-du-travail-frontend/src/api/modules/enterprises/service/fetchEnterprises.ts index 4096164259..970248ab74 100644 --- a/packages/code-du-travail-frontend/src/api/modules/enterprises/service/fetchEnterprises.ts +++ b/packages/code-du-travail-frontend/src/api/modules/enterprises/service/fetchEnterprises.ts @@ -35,14 +35,30 @@ export const fetchEnterprises = async ( } const jsonResponse: ApiRechercheEntrepriseResponse = await fetchReq.json(); + const specialCCList = [ + { + num: "9999", + title: "___Sans convention collective___", + }, + { + num: "9998", + title: "___Convention non encore en vigueur___", + }, + ]; + const entreprises = jsonResponse.results.map((result) => { const conventions = result.complements.liste_idcc?.map((idccNumber) => { + const specialCC = specialCCList.find(({ num }) => num === idccNumber); return { idcc: parseInt(idccNumber, 10), - shortTitle: `Convention collective ${idccNumber}`, + shortTitle: specialCC + ? specialCC.title + : `Convention collective ${idccNumber}`, id: idccNumber, - title: `Convention collective ${idccNumber}`, + title: specialCC + ? specialCC.title + : `Convention collective ${idccNumber}`, }; }) ?? []; diff --git a/packages/code-du-travail-frontend/src/api/modules/idcc/controller.ts b/packages/code-du-travail-frontend/src/api/modules/idcc/controller.ts index 8df841f8dc..6e81aa682f 100644 --- a/packages/code-du-travail-frontend/src/api/modules/idcc/controller.ts +++ b/packages/code-du-travail-frontend/src/api/modules/idcc/controller.ts @@ -13,8 +13,11 @@ export class IdccController { public async get() { try { - const { q } = this.req.query; - const response = await getIdccByQuery(q as string); + const { q, size } = this.req.query; + const response = await getIdccByQuery( + q as string, + size ? parseInt(size as string) : undefined + ); this.res.status(200).json(response); } catch (error) { if (error instanceof NotFoundError) { diff --git a/packages/code-du-travail-frontend/src/api/modules/idcc/queries.ts b/packages/code-du-travail-frontend/src/api/modules/idcc/queries.ts index 1330796d3a..2fbb82071b 100644 --- a/packages/code-du-travail-frontend/src/api/modules/idcc/queries.ts +++ b/packages/code-du-travail-frontend/src/api/modules/idcc/queries.ts @@ -1,6 +1,6 @@ import { SOURCES } from "@socialgouv/cdtn-utils"; -export function getIdccBody({ query, idccQuery }) { +export function getIdccBody({ query, idccQuery, size = 50 }) { return { _source: [ "id", @@ -74,6 +74,6 @@ export function getIdccBody({ query, idccQuery }) { }, }, }, - size: 50, + size, }; } diff --git a/packages/code-du-travail-frontend/src/api/modules/idcc/service.ts b/packages/code-du-travail-frontend/src/api/modules/idcc/service.ts index 21e0740a55..ad446284e2 100644 --- a/packages/code-du-travail-frontend/src/api/modules/idcc/service.ts +++ b/packages/code-du-travail-frontend/src/api/modules/idcc/service.ts @@ -4,10 +4,10 @@ import { getIdccBody } from "./queries"; export const parseIdcc = (query) => /^\d+$/.test(query) ? parseInt(query, 10) : undefined; -export const getIdccByQuery = async (query: string) => { +export const getIdccByQuery = async (query: string, size?: number) => { const idccQuery = parseIdcc(query); - const body: any = getIdccBody({ idccQuery, query }); + const body: any = getIdccBody({ idccQuery, query, size }); const response = await elasticsearchClient.search({ body, diff --git a/packages/code-du-travail-frontend/src/modules/Location/LocationSearchInput.tsx b/packages/code-du-travail-frontend/src/modules/Location/LocationSearchInput.tsx new file mode 100644 index 0000000000..214b34adb2 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/Location/LocationSearchInput.tsx @@ -0,0 +1,55 @@ +"use client"; +import { ApiGeoResult, searchCities } from "./searchCities"; +import { + Autocomplete, + AutocompleteProps, +} from "../common/Autocomplete/Autocomplete"; +import { useState } from "react"; + +type Props = Pick, "classes"> & { + className?: string; + onLocationChange?: (location: ApiGeoResult | undefined) => void; + defaultValue?: ApiGeoResult; +}; + +const detectIfPostalCode = (postalCodeOrName: string): boolean => { + if (/^\d{5}$/.test(postalCodeOrName)) { + return true; + } + return false; +}; + +export const LocationSearchInput = ({ + className, + onLocationChange, + classes, + defaultValue, +}: Props) => { + const [postalCode, setPostalCode] = useState(); + function itemToString(item: ApiGeoResult | null) { + return item + ? `${item.nom} (${postalCode ?? (item.codesPostaux.length > 1 ? item.codeDepartement : item.codesPostaux[0])})` + : ""; + } + + return ( + + className={className} + onChange={(value) => { + if (onLocationChange) onLocationChange(value); + }} + onInputValueChange={(value) => { + setPostalCode(detectIfPostalCode(value) ? value : undefined); + }} + displayLabel={itemToString} + search={searchCities} + hintText={"Ex: 75007"} + label={<>Code postal ou Ville (optionnel)} + state={"default"} + dataTestId={"locationSearchAutocomplete"} + classes={classes} + displayNoResult + defaultValue={defaultValue} + /> + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/Location/__tests__/LocationSearchInput.test.tsx b/packages/code-du-travail-frontend/src/modules/Location/__tests__/LocationSearchInput.test.tsx new file mode 100644 index 0000000000..18a2d0b88b --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/Location/__tests__/LocationSearchInput.test.tsx @@ -0,0 +1,65 @@ +import { getByLabelText, render, RenderResult } from "@testing-library/react"; +import { LocationSearchInput } from "../LocationSearchInput"; +import { searchCities } from "../searchCities"; +import { UserAction } from "../../../common"; +import { byLabelText } from "testing-library-selector"; +import { screen } from "@testing-library/dom"; +import { wait } from "@testing-library/user-event/dist/utils"; +import { ui } from "./ui"; + +jest.mock("../searchCities", () => ({ + searchCities: jest.fn(), +})); + +describe("LocationSearchInput", () => { + let rendering: RenderResult; + let userAction: UserAction; + beforeEach(() => { + jest.resetAllMocks(); + rendering = render(); + }); + it("Vérifier le déroulement de la liste de ville et sa fermeture", async () => { + (searchCities as jest.Mock).mockImplementation(() => + Promise.resolve([ + { + nom: "Paris", + codesPostaux: [ + "75001", + "75002", + "75003", + "75004", + "75005", + "75006", + "75007", + "75008", + "75009", + "75010", + "75011", + "75012", + "75013", + "75014", + "75015", + "75016", + "75017", + "75018", + "75019", + "75020", + "75116", + ], + population: 2133111, + codeDepartement: "75", + code: "75056", + _score: 0.43270345181469244, + }, + ]) + ); + userAction = new UserAction(); + userAction.setInput(ui.input.get(), "paris"); + await wait(); + expect(ui.AutocompleteItemParis.query()).toBeInTheDocument(); + expect(ui.input.get()).toHaveValue("paris"); + userAction.click(ui.inputCloseBtn.get()); + expect(ui.input.get()).toHaveValue(""); + expect(ui.AutocompleteItemParis.query()).not.toBeInTheDocument(); + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/Location/__tests__/ui.ts b/packages/code-du-travail-frontend/src/modules/Location/__tests__/ui.ts new file mode 100644 index 0000000000..c12ca58dbc --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/Location/__tests__/ui.ts @@ -0,0 +1,7 @@ +import { byText, byLabelText, byTestId } from "testing-library-selector"; + +export const ui = { + input: byLabelText(/Code postal ou Ville \(optionnel\)/), + inputCloseBtn: byTestId("locationSearchAutocomplete-autocomplete-close"), + AutocompleteItemParis: byText("Paris (75)"), +}; diff --git a/packages/code-du-travail-frontend/src/modules/Location/searchCities.ts b/packages/code-du-travail-frontend/src/modules/Location/searchCities.ts new file mode 100644 index 0000000000..313ce27d64 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/Location/searchCities.ts @@ -0,0 +1,36 @@ +import { + API_GEO_MAX_SEARCH_RESULTS, + API_GEO_URL, + DEBOUNCE_TIME_MS, +} from "../../config"; +import debounce from "debounce-promise"; + +export type ApiGeoResult = { + code: string; + nom: string; + population: number; + codeDepartement: string; + codesPostaux: string[]; + _score?: number; +}; + +const apiGeoSearchCommunes = async ( + search: string +): Promise => { + const fields = "nom,codesPostaux,population,codeDepartement"; + const response = await Promise.all([ + fetch(`${API_GEO_URL}/communes?nom=${search}&fields=${fields}`), + fetch(`${API_GEO_URL}/communes?codePostal=${search}&fields=${fields}`), + ]); + const apiResults = await Promise.all( + response.map((r) => r.json() as any as ApiGeoResult[]) + ); + const results = [ + ...apiResults[0].slice(0, API_GEO_MAX_SEARCH_RESULTS), + ...apiResults[1], + ]; + const sortedResult = results.sort((a, b) => b.population - a.population); + return sortedResult; +}; + +export const searchCities = debounce(apiGeoSearchCommunes, DEBOUNCE_TIME_MS); diff --git a/packages/code-du-travail-frontend/src/modules/common/Autocomplete/Autocomplete.tsx b/packages/code-du-travail-frontend/src/modules/common/Autocomplete/Autocomplete.tsx new file mode 100644 index 0000000000..ad4de714b7 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/common/Autocomplete/Autocomplete.tsx @@ -0,0 +1,228 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import Image from "next/image"; +import Button from "@codegouvfr/react-dsfr/Button"; +import Input, { InputProps } from "@codegouvfr/react-dsfr/Input"; +import { useCombobox } from "downshift"; +import { useState } from "react"; +import Spinner from "../Spinner.svg"; +import { css } from "@styled-system/css"; +import { redirect } from "next/navigation"; + +export type AutocompleteProps = InputProps & { + onChange?: (value: K | undefined) => void; + onError?: (value: string) => void; + onSearch?: (query: string, results: K[]) => void; + displayLabel: (item: K | null) => string; + search: (search: string) => Promise; + dataTestId?: string; + lineAsLink?: (value: K) => string; + displayNoResult?: boolean; + defaultValue?: K; + onInputValueChange?: (value: string) => void; +}; + +export const Autocomplete = ({ + className, + onChange, + onSearch, + onError, + onInputValueChange, + displayLabel, + lineAsLink, + search, + label, + state, + stateRelatedMessage, + hintText, + classes, + dataTestId, + displayNoResult, + defaultValue, +}: AutocompleteProps) => { + const [value, setValue] = useState( + displayLabel(defaultValue ?? null) + ); + const [loading, setLoading] = useState(false); + const [selectedResult, setSelectedResult] = useState( + defaultValue + ); + const [suggestions, setSuggestions] = useState([]); + const { + isOpen, + getMenuProps, + getInputProps, + highlightedIndex, + getItemProps, + } = useCombobox({ + items: suggestions, + itemToString: displayLabel, + selectedItem: selectedResult, + onSelectedItemChange: (changes) => { + setSelectedResult(changes.selectedItem); + setValue(changes.inputValue ?? ""); + if (onChange) onChange(changes.selectedItem); + if (lineAsLink) redirect(lineAsLink(changes.selectedItem)); + }, + }); + return ( + <> +
+ +
+ {!loading && (selectedResult || value) && ( +
+ + } + nativeInputProps={{ + type: "search", + value, + onChange: async (ev) => { + const inputValue = ev.target.value; + onInputValueChange?.(inputValue); + setValue(inputValue); + if (!inputValue) { + setSelectedResult(undefined); + onSearch?.(inputValue, []); + } + if (selectedResult || inputValue.length <= 1) { + return; + } + try { + setLoading(true); + const results = await search(inputValue); + onSearch?.(inputValue, results); + setSuggestions(results); + } catch (error) { + onError?.(error); + setSuggestions([]); + } finally { + setLoading(false); + } + }, + // @ts-ignore + "data-testid": dataTestId, + }} + className={`${fr.cx("fr-mb-0")}`} + hintText={hintText} + label={label} + state={state} + stateRelatedMessage={stateRelatedMessage} + classes={classes} + /> +
    + {value.length > 1 && + (isOpen && suggestions.length + ? suggestions.map((item, index) => ( + <> +
  • + +
  • + + )) + : displayNoResult && + !selectedResult && ( + <> +
  • + + Aucun résultat + +
  • + + ))} +
+
+ + ); +}; + +const autocompleteContainer = css({ + position: "relative", +}); + +const autocompleteListContainer = css({ + position: "absolute", + w: "calc(100% - 1rem)", + zIndex: 100, + bg: "var(--background-default-grey)", +}); + +const autocompleteButton = css({ + textAlign: "left", +}); + +const buttonActive = css({ + backgroundColor: "rgb(246, 246, 246)", +}); + +const addonBlock = css({ + position: "absolute", + right: 0, + height: "100%", + alignContent: "center", +}); + +const buttonClose = css({ + _before: { + width: "18px !important", + height: "18px !important", + }, + _hover: { + backgroundColor: "unset !important", + }, +}); diff --git a/packages/code-du-travail-frontend/src/modules/common/Autocomplete/index.ts b/packages/code-du-travail-frontend/src/modules/common/Autocomplete/index.ts new file mode 100644 index 0000000000..2e951ebff4 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/common/Autocomplete/index.ts @@ -0,0 +1 @@ +export * from "./Autocomplete"; diff --git a/packages/code-du-travail-frontend/src/modules/common/Spinner.svg b/packages/code-du-travail-frontend/src/modules/common/Spinner.svg new file mode 100644 index 0000000000..fa563880bb --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/common/Spinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearch.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearch.tsx new file mode 100644 index 0000000000..b5269baa29 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearch.tsx @@ -0,0 +1,38 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import { useState } from "react"; + +import Button from "@codegouvfr/react-dsfr/Button"; +import { ButtonStyle } from "../style"; +import { AgreementSearchInput } from "./AgreementSearchInput"; + +export const AgreementSearch = () => { + const [noResult, setNoResult] = useState(false); + return ( + <> + { + setNoResult(query.length > 2 && !result?.length); + }} + /> +
+ + {noResult && ( + + )} +
+ + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchInput.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchInput.tsx new file mode 100644 index 0000000000..d362fc048e --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchInput.tsx @@ -0,0 +1,123 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import { getRouteBySource, SOURCES } from "@socialgouv/cdtn-utils"; +import { useState } from "react"; + +import { Autocomplete } from "../../common/Autocomplete/Autocomplete"; +import { Agreement } from "../../../outils/types"; +import { searchAgreement } from "../search"; + +type Props = { + onSearch?: (query: string, value?: Agreement[]) => void; +}; + +export const AgreementSearchInput = ({ onSearch }: Props) => { + const [searchState, setSearchState] = useState< + "noSearch" | "lowSearch" | "notFoundSearch" | "errorSearch" | "fullSearch" + >("noSearch"); + const [error, setError] = useState(""); + const getStateMessage = () => { + switch (searchState) { + case "lowSearch": + return ( + <> + Indiquez au moins 3 caractères afin d'affiner votre recherche + + ); + case "notFoundSearch": + return ( + <> + Aucune convention collective n'a été trouvée. +
+ Vérifiez l’orthographe de votre recherche ou le chiffre IDCC présent + sur votre bulletin de paie + + ); + case "errorSearch": + return <>{error}; + } + }; + const getInputState = () => { + switch (searchState) { + case "lowSearch": + return "info"; + case "errorSearch": + case "notFoundSearch": + return "error"; + } + }; + return ( + <> +

+ Précisez et sélectionnez votre convention collective +

+
+ + dataTestId="AgreementSearchAutocomplete" + className={fr.cx("fr-col-12", "fr-mb-0")} + hintText="Ex : transport routier ou 1486" + label={ + <> + Nom de la convention collective ou son numéro + d’identification IDCC (4 chiffres) + + } + state={getInputState()} + stateRelatedMessage={getStateMessage()} + displayLabel={(item) => { + return item ? `${item.shortTitle} (IDCC ${item.num})` : ""; + }} + lineAsLink={(item) => { + return `/${getRouteBySource(SOURCES.CCN)}/${item.slug}`; + }} + search={searchAgreement} + onSearch={(query, agreements) => { + if (onSearch) onSearch(query, agreements); + if (!query) { + setSearchState("noSearch"); + } else if (!agreements.length && query.length <= 2) { + setSearchState("lowSearch"); + } else if (!agreements.length && query.length > 2) { + setSearchState("notFoundSearch"); + } else { + setSearchState("fullSearch"); + } + }} + onError={(message) => { + setSearchState("errorSearch"); + setError(message); + }} + /> + {searchState === "notFoundSearch" && ( + +

Il peut y avoir plusieurs explications à cela :

+
    +
  • + Votre convention collective a un autre code : si vous + le pouvez, utilisez le numéro Siret de votre entreprise. Ce + dernier doit être présent sur votre bulletin de paie. +
  • +
  • + Votre convention collective a un statut particulier : + administration ou établissements publics, associations, + secteur agricole, La Poste, La Croix Rouge etc. +
  • +
  • + Votre entreprise n’est rattachée à aucune convention + collective. +
  • +
+ + } + severity="info" + /> + )} +
+ + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchIntro.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchIntro.tsx new file mode 100644 index 0000000000..e382a465f0 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/AgreementSearchIntro.tsx @@ -0,0 +1,78 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Button from "@codegouvfr/react-dsfr/Button"; +import { css } from "@styled-system/css"; + +type Props = { + navigationUrl?: string; +}; + +export const AgreementSearchIntro = ({ + navigationUrl = "/outils/convention-collective", +}: Props) => { + return ( + <> +
+

+ La convention collective est un texte conclu au niveau d'une + branche d'activité (Ex : Transports routiers). Elle adapte + les règles du Code du travail sur des points précis, en fonction des + situations particulières de la branche (primes, congés, salaires + minima, préavis, prévoyance...) +

+

+ Vous pouvez retrouver le nom de votre convention collective sur votre + bulletin de paie ou sur votre contrat de travail. +

+
+
+ + +
+ + ); +}; + +const Paragraph = css({ + fontSize: "18px !important", +}); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/index.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/index.ts new file mode 100644 index 0000000000..0b3c10d16b --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementSearch/index.ts @@ -0,0 +1,3 @@ +export * from "./AgreementSearch"; +export * from "./AgreementSearchInput"; +export * from "./AgreementSearchIntro"; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/Agreements.tsx similarity index 95% rename from packages/code-du-travail-frontend/src/modules/convention-collective/Agreements.tsx rename to packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/Agreements.tsx index 1dfb08f578..d7452a1ae0 100644 --- a/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements.tsx +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/Agreements.tsx @@ -1,5 +1,5 @@ import { fr } from "@codegouvfr/react-dsfr"; -import { ContainerSimulator } from "../layout/ContainerSimulator"; +import { ContainerSimulator } from "../../layout/ContainerSimulator"; import { ElasticAgreement } from "@socialgouv/cdtn-types"; import { AgreementsSection } from "./AgreementsSection"; import { AgreementsGlossaire } from "./AgreementsGlossaire"; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementsGlossaire.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/AgreementsGlossaire.tsx similarity index 100% rename from packages/code-du-travail-frontend/src/modules/convention-collective/AgreementsGlossaire.tsx rename to packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/AgreementsGlossaire.tsx diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementsIntro.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/AgreementsIntro.tsx similarity index 80% rename from packages/code-du-travail-frontend/src/modules/convention-collective/AgreementsIntro.tsx rename to packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/AgreementsIntro.tsx index b33a0ac4f8..1c2da890da 100644 --- a/packages/code-du-travail-frontend/src/modules/convention-collective/AgreementsIntro.tsx +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/Agreements/AgreementsIntro.tsx @@ -2,13 +2,14 @@ import { fr } from "@codegouvfr/react-dsfr"; import Link from "next/link"; import { css } from "@styled-system/css"; import Image from "next/image"; -import { Highlight } from "@codegouvfr/react-dsfr/Highlight"; -import AgreementSearch from "./AgreementSearch.svg"; +import AgreementSearch from "../AgreementSearch.svg"; export const AgreementsIntro = () => { return ( <> -
+
{ Trouver sa convention collective
- - +
+

La convention collective est un texte conclu au niveau d'une - branche d'activité (Ex : Transports routiers). Elle adapte - les règles du Code du travail sur des points précis, en fonction des + branche d'activité (Ex: Transports routiers). Elle adapte les + règles du Code du travail sur des points précis, en fonction des situations particulières de la branche (primes, congés, salaires minima, préavis, prévoyance...) - -
-
- +

+

Vous pouvez retrouver le nom de votre convention collective sur votre bulletin de paie ou sur votre contrat de travail. - - +

+
({ + searchAgreement: jest.fn(), +})); + +describe("Trouver sa CC - recherche par nom de CC", () => { + describe("Test de l'autocomplete", () => { + let rendering: RenderResult; + let userAction: UserAction; + beforeEach(() => { + jest.resetAllMocks(); + rendering = render(); + }); + it("Vérifier le déroulement de la liste & effacement", async () => { + (searchAgreement as jest.Mock).mockImplementation(() => + Promise.resolve([ + { + id: "0016", + num: 16, + url: "https://www.legifrance.gouv.fr/affichIDCC.do?idConvention=KALICONT000005635624", + shortTitle: + "Transports routiers et activités auxiliaires du transport", + slug: "16-transports-routiers-et-activites-auxiliaires-du-transport", + title: + "Convention collective nationale des transports routiers et activités auxiliaires du transport du 21 décembre 1950", + }, + ]) + ); + userAction = new UserAction(); + userAction.setInput(ui.searchByName.input.get(), "16"); + await wait(); + expect( + ui.searchByName.autocompleteLines.IDCC16.name.query() + ).toBeInTheDocument(); + expect( + ui.searchByName.autocompleteLines.IDCC16.link.query() + ).toHaveAttribute( + "href", + "/convention-collective/16-transports-routiers-et-activites-auxiliaires-du-transport" + ); + expect(ui.searchByName.input.get()).toHaveValue("16"); + userAction.click(ui.searchByName.inputCloseBtn.get()); + expect(ui.searchByName.input.get()).toHaveValue(""); + }); + it("Vérifier l'affichage des erreurs", async () => { + (searchAgreement as jest.Mock).mockImplementation(() => + Promise.resolve([]) + ); + userAction = new UserAction(); + userAction.setInput(ui.searchByName.input.get(), "cccc"); + await wait(); + expect(ui.searchByName.errorNotFound.error.query()).toBeInTheDocument(); + expect(ui.searchByName.errorNotFound.info.query()).toBeInTheDocument(); + userAction.click(ui.searchByName.inputCloseBtn.get()); + expect( + ui.searchByName.errorNotFound.error.query() + ).not.toBeInTheDocument(); + expect( + ui.searchByName.errorNotFound.info.query() + ).not.toBeInTheDocument(); + }); + it("Vérifier l'affichage des infos si moins 2 caractères", () => { + (searchAgreement as jest.Mock).mockImplementation(() => + Promise.resolve([]) + ); + act(async () => { + userAction = new UserAction(); + userAction.setInput(ui.searchByName.input.get(), "cc"); + await wait(); + expect(ui.searchByName.infoNotFound.query()).toBeInTheDocument(); + userAction.click(ui.searchByName.inputCloseBtn.get()); + expect(ui.searchByName.infoNotFound.query()).not.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/queries.es.test.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/queries.es.test.ts index 6f4e589424..0f4434d6c4 100644 --- a/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/queries.es.test.ts +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/queries.es.test.ts @@ -1,10 +1,12 @@ /** @jest-environment node */ -import { fetchAgreements } from "../queries"; +import { fetchAllAgreements, fetchAgreementsByCdtnIds } from "../queries"; describe("Conventions collectives", () => { it("Récupération de toutes les conventions collectives", async () => { - const result = await fetchAgreements(["slug", "title", "num"]); + const result = await fetchAllAgreements({ + fields: ["slug", "title", "num"], + }); expect(result).toEqual([ { num: 843, @@ -32,4 +34,12 @@ describe("Conventions collectives", () => { }, ]); }); + + it("Récupération des cc filtrées", async () => { + const result = await fetchAgreementsByCdtnIds({ + fields: ["slug"], + cdtnIds: ["647c224c9b", "a25dfc974f", "98b9c85542"], + }); + expect(result.length).toEqual(3); + }); }); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/search.test.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/search.test.ts new file mode 100644 index 0000000000..0efffbb86f --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/search.test.ts @@ -0,0 +1,24 @@ +/** @jest-environment node */ + +import { searchAgreement } from "../search"; +import { nafError } from "../error"; + +describe("Test messages erreur api cc", () => { + it("Vérifier l'affichage du message d'erreur pour les mauvais code Naf", async () => { + try { + await searchAgreement("1234A"); + } catch (e) { + expect(e).toEqual(nafError); + } + }); + + it("Vérifier l'affichage du message d'erreur concernant du format du code", async () => { + try { + await searchAgreement("12345366"); + } catch (e) { + expect(e).toEqual( + "Numéro d’indentification (IDCC) incorrect. Ce numéro est composé de 4 chiffres uniquement." + ); + } + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/ui.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/ui.ts new file mode 100644 index 0000000000..ef45b06d6e --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/__tests__/ui.ts @@ -0,0 +1,68 @@ +import { + byText, + byLabelText, + byTestId, + byRole, +} from "testing-library-selector"; + +export const ui = { + searchByName: { + input: byLabelText( + /Nom de la convention collective ou son numéro d’identification IDCC \(4 chiffres\)/ + ), + inputCloseBtn: byTestId("AgreementSearchAutocomplete-autocomplete-close"), + autocompleteLines: { + IDCC16: { + name: byText( + /Transports routiers et activités auxiliaires du transport \(IDCC 16\)/ + ), + link: byRole("link", { + name: "Transports routiers et activités auxiliaires du transport (IDCC 16)", + }), + }, + }, + errorNotFound: { + error: byText(/Aucune convention collective n'a été trouvée\./), + info: byText(/Vous ne trouvez pas votre convention collective \?/), + }, + infoNotFound: byText( + /Indiquez au moins 3 caractères afin d'affiner votre recherche/ + ), + }, + searchByEnterprise: { + input: byLabelText(/Nom de votre entreprise ou numéro Siren\/Siret/), + inputLocation: byLabelText("Code postal ou Ville (optionnel)"), + submitButton: byText("Rechercher"), + resultLines: { + carrefour: { + title: byText("CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)"), + link: byRole("link", { + name: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + }), + }, + }, + errorNotFound: { + error: byText(/Aucune entreprise n'a été trouvée\./), + info: byText(/Vous ne trouvez pas votre entreprise \?/), + }, + }, + selection: { + carrefour: { + title: byText("CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)"), + activity: byText( + "Activité : Location-bail de propriété intellectuelle et de produits similaires, à l’exception des œuvres soumises à copyright" + ), + address: byText("ZI ROUTE DE PARIS 14120 MONDEVILLE"), + }, + agreement: { + IDCC2216: { + title: byText( + "Commerce de détail et de gros à prédominance alimentaire" + ), + link: byRole("link", { + name: "Commerce de détail et de gros à prédominance alimentaire", + }), + }, + }, + }, +}; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/agreementRelatedItems.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/agreementRelatedItems.ts new file mode 100644 index 0000000000..85449f879f --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/agreementRelatedItems.ts @@ -0,0 +1,25 @@ +import { SOURCES } from "@socialgouv/cdtn-utils"; + +export const agreementRelatedItems = [ + { + title: "Articles liés", + items: [ + { + url: `/fiche-service-public/convention-collective`, + title: "Convention collective", + source: SOURCES.SHEET_SP, + }, + { + url: `/fiche-service-public/comment-consulter-un-accord-dentreprise`, + source: SOURCES.SHEET_SP, + title: "Comment consulter un accord d'entreprise ?", + }, + { + url: `/droit-du-travail/#hierarchie`, + source: SOURCES.LABOUR_LAW, + title: + "Droit du travail: Existe-t-il une hiérarchie entre les textes ?", + }, + ], + }, +]; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/error.tsx b/packages/code-du-travail-frontend/src/modules/convention-collective/error.tsx new file mode 100644 index 0000000000..49b104d6bf --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/error.tsx @@ -0,0 +1,10 @@ +import React from "react"; + +export const nafError = ( + <> + Numéro d’indentification (IDCC) incorrect. Il semblerait que vous ayez saisi + un code APE (Activité Principale Exercée) ou NAF (Nomenclature des Activités + Françaises) et dont l’objectif est d’identifier l’activité principale de + l’entreprise. + +); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/index.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/index.ts index 7b93cd8ada..86e7fab132 100644 --- a/packages/code-du-travail-frontend/src/modules/convention-collective/index.ts +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/index.ts @@ -1,2 +1,6 @@ export * from "./queries"; -export * from "./Agreements"; +export * from "./Agreements/Agreements"; +export * from "./AgreementSearch/AgreementSearch"; +export * from "./AgreementSearch/AgreementSearchIntro"; +export * from "./AgreementSearch/AgreementSearchInput"; +export * from "./search"; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/queries.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/queries.ts index 7cb0354fcb..d67dd5b235 100644 --- a/packages/code-du-travail-frontend/src/modules/convention-collective/queries.ts +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/queries.ts @@ -3,30 +3,61 @@ import { orderByAlpha } from "../utils"; import { SOURCES } from "@socialgouv/cdtn-utils"; import { elasticDocumentsIndex, elasticsearchClient } from "../../api/utils"; -export const fetchAgreements = async ( - fields: K[], - sortBy?: K, - filters?: { - cdtnIds?: string[]; - } -): Promise[]> => { - const baseFilters: Array = [ - { term: { source: SOURCES.CCN } }, - { term: { isPublished: true } }, - { term: { contributions: true } }, - ]; +type Props = { + fields: K[]; + sortBy?: K; + size?: number; +}; - if (filters?.cdtnIds) { - baseFilters.push({ terms: { cdtnId: filters.cdtnIds } }); - } +const baseFilters = [ + { term: { source: SOURCES.CCN } }, + { term: { isPublished: true } }, + { term: { contributions: true } }, +]; +export const fetchAllAgreements = async ({ + fields, + sortBy, + size = 100, +}: Props): Promise[]> => { const response = await elasticsearchClient.search>({ query: { bool: { filter: baseFilters, }, }, - size: 100, + size, + _source: fields, + index: elasticDocumentsIndex, + }); + + const data = response.hits.hits + .map(({ _source }) => _source) + .filter((item) => item !== undefined); + if (sortBy) { + data.sort((a, b) => orderByAlpha(a, b, sortBy)); + } + return data; +}; + +type FetchAgreementsByCdtnIdsProps = Props & { + cdtnIds: string[]; +}; + +export const fetchAgreementsByCdtnIds = async < + K extends keyof ElasticAgreement, +>({ + fields, + sortBy, + cdtnIds, +}: FetchAgreementsByCdtnIdsProps): Promise[]> => { + const response = await elasticsearchClient.search>({ + query: { + bool: { + filter: [...baseFilters, { terms: { cdtnId: cdtnIds } }], + }, + }, + size: cdtnIds.length, _source: fields, index: elasticDocumentsIndex, }); diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/search.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/search.ts new file mode 100644 index 0000000000..fa2d6f0fd7 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/search.ts @@ -0,0 +1,52 @@ +import debounce from "debounce-promise"; +import { nafError } from "./error"; +import { SITE_URL } from "../../config"; +import { Agreement } from "../../outils/types"; + +const formatCCn = ({ num, id, slug, title, shortTitle, highlight, url }) => ({ + ...(highlight ? { highlight } : {}), + id, + num, + url, + shortTitle, + slug, + title, +}); + +export const onlyNumberError = + "Numéro d’indentification (IDCC) incorrect. Ce numéro est composé de 4 chiffres uniquement."; + +const apiIdcc = function createFetcher(query: string): Promise { + if (/^\d{4}[A-Za-z]$/.test(query.replace(/\W/g, ""))) { + return Promise.reject(nafError); + } + if (/^\d{5,}$/.test(query.replace(/^(\s+)|(\s+)$/g, ""))) { + return Promise.reject(onlyNumberError); + } + let url = `${SITE_URL}/api/idcc?q=${encodeURIComponent(query)}`; + + if (/^\d+$/.test(query.replace(/\W/g, ""))) { + url = `${SITE_URL}/api/idcc?q=${encodeURIComponent(parseInt(query.replace(/\W/g, "")))}&size=10`; + } + return fetch(url) + .then(async (response) => { + if (response.ok) { + return response + .json() + .then( + (results) => + results.hits.hits.map(({ _source }) => + formatCCn(_source) + ) as Agreement[] + ); + } + throw new Error(); + }) + .catch(() => { + return Promise.reject("Ce service est momentanément indisponible."); + }); +}; + +const searchAgreement = debounce(apiIdcc, 300); + +export { searchAgreement, apiIdcc }; diff --git a/packages/code-du-travail-frontend/src/modules/convention-collective/style.ts b/packages/code-du-travail-frontend/src/modules/convention-collective/style.ts new file mode 100644 index 0000000000..b4228440c4 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/convention-collective/style.ts @@ -0,0 +1,17 @@ +import { css } from "@styled-system/css"; + +export const CardTitleStyle = css({ + "& > a": { + _after: { + top: "calc(50% - 16px)", + }, + }, +}); + +export const ButtonStyle = css({ + maxH: "40px!", + width: "100%!", + md: { + w: "auto!", + }, +}); diff --git a/packages/code-du-travail-frontend/src/modules/documents/type.ts b/packages/code-du-travail-frontend/src/modules/documents/type.ts index d3825b5f63..21120d5c33 100644 --- a/packages/code-du-travail-frontend/src/modules/documents/type.ts +++ b/packages/code-du-travail-frontend/src/modules/documents/type.ts @@ -12,6 +12,7 @@ export const sources = [ SOURCES.LETTERS, SOURCES.CONTRIBUTIONS, SOURCES.EXTERNALS, + SOURCES.LABOUR_LAW, ] as const; export type RelatedItem = Pick & { diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearch.tsx b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearch.tsx new file mode 100644 index 0000000000..55f143d9f9 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearch.tsx @@ -0,0 +1,38 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import Button from "@codegouvfr/react-dsfr/Button"; + +import { ButtonStyle } from "../../convention-collective/style"; +import { EnterpriseAgreementSearchInput } from "./EnterpriseAgreementSearchInput"; +import { useSearchParams } from "next/navigation"; + +type Props = { + widgetMode?: boolean; +}; + +export const EnterpriseAgreementSearch = ({ widgetMode = false }: Props) => { + const searchParams = useSearchParams(); + const defaultSearch = searchParams?.get("q") ?? undefined; + const cp = searchParams?.get("cp"); + const defaultLocation = cp ? JSON.parse(atob(cp)) : undefined; + return ( + <> + + {!widgetMode && ( +
+ +
+ )} + + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearchInput.tsx b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearchInput.tsx new file mode 100644 index 0000000000..c7f8e41022 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSearchInput.tsx @@ -0,0 +1,319 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import Image from "next/image"; +import Button from "@codegouvfr/react-dsfr/Button"; +import Input from "@codegouvfr/react-dsfr/Input"; +import Badge from "@codegouvfr/react-dsfr/Badge"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import { Card } from "@codegouvfr/react-dsfr/Card"; +import { useEffect, useState } from "react"; +import { css } from "@styled-system/css"; + +import Spinner from "../../common/Spinner.svg"; +import { LocationSearchInput } from "../../Location/LocationSearchInput"; +import { searchEnterprises } from "../queries"; +import { Enterprise } from "../types"; +import { ApiGeoResult } from "../../Location/searchCities"; +import { CardTitleStyle, ButtonStyle } from "../../convention-collective/style"; + +type Props = { + widgetMode?: boolean; + defaultSearch?: string; + defaultLocation?: ApiGeoResult; +}; + +export const EnterpriseAgreementSearchInput = ({ + widgetMode = false, + defaultSearch, + defaultLocation, +}: Props) => { + const [searchState, setSearchState] = useState< + "noSearch" | "notFoundSearch" | "errorSearch" | "fullSearch" | "required" + >("noSearch"); + const [search, setSearch] = useState(defaultSearch); + const [loading, setLoading] = useState(false); + const [location, setLocation] = useState( + defaultLocation + ); + const [enterprises, setEnterprises] = useState(); + const [error, setError] = useState(""); + const getStateMessage = () => { + switch (searchState) { + case "notFoundSearch": + return ( + <> + Aucune entreprise n'a été trouvée. +
+ Vérifiez l’orthographe des termes de recherche + + ); + case "required": + return <>Le nom de l'entreprise doit être renseigné; + case "errorSearch": + return <>{error}; + } + }; + const getStateMargin = () => { + switch (searchState) { + case "notFoundSearch": + return "fr-mb-14v"; + case "errorSearch": + case "required": + return "fr-mb-9v"; + } + return "fr-mb-0"; + }; + const getInputState = () => { + switch (searchState) { + case "errorSearch": + case "notFoundSearch": + case "required": + return "error"; + } + }; + const getQueries = () => { + const jsonString = JSON.stringify(location); + const base64String = btoa(jsonString); + return search + ? `?q=${encodeURIComponent(search)}${ + jsonString ? `&cp=${base64String}` : "" + }` + : ""; + }; + const onSubmit = async () => { + if (!search) { + setSearchState("required"); + return; + } + setLoading(true); + try { + const result = await searchEnterprises({ + query: search, + codesPostaux: location?.codesPostaux, + }); + setSearchState(!result.length ? "errorSearch" : "fullSearch"); + setSearchState( + search.length > 0 && !result.length ? "notFoundSearch" : "noSearch" + ); + setEnterprises(result); + } catch (e) { + setSearchState("errorSearch"); + setEnterprises(undefined); + setError(e); + } finally { + setLoading(false); + } + }; + useEffect(() => { + if (defaultSearch) { + onSubmit(); + } + }, [defaultSearch]); + return ( + <> +

+ Précisez votre entreprise +

+
{ + event.preventDefault(); + await onSubmit(); + }} + > + + Ex : Café de la mairie ou 40123778000127 (présent sur la + fiche de paie du salarié) + + } + label={<>Nom de votre entreprise ou numéro Siren/Siret} + state={getInputState()} + stateRelatedMessage={getStateMessage()} + nativeInputProps={{ + value: search, + onChange: (event) => { + setSearch(event.target.value); + }, + }} + classes={{ + label: css({ + "& > button": { + padding: "0!", + minHeight: "0!", + height: "20px!", + width: "24px!", + marginLeft: "3px!", + }, + }), + }} + /> + +
+ +
+ + +
+
+ {!!enterprises?.length && !loading && ( +

+ {enterprises.length} + {enterprises.length > 1 + ? " entreprises trouvées" + : " entreprise trouvée"} +

+ )} + {loading && ( +
+

Chargement en cours

+
+ Chargement en cours +
+
+ )} + {searchState === "notFoundSearch" && ( + Vous ne trouvez pas votre entreprise ?} + description={ + <> +

Il peut y avoir plusieurs explications à cela :

+
    +
  • + Votre entreprise a été enregistrée sous un autre nom ou un + autre code : si vous le pouvez, utilisez son numéro + Siret. Ce dernier doit être présent sur votre bulletin de + paie. +
  • +
  • + Votre entreprise a un statut particulier : + administration ou établissements publics, associations, + secteur agricole, La Poste, La Croix Rouge etc. +
  • +
  • Votre entreprise n’a pas de convention collective.
  • +
+ + } + severity="info" + /> + )} +
+ {!!enterprises?.length && + !loading && + enterprises?.map((enterprise) => ( + Activité : {enterprise.activitePrincipale} + ) : undefined + } + end={{`${enterprise.matching} établissements`}} + size="large" + title={enterprise.label} + classes={{ + title: `${fr.cx("fr-h5")} ${CardTitleStyle}`, + content: fr.cx("fr-px-2w", "fr-pt-1w", "fr-pb-8w"), + desc: fr.cx("fr-mt-1w", "fr-mr-6w"), + end: fr.cx("fr-mt-0", "fr-pt-1w", "fr-pb-2w"), + }} + /> + ))} +
+
+

+ Votre recherche concerne les assistants maternels, employés de + maison ? +

+ +
+ + ); +}; + +const CardSmallTitleStyle = css({ + fontSize: "initial !important", + "& > a": { + _after: { + top: "calc(50% - 8px)", + }, + }, +}); + +const SpinnerBlock = css({ + height: "100%", + alignContent: "center", + marginTop: "0.5rem", +}); + +const ButtonContainer = css({ + md: { + maxW: "164px", + }, +}); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSelection.tsx b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSelection.tsx new file mode 100644 index 0000000000..19ab94685f --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/EnterpriseAgreementSelection.tsx @@ -0,0 +1,146 @@ +"use client"; +import { fr } from "@codegouvfr/react-dsfr"; +import Card from "@codegouvfr/react-dsfr/Card"; +import Button from "@codegouvfr/react-dsfr/Button"; +import { Enterprise } from "../types"; +import { ButtonStyle, CardTitleStyle } from "../../convention-collective/style"; +import { css } from "@styled-system/css"; +import { useSearchParams } from "next/navigation"; + +type Props = { + enterprise: Omit; + widgetMode?: boolean; +}; + +export const EnterpriseAgreementSelection = ({ + enterprise, + widgetMode = false, +}: Props) => { + const searchParams = useSearchParams(); + return ( + <> +

+ {enterprise.conventions.length === 0 ? ( + `Aucune convention collective n'a été déclarée pour l'entreprise` + ) : enterprise.conventions.length === 1 ? ( + <>1 convention collective trouvée pour : + ) : ( + <> + {enterprise.conventions.length} conventions collectives trouvées + pour : + + )} +

+

+ {enterprise.label} +

+ {enterprise.activitePrincipale && ( +

+ Activité : {enterprise.activitePrincipale} +

+ )} +

{enterprise.address}

+ {enterprise.conventions?.map((agreement) => { + const { slug, url, contributions } = agreement; + let disabled = false; + let description; + if (slug && !(url || contributions)) { + description = + "Nous n’avons pas d’informations concernant cette convention collective"; + disabled = true; + } else if (!slug) { + description = + "Cette convention collective déclarée par l’entreprise n’est pas reconnue par notre site"; + disabled = true; + } else { + description = + "Retrouvez les questions-réponses les plus fréquentes organisées par thème et élaborées par le Ministère du travail concernant cette convention collective"; + } + return ( + { + ev.preventDefault(); + }, + }), + ...(widgetMode + ? { + target: "_blank", + onClick: (ev) => { + if (disabled) ev.preventDefault(); + window.parent?.postMessage( + { + name: "agreement", + kind: "select", + extra: { + idcc: agreement.num, + title: agreement.title, + }, + }, + "*" + ); + }, + } + : {}), + }} + border + enlargeLink + size="large" + desc={description} + title={`${agreement.shortTitle} IDCC ${agreement.id}`} + classes={{ + title: `${fr.cx("fr-h5")} ${CardTitleStyle} ${disabled ? disabledTitle : ""}`, + content: `${fr.cx("fr-px-2w", "fr-pt-1w", "fr-pb-7v")} ${disabled ? disabledContent : ""}`, + desc: fr.cx("fr-mt-1w", "fr-mr-6w"), + end: fr.cx("fr-hidden"), + root: `${disabled ? disabledRoot : ""}`, + }} + /> + ); + })} + +
+ +
+ + ); +}; + +const disabledRoot = css({ + "&:hover": { + backgroundColor: "unset", + }, + color: `var(--text-disabled-grey) !important`, +}); + +const disabledTitle = css({ + "& a,button": { + color: `var(--text-disabled-grey) !important`, + cursor: "not-allowed !important", + _before: { + cursor: "not-allowed", + }, + }, +}); + +const disabledContent = css({ + cursor: "not-allowed", +}); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/AgreementSelection.test.tsx b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/AgreementSelection.test.tsx new file mode 100644 index 0000000000..8e0ca4a2b7 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/AgreementSelection.test.tsx @@ -0,0 +1,125 @@ +import { render, RenderResult, screen } from "@testing-library/react"; +import React from "react"; +import { EnterpriseAgreementSelection } from "../EnterpriseAgreementSelection"; +import { ui } from "./ui"; + +const defaultEnterprise = { + activitePrincipale: + "Location-bail de propriété intellectuelle et de produits similaires, à l’exception des œuvres soumises à copyright", + etablissements: 1294, + highlightLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + label: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + simpleLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + matching: 1294, + siren: "345130488", + address: "ZI ROUTE DE PARIS 14120 MONDEVILLE", + firstMatchingEtablissement: { + siret: "34513048800017", + address: "ZI ROUTE DE PARIS 14120 MONDEVILLE", + }, + conventions: [ + { + id: "2216", + contributions: true, + num: 2216, + shortTitle: "Commerce de détail et de gros à prédominance alimentaire", + title: + "Convention collective nationale du commerce de détail et de gros à prédominance alimentaire du 12 juillet 2001. Etendue par arrêté du 26 juillet 2002 JORF 6 août 2002.", + url: "https://www.legifrance.gouv.fr/affichIDCC.do?idConvention=KALICONT000005635085", + slug: "2216-commerce-de-detail-et-de-gros-a-predominance-alimentaire", + }, + ], +}; + +describe("Trouver sa CC - recherche par nom d'entreprise CC", () => { + let rendering: RenderResult; + it("Vérifier l'affichage de la selection", async () => { + rendering = render( + + ); + expect( + ui.enterpriseAgreementSelection.carrefour.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.carrefour.activity.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.carrefour.address.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.description.known.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).toHaveAttribute( + "href", + "/convention-collective/2216-commerce-de-detail-et-de-gros-a-predominance-alimentaire" + ); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).not.toHaveAttribute("target", "_blank"); + }); + + it("Vérifier l'affichage de la selection avec une CC sans slug", async () => { + rendering = render( + + ); + expect( + ui.enterpriseAgreementSelection.description.unknown.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).not.toBeInTheDocument(); + }); + + it("Vérifier l'affichage de la selection avec une CC sans url et contribution", async () => { + rendering = render( + + ); + expect( + ui.enterpriseAgreementSelection.description.notFound.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).not.toBeInTheDocument(); + }); + + it("Vérifier l'affichage de la selection en widgetMode", async () => { + rendering = render( + + ); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).toHaveAttribute( + "href", + "/convention-collective/2216-commerce-de-detail-et-de-gros-a-predominance-alimentaire" + ); + expect( + ui.enterpriseAgreementSelection.agreement.IDCC2216.link.query() + ).toHaveAttribute("target", "_blank"); + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/EnterpriseAgreementSearch.test.tsx b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/EnterpriseAgreementSearch.test.tsx new file mode 100644 index 0000000000..00e56302dc --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/EnterpriseAgreementSearch.test.tsx @@ -0,0 +1,168 @@ +import { render, RenderResult } from "@testing-library/react"; +import { UserAction } from "../../../../common"; +import React from "react"; +import { EnterpriseAgreementSearch } from "../EnterpriseAgreementSearch"; +import { ui } from "./ui"; +import { wait } from "@testing-library/user-event/dist/utils"; +import { searchEnterprises } from "../../queries"; + +jest.mock("../../queries", () => ({ + searchEnterprises: jest.fn(), +})); + +describe("Trouver sa CC - recherche par nom d'entreprise CC", () => { + describe("Test de l'autocomplete", () => { + let rendering: RenderResult; + let userAction: UserAction; + beforeEach(() => { + jest.resetAllMocks(); + }); + it("Vérifier l'affichage de la recherche", async () => { + rendering = render(); + (searchEnterprises as jest.Mock).mockImplementation(() => + Promise.resolve([ + { + activitePrincipale: + "Location-bail de propriété intellectuelle et de produits similaires, à l’exception des œuvres soumises à copyright", + etablissements: 1294, + highlightLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + label: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + simpleLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + matching: 1294, + siren: "345130488", + address: "ZI ROUTE DE PARIS 14120 MONDEVILLE", + firstMatchingEtablissement: { + siret: "34513048802674", + address: "N°6639 205 RUE SAINT-HONORE 75001 PARIS", + }, + conventions: [ + { + id: "2216", + contributions: true, + num: 2216, + shortTitle: + "Commerce de détail et de gros à prédominance alimentaire", + title: + "Convention collective nationale du commerce de détail et de gros à prédominance alimentaire du 12 juillet 2001. Etendue par arrêté du 26 juillet 2002 JORF 6 août 2002.", + url: "https://www.legifrance.gouv.fr/affichIDCC.do?idConvention=KALICONT000005635085", + slug: "2216-commerce-de-detail-et-de-gros-a-predominance-alimentaire", + }, + ], + }, + ]) + ); + userAction = new UserAction(); + userAction.setInput( + ui.enterpriseAgreementSearch.input.get(), + "carrefour" + ); + userAction.click(ui.enterpriseAgreementSearch.submitButton.get()); + await wait(); + expect( + ui.enterpriseAgreementSearch.resultLines.carrefour.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSearch.resultLines.carrefour.link.query() + ).toHaveAttribute( + "href", + "/outils/convention-collective/entreprise/345130488?q=carrefour" + ); + expect( + ui.enterpriseAgreementSearch.childminder.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSearch.childminder.link.query() + ).toHaveAttribute( + "href", + "/convention-collective/3239-particuliers-employeurs-et-emploi-a-domicile" + ); + expect( + ui.enterpriseAgreementSearch.childminder.link.query() + ).not.toHaveAttribute("target", "_blank"); + }); + + it("Vérifier l'affichage de l'erreur si aucun résultat", async () => { + rendering = render(); + (searchEnterprises as jest.Mock).mockImplementation(() => + Promise.resolve([]) + ); + userAction = new UserAction(); + userAction.setInput( + ui.enterpriseAgreementSearch.input.get(), + "recherche" + ); + userAction.click(ui.enterpriseAgreementSearch.submitButton.get()); + await wait(); + expect( + ui.enterpriseAgreementSearch.errorNotFound.error.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSearch.errorNotFound.info.query() + ).toBeInTheDocument(); + }); + + it("Vérifier l'affichage de la recherche en mode widget", async () => { + rendering = render(); + (searchEnterprises as jest.Mock).mockImplementation(() => + Promise.resolve([ + { + activitePrincipale: + "Location-bail de propriété intellectuelle et de produits similaires, à l’exception des œuvres soumises à copyright", + etablissements: 1294, + highlightLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + label: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + simpleLabel: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + matching: 1294, + siren: "345130488", + address: "ZI ROUTE DE PARIS 14120 MONDEVILLE", + firstMatchingEtablissement: { + siret: "34513048802674", + address: "N°6639 205 RUE SAINT-HONORE 75001 PARIS", + }, + conventions: [ + { + id: "2216", + contributions: true, + num: 2216, + shortTitle: + "Commerce de détail et de gros à prédominance alimentaire", + title: + "Convention collective nationale du commerce de détail et de gros à prédominance alimentaire du 12 juillet 2001. Etendue par arrêté du 26 juillet 2002 JORF 6 août 2002.", + url: "https://www.legifrance.gouv.fr/affichIDCC.do?idConvention=KALICONT000005635085", + slug: "2216-commerce-de-detail-et-de-gros-a-predominance-alimentaire", + }, + ], + }, + ]) + ); + userAction = new UserAction(); + userAction.setInput( + ui.enterpriseAgreementSearch.input.get(), + "carrefour" + ); + userAction.click(ui.enterpriseAgreementSearch.submitButton.get()); + await wait(); + expect( + ui.enterpriseAgreementSearch.resultLines.carrefour.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSearch.resultLines.carrefour.link.query() + ).toHaveAttribute( + "href", + "/widgets/convention-collective/entreprise/345130488?q=carrefour" + ); + expect( + ui.enterpriseAgreementSearch.childminder.title.query() + ).toBeInTheDocument(); + expect( + ui.enterpriseAgreementSearch.childminder.link.query() + ).toHaveAttribute( + "href", + "/convention-collective/3239-particuliers-employeurs-et-emploi-a-domicile" + ); + expect( + ui.enterpriseAgreementSearch.childminder.link.query() + ).toHaveAttribute("target", "_blank"); + }); + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/ui.ts b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/ui.ts new file mode 100644 index 0000000000..2930cd7ba9 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/__tests__/ui.ts @@ -0,0 +1,57 @@ +import { byText, byLabelText, byRole } from "testing-library-selector"; + +export const ui = { + enterpriseAgreementSearch: { + input: byLabelText(/Nom de votre entreprise ou numéro Siren\/Siret/), + inputLocation: byLabelText("Code postal ou Ville (optionnel)"), + submitButton: byText("Rechercher"), + childminder: { + title: byText("Particuliers employeurs et emploi à domicile"), + link: byRole("link", { + name: "Particuliers employeurs et emploi à domicile", + }), + }, + resultLines: { + carrefour: { + title: byText("CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)"), + link: byRole("link", { + name: "CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)", + }), + }, + }, + errorNotFound: { + error: byText(/Aucune entreprise n'a été trouvée\./), + info: byText(/Vous ne trouvez pas votre entreprise \?/), + }, + }, + enterpriseAgreementSelection: { + carrefour: { + title: byText("CARREFOUR PROXIMITE FRANCE (SHOPI-8 A HUIT)"), + activity: byText( + "Activité : Location-bail de propriété intellectuelle et de produits similaires, à l’exception des œuvres soumises à copyright" + ), + address: byText("ZI ROUTE DE PARIS 14120 MONDEVILLE"), + }, + description: { + notFound: byText( + "Nous n’avons pas d’informations concernant cette convention collective" + ), + unknown: byText( + "Cette convention collective déclarée par l’entreprise n’est pas reconnue par notre site" + ), + known: byText( + "Retrouvez les questions-réponses les plus fréquentes organisées par thème et élaborées par le Ministère du travail concernant cette convention collective" + ), + }, + agreement: { + IDCC2216: { + title: byText( + "Commerce de détail et de gros à prédominance alimentaire IDCC 2216" + ), + link: byRole("link", { + name: "Commerce de détail et de gros à prédominance alimentaire IDCC 2216", + }), + }, + }, + }, +}; diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/index.ts b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/index.ts new file mode 100644 index 0000000000..8fa19fd2a3 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/index.ts @@ -0,0 +1,3 @@ +export * from "./EnterpriseAgreementSearch"; +export * from "./EnterpriseAgreementSearchInput"; +export * from "./EnterpriseAgreementSelection"; diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/store.ts b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/store.ts new file mode 100644 index 0000000000..cccc46473a --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/EnterpriseAgreementSearch/store.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +export const useEnterpriseSearchStore = create<{ + search: string; + setSearch: (value: string) => void; +}>((set) => ({ + search: "", + setSearch: (search) => { + set(() => ({ search })); + }, +})); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/__tests__/queries.test.ts b/packages/code-du-travail-frontend/src/modules/enterprise/__tests__/queries.test.ts new file mode 100644 index 0000000000..52c00e192c --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/__tests__/queries.test.ts @@ -0,0 +1,35 @@ +/** @jest-environment node */ + +import { searchEnterprises } from "../queries"; + +describe("Test messages erreur api entreprise", () => { + it("Vérifier l'affichage du message d'erreur siren/siret", async () => { + try { + await searchEnterprises({ query: "123" }); + } catch (e) { + expect(e).toEqual( + "Veuillez indiquer un numéro Siret (14 chiffres) ou Siren (9 chiffres) valide" + ); + } + }); + + it("Vérifier l'affichage du message d'erreur 14 chiffres obligatoire", async () => { + try { + await searchEnterprises({ query: "123436663636" }); + } catch (e) { + expect(e).toEqual( + "Veuillez indiquer un numéro Siret (14 chiffres obligatoire)" + ); + } + }); + + it("Vérifier l'affichage du message d'erreur pas de lettre", async () => { + try { + await searchEnterprises({ query: "A23436663636366" }); + } catch (e) { + expect(e).toEqual( + "Veuillez indiquer un numéro Siret (14 chiffres uniquement)" + ); + } + }); +}); diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/index.ts b/packages/code-du-travail-frontend/src/modules/enterprise/index.ts new file mode 100644 index 0000000000..ffe960f02f --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/index.ts @@ -0,0 +1,2 @@ +export * from "./EnterpriseAgreementSearch"; +export * from "./queries"; diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/queries.ts b/packages/code-du-travail-frontend/src/modules/enterprise/queries.ts new file mode 100644 index 0000000000..8f4f10611d --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/queries.ts @@ -0,0 +1,62 @@ +import debounce from "debounce-promise"; +import { SITE_URL } from "../../config"; +import { Enterprise } from "./types"; + +export type SearchParams = { + query: string; + codesPostaux?: string[]; +}; + +export const messageFetchSearchCcOrEnterprise = + "Ce service est momentanément indisponible. Dans le cas de l'utilisation d'un simulateur, vous pourrait tout de même poursuivre la simulation pour obtenir le résultat prévu par le code du travail en sélectionnant l'option \"Je ne souhaite pas renseigner ma convention collective (je passe l'étape)\""; + +const siretSirenError = + "Veuillez indiquer un numéro Siret (14 chiffres) ou Siren (9 chiffres) valide"; + +const siretLengthError = + "Veuillez indiquer un numéro Siret (14 chiffres obligatoire)"; + +const siretNumberError = + "Veuillez indiquer un numéro Siret (14 chiffres uniquement)"; + +const apiEnterprises = function createFetcher( + searchParams: SearchParams +): Promise { + if ( + /^\d{2,8}$/.test(searchParams.query.replace(/\s/g, "")) || + /^\d{4}[A-Za-z]$/.test(searchParams.query.replace(/\W/g, "")) + ) { + return Promise.reject(siretSirenError); + } + if ( + /^\d{10,13}$/.test(searchParams.query.replace(/\s/g, "")) || + /^\d{15,}$/.test(searchParams.query.replace(/\s/g, "")) + ) { + return Promise.reject(siretLengthError); + } + if (/\D+\d{14}/.test(searchParams.query.replace(/\s/g, ""))) { + return Promise.reject(siretNumberError); + } + + const url = `${SITE_URL}/api/enterprises?q=${encodeURIComponent(searchParams.query)}${ + searchParams.codesPostaux + ? `&cp=${encodeURIComponent(searchParams.codesPostaux.join(","))}` + : "" + }`; + + return fetch(url) + .then(async (response) => { + if (response.ok) { + const res = await response.json(); + return res.entreprises; + } + throw new Error(); + }) + .catch(() => { + return Promise.reject(messageFetchSearchCcOrEnterprise); + }); +}; + +const searchEnterprises = debounce(apiEnterprises, 300); + +export { searchEnterprises }; diff --git a/packages/code-du-travail-frontend/src/modules/enterprise/types.ts b/packages/code-du-travail-frontend/src/modules/enterprise/types.ts new file mode 100644 index 0000000000..4fa078f000 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/enterprise/types.ts @@ -0,0 +1,31 @@ +import { ElasticAgreement } from "@socialgouv/cdtn-types"; + +export type ApiEnterpriseData = { + entreprises: Enterprise[] | undefined; +}; + +export type MatchingEtablissement = { + siret: string; + address: string; +}; + +export type Enterprise = { + activitePrincipale?: string; + conventions: EnterpriseAgreement[]; + complements: { + liste_idcc: string[]; + }; + etablissements: number; + highlightLabel: string; + label: string; + matching: number; + simpleLabel: string; + siren: string; + address?: string; + firstMatchingEtablissement?: MatchingEtablissement; +}; + +export type EnterpriseAgreement = Pick< + ElasticAgreement, + "id" | "contributions" | "num" | "shortTitle" | "title" | "url" | "slug" +>; diff --git a/packages/code-du-travail-frontend/src/modules/home/queries.ts b/packages/code-du-travail-frontend/src/modules/home/queries.ts index b9fcd2655a..08d0b41670 100644 --- a/packages/code-du-travail-frontend/src/modules/home/queries.ts +++ b/packages/code-du-travail-frontend/src/modules/home/queries.ts @@ -4,7 +4,7 @@ import { fetchHighLights } from "../highlights"; import { fetchTools } from "../outils"; import { fetchModels } from "../modeles-de-courriers"; import { fetchContributions } from "../contributions"; -import { fetchAgreements } from "../convention-collective"; +import { fetchAgreementsByCdtnIds } from "../convention-collective"; export type HomeCardItem = { description: string; @@ -92,18 +92,15 @@ export const fetchHomeData = async (): Promise => { title: contribution.title, })); - const agreements = await fetchAgreements( - ["source", "slug", "shortTitle", "description"], - undefined, - { - cdtnIds: [ - "39ac98db5d", // 573-commerces-de-gros - "81c96604dc", // 2609-batiment-etam - "2f57b6af7c", // 3248-metallurgie - "d825ef1df2", // 3239-particuliers-employeurs-et-emploi-a-domicile - ], - } - ); + const agreements = await fetchAgreementsByCdtnIds({ + fields: ["source", "slug", "shortTitle", "description"], + cdtnIds: [ + "39ac98db5d", // 573-commerces-de-gros + "81c96604dc", // 2609-batiment-etam + "2f57b6af7c", // 3248-metallurgie + "d825ef1df2", // 3239-particuliers-employeurs-et-emploi-a-domicile + ], + }); const parsedAgreements = agreements.map((agreement) => ({ description: agreement.description, link: `/${getRouteBySource(agreement.source)}/${agreement.slug}`, diff --git a/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementBlock.tsx b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementBlock.tsx new file mode 100644 index 0000000000..90cafb42e2 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementBlock.tsx @@ -0,0 +1,40 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Image from "next/image"; +import AgreementSearch from "../../convention-collective/AgreementSearch.svg"; +import { css } from "@styled-system/css"; +import { ReactNode } from "react"; + +type Props = { + children: ReactNode; + title?: string; + description?: string; + noBackground?: boolean; +}; + +export const FindAgreementBlock = ({ + children, + title = "Trouver sa convention collective", + noBackground = false, +}: Props) => { + return ( +
+
+ {title} +

{title}

+
+
{children}
+
+ ); +}; + +const block = css({ + background: "var(--background-alt-blue-cumulus) !important", +}); diff --git a/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementLayout.tsx b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementLayout.tsx new file mode 100644 index 0000000000..2c55d071a0 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementLayout.tsx @@ -0,0 +1,31 @@ +"use client"; +import { ContainerSimulator } from "../../layout/ContainerSimulator"; +import { RelatedItem } from "../../documents"; +import { ReactNode } from "react"; +import { FindAgreementBlock } from "./FindAgreementBlock"; + +type Props = { + children: ReactNode; + relatedItems: { + items: RelatedItem[]; + title: string; + }[]; + description: string; +}; + +export const FindAgreementLayout = ({ + children, + relatedItems, + description, +}: Props) => { + return ( + + {children} + + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementWidgetLayout.tsx b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementWidgetLayout.tsx new file mode 100644 index 0000000000..8584336eca --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/FindAgreementWidgetLayout.tsx @@ -0,0 +1,18 @@ +"use client"; +import { ReactNode } from "react"; +import { FindAgreementBlock } from "./FindAgreementBlock"; +import { useIframeResizer } from "../../../../src/common/hooks"; + +type Props = { + children: ReactNode; +}; + +export const FindAgreementWidgetLayout = ({ children }: Props) => { + useIframeResizer(); + return ( + <> + + {children} + + ); +}; diff --git a/packages/code-du-travail-frontend/src/modules/outils/convention-collective/index.ts b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/index.ts new file mode 100644 index 0000000000..94a76a5c14 --- /dev/null +++ b/packages/code-du-travail-frontend/src/modules/outils/convention-collective/index.ts @@ -0,0 +1,3 @@ +export * from "./FindAgreementLayout"; +export * from "./FindAgreementBlock"; +export * from "./FindAgreementWidgetLayout"; diff --git a/packages/code-du-travail-frontend/src/modules/outils/index.ts b/packages/code-du-travail-frontend/src/modules/outils/index.ts index b69c251208..16a2c29fcc 100644 --- a/packages/code-du-travail-frontend/src/modules/outils/index.ts +++ b/packages/code-du-travail-frontend/src/modules/outils/index.ts @@ -1 +1,2 @@ export * from "./queries"; +export * from "./convention-collective"; diff --git a/packages/code-du-travail-frontend/src/modules/plan-du-site/queries.ts b/packages/code-du-travail-frontend/src/modules/plan-du-site/queries.ts index 11d833d74e..5b8627f624 100644 --- a/packages/code-du-travail-frontend/src/modules/plan-du-site/queries.ts +++ b/packages/code-du-travail-frontend/src/modules/plan-du-site/queries.ts @@ -2,7 +2,7 @@ import { ContributionElasticDocument, ElasticAgreement, } from "@socialgouv/cdtn-types"; -import { fetchAgreements } from "../convention-collective/queries"; +import { fetchAllAgreements } from "../convention-collective/queries"; import { orderByAlpha } from "../utils"; import { fetchRootThemes } from "../themes"; import { fetchTools } from "../outils"; @@ -32,10 +32,10 @@ export const fetchSitemapData = async () => { const themes = await getAllThemesAndSubThemes(); const tools = await fetchTools(["slug", "title"]); const modeles = await fetchModels(["slug", "title"]); - const agreements = await fetchAgreements( - ["slug", "shortTitle", "num"], - "shortTitle" - ); + const agreements = await fetchAllAgreements({ + fields: ["slug", "shortTitle", "num"], + sortBy: "shortTitle", + }); const informations = await fetchAllInformations(["slug", "title"], "title"); const contributions = await getAllContributionsGroupByQuestion(agreements); const response: GetSitemapPage = { diff --git a/packages/code-du-travail-frontend/src/outils/common/Agreement/EnterpriseSearch/Location/Search.tsx b/packages/code-du-travail-frontend/src/outils/common/Agreement/EnterpriseSearch/Location/Search.tsx index 6ec693aeca..30fd693ede 100644 --- a/packages/code-du-travail-frontend/src/outils/common/Agreement/EnterpriseSearch/Location/Search.tsx +++ b/packages/code-du-travail-frontend/src/outils/common/Agreement/EnterpriseSearch/Location/Search.tsx @@ -71,9 +71,10 @@ export const LocationSearchInput = (props: Props) => { }); function itemToString(item: ApiGeoResult | null) { - return item + const result = item ? `${item.nom} (${postalCode ?? (item.codesPostaux.length > 1 ? item.codeDepartement : item.codesPostaux[0])})` : ""; + return result; } const onSearch = (e: React.MouseEvent) => { diff --git a/yarn.lock b/yarn.lock index 64b90c74f9..2e33e283a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5815,6 +5815,7 @@ __metadata: version: 0.0.0-use.local resolution: "@socialgouv/code-du-travail@workspace:." dependencies: + cypress-iframe: ^1.0.1 husky: ^9.1.6 lerna: 8.1.8 languageName: unknown