/* blueyetutor — /history
* Workbook history. Card list, expand to view answer key (manifest items)
* + re-download. Answer key is recoverable from manifest even if the
* printed PDF was generated with `answers_included=false`.
*
* Assumed endpoints:
* GET /api/workbooks → WbHistory[]
* GET /api/workbooks/{id}/pdf → file download
*/
const { useState: histUseState } = React;
function HistoryPage() {
const store = useStore();
const rows = store.history;
return (
History
지금까지 만든 문제집
생성한 모든 문제집·Mock Exam·오답노트. 답지는 PDF 미포함이어도 여기서 항상 확인 가능합니다.
{rows.length === 0 ? (
location.hash = "#/workbook"}>문제집 만들러 가기} />
) : (
{rows.map(w => )}
)}
);
}
function HistoryRow({ w }) {
const [open, setOpen] = histUseState(false);
const [keyOpen, setKeyOpen] = histUseState(!w.answers_included);
const [assignOpen, setAssignOpen] = histUseState(false);
const [sEmail, setSEmail] = histUseState("");
const [sTitle, setSTitle] = histUseState(w.file_name || "과제");
const [aBusy, setABusy] = histUseState(false);
const [aMsg, setAMsg] = histUseState(null); // {ok, text}
const doAssign = async () => {
const email = sEmail.trim().toLowerCase();
if (!/\S+@\S+\.\S+/.test(email)) { setAMsg({ ok: false, text: "이메일 형식이 올바르지 않습니다." }); return; }
setABusy(true); setAMsg(null);
const r = await window.storeActions.assignWorkbook(w.id, email, sTitle.trim() || w.file_name);
setABusy(false);
if (r && r.ok) setAMsg({ ok: true, text: `${email} 에게 배정했습니다 (MCQ ${r.n_mcq} · FRQ ${r.n_frq}). 학생은 이 이메일로 가입하면 됩니다.` });
else setAMsg({ ok: false, text: (r && r.message) || "배정에 실패했습니다." });
};
return (
setOpen(!open)}>
{open ?
:
}
{w.created_at}
{w.subject || "?"}
{w.qtype || "?"}
{w.n || "?"}문제
{w.answers_included
?
답지 포함
:
답지 없음}
setAssignOpen(false)}
footer={<>
>}>
{open && (
단원: {w.units || "-"} · 파일: {w.file_name}
{keyOpen && }
)}
);
}
function HistoryAnswers({ manifest }) {
const items = (manifest && manifest.items) || [];
const mcq = items.filter(it => it.qtype === "MCQ");
const frq = items.filter(it => it.qtype === "FRQ");
if (items.length === 0) return (manifest 없음 — 이 기록은 답 재현 불가)
;
return (
{mcq.length > 0 && (
MCQ 정답
| 문항 | 정답 | 출처 |
{mcq.map(it => (
| {it.n} |
{it.answer || "?"} |
{it.pdf_id} Q{it.qnum} |
))}
)}
{frq.length > 0 && (
FRQ scoring guide
{frq.map(it => (
FRQ {it.n} — {it.pdf_id} Q{it.qnum}
))}
)}
);
}
// Lazily fetch a finished workbook's FRQ scoring-guide crops for "답 확인".
function ScoringImgs({ pdfId, qnum }) {
const [urls, setUrls] = histUseState(null);
React.useEffect(() => {
let live = true;
fetch(`/api/scoring/${encodeURIComponent(pdfId)}/${qnum}`, { credentials: "include" })
.then(r => r.ok ? r.json() : { urls: [] })
.then(d => { if (live) setUrls(d.urls || []); })
.catch(() => { if (live) setUrls([]); });
return () => { live = false; };
}, [pdfId, qnum]);
if (urls === null) return scoring 불러오는 중…
;
if (urls.length === 0) return (scoring 크롭 없음)
;
return (
{urls.map((u, i) => )}
);
}
Object.assign(window, { HistoryPage, HistoryRow, HistoryAnswers });