ajout de la pagination des résultats de recherche

This commit is contained in:
2026-03-18 14:54:01 +01:00
parent 1d9f80e63d
commit 3bbc60fd68
5 changed files with 325 additions and 101 deletions
+7 -4
View File
@@ -1,6 +1,9 @@
export function buildURL(query) {
export function buildURL(query, limit = 20, offset = 0) {
let url =
"https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?limit=10"
"https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?"
url += "limit=" + limit
url += "&offset=" + offset
if (query && query.trim() !== "") {
url += "&where=search(lib_for_voe_ins, '" + query + "')"
@@ -9,8 +12,8 @@ export function buildURL(query) {
return url
}
export async function fetchFormations(query) {
const url = buildURL(query)
export async function fetchFormations(query, limit = 20, offset = 0) {
const url = buildURL(query, limit, offset)
const response = await fetch(url)
if (!response.ok) {
+62 -4
View File
@@ -1,6 +1,6 @@
<app>
<div class="page">
<h1>Mini projet Parcoursup</h1>
<h1>Open Data Parcoursup</h1>
<p class="subtitle">Recherche de formations Parcoursup avec Riot</p>
<p><b>{ state.selectedFormations.length }</b> formation(s) sélectionnée(s)</p>
@@ -8,7 +8,12 @@
<div if={ !state.selected }>
<search-bar onsearch={ launchSearch }></search-bar>
<p if={ state.hasSearched && !state.loading }>
<b>{ state.query }</b> : { state.total } résultat(s)
</p>
<div class="layout">
<div>
<result-list
results={ state.results }
hasSearched={ state.hasSearched }
@@ -17,6 +22,19 @@
onselect={ addToSelection }>
</result-list>
<div class="compare-controls" if={ state.total > state.limit }>
<button onclick={ previousPage } disabled={ state.page === 1 }>
Précédent
</button>
<p>Page { state.page } / { getTotalPages() }</p>
<button onclick={ nextPage } disabled={ state.page === getTotalPages() }>
Suivant
</button>
</div>
</div>
<map-view results={ state.results }></map-view>
</div>
@@ -109,7 +127,11 @@
selectedFormations: [],
note: 12,
serie: 'general',
sortBy: 'nom'
sortBy: 'nom',
query: '',
page: 1,
limit: 20,
total: 0
},
onMounted() {
@@ -130,11 +152,28 @@
this.update({
loading: true,
hasSearched: true,
selected: null
selected: null,
query: query,
page: 1
})
await this.loadPage(1)
},
async loadPage(page) {
this.update({
loading: true
})
try {
const data = await window.fetchFormations(query)
const offset = (page - 1) * this.state.limit
const data = await window.fetchFormations(
this.state.query,
this.state.limit,
offset
)
const formations = []
if (data.results) {
@@ -146,17 +185,36 @@
this.update({
results: formations,
total: data.total_count ? data.total_count : 0,
page: page,
loading: false
})
} catch (error) {
console.error(error)
this.update({
results: [],
total: 0,
loading: false
})
}
},
getTotalPages() {
return Math.ceil(this.state.total / this.state.limit)
},
async nextPage() {
if (this.state.page < this.getTotalPages()) {
await this.loadPage(this.state.page + 1)
}
},
async previousPage() {
if (this.state.page > 1) {
await this.loadPage(this.state.page - 1)
}
},
showDetail(index) {
this.update({
selected: this.state.results[index]
+119 -55
View File
@@ -1,89 +1,153 @@
<detail-view>
<div if={ props.formation } class="detail-card">
<h2>{ props.formation.nom }</h2>
<div if={ props.formation } class="detail-page">
<h2>Formation</h2>
<p><b>Établissement :</b> { props.formation.etablissement }</p>
<h1 class="formation-title">{ props.formation.etablissement } - { props.formation.nom }</h1>
<div class="formation-meta">
<p><b>Ville :</b> { props.formation.ville }</p>
<p><b>Département :</b> { props.formation.departement } - { props.formation.departementLib }</p>
<p><b>Département :</b> { props.formation.departement } { props.formation.departementLib }</p>
<p><b>Académie :</b> { props.formation.academie }</p>
<p><b>Région :</b> { props.formation.region }</p>
<p><b>Contrat :</b> { props.formation.contrat }</p>
<hr>
<h3>Formation</h3>
<p><b>Filière :</b> { props.formation.filiere }</p>
<p><b>Sélectivité :</b> { props.formation.selectivite }</p>
<p>{ props.formation.contrat }</p>
<p><b>Capacité :</b> { props.formation.capacite }</p>
<p><b>Candidats :</b> { props.formation.candidats }</p>
<p><b>Admis :</b> { props.formation.admis }</p>
<p><b>Taux d'accès :</b> { props.formation.tauxAcces }%</p>
</div>
<hr>
<div class="detail-grid">
<div>
<h2>Phase principale d'admission</h2>
<h3>Profil des admis</h3>
<div class="chart-container">
<div class="chart-title">Répartition des bacs</div>
<table class="charts-css bar show-labels">
<table class="detail-table">
<thead>
<tr>
<th>Bac</th>
<th>Voeux</th>
<th>Classés</th>
<th>Propositions</th>
<th>Acceptés</th>
</tr>
</thead>
<tbody>
<tr>
<th>Général</th>
<td style="--size: { props.formation.pctGeneral / 100 }">
{ props.formation.pctGeneral }%
</td>
<td>Gén</td>
<td>{ props.formation.voePPGeneral }</td>
<td>{ props.formation.classesPPGeneral }</td>
<td>{ props.formation.propositionsPPGeneral }</td>
<td>{ props.formation.acceptesPPGeneral }</td>
</tr>
<tr>
<th>Technologique</th>
<td style="--size: { props.formation.pctTechno / 100 }">
{ props.formation.pctTechno }%
</td>
<td>Techno</td>
<td>{ props.formation.voePPTechno }</td>
<td>{ props.formation.classesPPTechno }</td>
<td>{ props.formation.propositionsPPTechno }</td>
<td>{ props.formation.acceptesPPTechno }</td>
</tr>
<tr>
<th>Professionnel</th>
<td style="--size: { props.formation.pctPro / 100 }">
{ props.formation.pctPro }%
</td>
<td>Pro</td>
<td>{ props.formation.voePPPro }</td>
<td>{ props.formation.classesPPPro }</td>
<td>{ props.formation.propositionsPPPro }</td>
<td>{ props.formation.acceptesPPPro }</td>
</tr>
<tr>
<td>Autres</td>
<td>{ props.formation.voePPAutres }</td>
<td>{ props.formation.classesPPAutres }</td>
<td>{ props.formation.propositionsPPAutres }</td>
<td>{ props.formation.acceptesPPAutres }</td>
</tr>
<tr class="total-row">
<td>Total</td>
<td>{ props.formation.voePPTotal }</td>
<td>{ props.formation.classesPPTotal }</td>
<td>{ props.formation.propositionsPPTotal }</td>
<td>{ props.formation.acceptesPPTotal }</td>
</tr>
</tbody>
</table>
</div>
<hr>
<div class="timeline-box">
<h3>Vitesse de remplissage</h3>
<h3>Mentions au bac</h3>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div>
<b>Ouverture 30 mai</b><br />
{ props.formation.pctDebutPhase }%
</div>
</div>
<div class="chart-container">
<table class="charts-css bar show-labels">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div>
<b>16 juin</b><br />
{ props.formation.pctDateBac }%
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div>
<b>11 juillet</b><br />
{ props.formation.pctFinPhase }%
</div>
</div>
</div>
</div>
</div>
<h2>Phase complémentaire d'admission</h2>
<table class="detail-table">
<thead>
<tr>
<th>Bac</th>
<th>Voeux</th>
<th>Classés</th>
<th>Propositions</th>
<th>Acceptés</th>
</tr>
</thead>
<tbody>
<tr>
<th>Sans mention</th>
<td style="--size: { props.formation.pctSansMention / 100 }">
{ props.formation.pctSansMention }%
</td>
<td>Gén</td>
<td>{ props.formation.voePCGeneral }</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>AB</th>
<td style="--size: { props.formation.pctAB / 100 }">
{ props.formation.pctAB }%
</td>
<td>Techno</td>
<td>{ props.formation.voePCTechno }</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>B</th>
<td style="--size: { props.formation.pctB / 100 }">
{ props.formation.pctB }%
</td>
<td>Pro</td>
<td>{ props.formation.voePCPro }</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>TB</th>
<td style="--size: { props.formation.pctTB / 100 }">
{ props.formation.pctTB }%
</td>
<td>Autres</td>
<td>{ props.formation.voePCAutres }</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="total-row">
<td>Total</td>
<td>{ props.formation.voePCTotal }</td>
<td>{ props.formation.classesPCTotal }</td>
<td>{ props.formation.acceptesPCTotal }</td>
<td>{ props.formation.acceptesPCTotal }</td>
</tr>
</tbody>
</table>
</div>
<button onclick={ () => props.onback() }>Retour</button>
</div>
</detail-view>
+34 -7
View File
@@ -33,7 +33,6 @@ export function createFormation(raw) {
? raw.g_olocalisation_des_formations.lon
: null,
// profils admis
pctFemmes: raw.pct_f,
pctBoursiers: raw.pct_bours,
pctNeoBac: raw.pct_neobac,
@@ -48,19 +47,47 @@ export function createFormation(raw) {
pctTB: raw.pct_tb,
pctTBF: raw.pct_tbf,
// vitesse de remplissage / phase principale
pctDebutPhase: raw.pct_acc_debutpp,
pctDateBac: raw.pct_acc_datebac,
pctFinPhase: raw.pct_acc_finpp,
// chiffres bruts utiles
admisDebutPhase: raw.acc_debutpp,
admisDateBac: raw.acc_datebac,
admisFinPhase: raw.acc_finpp,
admisGeneral: raw.acc_bg,
admisTechno: raw.acc_bt,
admisPro: raw.acc_bp,
admisAutres: raw.acc_at
// phase principale
voePPGeneral: raw.nb_voe_pp_bg,
voePPTechno: raw.nb_voe_pp_bt,
voePPPro: raw.nb_voe_pp_bp,
voePPAutres: raw.nb_voe_pp_at,
voePPTotal: raw.nb_voe_pp,
classesPPGeneral: raw.nb_cla_pp_bg,
classesPPTechno: raw.nb_cla_pp_bt,
classesPPPro: raw.nb_cla_pp_bp,
classesPPAutres: raw.nb_cla_pp_at,
classesPPTotal: raw.nb_cla_pp,
propositionsPPGeneral: raw.prop_tot_bg,
propositionsPPTechno: raw.prop_tot_bt,
propositionsPPPro: raw.prop_tot_bp,
propositionsPPAutres: raw.prop_tot_at,
propositionsPPTotal: raw.prop_tot,
acceptesPPGeneral: raw.acc_bg,
acceptesPPTechno: raw.acc_bt,
acceptesPPPro: raw.acc_bp,
acceptesPPAutres: raw.acc_at,
acceptesPPTotal: raw.acc_pp,
// phase complémentaire
voePCGeneral: raw.nb_voe_pc_bg,
voePCTechno: raw.nb_voe_pc_bt,
voePCPro: raw.nb_voe_pc_bp,
voePCAutres: raw.nb_voe_pc_at,
voePCTotal: raw.nb_voe_pc,
classesPCTotal: raw.nb_cla_pc,
acceptesPCTotal: raw.acc_pc
}
}
+75 -3
View File
@@ -164,9 +164,81 @@ body {
font-weight: bold;
}
@media (min-width: 900px) {
.layout {
grid-template-columns: 1.2fr 1fr;
.detail-page {
background: #f5f5f5;
}
.formation-title {
color: #3d4fff;
font-size: 2rem;
line-height: 1.15;
margin-bottom: 20px;
}
.formation-meta p {
margin: 8px 0;
font-size: 1.1rem;
}
.detail-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 40px;
align-items: start;
margin-top: 20px;
margin-bottom: 30px;
}
.detail-table {
width: 100%;
border-collapse: collapse;
background: white;
}
.detail-table th,
.detail-table td {
border: 1px solid #d7d7d7;
padding: 14px 12px;
text-align: left;
}
.detail-table th {
background: #fafafa;
font-size: 1.05rem;
}
.total-row td {
font-weight: bold;
}
.timeline-box {
padding-top: 10px;
}
.timeline {
position: relative;
margin-top: 30px;
padding-left: 30px;
border-left: 2px solid #d9d9d9;
}
.timeline-item {
position: relative;
margin-bottom: 45px;
}
.timeline-dot {
position: absolute;
left: -39px;
top: 6px;
width: 14px;
height: 14px;
background: #21c8b4;
border-radius: 50%;
}
@media (max-width: 900px) {
.detail-grid {
grid-template-columns: 1fr;
}
}