From b01d5140ece0ee9cd5ff2a428250a74b7f29acd4 Mon Sep 17 00:00:00 2001 From: Ananda Date: Mon, 6 Jan 2025 18:00:12 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20evenement=20matomo=20pour=20les=20pages?= =?UTF-8?q?=20de=20d=C3=A9tails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context/FormationDetailsContext.tsx | 62 +++++++++++++++++++ .../context/FormationDetailsHeaderContext.tsx | 47 -------------- .../details/[id]/FormationContent.tsx | 20 +++--- .../details/[id]/FormationHeader.tsx | 4 +- .../details/[id]/FormationResume.tsx | 6 +- .../hooks/useFormationLink.ts | 14 ++++- ui/app/(accompagnateur)/hooks/useMatomo.ts | 10 ++- ui/app/components/ConsentManagement.tsx | 12 ++-- 8 files changed, 100 insertions(+), 75 deletions(-) create mode 100644 ui/app/(accompagnateur)/context/FormationDetailsContext.tsx delete mode 100644 ui/app/(accompagnateur)/context/FormationDetailsHeaderContext.tsx diff --git a/ui/app/(accompagnateur)/context/FormationDetailsContext.tsx b/ui/app/(accompagnateur)/context/FormationDetailsContext.tsx new file mode 100644 index 0000000..ecce128 --- /dev/null +++ b/ui/app/(accompagnateur)/context/FormationDetailsContext.tsx @@ -0,0 +1,62 @@ +import { createContext, useCallback, useContext, useEffect, useState } from "react"; +import { FormationDetail } from "shared"; +import { useMatomo } from "../hooks/useMatomo"; +import { formationDetailToKey } from "../hooks/useFormationLink"; + +type DetailsHeaderSize = { + headerHeight: number; + resumeHeight: number; +}; + +type DetailsHeaderSizeParams = { + headerHeight?: number; + resumeHeight?: number; +}; + +const FormationDetailsContext = createContext<{ + headersSize: DetailsHeaderSize; + setHeadersSize: (params: DetailsHeaderSizeParams) => void; +}>({ + headersSize: { headerHeight: 0, resumeHeight: 0 }, + setHeadersSize: (params: DetailsHeaderSizeParams) => {}, +}); + +const FormationDetailsProvider = ({ + formationDetail, + children, +}: { + formationDetail: FormationDetail; + children: React.ReactNode; +}) => { + const [headersSize, setHeadersSize] = useState({ headerHeight: 0, resumeHeight: 0 }); + const matomo = useMatomo(); + + useEffect(() => { + matomo.push(["trackEvent", "details", "uai", formationDetail.etablissement.uai]); + matomo.push(["trackEvent", "details", "code", formationDetailToKey(formationDetail)]); + }, [matomo, formationDetail]); + + const setHeaderSizeCb = useCallback( + (newHeadersSize: DetailsHeaderSizeParams) => { + setHeadersSize((prevState) => ({ ...prevState, ...newHeadersSize })); + }, + [setHeadersSize] + ); + + return ( + + {children} + + ); +}; + +export const useFormationsDetails = () => { + return useContext(FormationDetailsContext); +}; + +export default FormationDetailsProvider; diff --git a/ui/app/(accompagnateur)/context/FormationDetailsHeaderContext.tsx b/ui/app/(accompagnateur)/context/FormationDetailsHeaderContext.tsx deleted file mode 100644 index ec82e88..0000000 --- a/ui/app/(accompagnateur)/context/FormationDetailsHeaderContext.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { createContext, useCallback, useContext, useState } from "react"; - -type DetailsHeaderSize = { - headerHeight: number; - resumeHeight: number; -}; - -type DetailsHeaderSizeParams = { - headerHeight?: number; - resumeHeight?: number; -}; - -const FormationDetailsHeaderContext = createContext<{ - headersSize: DetailsHeaderSize; - setHeadersSize: (params: DetailsHeaderSizeParams) => void; -}>({ - headersSize: { headerHeight: 0, resumeHeight: 0 }, - setHeadersSize: (params: DetailsHeaderSizeParams) => {}, -}); - -const FormationDetailsHeaderProvider = ({ children }: { children: React.ReactNode }) => { - const [headersSize, setHeadersSize] = useState({ headerHeight: 0, resumeHeight: 0 }); - - const setHeaderSizeCb = useCallback( - (newHeadersSize: DetailsHeaderSizeParams) => { - setHeadersSize((prevState) => ({ ...prevState, ...newHeadersSize })); - }, - [setHeadersSize] - ); - - return ( - - {children} - - ); -}; - -export const useFormationsDetailsHeadersSize = () => { - return useContext(FormationDetailsHeaderContext); -}; - -export default FormationDetailsHeaderProvider; diff --git a/ui/app/(accompagnateur)/details/[id]/FormationContent.tsx b/ui/app/(accompagnateur)/details/[id]/FormationContent.tsx index bac2b37..19730c2 100644 --- a/ui/app/(accompagnateur)/details/[id]/FormationContent.tsx +++ b/ui/app/(accompagnateur)/details/[id]/FormationContent.tsx @@ -15,16 +15,14 @@ import FormationBlockAdmission from "./FormationBlockAdmission"; import FormationBlockFormation from "./FormationBlockFormation"; import FormationBlockAccesEmploi from "./FormationBlockAccesEmploi"; import FormationSimilare from "./FormationSimilaire"; -import FormationDetailsHeaderProvider, { - useFormationsDetailsHeadersSize, -} from "../../context/FormationDetailsHeaderContext"; +import FormationDetailsProvider, { useFormationsDetails } from "../../context/FormationDetailsContext"; const FormationContent = React.memo(function ({ formationDetail }: { formationDetail: FormationDetail }) { const { formation, etablissement } = formationDetail; const theme = useTheme(); - const { headersSize } = useFormationsDetailsHeadersSize(); + const { headersSize } = useFormationsDetails(); const cssAnchor = css` ${theme.breakpoints.up("md")} { @@ -91,17 +89,13 @@ const FormationContent = React.memo(function ({ formationDetail }: { formationDe }); FormationContent.displayName = "FormationContent"; -const FormationContentWithHeaderProvider = React.memo(function ({ - formationDetail, -}: { - formationDetail: FormationDetail; -}) { +const FormationContentWithProvider = React.memo(function ({ formationDetail }: { formationDetail: FormationDetail }) { return ( - + - + ); }); -FormationContentWithHeaderProvider.displayName = "FormationContentWithHeaderProvider"; +FormationContentWithProvider.displayName = "FormationContentWithProvider"; -export default FormationContentWithHeaderProvider; +export default FormationContentWithProvider; diff --git a/ui/app/(accompagnateur)/details/[id]/FormationHeader.tsx b/ui/app/(accompagnateur)/details/[id]/FormationHeader.tsx index 371c412..f63df12 100644 --- a/ui/app/(accompagnateur)/details/[id]/FormationHeader.tsx +++ b/ui/app/(accompagnateur)/details/[id]/FormationHeader.tsx @@ -19,7 +19,7 @@ import FormationDisponible from "./FormationDisponible"; import Link from "#/app/components/Link"; import { TagApprentissage } from "#/app/(accompagnateur)/components/Apprentissage"; import { formatLibelle, formatStatut } from "#/app/utils/formation"; -import { useFormationsDetailsHeadersSize } from "../../context/FormationDetailsHeaderContext"; +import { useFormationsDetails } from "../../context/FormationDetailsContext"; import { useHideOnScroll } from "../../hooks/useHideOnScroll"; const FormationHeader = React.memo(function ({ formationDetail }: { formationDetail: FormationDetail }) { @@ -30,7 +30,7 @@ const FormationHeader = React.memo(function ({ formationDetail }: { formationDet const isDownSm = useMediaQuery((theme) => theme.breakpoints.down("md")); - const { headersSize, setHeadersSize } = useFormationsDetailsHeadersSize(); + const { headersSize, setHeadersSize } = useFormationsDetails(); const refHeader = React.useRef(null); const stickyHeaderSize = useSize(refHeader); diff --git a/ui/app/(accompagnateur)/details/[id]/FormationResume.tsx b/ui/app/(accompagnateur)/details/[id]/FormationResume.tsx index 97aad5d..adaebc1 100644 --- a/ui/app/(accompagnateur)/details/[id]/FormationResume.tsx +++ b/ui/app/(accompagnateur)/details/[id]/FormationResume.tsx @@ -14,7 +14,7 @@ import React from "react"; import Divider from "#/app/components/Divider"; import { useScrollspy } from "../../hooks/useScrollSpy"; import { useSize } from "../../hooks/useSize"; -import { useFormationsDetailsHeadersSize } from "../../context/FormationDetailsHeaderContext"; +import { useFormationsDetails } from "../../context/FormationDetailsContext"; import { Theme } from "@mui/material"; interface FormationResumeBlockProps { @@ -281,7 +281,7 @@ const FormationResume = React.memo(function ({ hideTag?: boolean; }) { const [currentSection, setCurrentSection] = useState("la-formation"); - const { headersSize } = useFormationsDetailsHeadersSize(); + const { headersSize } = useFormationsDetails(); const activeId = useScrollspy( ["la-formation", "l-admission", "poursuite-etudes", "acces-emploi"], headersSize.headerHeight + headersSize.resumeHeight + 1 @@ -372,7 +372,7 @@ const FormationResumeHideTagFix = React.memo(function ({ hideTag?: boolean; }) { const theme = useTheme(); - const { headersSize, setHeadersSize } = useFormationsDetailsHeadersSize(); + const { headersSize, setHeadersSize } = useFormationsDetails(); const refResume = React.useRef(null); const stickyResumeSize = useSize(refResume); diff --git a/ui/app/(accompagnateur)/hooks/useFormationLink.ts b/ui/app/(accompagnateur)/hooks/useFormationLink.ts index c546185..809b6e2 100644 --- a/ui/app/(accompagnateur)/hooks/useFormationLink.ts +++ b/ui/app/(accompagnateur)/hooks/useFormationLink.ts @@ -1,6 +1,16 @@ import { FormationDetail, FormationFamilleMetierDetail } from "shared"; import { useSearchParams } from "next/navigation"; +export function formationDetailToKey(formationDetail: FormationDetail | FormationFamilleMetierDetail) { + if (!formationDetail || !formationDetail.etablissement) { + return null; + } + + return `${formationDetail.formation.cfd}-${formationDetail.formation.codeDispositif || ""}-${ + formationDetail.etablissement.uai + }-${formationDetail.formation.voie}`; +} + export const useFormationLink = ({ formationDetail, longitude, @@ -18,9 +28,7 @@ export const useFormationLink = ({ return null; } - const key = `${formationDetail.formation.cfd}-${formationDetail.formation.codeDispositif || ""}-${ - formationDetail.etablissement.uai - }-${formationDetail.formation.voie}`; + const key = formationDetailToKey(formationDetail); const parameters = new URLSearchParams({ ...(latitudeParams ? { latitude: latitudeParams } : {}), ...(longitudeParams ? { longitude: longitudeParams } : {}), diff --git a/ui/app/(accompagnateur)/hooks/useMatomo.ts b/ui/app/(accompagnateur)/hooks/useMatomo.ts index 1454fa2..271e10e 100644 --- a/ui/app/(accompagnateur)/hooks/useMatomo.ts +++ b/ui/app/(accompagnateur)/hooks/useMatomo.ts @@ -2,11 +2,10 @@ import { useConsent } from "#/app/components/ConsentManagement"; import { push } from "@socialgouv/matomo-next"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; export const useMatomo = () => { const { finalityConsent } = useConsent(); - const pushWithConsent = useCallback( (...args: Parameters): void => { if (!finalityConsent?.analytics) { @@ -17,5 +16,10 @@ export const useMatomo = () => { }, [finalityConsent] ); - return { push: pushWithConsent }; + + const matomo = useMemo(() => { + return { push: pushWithConsent }; + }, [pushWithConsent]); + + return matomo; }; diff --git a/ui/app/components/ConsentManagement.tsx b/ui/app/components/ConsentManagement.tsx index ee2402d..b7b011c 100644 --- a/ui/app/components/ConsentManagement.tsx +++ b/ui/app/components/ConsentManagement.tsx @@ -2,7 +2,7 @@ import { useLocalStorage } from "usehooks-ts"; import { useSearchParams } from "next/navigation"; import { createConsentManagement } from "@codegouvfr/react-dsfr/consentManagement"; -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; export const { ConsentBannerAndConsentManagement, @@ -31,14 +31,18 @@ export const { disableTrackingParams !== null ? disableTrackingParams : false ); + const consent = useMemo(() => { + return { + finalityConsent: { analytics: !disableTracking }, + }; + }, [disableTracking]); + useEffect(() => { if (disableTrackingParams !== null) { saveDisableTracking(disableTrackingParams); } }, [saveDisableTracking, disableTrackingParams]); - return { - finalityConsent: { analytics: !disableTracking }, - }; + return consent; }, };