Skip to content

Commit 457235a

Browse files
committed
fix(outcomes): split croisements and comparison
1 parent 0d248a2 commit 457235a

16 files changed

Lines changed: 245 additions & 174 deletions

File tree

‎client/src/boards/integration/charts-registry.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ export const chartsRegistry = {
190190
outcomesRepartition: lazy(
191191
() => import("../outcomes/pages/repartition/charts/repartition-column")
192192
),
193+
outcomesCroisementsHeatmap: lazy(
194+
() => import("../outcomes/pages/croisements/heatmap")
195+
),
196+
outcomesComparaisonProfils: lazy(
197+
() => import("../outcomes/pages/comparaison-profils")
198+
),
193199
};
194200

195201
export type ChartId = keyof typeof chartsRegistry;

‎client/src/boards/outcomes/components/layouts/global-layout.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const NAV_ITEMS = [
1515
{ key: "repartition", labelKey: "repartition", to: "/devenir-etudiants/repartition" },
1616
{ key: "plus-haut-diplome", labelKey: "plusHautDiplome", to: "/devenir-etudiants/plus-haut-diplome" },
1717
{ key: "croisements", labelKey: "croisements", to: "/devenir-etudiants/croisements" },
18+
{ key: "comparaison-profils", labelKey: "comparaisonProfils", to: "/devenir-etudiants/comparaison-profils" },
1819
{ key: "methodologie", labelKey: "methodologie", to: "/devenir-etudiants/methodologie" },
1920
] as const;
2021

‎client/src/boards/outcomes/components/layouts/i18n.json‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
"en": "Highest degree"
1717
},
1818
"croisements": {
19-
"fr": "Croisements et profils",
20-
"en": "Crossings and profiles"
19+
"fr": "Croisements",
20+
"en": "Crossings"
21+
},
22+
"comparaisonProfils": {
23+
"fr": "Comparaison de profils",
24+
"en": "Profiles comparison"
2125
},
2226
"methodologie": {
2327
"fr": "Méthodologie",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Col, Container, Row, Title } from "@dataesr/dsfr-plus";
2+
3+
import Callout from "../../../../components/callout.tsx";
4+
import OutcomesDefinitionsTable from "../../components/definitions-table/index.tsx";
5+
import { OUTCOMES_DEFINITIONS } from "../../components/definitions-table/data.tsx";
6+
import { useOutcomesPlusHautDiplome } from "../../api";
7+
8+
import "../croisements/styles.scss";
9+
import ProfilesTab from "./profiles/index.tsx";
10+
11+
const COHORT_YEAR = "2019-2020";
12+
const COHORT_SITUATION = "L1";
13+
14+
export default function ComparaisonProfilsPage() {
15+
const { data: baseData, isLoading: baseLoading } = useOutcomesPlusHautDiplome({
16+
cohorteAnnee: COHORT_YEAR,
17+
cohorteSituation: COHORT_SITUATION,
18+
filters: {},
19+
});
20+
21+
return (
22+
<Container className="outcomes-section-page outcomes-croisements">
23+
<Row gutters>
24+
<Col>
25+
<Callout className="fr-mb-2w" colorFamily="pink-tuile" icon="fr-icon-alert-line">
26+
Version sous embargo à ne pas diffuser
27+
</Callout>
28+
</Col>
29+
</Row>
30+
31+
<Row gutters>
32+
<Col>
33+
<Title as="h1" look="h4" className="fr-mb-1w">
34+
Comparer les trajectoires de plusieurs profils
35+
</Title>
36+
</Col>
37+
</Row>
38+
39+
<ProfilesTab
40+
axisOptions={baseData?.filterOptions ?? null}
41+
isLoadingOptions={baseLoading}
42+
cohortTotal={baseData?.totalStudents ?? 0}
43+
/>
44+
45+
<OutcomesDefinitionsTable definitions={OUTCOMES_DEFINITIONS} />
46+
</Container>
47+
);
48+
}

client/src/boards/outcomes/pages/croisements/profiles/index.tsx renamed to client/src/boards/outcomes/pages/comparaison-profils/profiles/index.tsx

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Button, Col, Row, Text, Title } from "@dataesr/dsfr-plus";
22
import { useQueries } from "@tanstack/react-query";
33
import { useMemo, useState } from "react";
4+
import { useSearchParams } from "react-router-dom";
45

56
import ChartWrapper from "../../../../../components/chart-wrapper";
67
import DefaultSkeleton from "../../../../../components/charts-skeletons/default";
@@ -28,6 +29,32 @@ const RELATIVE_YEARS = [0, 1, 2, 3, 4];
2829
type Profile = Partial<Record<OutcomesFilterField, string | null>>;
2930
type AxisOptions = Record<OutcomesFilterField, OutcomesFilterOption[]>;
3031

32+
function buildProfileParams(profiles: Profile[]): string {
33+
const params = new URLSearchParams();
34+
params.set("profiles", String(profiles.length));
35+
profiles.forEach((profile, i) => {
36+
const prefix = PROFILE_BADGES[i].toLowerCase();
37+
PROFILE_FIELDS.forEach(({ field }) => {
38+
const val = profile[field];
39+
if (val) params.set(`${prefix}_${field}`, val);
40+
});
41+
});
42+
return params.toString();
43+
}
44+
45+
function parseProfilesFromURL(searchParams: URLSearchParams): Profile[] {
46+
const count = Math.min(3, Math.max(2, Number(searchParams.get("profiles")) || 2));
47+
return PROFILE_BADGES.slice(0, count).map((badge) => {
48+
const prefix = badge.toLowerCase();
49+
const profile: Profile = {};
50+
PROFILE_FIELDS.forEach(({ field }) => {
51+
const val = searchParams.get(`${prefix}_${field}`);
52+
if (val) profile[field] = val;
53+
});
54+
return profile;
55+
});
56+
}
57+
3158
async function fetchProfileData(profile: Profile) {
3259
const params = new URLSearchParams({
3360
cohorte_annee: COHORT_YEAR,
@@ -109,20 +136,24 @@ function ProfileCard({ badge, profile, axisOptions, canRemove, tauxDipl, total,
109136
onSelect={(value) => onChange({ ...profile, [field]: value })}
110137
/>
111138
))}
112-
<Text size="sm" className="fr-mb-0" bold>Taux de diplômés du supérieur</Text>
113-
<Title as="h3" look="h3" className={`fr-mb-0 outcomes-croisements__value--${badge}`}>
114-
{isLoading ? "…" : tauxDipl !== undefined ? `${tauxDipl.toFixed(0)}%` : "n/a"}
115-
</Title>
116-
{!isLoading && total !== undefined && (
117-
<>
118-
<Text size="sm" className="fr-mb-0">
119-
Part de la cohorte : <strong>{sharePct !== null ? `${sharePct.toFixed(1)}%` : "—"}</strong>
120-
</Text>
121-
<Text size="sm" className="fr-mb-0">
122-
Effectif : <strong>{total.toLocaleString("fr-FR")}</strong> étudiants
123-
</Text>
124-
</>
125-
)}
139+
<Row gutters className="fr-mt-2w" >
140+
<Col xs={6}>
141+
<Text size="sm" className="fr-mb-0">Taux de diplômés du supérieur</Text>
142+
<p className={`fr-h3 fr-mb-0 outcomes-croisements__value--${badge}`}>
143+
{isLoading ? "…" : tauxDipl !== undefined ? `${tauxDipl.toFixed(0)}%` : "n/a"}
144+
</p>
145+
</Col>
146+
{!isLoading && total !== undefined && (
147+
<Col xs={6}>
148+
<Text size="sm" className="fr-mb-0">
149+
Part de la cohorte : <strong>{sharePct !== null ? `${sharePct.toFixed(1)}%` : "—"}</strong>
150+
</Text>
151+
<Text size="sm" className="fr-mb-0">
152+
Effectif : <strong>{total.toLocaleString("fr-FR")}</strong> étudiants
153+
</Text>
154+
</Col>
155+
)}
156+
</Row>
126157
</div>
127158
);
128159
}
@@ -134,7 +165,8 @@ interface ProfilesTabProps {
134165
}
135166

136167
export default function ProfilesTab({ axisOptions, isLoadingOptions, cohortTotal }: ProfilesTabProps) {
137-
const [profiles, setProfiles] = useState<Profile[]>([{}, {}]);
168+
const [searchParams] = useSearchParams();
169+
const [profiles, setProfiles] = useState<Profile[]>(() => parseProfilesFromURL(searchParams));
138170

139171
const queries = useQueries({
140172
queries: profiles.map((p, i) => ({
@@ -180,6 +212,8 @@ export default function ProfilesTab({ axisOptions, isLoadingOptions, cohortTotal
180212
return <DefaultSkeleton height="320px" />;
181213
}
182214

215+
const integrationURL = `/integration?chart_id=outcomesComparaisonProfils&${buildProfileParams(profiles)}`;
216+
183217
return (
184218
<>
185219
<Text className="fr-mb-2w">
@@ -218,47 +252,43 @@ export default function ProfilesTab({ axisOptions, isLoadingOptions, cohortTotal
218252
</Row>
219253
)}
220254

221-
<Title as="h3" look="h5" className="fr-mt-3w fr-mb-1w">Position au fil des années</Title>
222-
<Text size="sm" className="fr-mb-1w">Part de chaque profil encore inscrite en formation supérieure</Text>
255+
<Text size="sm" className="fr-mt-3w">Part de chaque profil encore inscrite en formation supérieure</Text>
223256
<ChartWrapper
224-
hideTitle
225-
config={{ id: "outcomes-profiles-line", title: "Position au fil des années" }}
257+
config={{ id: "outcomes-profiles-line", title: "Position au fil des années", integrationURL, sources: [{ label: { fr: <>MESRE-SIES</> }, url: { fr: "https://data.enseignementsup-recherche.gouv.fr" } }] }}
226258
options={lineOptions}
227259
/>
228260

229-
<Title as="h3" look="h5" className="fr-mt-3w fr-mb-1w">Diplômés du supérieur vs sortants sans diplôme</Title>
230-
<Text size="sm" className="fr-mb-1w">Répartition à 5 ans par profil</Text>
261+
<Text size="sm" className="fr-mt-3w">Répartition à 5 ans par profil</Text>
231262
<ChartWrapper
232-
hideTitle
233-
config={{ id: "outcomes-profiles-stack", title: "Diplômés du supérieur vs sortants sans diplôme" }}
263+
config={{ id: "outcomes-profiles-stack", title: "Diplômés du supérieur vs sortants sans diplôme", integrationURL, sources: [{ label: { fr: <>MESRE-SIES</> }, url: { fr: "https://data.enseignementsup-recherche.gouv.fr" } }] }}
234264
options={stackOptions}
235265
/>
236266

237267
<Title as="h2" look="h5" className="fr-mt-3w fr-mb-1w">Plus haut diplôme obtenu en 2023-2024</Title>
238-
<Text size="sm" className="fr-mb-1w">Répartition par type de diplôme pour chaque profil</Text>
268+
<Text size="sm" className="fr-mt-3w">Répartition par type de diplôme pour chaque profil</Text>
239269
<Row gutters>
240270
{profiles.map((_, i) => {
241271
const d = queries[i]?.data;
242272
const badge = PROFILE_BADGES[i];
243273
const colSize = profiles.length === 3 ? 4 : 6;
244274
return (
245275
<Col key={i} xs={12} md={colSize}>
246-
<div className={`outcomes-croisements__card outcomes-croisements__card--${badge} fr-p-2w`}>
276+
<div >
247277
<Title as="h3" look="h6" className="fr-mb-1w">Profil {badge}</Title>
248278
{!d || !d.rows?.length ? (
249279
<Text size="sm" className="fr-mb-0">{queries[i]?.isLoading ? "Chargement…" : "Sélectionnez des critères"}</Text>
250280
) : (
251281
<ChartWrapper
252282
hideTitle
253-
config={{ id: `outcomes-profiles-donut-${badge}`, title: `Plus haut diplôme — Profil ${badge}` }}
283+
config={{ id: `outcomes-profiles-donut-${badge}`, title: `Plus haut diplôme — Profil ${badge}`, sources: [{ label: { fr: <>MESRE-SIES</> }, url: { fr: "https://data.enseignementsup-recherche.gouv.fr" } }] }}
254284
options={createDiplomaDonutOptions(d.rows, { effectif: d.nonDipl }, `Profil ${badge}`)}
255285
/>
256286
)}
257287
</div>
258288
</Col>
259289
);
260290
})}
261-
</Row>
291+
</Row >
262292
</>
263293
);
264294
}

client/src/boards/outcomes/pages/croisements/profiles/options.tsx renamed to client/src/boards/outcomes/pages/comparaison-profils/profiles/options.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ interface ProfileSeriesInput {
2121
export function createProfilesLineOptions(profiles: ProfileSeriesInput[]) {
2222
return createChartOptions("line", ({
2323
accessibility: { enabled: false },
24-
caption: {
25-
align: "left",
26-
style: { color: "var(--text-mention-grey)", fontSize: "11px" },
27-
text: "Source : MESRE-SIES.",
28-
verticalAlign: "bottom",
29-
},
3024
chart: { type: "line", backgroundColor: "transparent", height: 380 },
3125
title: { text: undefined },
3226
legend: { enabled: true, itemStyle: { color: getCssColor("text-default-grey") } },
@@ -100,7 +94,7 @@ export function createProfilesDiplomaStackOptions(profiles: ProfileStackInput[])
10094
verticalAlign: "bottom",
10195
},
10296
chart: { type: "column", backgroundColor: "transparent", height: 320 },
103-
title: { text: undefined },
97+
title: { text: " " },
10498
legend: { enabled: true, itemStyle: { color: getCssColor("text-default-grey") } },
10599
xAxis: {
106100
categories,

0 commit comments

Comments
 (0)