Skip to content

Commit

Permalink
feat: rend chaque voeu parcoursup cliquable (619)
Browse files Browse the repository at this point in the history
  • Loading branch information
aureliadelzottoOCTO authored and Numero7 committed Nov 29, 2024
1 parent 5dcffcb commit 0a93ce5
Show file tree
Hide file tree
Showing 42 changed files with 595 additions and 483 deletions.
2 changes: 2 additions & 0 deletions app/front/src/components/Bouton/Bouton.interface.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type BoutonSqueletteProps } from "@/components/BoutonSquelette/BoutonSquelette.interface";
import { AriaRole } from "react";

export type BoutonProps = {
label: BoutonSqueletteProps["label"];
Expand All @@ -11,4 +12,5 @@ export type BoutonProps = {
formId?: string;
ariaControls?: string;
dataFrOpened?: string;
rôle?: AriaRole;
};
2 changes: 2 additions & 0 deletions app/front/src/components/Bouton/Bouton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Bouton = ({
formId,
ariaControls,
dataFrOpened,
rôle = "button",
}: BoutonProps) => {
return (
<button
Expand All @@ -21,6 +22,7 @@ const Bouton = ({
disabled={désactivé}
form={formId}
onClick={auClic}
role={rôle}
type={type}
>
<BoutonSquelette
Expand Down
11 changes: 11 additions & 0 deletions app/front/src/components/Recherche/Recherche.interface.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type RechercheProps<T> = {
rechercheCallback: (recherche: string) => T[];
label: string;
description?: string;
nombreDeCaractèresMinimumRecherche: number;
};

export type UseRechercheArgs<T> = {
nombreDeCaractèresMinimumRecherche: RechercheProps<T>["nombreDeCaractèresMinimumRecherche"];
rechercheCallback: RechercheProps<T>["rechercheCallback"];
};
39 changes: 39 additions & 0 deletions app/front/src/components/Recherche/Recherche.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RechercheProps } from "./Recherche.interface";
import useRecherche from "./useRecherche";
import ChampDeRecherche from "@/components/ChampDeRecherche/ChampDeRecherche";
import { i18n } from "@/configuration/i18n/i18n.ts";

const Recherche = <T extends object>({
rechercheCallback,
label,
description,
nombreDeCaractèresMinimumRecherche,
}: RechercheProps<T>) => {
const { debouncedSetRecherche, statusChampDeRecherche, nombreDeRésultats } = useRecherche({
nombreDeCaractèresMinimumRecherche,
rechercheCallback,
});

return (
<div>
<ChampDeRecherche
auChangement={(événement) => debouncedSetRecherche(événement.target.value ?? undefined)}
entête={{ description, label }}
obligatoire={false}
status={statusChampDeRecherche}
/>
<div
aria-live="polite"
className="sr-only"
>
{nombreDeRésultats && nombreDeRésultats > 0 && (
<p>
{nombreDeRésultats} {i18n.ACCESSIBILITÉ.NOUVEAUX_RÉSULATS}
</p>
)}
</div>
</div>
);
};

export default Recherche;
41 changes: 41 additions & 0 deletions app/front/src/components/Recherche/useRecherche.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { UseRechercheArgs } from "./Recherche.interface";
import { i18n } from "@/configuration/i18n/i18n";
import { StatusFormulaire } from "@/types/commons";
import { useState } from "react";
import { useDebounceCallback } from "usehooks-ts";

export default function useRecherche<T>({
nombreDeCaractèresMinimumRecherche,
rechercheCallback,
}: UseRechercheArgs<T>) {
const [statusChampDeRecherche, setStatusChampDeRecherche] = useState<StatusFormulaire | undefined>();
const [nombreDeRésultats, setNombreDeRésultats] = useState<number | undefined>(undefined);

const lancerRecherche = (recherche?: string): void => {
setStatusChampDeRecherche(undefined);

if (recherche && recherche.length < nombreDeCaractèresMinimumRecherche) {
setStatusChampDeRecherche({
type: "erreur" as const,
message: `${i18n.COMMUN.ERREURS_FORMULAIRES.AU_MOINS_X_CARACTÈRES} ${nombreDeCaractèresMinimumRecherche} ${i18n.COMMUN.ERREURS_FORMULAIRES.CARACTÈRES}`,
});
} else if (recherche) {
const résultats = rechercheCallback(recherche);
setNombreDeRésultats(résultats.length);

if (résultats.length === 0) {
setStatusChampDeRecherche({ type: "erreur" as const, message: i18n.COMMUN.ERREURS_FORMULAIRES.AUCUN_RÉSULTAT });
}
} else {
setNombreDeRésultats(undefined);
}
};

const debouncedSetRecherche = useDebounceCallback(lancerRecherche, 400);

return {
statusChampDeRecherche,
debouncedSetRecherche,
nombreDeRésultats,
};
}
1 change: 1 addition & 0 deletions app/front/src/components/Titre/Titre.interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type TitreProps = {
| "text--lead"
| "text--lg"
| "text"
| "text--md"
| "text--sm"
| "text--xs";
};
14 changes: 10 additions & 4 deletions app/front/src/configuration/i18n/locales/localeFR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const localeFR = {
CHOIX: {
TITRE: "Dis-nous en plus sur ce choix",
AMBITIONS: {
LÉGENDE: "Je dirais que c’est un choix ...",
TITRE: "Je dirais que c’est un choix ...",
PLAN_B: {
LABEL: "Plan B",
EMOJI: "🛟",
Expand All @@ -127,12 +127,13 @@ export const localeFR = {
},
},
VOEUX: {
LÉGENDE: "Établissements pour lesquels je souhaite candidater",
TITRE: "Établissements pour lesquels je souhaite candidater",
LIENS: {
PARCOURSUP: "Carte Parcoursup",
PRÉFÉRENCES: "Préférences de villes",
},
PAR_COMMUNE: {
TITRE: "Établissements",
RAYON: "Dans un rayon de",
VOIR_PLUS: "établissements dans ce rayon, retrouve toute l’offre de formation sur la",
AUCUN_VOEU_À_PROXIMITÉ: "Il n’existe pas d’offres dans un rayon de",
Expand All @@ -144,11 +145,14 @@ export const localeFR = {
RAPPEL: "Tu peux paramétrer les villes dans lesquelles tu souhaiterais étudier.",
LIEN_PRÉFÉRENCES: "Préférences de villes ›",
LABEL: "Établissements",
DESCRIPTION:
"Commence à taper puis sélectionne des établissements. Les résultats retournés sont limités à 5, retrouve toute l'offre de formation sur la carte Parcoursup.",
DESCRIPTION: "Commence à taper puis sélectionne des établissements",
SÉLECTIONNÉS: "Établissement(s) sélectionné(s)",
VOIR_PLUS: "établissement(s) conforme(s) à ta recherche, retrouve toute l’offre de formation sur la",
},
MA_SÉLECTION: {
TITRE: "Ma sélection",
AUCUN: "Aucun établissement sélectionné en favoris",
},
},
COMMENTAIRE: {
LABEL: "Note additionnelle",
Expand Down Expand Up @@ -469,6 +473,7 @@ export const localeFR = {
CHARGEMENT: "Chargement",
FAVORIS: "Favoris",
MASQUÉ: "Masqué",
METTRE_EN_FAVORI: "Mettre en favori",
LIEN_EXTERNE: "ouvre un lien externe",
LIEN_EMAIL: "envoyer un email",
LIEN_TÉLÉPHONE: "composer le numéro",
Expand All @@ -482,5 +487,6 @@ export const localeFR = {
FERMER_MODALE: "Fermer la fenêtre modale",
CONTENU: "Contenu",
PIED_PAGE: "Pied de page",
NOUVEAUX_RÉSULATS: "nouveaux résultats",
},
} as const;
18 changes: 10 additions & 8 deletions app/front/src/features/formation/domain/formation.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import {
type DuréeÉtudesPrévueÉlève,
} from "@/features/référentielDonnées/domain/référentielDonnées.interface";

export type Voeu = {
id: string;
nom: string;
commune: {
nom: string;
code: string;
};
};

export type Formation = {
id: string;
nom: string;
Expand Down Expand Up @@ -34,14 +43,7 @@ export type Formation = {
};
formationsAssociées: string[];
critèresAnalyse: Array<{ nom: string; pourcentage: number }>;
voeux: Array<{
id: string;
nom: string;
commune: {
nom: string;
code: string;
};
}>;
voeux: Array<Voeu>;
voeuxParCommuneFavorites: Array<{
commune: {
nom: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const BoutonsActionsFicheFormation = ({ formation }: BoutonsActionsFicheFormatio
className="fr-collapse"
id="accordeon-voeux"
>
<VoeuxFicheFormation formation={formation} />
<VoeuxFicheFormation />
</div>
</section>
)}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { type AmbitionProps } from "./Ambition.interface.tsx";
import useAmbition from "./useAmbition.tsx";
import TagFiltreAvecEmoji from "@/components/TagFiltreAvecEmoji/TagFiltreAvecEmoji";
import Titre from "@/components/Titre/Titre.tsx";
import { i18n } from "@/configuration/i18n/i18n";

const Ambition = ({ ambitionActuelle, formationId }: AmbitionProps) => {
const { ambitions, mettreAJourAmbition, key } = useAmbition({ formationId });
const Ambition = () => {
const { ambitions, mettreAJourAmbition, key, ambitionActuelle } = useAmbition();

return (
<div>
<p className="mb-4 font-medium text-[--text-label-grey]">{i18n.PAGE_FORMATION.CHOIX.AMBITIONS.LÉGENDE}</p>
<div className="*:mb-4">
<Titre
niveauDeTitre="h3"
styleDeTitre="text--lg"
>
{i18n.PAGE_FORMATION.CHOIX.AMBITIONS.TITRE}
</Titre>
</div>
<ul className="m-0 flex list-none flex-wrap justify-start gap-4 p-0">
{ambitions.map((ambition) => (
<li key={ambition.niveau}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { type useAmbitionArgs } from "./Ambition.interface.tsx";
import { élémentAffichéListeEtAperçuStore } from "@/components/_layout/ListeEtAperçuLayout/store/useListeEtAperçu/useListeEtAperçu.ts";
import { i18n } from "@/configuration/i18n/i18n";
import { type FormationFavorite } from "@/features/élève/domain/élève.interface";
import useÉlève from "@/features/élève/ui/hooks/useÉlève/useÉlève";

export default function useAmbition({ formationId }: useAmbitionArgs) {
export default function useAmbition() {
const { mettreÀJourUneFormationFavorite, élève } = useÉlève({});
const formationAffichée = élémentAffichéListeEtAperçuStore();

const ambitionActuelle =
élève?.formationsFavorites?.find((formationFavorite) => formationAffichée.id === formationFavorite.id)
?.niveauAmbition ?? null;

const ambitions: Array<{ niveau: NonNullable<FormationFavorite["niveauAmbition"]>; emoji: string; libellé: string }> =
[
Expand All @@ -26,18 +31,19 @@ export default function useAmbition({ formationId }: useAmbitionArgs) {
];

const mettreAJourAmbition = (niveauAmbition: FormationFavorite["niveauAmbition"]) => {
if (!élève) return;
if (!élève || !formationAffichée.id) return;

const formationFavorite = élève.formationsFavorites?.find(({ id }) => id === formationId);
const formationFavorite = élève.formationsFavorites?.find(({ id }) => id === formationAffichée.id);

if (formationFavorite && niveauAmbition === formationFavorite.niveauAmbition) {
void mettreÀJourUneFormationFavorite(formationId, { niveauAmbition: null });
void mettreÀJourUneFormationFavorite(formationAffichée.id, { niveauAmbition: null });
} else {
void mettreÀJourUneFormationFavorite(formationId, { niveauAmbition });
void mettreÀJourUneFormationFavorite(formationAffichée.id, { niveauAmbition });
}
};

return {
ambitionActuelle,
ambitions,
mettreAJourAmbition,
key: JSON.stringify(élève?.formationsFavorites),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { type ChoixFormationProps } from "./ChoixFormation.interface.tsx";
import Voeux from "./Voeux/Voeux";
import { élémentAffichéListeEtAperçuStore } from "@/components/_layout/ListeEtAperçuLayout/store/useListeEtAperçu/useListeEtAperçu.ts";
import Titre from "@/components/Titre/Titre";
import { i18n } from "@/configuration/i18n/i18n";
import Ambition from "@/features/formation/ui/FicheFormation/ChoixFormation/Ambition/Ambition.tsx";
import Commentaire from "@/features/formation/ui/FicheFormation/ChoixFormation/Commentaire/Commentaire.tsx";
import { élèveQueryOptions } from "@/features/élève/ui/élèveQueries";
import { useQuery } from "@tanstack/react-query";

const ChoixFormation = ({ formation }: ChoixFormationProps) => {
const ChoixFormation = () => {
const formationAffichée = élémentAffichéListeEtAperçuStore();
const { data: élève } = useQuery(élèveQueryOptions);

if (!élève) return null;

const détailFavori = élève.formationsFavorites?.find((formationFavorite) => formation.id === formationFavorite.id);
if (!élève || !formationAffichée) return null;

return (
<div className="grid gap-6 border border-solid border-[--border-default-grey] px-4 py-8 shadow-md sm:px-10">
Expand All @@ -24,14 +23,11 @@ const ChoixFormation = ({ formation }: ChoixFormationProps) => {
{i18n.PAGE_FORMATION.CHOIX.TITRE}
</Titre>
</div>
<Ambition
ambitionActuelle={détailFavori?.niveauAmbition}
formationId={formation.id}
/>
<Ambition />
<hr className="pb-[1px]" />
<Voeux formation={formation} />
<Voeux />
<hr className="pb-[1px]" />
<Commentaire formationId={formation.id} />
<Commentaire />
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
import { type Formation } from "@/features/formation/domain/formation.interface";

export type CommentaireProps = {
formationId: Formation["id"];
};

export type UseCommentaireArgs = {
formationId: CommentaireProps["formationId"];
};

interface FormElements extends HTMLFormControlsCollection {
commentaire: HTMLTextAreaElement;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { type CommentaireProps } from "./Commentaire.interface.tsx";
import useCommentaire from "./useCommentaire.tsx";
import Bouton from "@/components/Bouton/Bouton";
import ChampZoneDeTexte from "@/components/ChampZoneDeTexte/ChampZoneDeTexte";
import { i18n } from "@/configuration/i18n/i18n";

const Commentaire = ({ formationId }: CommentaireProps) => {
const { enregistrerLeCommentaire, commentaireParDéfaut, status } = useCommentaire({ formationId });
const Commentaire = () => {
const { enregistrerLeCommentaire, commentaireParDéfaut, status } = useCommentaire();

return (
<form
Expand Down
Loading

0 comments on commit 0a93ce5

Please sign in to comment.