Paper preview in ZenithPrep style: stat cards, subject sections
shared() now renders: stat cards (questions, duration, max marks, +4/-1), per-subject sections with q-ranges and chapter lists, a 'take this paper' card with the save-and-next explainer and privacy note, then the full question list. New .kv/.grid2/.subj-sec/.ch-list CSS with mobile single-column fallback.
This commit is contained in:
parent
600ff95315
commit
374e2e2056
2 changed files with 88 additions and 3 deletions
50
src/views.rs
50
src/views.rs
|
|
@ -1216,6 +1216,39 @@ pub fn dashboard(ctx: &Ctx, user: &str, tests: &[TestRow], weak: &[(Bucket, Stri
|
|||
}
|
||||
|
||||
pub fn shared(ctx: &Ctx, title: &str, duration: i64, questions: &[Question], paper_id: &str, token: &str, signed: bool) -> String {
|
||||
use std::collections::BTreeMap;
|
||||
// Group questions by subject, preserving first-seen order.
|
||||
let mut order: Vec<String> = Vec::new();
|
||||
let mut by_subj: BTreeMap<String, Vec<(usize, &Question)>> = BTreeMap::new();
|
||||
for (i, q) in questions.iter().enumerate() {
|
||||
if !by_subj.contains_key(&q.subject) {
|
||||
order.push(q.subject.clone());
|
||||
}
|
||||
by_subj.entry(q.subject.clone()).or_default().push((i, q));
|
||||
}
|
||||
let n = questions.len();
|
||||
let max_marks = n as i64 * 4;
|
||||
// Subject sections with chapter lists.
|
||||
let mut sections = String::new();
|
||||
for subj in &order {
|
||||
let qs = &by_subj[subj];
|
||||
let start = qs.first().map(|(i, _)| i + 1).unwrap_or(1);
|
||||
let end = qs.last().map(|(i, _)| i + 1).unwrap_or(n);
|
||||
let mut chapters: Vec<String> = Vec::new();
|
||||
for (_, q) in qs {
|
||||
if !chapters.iter().any(|c| c == &q.chapter) {
|
||||
chapters.push(q.chapter.clone());
|
||||
}
|
||||
}
|
||||
let ch_list: String = chapters.iter().map(|c| format!("<li>{}</li>", esc(c))).collect();
|
||||
sections.push_str(&format!(
|
||||
r#"<div class="card subj-sec"><h3>{subj} <span class="dim">q{start}–q{end}</span></h3><ul class="ch-list">{chs}</ul></div>"#,
|
||||
subj = esc(subj),
|
||||
start = start,
|
||||
end = end,
|
||||
chs = ch_list
|
||||
));
|
||||
}
|
||||
let qs: String = questions
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
|
@ -1253,12 +1286,23 @@ pub fn shared(ctx: &Ctx, title: &str, duration: i64, questions: &[Question], pap
|
|||
)
|
||||
};
|
||||
let body = format!(
|
||||
r#"<h1>{title}</h1><p class="meta">{count} questions · {dur} minutes · no answers or scores on this page.</p>{cta}<p><button class="btn" type="button" onclick="window.print()">print / PDF</button></p><div style="margin-top:16px">{qs}</div>"#,
|
||||
r#"<h1>Practice paper</h1><p class="meta">{title} · JEE Main pattern · no answers or scores on this page.</p>
|
||||
<div class="kv">
|
||||
<div class="card"><span class="dim">questions</span><b>{count}</b></div>
|
||||
<div class="card"><span class="dim">duration</span><b>{dur} min</b></div>
|
||||
<div class="card"><span class="dim">maximum marks</span><b>{max}</b></div>
|
||||
<div class="card"><span class="dim">marking</span><b>+4 / −1</b></div>
|
||||
</div>
|
||||
<div class="grid2"><div>{sections}</div><div class="card"><h3>take this paper</h3><p class="meta">The online test works like the real exam: answers count only after save & next.</p>{cta}<p><button class="btn" type="button" onclick="window.print()">print / PDF</button></p><p class="meta">Only the question paper is shared. Your answers and score stay private.</p></div></div>
|
||||
<h2>every question</h2>
|
||||
<div style="margin-top:12px">{qs}</div>"#,
|
||||
title = esc(title),
|
||||
count = questions.len(),
|
||||
dur = duration,
|
||||
qs = qs,
|
||||
cta = cta
|
||||
max = max_marks,
|
||||
sections = sections,
|
||||
cta = cta,
|
||||
qs = qs
|
||||
);
|
||||
shell(ctx, None, title, body)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2229,3 +2229,44 @@ img.brand-logo {
|
|||
vertical-align: middle;
|
||||
filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.3));
|
||||
}
|
||||
|
||||
/* ===== paper preview: stat cards + subject sections ===== */
|
||||
.kv {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.kv .card {
|
||||
text-align: center;
|
||||
padding: 16px 12px;
|
||||
}
|
||||
.kv .card b {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.grid2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
gap: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.grid2 { grid-template-columns: 1fr; }
|
||||
}
|
||||
.subj-sec { margin-bottom: 12px; }
|
||||
.subj-sec h3 { margin-bottom: 8px; }
|
||||
.ch-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
columns: 2;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-main);
|
||||
}
|
||||
.ch-list li { margin-bottom: 4px; }
|
||||
.ch-list li::before { content: "• "; color: var(--text-dim); }
|
||||
@media (max-width: 560px) {
|
||||
.ch-list { columns: 1; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue