first commit

This commit is contained in:
2026-08-10 21:35:17 +09:00
commit 02d9d0785b
22 changed files with 3853 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
class HistoryPage extends HTMLElement {
async connectedCallback() {
this.className = 'screen';
this.replaceChildren(createTopbar([createButton('새 시험 시작', () => showStartScreen())]));
const heading = document.createElement('h1');
heading.className = 'page-title';
heading.textContent = '지난 기록';
this.appendChild(heading);
const body = document.createElement('div');
body.innerHTML = `<div class="inline-loading"><span class="spinner small"></span>
<span>기록을 불러오는 중이에요…</span></div>`;
this.appendChild(body);
let tests;
try {
({ tests } = await api.tests());
} catch (err) {
body.replaceChildren();
const error = document.createElement('p');
error.className = 'error-box';
error.textContent = err.message;
body.appendChild(error);
return;
}
if (!tests.length) {
body.replaceChildren();
const empty = document.createElement('div');
empty.className = 'card empty';
empty.innerHTML = `<p>아직 저장된 기록이 없습니다.</p>`;
empty.appendChild(
createButton('첫 시험 시작하기', () => showStartScreen(), 'primary')
);
body.appendChild(empty);
return;
}
body.replaceChildren(this.renderStats(tests), this.renderList(tests));
}
renderStats(tests) {
const totals = tests.reduce(
(acc, test) => ({
questions: acc.questions + test.score.total,
correct: acc.correct + test.score.correct,
overconfident: acc.overconfident + test.score.overconfident,
}),
{ questions: 0, correct: 0, overconfident: 0 }
);
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="insight">
<div><strong></strong>본 시험</div>
<div><strong></strong>푼 문제</div>
<div><strong></strong>평균 정답률</div>
<div class="${totals.overconfident ? 'danger' : ''}">
<strong></strong>확신했지만 틀림
</div>
</div>
`;
const tiles = card.querySelectorAll('.insight strong');
tiles[0].textContent = tests.length;
tiles[1].textContent = totals.questions;
tiles[2].textContent = `${Math.round((totals.correct / totals.questions) * 100)}%`;
tiles[3].textContent = totals.overconfident;
card.querySelector('.insight').classList.toggle('risk', totals.overconfident > 0);
return card;
}
renderList(tests) {
const list = document.createElement('div');
list.className = 'result-list';
tests.forEach((test) => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'card history-item';
item.onclick = () => showHistoryDetail(test.id);
const top = document.createElement('div');
top.className = 'history-top';
const range = document.createElement('span');
range.className = 'history-range';
range.textContent = `${test.school} ${test.subject} · ${test.chapter}`;
const percent = document.createElement('span');
percent.className = 'history-score';
percent.textContent = `${test.score.percent}`;
top.append(range, percent);
const meta = document.createElement('p');
meta.className = 'muted';
meta.textContent =
`${test.student} · ${formatDate(test.createdAt)} · ` +
`난이도 ${DIFFICULTY_LABEL[test.difficulty] ?? test.difficulty} · ` +
`${test.score.total}문제 중 ${test.score.correct}문제 정답`;
const tags = document.createElement('p');
tags.className = 'history-tags';
const badges = [];
if (test.score.overconfident) {
badges.push(['bad', `확신했지만 틀림 ${test.score.overconfident}`]);
}
if (test.explainedCount) badges.push(['', `해설 ${test.explainedCount}`]);
if (test.twinCount) badges.push(['', `쌍둥이 문제 ${test.twinCount}`]);
if (test.askedCount) badges.push(['', `후속 질문 ${test.askedCount}`]);
badges.forEach(([tone, label]) => {
const badge = document.createElement('span');
badge.className = `badge ${tone}`.trim();
badge.textContent = label;
tags.appendChild(badge);
});
item.append(top, meta, tags);
list.appendChild(item);
});
return list;
}
}
customElements.define('history-page', HistoryPage);