ajout des elements
This commit is contained in:
+92
-4
@@ -1,19 +1,45 @@
|
|||||||
export function buildURL(query, limit = 20, offset = 0) {
|
export function buildURL(query, limit = 20, offset = 0, filters = {}) {
|
||||||
let url =
|
let url =
|
||||||
"https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?"
|
"https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?"
|
||||||
|
|
||||||
url += "limit=" + limit
|
url += "limit=" + limit
|
||||||
url += "&offset=" + offset
|
url += "&offset=" + offset
|
||||||
|
|
||||||
|
var conditions = []
|
||||||
|
|
||||||
if (query && query.trim() !== "") {
|
if (query && query.trim() !== "") {
|
||||||
url += "&where=search(lib_for_voe_ins, '" + query + "')"
|
conditions.push("search(lib_for_voe_ins, '" + query + "')")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.filiere && filters.filiere !== "") {
|
||||||
|
conditions.push("fili='" + filters.filiere + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.selectivite && filters.selectivite !== "") {
|
||||||
|
conditions.push("select_form='" + filters.selectivite + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.region && filters.region !== "") {
|
||||||
|
conditions.push("region_etab_aff='" + filters.region + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.tauxMin && filters.tauxMin > 0) {
|
||||||
|
conditions.push("taux_acces_ens>=" + filters.tauxMin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.tauxMax && filters.tauxMax < 100) {
|
||||||
|
conditions.push("taux_acces_ens<=" + filters.tauxMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
url += "&where=" + conditions.join(" AND ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchFormations(query, limit = 20, offset = 0) {
|
export async function fetchFormations(query, limit = 20, offset = 0, filters = {}) {
|
||||||
const url = buildURL(query, limit, offset)
|
const url = buildURL(query, limit, offset, filters)
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -22,3 +48,65 @@ export async function fetchFormations(query, limit = 20, offset = 0) {
|
|||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchFormationHistory(codUai, nomFormation) {
|
||||||
|
var datasets = {
|
||||||
|
2020: "fr-esr-parcoursup_2020",
|
||||||
|
2021: "fr-esr-parcoursup_2021",
|
||||||
|
2022: "fr-esr-parcoursup_2022",
|
||||||
|
2023: "fr-esr-parcoursup_2023",
|
||||||
|
2024: "fr-esr-parcoursup_2024",
|
||||||
|
2025: "fr-esr-parcoursup"
|
||||||
|
}
|
||||||
|
|
||||||
|
var history = []
|
||||||
|
var searchName = nomFormation.substring(0, 40).replace(/'/g, "\\'")
|
||||||
|
var years = [2020, 2021, 2022, 2023, 2024, 2025]
|
||||||
|
|
||||||
|
for (var i = 0; i < years.length; i++) {
|
||||||
|
var year = years[i]
|
||||||
|
var dataset = datasets[year]
|
||||||
|
|
||||||
|
try {
|
||||||
|
var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/"
|
||||||
|
+ dataset + "/records?"
|
||||||
|
+ "limit=5"
|
||||||
|
+ "&where=cod_uai%3D'" + codUai + "' AND search(lib_for_voe_ins, '" + searchName + "')"
|
||||||
|
+ "&select=cod_uai,lib_for_voe_ins,voe_tot,acc_tot,pct_sansmention,pct_ab,pct_b,pct_tb,pct_tbf,pct_bg,pct_bt,pct_bp"
|
||||||
|
|
||||||
|
var response = await fetch(url)
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
var data = await response.json()
|
||||||
|
|
||||||
|
if (data.results && data.results.length > 0) {
|
||||||
|
var r = data.results[0]
|
||||||
|
var taux = 0
|
||||||
|
|
||||||
|
if (r.voe_tot && r.voe_tot > 0) {
|
||||||
|
taux = Math.round((r.acc_tot / r.voe_tot) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
annee: year,
|
||||||
|
tauxAcces: taux,
|
||||||
|
candidats: r.voe_tot || 0,
|
||||||
|
admis: r.acc_tot || 0,
|
||||||
|
pctSansMention: r.pct_sansmention || 0,
|
||||||
|
pctAB: r.pct_ab || 0,
|
||||||
|
pctB: r.pct_b || 0,
|
||||||
|
pctTB: r.pct_tb || 0,
|
||||||
|
pctTBF: r.pct_tbf || 0,
|
||||||
|
pctGeneral: r.pct_bg || 0,
|
||||||
|
pctTechno: r.pct_bt || 0,
|
||||||
|
pctPro: r.pct_bp || 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Erreur pour " + year + ":", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
|||||||
+237
-121
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
|||||||
<detail-view>
|
<detail-view>
|
||||||
<div if={ props.formation } class="detail-page">
|
<div if={ props.formation } class="detail-page">
|
||||||
<h2>Formation</h2>
|
<h2>Formation</h2>
|
||||||
|
|
||||||
<h1 class="formation-title">{ props.formation.etablissement } - { props.formation.nom }</h1>
|
<h1 class="formation-title">{ props.formation.etablissement } - { props.formation.nom }</h1>
|
||||||
|
|
||||||
<div class="formation-meta">
|
<div class="formation-meta">
|
||||||
<p><b>Ville :</b> { props.formation.ville }</p>
|
<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>
|
||||||
@@ -15,7 +13,6 @@
|
|||||||
<div class="detail-grid">
|
<div class="detail-grid">
|
||||||
<div>
|
<div>
|
||||||
<h2>Phase principale d'admission</h2>
|
<h2>Phase principale d'admission</h2>
|
||||||
|
|
||||||
<table class="detail-table">
|
<table class="detail-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -68,7 +65,6 @@
|
|||||||
|
|
||||||
<div class="timeline-box">
|
<div class="timeline-box">
|
||||||
<h3>Vitesse de remplissage</h3>
|
<h3>Vitesse de remplissage</h3>
|
||||||
|
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
<div class="timeline-item">
|
<div class="timeline-item">
|
||||||
<div class="timeline-dot"></div>
|
<div class="timeline-dot"></div>
|
||||||
@@ -77,7 +73,6 @@
|
|||||||
{ props.formation.pctDebutPhase }%
|
{ props.formation.pctDebutPhase }%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timeline-item">
|
<div class="timeline-item">
|
||||||
<div class="timeline-dot"></div>
|
<div class="timeline-dot"></div>
|
||||||
<div>
|
<div>
|
||||||
@@ -85,7 +80,6 @@
|
|||||||
{ props.formation.pctDateBac }%
|
{ props.formation.pctDateBac }%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timeline-item">
|
<div class="timeline-item">
|
||||||
<div class="timeline-dot"></div>
|
<div class="timeline-dot"></div>
|
||||||
<div>
|
<div>
|
||||||
@@ -98,7 +92,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Phase complémentaire d'admission</h2>
|
<h2>Phase complémentaire d'admission</h2>
|
||||||
|
|
||||||
<table class="detail-table">
|
<table class="detail-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -153,127 +146,49 @@
|
|||||||
<h2 class="charts-heading">Profil des admis</h2>
|
<h2 class="charts-heading">Profil des admis</h2>
|
||||||
|
|
||||||
<div class="charts-section">
|
<div class="charts-section">
|
||||||
|
|
||||||
<!-- Graphique 1 : Répartition par type de bac -->
|
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<h3>Répartition par type de bac</h3>
|
<h3>Répartition par type de bac</h3>
|
||||||
|
<div id="chart-bac" ref="chartBac"></div>
|
||||||
<div id="chart-bac">
|
|
||||||
<table class="charts-css column show-labels show-primary-axis show-4-secondary-axes data-spacing-10">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Type</th>
|
|
||||||
<th scope="col">Pourcentage</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Général</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctGeneral) }; --color: #3d7fff;">
|
|
||||||
<span class="data">{ props.formation.pctGeneral || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Techno</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctTechno) }; --color: #f59e0b;">
|
|
||||||
<span class="data">{ props.formation.pctTechno || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Pro</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctPro) }; --color: #10b981;">
|
|
||||||
<span class="data">{ props.formation.pctPro || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Graphique 2 : Mentions au bac -->
|
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<h3>Mentions au bac des admis</h3>
|
<h3>Mentions au bac des admis</h3>
|
||||||
|
<div id="chart-mentions" ref="chartMentions"></div>
|
||||||
<div id="chart-mentions">
|
|
||||||
<table class="charts-css column show-labels show-primary-axis show-4-secondary-axes data-spacing-10">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Mention</th>
|
|
||||||
<th scope="col">Pourcentage</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Sans</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctSansMention) }; --color: #94a3b8;">
|
|
||||||
<span class="data">{ props.formation.pctSansMention || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">AB</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctAB) }; --color: #60a5fa;">
|
|
||||||
<span class="data">{ props.formation.pctAB || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Bien</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctB) }; --color: #34d399;">
|
|
||||||
<span class="data">{ props.formation.pctB || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">TB</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctTB) }; --color: #fbbf24;">
|
|
||||||
<span class="data">{ props.formation.pctTB || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">TB Féli.</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctTBF) }; --color: #f472b6;">
|
|
||||||
<span class="data">{ props.formation.pctTBF || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Graphique 3 : Profil sociologique (barres horizontales, pleine largeur) -->
|
|
||||||
<div class="chart-wrapper chart-full">
|
<div class="chart-wrapper chart-full">
|
||||||
<h3>Profil sociologique</h3>
|
<h3>Profil sociologique</h3>
|
||||||
|
<div id="chart-profil" ref="chartProfil"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="chart-profil">
|
<!-- ===================== ÉVOLUTION HISTORIQUE ===================== -->
|
||||||
<table class="charts-css bar show-labels show-primary-axis show-4-secondary-axes data-spacing-14">
|
|
||||||
<thead>
|
<h2 class="charts-heading">Évolution depuis 2020</h2>
|
||||||
<tr>
|
|
||||||
<th scope="col">Catégorie</th>
|
<div if={ state.loadingHistory } class="message">
|
||||||
<th scope="col">Pourcentage</th>
|
Chargement de l'historique...
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
|
||||||
<tbody>
|
<div if={ !state.loadingHistory && state.history.length === 0 } class="message">
|
||||||
<tr>
|
Aucune donnée historique disponible pour cette formation.
|
||||||
<th scope="row">Femmes</th>
|
</div>
|
||||||
<td style="--size: { safe(props.formation.pctFemmes) }; --color: #a78bfa;">
|
|
||||||
<span class="data">{ props.formation.pctFemmes || 0 }%</span>
|
<div class="charts-section" if={ state.history.length > 0 }>
|
||||||
</td>
|
<div class="chart-wrapper">
|
||||||
</tr>
|
<h3>Taux d'accès par année</h3>
|
||||||
<tr>
|
<div id="chart-evolution-taux" ref="chartTaux"></div>
|
||||||
<th scope="row">Boursiers</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctBoursiers) }; --color: #fb923c;">
|
|
||||||
<span class="data">{ props.formation.pctBoursiers || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Néo-bac</th>
|
|
||||||
<td style="--size: { safe(props.formation.pctNeoBac) }; --color: #2dd4bf;">
|
|
||||||
<span class="data">{ props.formation.pctNeoBac || 0 }%</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<h3>Nombre de candidats et admis</h3>
|
||||||
|
<div id="chart-evolution-candidats" ref="chartCandidats"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-wrapper chart-full">
|
||||||
|
<h3>Évolution des mentions au bac</h3>
|
||||||
|
<div ref="chartMentionsHist"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick={ () => props.onback() } class="btn-retour">Retour à la liste</button>
|
<button onclick={ () => props.onback() } class="btn-retour">Retour à la liste</button>
|
||||||
@@ -281,12 +196,191 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
state: {
|
||||||
|
history: [],
|
||||||
|
loadingHistory: false
|
||||||
|
},
|
||||||
|
|
||||||
safe(val) {
|
safe(val) {
|
||||||
if (val === null || val === undefined || isNaN(val)) return 0
|
if (val === null || val === undefined || isNaN(val)) return 0
|
||||||
var v = val / 100
|
var v = val / 100
|
||||||
if (v > 1) return 1
|
if (v > 1) return 1
|
||||||
if (v < 0) return 0
|
if (v < 0) return 0
|
||||||
return Math.round(v * 100) / 100
|
return Math.round(v * 100) / 100
|
||||||
|
},
|
||||||
|
|
||||||
|
onMounted() {
|
||||||
|
this.renderCharts()
|
||||||
|
this.loadHistory()
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdated() {
|
||||||
|
this.renderCharts()
|
||||||
|
if (this.state.history.length > 0) {
|
||||||
|
this.renderHistoryCharts()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCharts() {
|
||||||
|
var f = this.props.formation
|
||||||
|
if (!f) return
|
||||||
|
|
||||||
|
var chartBac = this.$('[ref="chartBac"]')
|
||||||
|
var chartMentions = this.$('[ref="chartMentions"]')
|
||||||
|
var chartProfil = this.$('[ref="chartProfil"]')
|
||||||
|
|
||||||
|
if (chartBac) {
|
||||||
|
chartBac.innerHTML = this.buildColumnChart([
|
||||||
|
{ label: 'Général', value: f.pctGeneral, color: '#3d7fff' },
|
||||||
|
{ label: 'Techno', value: f.pctTechno, color: '#f59e0b' },
|
||||||
|
{ label: 'Pro', value: f.pctPro, color: '#10b981' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chartMentions) {
|
||||||
|
chartMentions.innerHTML = this.buildColumnChart([
|
||||||
|
{ label: 'Sans', value: f.pctSansMention, color: '#94a3b8' },
|
||||||
|
{ label: 'AB', value: f.pctAB, color: '#60a5fa' },
|
||||||
|
{ label: 'Bien', value: f.pctB, color: '#34d399' },
|
||||||
|
{ label: 'TB', value: f.pctTB, color: '#fbbf24' },
|
||||||
|
{ label: 'TB Féli.', value: f.pctTBF, color: '#f472b6' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chartProfil) {
|
||||||
|
chartProfil.innerHTML = this.buildBarChart([
|
||||||
|
{ label: 'Femmes', value: f.pctFemmes, color: '#a78bfa' },
|
||||||
|
{ label: 'Boursiers', value: f.pctBoursiers, color: '#fb923c' },
|
||||||
|
{ label: 'Néo-bac', value: f.pctNeoBac, color: '#2dd4bf' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadHistory() {
|
||||||
|
var f = this.props.formation
|
||||||
|
if (!f) return
|
||||||
|
|
||||||
|
// Extraire le cod_uai de l'id (format: codUai-nomFormation)
|
||||||
|
var codUai = f.id.split('-')[0]
|
||||||
|
|
||||||
|
if (!codUai || !window.fetchFormationHistory) return
|
||||||
|
|
||||||
|
this.update({ loadingHistory: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
var history = await window.fetchFormationHistory(codUai, f.nom)
|
||||||
|
this.update({ history: history, loadingHistory: false })
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Erreur historique:', e)
|
||||||
|
this.update({ history: [], loadingHistory: false })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHistoryCharts() {
|
||||||
|
var hist = this.state.history
|
||||||
|
if (!hist || hist.length === 0) return
|
||||||
|
|
||||||
|
var chartTaux = this.$('[ref="chartTaux"]')
|
||||||
|
var chartCandidats = this.$('[ref="chartCandidats"]')
|
||||||
|
var chartMentionsHist = this.$('[ref="chartMentionsHist"]')
|
||||||
|
|
||||||
|
// Graphique : taux d'accès par année
|
||||||
|
if (chartTaux) {
|
||||||
|
var items = []
|
||||||
|
for (var i = 0; i < hist.length; i++) {
|
||||||
|
items.push({
|
||||||
|
label: '' + hist[i].annee,
|
||||||
|
value: hist[i].tauxAcces,
|
||||||
|
color: '#1a936f'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
chartTaux.innerHTML = this.buildColumnChart(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graphique : candidats vs admis (normalisé sur le max)
|
||||||
|
if (chartCandidats) {
|
||||||
|
var maxCandidats = 0
|
||||||
|
for (var i = 0; i < hist.length; i++) {
|
||||||
|
if (hist[i].candidats > maxCandidats) maxCandidats = hist[i].candidats
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = ''
|
||||||
|
for (var i = 0; i < hist.length; i++) {
|
||||||
|
var h = hist[i]
|
||||||
|
var sizeCand = maxCandidats > 0 ? Math.round((h.candidats / maxCandidats) * 100) / 100 : 0
|
||||||
|
var sizeAdmis = maxCandidats > 0 ? Math.round((h.admis / maxCandidats) * 100) / 100 : 0
|
||||||
|
|
||||||
|
rows += '<tr>'
|
||||||
|
rows += '<th scope="row">' + h.annee + '</th>'
|
||||||
|
rows += '<td style="--size: ' + sizeCand + '; --color: #2a5298;">'
|
||||||
|
rows += '<span class="data">' + h.candidats + '</span></td>'
|
||||||
|
rows += '<td style="--size: ' + sizeAdmis + '; --color: #1a936f;">'
|
||||||
|
rows += '<span class="data">' + h.admis + '</span></td>'
|
||||||
|
rows += '</tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
chartCandidats.innerHTML = '<table class="charts-css column multiple show-labels show-primary-axis show-4-secondary-axes data-spacing-10">'
|
||||||
|
+ '<thead><tr><th scope="col">Année</th><th scope="col">Candidats</th><th scope="col">Admis</th></tr></thead>'
|
||||||
|
+ '<tbody>' + rows + '</tbody></table>'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tableau : évolution des mentions
|
||||||
|
if (chartMentionsHist) {
|
||||||
|
var table = '<table class="detail-table">'
|
||||||
|
table += '<thead><tr><th>Année</th><th>Sans mention</th><th>AB</th><th>Bien</th><th>TB</th><th>TB Féli.</th></tr></thead>'
|
||||||
|
table += '<tbody>'
|
||||||
|
|
||||||
|
for (var i = 0; i < hist.length; i++) {
|
||||||
|
var h = hist[i]
|
||||||
|
table += '<tr>'
|
||||||
|
table += '<td><b>' + h.annee + '</b></td>'
|
||||||
|
table += '<td>' + h.pctSansMention + '%</td>'
|
||||||
|
table += '<td>' + h.pctAB + '%</td>'
|
||||||
|
table += '<td>' + h.pctB + '%</td>'
|
||||||
|
table += '<td>' + h.pctTB + '%</td>'
|
||||||
|
table += '<td>' + h.pctTBF + '%</td>'
|
||||||
|
table += '</tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
table += '</tbody></table>'
|
||||||
|
chartMentionsHist.innerHTML = table
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
buildColumnChart(items) {
|
||||||
|
var rows = ''
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var item = items[i]
|
||||||
|
var val = this.safe(item.value)
|
||||||
|
var display = item.value || 0
|
||||||
|
rows += '<tr>'
|
||||||
|
rows += '<th scope="row">' + item.label + '</th>'
|
||||||
|
rows += '<td style="--size: ' + val + '; --color: ' + item.color + ';">'
|
||||||
|
rows += '<span class="data">' + display + '%</span>'
|
||||||
|
rows += '</td></tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<table class="charts-css column show-labels show-primary-axis show-4-secondary-axes data-spacing-10">'
|
||||||
|
+ '<thead><tr><th scope="col">Type</th><th scope="col">%</th></tr></thead>'
|
||||||
|
+ '<tbody>' + rows + '</tbody></table>'
|
||||||
|
},
|
||||||
|
|
||||||
|
buildBarChart(items) {
|
||||||
|
var rows = ''
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var item = items[i]
|
||||||
|
var val = this.safe(item.value)
|
||||||
|
var display = item.value || 0
|
||||||
|
rows += '<tr>'
|
||||||
|
rows += '<th scope="row">' + item.label + '</th>'
|
||||||
|
rows += '<td style="--size: ' + val + '; --color: ' + item.color + ';">'
|
||||||
|
rows += '<span class="data">' + display + '%</span>'
|
||||||
|
rows += '</td></tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<table class="charts-css bar show-labels show-primary-axis show-4-secondary-axes data-spacing-14">'
|
||||||
|
+ '<thead><tr><th scope="col">Catégorie</th><th scope="col">%</th></tr></thead>'
|
||||||
|
+ '<tbody>' + rows + '</tbody></table>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
onMounted() {
|
onMounted() {
|
||||||
const mapElement = this.$('div[ref="map"]')
|
var mapElement = this.$('div[ref="map"]')
|
||||||
|
|
||||||
this.map = L.map(mapElement).setView([46.8, 2.5], 6)
|
this.map = L.map(mapElement).setView([46.8, 2.5], 6)
|
||||||
|
|
||||||
@@ -16,21 +16,37 @@
|
|||||||
}).addTo(this.map)
|
}).addTo(this.map)
|
||||||
|
|
||||||
this.markersLayer = L.layerGroup().addTo(this.map)
|
this.markersLayer = L.layerGroup().addTo(this.map)
|
||||||
|
this.markersById = {}
|
||||||
this.refreshMarkers()
|
this.refreshMarkers()
|
||||||
|
|
||||||
// important : Leaflet calcule parfois mal la taille au montage
|
var self = this
|
||||||
setTimeout(() => {
|
|
||||||
this.map.invalidateSize()
|
setTimeout(function() {
|
||||||
}, 100)
|
if (self.map) self.map.invalidateSize()
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
if (self.map) self.map.invalidateSize()
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
window.mapFocus = function(id) {
|
||||||
|
self.focusFormation(id)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdated() {
|
onUpdated() {
|
||||||
this.refreshMarkers()
|
this.refreshMarkers()
|
||||||
|
|
||||||
|
var self = this
|
||||||
|
|
||||||
if (this.map) {
|
if (this.map) {
|
||||||
setTimeout(() => {
|
setTimeout(function() {
|
||||||
this.map.invalidateSize()
|
self.map.invalidateSize()
|
||||||
}, 50)
|
}, 100)
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
self.map.invalidateSize()
|
||||||
|
}, 300)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -39,6 +55,7 @@
|
|||||||
this.map.remove()
|
this.map.remove()
|
||||||
this.map = null
|
this.map = null
|
||||||
}
|
}
|
||||||
|
window.mapFocus = null
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshMarkers() {
|
refreshMarkers() {
|
||||||
@@ -47,18 +64,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.markersLayer.clearLayers()
|
this.markersLayer.clearLayers()
|
||||||
|
this.markersById = {}
|
||||||
|
|
||||||
const points = []
|
var points = []
|
||||||
const results = this.props.results || []
|
var results = this.props.results || []
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (var i = 0; i < results.length; i++) {
|
||||||
const f = results[i]
|
var f = results[i]
|
||||||
|
|
||||||
if (f.latitude != null && f.longitude != null) {
|
if (f.latitude != null && f.longitude != null) {
|
||||||
const marker = L.marker([f.latitude, f.longitude])
|
var marker = L.marker([f.latitude, f.longitude])
|
||||||
marker.bindPopup(`<b>${f.nom}</b><br>${f.ville}`)
|
marker.bindPopup('<b>' + f.nom + '</b><br>' + f.ville)
|
||||||
marker.addTo(this.markersLayer)
|
marker.addTo(this.markersLayer)
|
||||||
|
|
||||||
|
this.markersById[f.id] = marker
|
||||||
points.push([f.latitude, f.longitude])
|
points.push([f.latitude, f.longitude])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +87,25 @@
|
|||||||
} else {
|
} else {
|
||||||
this.map.setView([46.8, 2.5], 6)
|
this.map.setView([46.8, 2.5], 6)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
focusFormation(id) {
|
||||||
|
var marker = this.markersById[id]
|
||||||
|
|
||||||
|
if (marker && this.map) {
|
||||||
|
var mapEl = this.$('div[ref="map"]')
|
||||||
|
if (mapEl) {
|
||||||
|
mapEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
self.map.invalidateSize()
|
||||||
|
self.map.setView(marker.getLatLng(), 13, { animate: true })
|
||||||
|
marker.openPopup()
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,6 +17,17 @@
|
|||||||
|
|
||||||
<button onclick={ () => props.ondetail(i) }>Voir détail</button>
|
<button onclick={ () => props.ondetail(i) }>Voir détail</button>
|
||||||
<button onclick={ () => props.onselect(i) }>Ajouter à la sélection</button>
|
<button onclick={ () => props.onselect(i) }>Ajouter à la sélection</button>
|
||||||
|
<button onclick={ () => locateOnMap(f) } if={ f.latitude != null }>Localiser</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
locateOnMap(f) {
|
||||||
|
if (window.mapFocus) {
|
||||||
|
window.mapFocus(f.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</result-list>
|
</result-list>
|
||||||
@@ -2,25 +2,132 @@
|
|||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Ex : BUT informatique"
|
placeholder="Ex : BUT informatique, Licence droit..."
|
||||||
oninput={ updateQuery }
|
oninput={ updateQuery }
|
||||||
value={ state.query }
|
value={ state.query }
|
||||||
|
onkeydown={ handleKey }
|
||||||
/>
|
/>
|
||||||
<button onclick={ submitSearch }>Rechercher</button>
|
<button onclick={ submitSearch }>Rechercher</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="filters-toggle">
|
||||||
|
<button class="btn btn-small btn-outline" onclick={ toggleFilters }>
|
||||||
|
{ state.showFilters ? 'Masquer les filtres' : 'Filtres avancés' }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters-panel" if={ state.showFilters }>
|
||||||
|
<div class="filter-row">
|
||||||
|
<div class="filter-item">
|
||||||
|
<label>Type de formation</label>
|
||||||
|
<select onchange={ updateFiliere }>
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<option value="BTS">BTS</option>
|
||||||
|
<option value="BUT">BUT</option>
|
||||||
|
<option value="Licence">Licence</option>
|
||||||
|
<option value="Licence_Las">Licence - L.AS</option>
|
||||||
|
<option value="CPGE">CPGE</option>
|
||||||
|
<option value="BTS Agricole">BTS Agricole</option>
|
||||||
|
<option value="DN MADE">DN MADE</option>
|
||||||
|
<option value="DCG">DCG</option>
|
||||||
|
<option value="Ecole de Commerce">École de Commerce</option>
|
||||||
|
<option value="Ecole d'Ingénieurs">École d'Ingénieurs</option>
|
||||||
|
<option value="IFSI">IFSI</option>
|
||||||
|
<option value="PASS">PASS</option>
|
||||||
|
<option value="EFTS">EFTS</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<label>Sélectivité</label>
|
||||||
|
<select onchange={ updateSelectivite }>
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<option value="formation sélective">Sélective</option>
|
||||||
|
<option value="formation non sélective">Non sélective</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<label>Région</label>
|
||||||
|
<select onchange={ updateRegion }>
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<option value="Auvergne-Rhône-Alpes">Auvergne-Rhône-Alpes</option>
|
||||||
|
<option value="Bourgogne-Franche-Comté">Bourgogne-Franche-Comté</option>
|
||||||
|
<option value="Bretagne">Bretagne</option>
|
||||||
|
<option value="Centre-Val de Loire">Centre-Val de Loire</option>
|
||||||
|
<option value="Corse">Corse</option>
|
||||||
|
<option value="Grand Est">Grand Est</option>
|
||||||
|
<option value="Guadeloupe">Guadeloupe</option>
|
||||||
|
<option value="Guyane">Guyane</option>
|
||||||
|
<option value="Hauts-de-France">Hauts-de-France</option>
|
||||||
|
<option value="Ile-de-France">Île-de-France</option>
|
||||||
|
<option value="La Réunion">La Réunion</option>
|
||||||
|
<option value="Martinique">Martinique</option>
|
||||||
|
<option value="Mayotte">Mayotte</option>
|
||||||
|
<option value="Normandie">Normandie</option>
|
||||||
|
<option value="Nouvelle-Aquitaine">Nouvelle-Aquitaine</option>
|
||||||
|
<option value="Occitanie">Occitanie</option>
|
||||||
|
<option value="Pays de la Loire">Pays de la Loire</option>
|
||||||
|
<option value="Provence-Alpes-Côte d'Azur">PACA</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-row">
|
||||||
|
<div class="filter-item">
|
||||||
|
<label>Taux d'accès min (%)</label>
|
||||||
|
<input type="number" min="0" max="100" value={ state.tauxMin } oninput={ updateTauxMin } placeholder="0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<label>Taux d'accès max (%)</label>
|
||||||
|
<input type="number" min="0" max="100" value={ state.tauxMax } oninput={ updateTauxMax } placeholder="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
state: {
|
state: {
|
||||||
query: ''
|
query: '',
|
||||||
|
showFilters: false,
|
||||||
|
filiere: '',
|
||||||
|
selectivite: '',
|
||||||
|
region: '',
|
||||||
|
tauxMin: 0,
|
||||||
|
tauxMax: 100
|
||||||
},
|
},
|
||||||
|
|
||||||
updateQuery(e) {
|
updateQuery(e) {
|
||||||
this.update({ query: e.target.value })
|
this.update({ query: e.target.value })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleKey(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.submitSearch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFilters() {
|
||||||
|
this.update({ showFilters: !this.state.showFilters })
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFiliere(e) { this.update({ filiere: e.target.value }) },
|
||||||
|
updateSelectivite(e) { this.update({ selectivite: e.target.value }) },
|
||||||
|
updateRegion(e) { this.update({ region: e.target.value }) },
|
||||||
|
updateTauxMin(e) { this.update({ tauxMin: Number(e.target.value) }) },
|
||||||
|
updateTauxMax(e) { this.update({ tauxMax: Number(e.target.value) }) },
|
||||||
|
|
||||||
submitSearch() {
|
submitSearch() {
|
||||||
this.props.onsearch(this.state.query)
|
var filters = {
|
||||||
|
filiere: this.state.filiere,
|
||||||
|
selectivite: this.state.selectivite,
|
||||||
|
region: this.state.region,
|
||||||
|
tauxMin: this.state.tauxMin,
|
||||||
|
tauxMax: this.state.tauxMax
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onsearch(this.state.query, filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+37
-18
@@ -12,27 +12,46 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/riot@9/riot+compiler.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/riot@9/riot+compiler.min.js"></script>
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app></app>
|
<app></app>
|
||||||
|
|
||||||
<script src="./components/search-bar.riot" type="riot"></script>
|
<script src="./components/search-bar.riot" type="riot"></script>
|
||||||
<script src="./components/result-list.riot" type="riot"></script>
|
<script src="./components/result-list.riot" type="riot"></script>
|
||||||
<script src="./components/detail-view.riot" type="riot"></script>
|
<script src="./components/detail-view.riot" type="riot"></script>
|
||||||
<script src="./components/map-view.riot" type="riot"></script>
|
<script src="./components/map-view.riot" type="riot"></script>
|
||||||
<script src="./app.riot" type="riot"></script>
|
<script src="./app.riot" type="riot"></script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { fetchFormations } from './api.js'
|
import { fetchFormations, fetchFormationHistory } from './api.js'
|
||||||
import { createFormation } from './formation.js'
|
import { createFormation } from './formation.js'
|
||||||
|
import {
|
||||||
|
auth,
|
||||||
|
db,
|
||||||
|
createAccount,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
onUserChanged,
|
||||||
|
saveUserData,
|
||||||
|
loadUserData
|
||||||
|
} from './firebase.js'
|
||||||
|
|
||||||
window.fetchFormations = fetchFormations
|
window.fetchFormations = fetchFormations
|
||||||
window.createFormation = createFormation
|
window.createFormation = createFormation
|
||||||
</script>
|
window.fetchFormationHistory = fetchFormationHistory
|
||||||
|
|
||||||
<script>
|
window.firebaseServices = {
|
||||||
riot.compile().then(() => {
|
auth,
|
||||||
|
db,
|
||||||
|
createAccount,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
onUserChanged,
|
||||||
|
saveUserData,
|
||||||
|
loadUserData
|
||||||
|
}
|
||||||
|
|
||||||
|
await riot.compile()
|
||||||
riot.mount('app')
|
riot.mount('app')
|
||||||
})
|
</script>
|
||||||
</script>
|
</body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
+233
-132
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user