Files
enuxia-quiz/src/pages/admin/quiz_results.rs
T
Julien Denizot 6c092dc014 first version
2026-04-13 17:56:39 +02:00

135 lines
6.5 KiB
Rust

use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
use crate::server::admin::{get_quiz_submissions, export_submissions_csv};
#[component]
pub fn QuizResultsPage() -> impl IntoView {
let params = use_params_map();
let quiz_id = move || params.get().get("quiz_id").and_then(|s| s.parse::<i64>().ok()).unwrap_or(0);
let submissions = Resource::new(quiz_id, |id| get_quiz_submissions(id));
let csv_data = RwSignal::new(Option::<String>::None);
let export_action = Action::new(move |_: &()| {
let id = quiz_id();
async move {
if let Ok(csv) = export_submissions_csv(id).await {
csv_data.set(Some(csv));
}
}
});
view! {
<div class="admin-page">
<div class="admin-topbar">
<div>
<h1 style="
background:linear-gradient(135deg,#4F8EFF,#8B5CF6);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
background-clip:text;
">"Résultats"</h1>
<p style="font-size:13px;color:var(--color-enuxia-muted);margin-top:2px;">
"Liste des soumissions pour ce quiz."
</p>
</div>
<div class="topbar-actions">
<button
class="btn-purple"
on:click=move |_| { export_action.dispatch(()); }
>
"↓ Exporter CSV"
</button>
<a href=move || format!("/admin/quiz/{}/stats", quiz_id())
class="btn-ghost">"📊 Stats"</a>
<a href="/admin" class="btn-ghost">"← Dashboard"</a>
</div>
</div>
// CSV export
{move || csv_data.get().map(|csv| view! {
<div class="csv-box">
<p>"✓ CSV généré — copiez le contenu ci-dessous"</p>
<textarea readonly rows="8">{csv}</textarea>
</div>
})}
<Suspense fallback=|| view! {
<p style="color:var(--color-enuxia-muted);font-size:14px;">"Chargement..."</p>
}>
{move || submissions.get().map(|result| {
match result {
Ok(list) if list.is_empty() => view! {
<div style="
text-align:center; padding:60px 24px;
border-radius:16px;
background:var(--color-enuxia-dark-3);
border:1px solid var(--color-enuxia-border);
">
<p style="font-size:32px;margin-bottom:12px;">"📭"</p>
<p style="font-size:16px;font-weight:600;color:var(--color-enuxia-text);">
"Aucune soumission"
</p>
<p style="font-size:13px;color:var(--color-enuxia-muted);margin-top:4px;">
"Les résultats apparaîtront ici quand des étudiants auront passé le quiz."
</p>
</div>
}.into_any(),
Ok(list) => view! {
<table class="data-table">
<thead>
<tr>
<th>"Nom"</th>
<th>"Prénom"</th>
<th>"Score"</th>
<th>"%"</th>
<th>"Date"</th>
<th>"Détail"</th>
</tr>
</thead>
<tbody>
{list.into_iter().map(|s| {
let pct = if s.total > 0 {
(s.score * 100) / s.total
} else { 0 };
let sid = s.id;
let pct_color = if pct >= 80 { "#22C55E" }
else if pct >= 50 { "#4F8EFF" }
else { "#f87171" };
view! {
<tr>
<td style="font-weight:600;">{s.last_name.clone()}</td>
<td>{s.first_name.clone()}</td>
<td>{format!("{} / {}", s.score, s.total)}</td>
<td>
<span style=format!(
"font-weight:700;color:{};", pct_color
)>
{format!("{}%", pct)}
</span>
</td>
<td style="color:var(--color-enuxia-muted);font-size:13px;">
{s.submitted_at.format("%d/%m/%Y %H:%M").to_string()}
</td>
<td>
<a href=format!("/admin/submission/{}", sid)
class="table-link">
"Voir →"
</a>
</td>
</tr>
}
}).collect_view()}
</tbody>
</table>
}.into_any(),
Err(_) => view! {
<div class="field-error">"Erreur de chargement."</div>
}.into_any(),
}
})}
</Suspense>
</div>
}
}