| |
| import json |
| import os |
| from datetime import datetime |
| import streamlit as st |
| from utils import db as dbapi |
| import utils.api as api |
|
|
| |
| USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" |
|
|
| |
| def _pill(text): |
| return f"<span style='background:#eef6ff;border:1px solid #cfe3ff;border-radius:999px;padding:2px 8px;font-size:12px;margin-right:6px'>{text}</span>" |
|
|
| def _progress(val: float): |
| pct = max(0, min(100, int(round(val * 100)))) |
| return f""" |
| <div style="height:8px;background:#eef2ff;border-radius:999px;overflow:hidden"> |
| <div style="width:{pct}%;height:100%;background:#3b82f6"></div> |
| </div> |
| """ |
|
|
| def _fmt_date(v): |
| if isinstance(v, datetime): |
| return v.strftime("%Y-%m-%d") |
| try: |
| s = str(v) |
| return s[:10] |
| except Exception: |
| return "" |
|
|
| |
| def _generate_quiz_from_text(content: str, n_questions: int = 5, subject: str = "finance", level: str = "beginner"): |
| """ |
| Calls your backend, which uses GEN_MODEL (llama-3.1-8b-instruct). |
| Returns a normalized list like: |
| [{"question":"...","options":["A","B","C","D"],"answer_key":"B","points":1}, ...] |
| """ |
| def _normalize(items): |
| out = [] |
| for it in (items or [])[:n_questions]: |
| q = str(it.get("question", "")).strip() |
| opts = it.get("options", []) |
| if not q or not isinstance(opts, list): |
| continue |
| while len(opts) < 4: |
| opts.append("Option") |
| opts = opts[:4] |
| key = str(it.get("answer_key", "A")).strip().upper()[:1] |
| if key not in ("A","B","C","D"): |
| key = "A" |
| out.append({"question": q, "options": opts, "answer_key": key, "points": 1}) |
| return out |
|
|
| try: |
| resp = api.generate_quiz_from_text(content, n_questions=n_questions, subject=subject, level=level) |
| items = resp.get("items", resp) |
| return _normalize(items) |
| except Exception as e: |
| with st.expander("Quiz generation error details"): |
| st.code(str(e)) |
| st.warning("Quiz generation failed via backend. Check the /quiz/generate endpoint and GEN_MODEL.") |
| return [] |
|
|
| |
| def _list_classes_by_teacher(teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_classes_by_teacher"): |
| return dbapi.list_classes_by_teacher(teacher_id) |
| try: |
| return api.list_classes_by_teacher(teacher_id) |
| except Exception: |
| return [] |
|
|
| def _list_all_students_for_teacher(teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_all_students_for_teacher"): |
| return dbapi.list_all_students_for_teacher(teacher_id) |
| try: |
| return api.list_all_students_for_teacher(teacher_id) |
| except Exception: |
| return [] |
|
|
| def _list_lessons_by_teacher(teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_lessons_by_teacher"): |
| return dbapi.list_lessons_by_teacher(teacher_id) |
| try: |
| return api.list_lessons_by_teacher(teacher_id) |
| except Exception: |
| return [] |
|
|
| def _list_quizzes_by_teacher(teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_quizzes_by_teacher"): |
| return dbapi.list_quizzes_by_teacher(teacher_id) |
| try: |
| return api.list_quizzes_by_teacher(teacher_id) |
| except Exception: |
| return [] |
|
|
| def _create_lesson(teacher_id: int, title: str, description: str, subject: str, level: str, sections: list[dict]): |
| if USE_LOCAL_DB and hasattr(dbapi, "create_lesson"): |
| return dbapi.create_lesson(teacher_id, title, description, subject, level, sections) |
| return api.create_lesson(teacher_id, title, description, subject, level, sections) |
|
|
| def _update_lesson(lesson_id: int, teacher_id: int, title: str, description: str, subject: str, level: str, sections: list[dict]): |
| if USE_LOCAL_DB and hasattr(dbapi, "update_lesson"): |
| return dbapi.update_lesson(lesson_id, teacher_id, title, description, subject, level, sections) |
| return api.update_lesson(lesson_id, teacher_id, title, description, subject, level, sections) |
|
|
| def _delete_lesson(lesson_id: int, teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "delete_lesson"): |
| return dbapi.delete_lesson(lesson_id, teacher_id) |
| return api.delete_lesson(lesson_id, teacher_id) |
|
|
| def _get_lesson(lesson_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "get_lesson"): |
| return dbapi.get_lesson(lesson_id) |
| return api.get_lesson(lesson_id) |
|
|
| def _create_quiz(lesson_id: int, title: str, items: list[dict], settings: dict): |
| if USE_LOCAL_DB and hasattr(dbapi, "create_quiz"): |
| return dbapi.create_quiz(lesson_id, title, items, settings) |
| return api.create_quiz(lesson_id, title, items, settings) |
|
|
| def _update_quiz(quiz_id: int, teacher_id: int, title: str, items: list[dict], settings: dict): |
| if USE_LOCAL_DB and hasattr(dbapi, "update_quiz"): |
| return dbapi.update_quiz(quiz_id, teacher_id, title, items, settings) |
| return api.update_quiz(quiz_id, teacher_id, title, items, settings) |
|
|
| def _delete_quiz(quiz_id: int, teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "delete_quiz"): |
| return dbapi.delete_quiz(quiz_id, teacher_id) |
| return api.delete_quiz(quiz_id, teacher_id) |
|
|
| def _list_assigned_students_for_lesson(lesson_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_assigned_students_for_lesson"): |
| return dbapi.list_assigned_students_for_lesson(lesson_id) |
| return api.list_assigned_students_for_lesson(lesson_id) |
|
|
| def _list_assigned_students_for_quiz(quiz_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "list_assigned_students_for_quiz"): |
| return dbapi.list_assigned_students_for_quiz(quiz_id) |
| return api.list_assigned_students_for_quiz(quiz_id) |
|
|
| def _assign_to_class(lesson_id: int | None, quiz_id: int | None, class_id: int, teacher_id: int): |
| if USE_LOCAL_DB and hasattr(dbapi, "assign_to_class"): |
| return dbapi.assign_to_class(lesson_id, quiz_id, class_id, teacher_id) |
| return api.assign_to_class(lesson_id, quiz_id, class_id, teacher_id) |
|
|
| |
| def _create_lesson_panel(teacher_id: int): |
| st.markdown("### ✍️ Create New Lesson") |
|
|
| classes = _list_classes_by_teacher(teacher_id) |
| class_opts = {f"{c['name']} (code {c['code']})": c["class_id"] for c in classes} if classes else {} |
|
|
| if "cl_topic_count" not in st.session_state: |
| st.session_state.cl_topic_count = 2 |
|
|
| cols_btn = st.columns([1,1,6]) |
| with cols_btn[0]: |
| if st.button("➕ Add topic", type="secondary"): |
| st.session_state.cl_topic_count = min(20, st.session_state.cl_topic_count + 1) |
| st.rerun() |
| with cols_btn[1]: |
| if st.button("➖ Remove last", type="secondary", disabled=st.session_state.cl_topic_count <= 1): |
| st.session_state.cl_topic_count = max(1, st.session_state.cl_topic_count - 1) |
| st.rerun() |
|
|
| with st.form("create_lesson_form", clear_on_submit=False): |
| c1, c2 = st.columns([2,1]) |
| title = c1.text_input("Title", placeholder="e.g., Jamaican Money Recognition") |
| level = c2.selectbox("Level", ["beginner","intermediate","advanced"], index=0) |
| description = st.text_area("Short description") |
| subject = st.selectbox("Subject", ["numeracy","finance"], index=0) |
|
|
| st.markdown("#### Topics") |
| topic_rows = [] |
| for i in range(1, st.session_state.cl_topic_count + 1): |
| with st.expander(f"Topic {i}", expanded=True if i <= 2 else False): |
| t = st.text_input(f"Topic {i} title", key=f"t_title_{i}") |
| b = st.text_area(f"Topic {i} content", key=f"t_body_{i}", height=150) |
| topic_rows.append((t, b)) |
|
|
| add_summary = st.checkbox("Append a Summary section at the end", value=True) |
| summary_text = "" |
| if add_summary: |
| summary_text = st.text_area( |
| "Summary notes", |
| key="summary_notes", |
| height=120, |
| placeholder="Key ideas, local examples, common mistakes, quick recap..." |
| ) |
|
|
| st.markdown("#### Assign to class (optional)") |
| assign_classes = st.multiselect("Choose one or more classes", list(class_opts.keys())) |
|
|
| st.markdown("#### Auto-generate a quiz from this lesson (optional)") |
| gen_quiz = st.checkbox("Generate a quiz from content", value=False) |
| q_count = st.slider("", 3, 10, 5) |
|
|
| submitted = st.form_submit_button("Create lesson", type="primary") |
|
|
| if not submitted: |
| return |
|
|
| sections = [] |
| for t, b in topic_rows: |
| if (t or b): |
| sections.append({"title": t or "Topic", "content": b or ""}) |
|
|
| if add_summary: |
| sections.append({ |
| "title": "Summary", |
| "content": (summary_text or "Write a short recap of the most important ideas.").strip() |
| }) |
|
|
| if not title or not sections: |
| st.error("Please add a title and at least one topic.") |
| return |
|
|
| |
| try: |
| lesson_id = _create_lesson(teacher_id, title, description, subject, level, sections) |
| st.success(f"✅ Lesson created (ID {lesson_id}).") |
| except Exception as e: |
| st.error(f"Failed to create lesson: {e}") |
| return |
|
|
| |
| for label in assign_classes: |
| try: |
| _assign_to_class(lesson_id, None, class_opts[label], teacher_id) |
| except Exception as e: |
| st.warning(f"Could not assign to {label}: {e}") |
|
|
| |
| if gen_quiz: |
| text = "\n\n".join([s["title"] + "\n" + (s["content"] or "") for s in sections]) |
| with st.spinner("Generating quiz from lesson content..."): |
| items = _generate_quiz_from_text(text, n_questions=q_count, subject=subject, level=level) |
| if items: |
| try: |
| qid = _create_quiz(lesson_id, f"{title} - Quiz", items, {}) |
| st.success(f"🧠 Quiz generated and saved (ID {qid}).") |
| for label in assign_classes: |
| _assign_to_class(lesson_id, qid, class_opts[label], teacher_id) |
| except Exception as e: |
| st.warning(f"Lesson saved, but failed to save quiz: {e}") |
|
|
| st.session_state.show_create_lesson = False |
| st.rerun() |
|
|
| def _create_quiz_panel(teacher_id: int): |
| st.markdown("### 🏆 Create New Quiz") |
|
|
| lessons = _list_lessons_by_teacher(teacher_id) |
| lesson_map = {f"{L['title']} (#{L['lesson_id']})": L["lesson_id"] for L in lessons} |
| if not lesson_map: |
| st.info("Create a lesson first, then link a quiz to it.") |
| return |
|
|
| if "cq_q_count" not in st.session_state: |
| st.session_state.cq_q_count = 5 |
|
|
| with st.form("create_quiz_form", clear_on_submit=False): |
| c1, c2 = st.columns([2,1]) |
| title = c1.text_input("Title", placeholder="e.g., Currency Basics Quiz") |
| lesson_label = c2.selectbox("Linked Lesson", list(lesson_map.keys())) |
|
|
| st.markdown("#### Questions (up to 10)") |
| items = [] |
| for i in range(1, st.session_state.cq_q_count + 1): |
| with st.expander(f"Question {i}", expanded=(i <= 2)): |
| q = st.text_area(f"Prompt {i}", key=f"q_{i}") |
| cA, cB = st.columns(2) |
| a = cA.text_input(f"Option A (correct?)", key=f"optA_{i}") |
| b = cB.text_input(f"Option B", key=f"optB_{i}") |
| cC, cD = st.columns(2) |
| c = cC.text_input(f"Option C", key=f"optC_{i}") |
| d = cD.text_input(f"Option D", key=f"optD_{i}") |
| correct = st.radio("Correct answer", ["A","B","C","D"], index=0, key=f"ans_{i}", horizontal=True) |
| items.append({"question": q, "options": [a,b,c,d], "answer_key": correct, "points": 1}) |
|
|
| row = st.columns([1,1,4,2]) |
| with row[0]: |
| if st.form_submit_button("➕ Add question", type="secondary", disabled=st.session_state.cq_q_count >= 10): |
| st.session_state.cq_q_count = min(10, st.session_state.cq_q_count + 1) |
| st.rerun() |
| with row[1]: |
| if st.form_submit_button("➖ Remove last", type="secondary", disabled=st.session_state.cq_q_count <= 1): |
| st.session_state.cq_q_count = max(1, st.session_state.cq_q_count - 1) |
| st.rerun() |
|
|
| submitted = row[3].form_submit_button("Create quiz", type="primary") |
|
|
| if not submitted: |
| return |
| if not title: |
| st.error("Please add a quiz title.") |
| return |
|
|
| cleaned = [] |
| for it in items: |
| q = (it["question"] or "").strip() |
| opts = [o for o in it["options"] if (o or "").strip()] |
| if len(opts) < 2 or not q: |
| continue |
| while len(opts) < 4: |
| opts.append("Option") |
| cleaned.append({"question": q, "options": opts[:4], "answer_key": it["answer_key"], "points": 1}) |
|
|
| if not cleaned: |
| st.error("Add at least one valid question.") |
| return |
|
|
| try: |
| qid = _create_quiz(lesson_map[lesson_label], title, cleaned, {}) |
| st.success(f"✅ Quiz created (ID {qid}).") |
| st.session_state.show_create_quiz = False |
| st.rerun() |
| except Exception as e: |
| st.error(f"Failed to create quiz: {e}") |
|
|
| def _edit_lesson_panel(teacher_id: int, lesson_id: int): |
| try: |
| data = _get_lesson(lesson_id) |
| except Exception as e: |
| st.error(f"Could not load lesson #{lesson_id}: {e}") |
| return |
|
|
| L = data.get("lesson", {}) |
| secs = data.get("sections", []) or [] |
|
|
| key_cnt = f"el_cnt_{lesson_id}" |
| if key_cnt not in st.session_state: |
| st.session_state[key_cnt] = max(1, len(secs)) |
|
|
| st.markdown("### ✏️ Edit Lesson") |
|
|
| tools = st.columns([1,1,8]) |
| with tools[0]: |
| if st.button("➕ Add section", key=f"el_add_{lesson_id}", use_container_width=True): |
| st.session_state[key_cnt] = min(50, st.session_state[key_cnt] + 1) |
| st.rerun() |
| with tools[1]: |
| if st.button("➖ Remove last", key=f"el_rem_{lesson_id}", |
| disabled=st.session_state[key_cnt] <= 1, use_container_width=True): |
| st.session_state[key_cnt] = max(1, st.session_state[key_cnt] - 1) |
| st.rerun() |
|
|
| with st.form(f"edit_lesson_form_{lesson_id}", clear_on_submit=False): |
| c1, c2 = st.columns([2,1]) |
| title = c1.text_input("Title", value=L.get("title") or "") |
| level = c2.selectbox( |
| "Level", |
| ["beginner","intermediate","advanced"], |
| index=["beginner","intermediate","advanced"].index(L.get("level") or "beginner") |
| ) |
| description = st.text_area("Short description", value=L.get("description") or "") |
| subject = st.selectbox("Subject", ["numeracy","finance"], index=(0 if (L.get("subject")=="numeracy") else 1)) |
|
|
| st.markdown("#### Sections") |
| edited_sections = [] |
| total = st.session_state[key_cnt] |
| for i in range(1, total + 1): |
| s = secs[i-1] if i-1 < len(secs) else {"title":"", "content":""} |
| with st.expander(f"Section {i}", expanded=(i <= 2)): |
| t = st.text_input(f"Title {i}", value=s.get("title") or "", key=f"el_t_{lesson_id}_{i}") |
| b = st.text_area(f"Content {i}", value=s.get("content") or "", height=150, key=f"el_b_{lesson_id}_{i}") |
| edited_sections.append({"title": t or "Section", "content": b or ""}) |
|
|
| save = st.form_submit_button("💾 Save changes", type="primary", use_container_width=True) |
|
|
| actions = st.columns([8,2]) |
| with actions[1]: |
| cancel_clicked = st.button("✖ Cancel", key=f"el_cancel_{lesson_id}", type="secondary", use_container_width=True) |
|
|
| if cancel_clicked: |
| st.session_state.show_edit_lesson = False |
| st.session_state.edit_lesson_id = None |
| st.rerun() |
|
|
| if not save: |
| return |
|
|
| if not title or not any((s["title"] or s["content"]).strip() for s in edited_sections): |
| st.error("Title and at least one non-empty section are required.") |
| return |
|
|
| ok = False |
| try: |
| ok = _update_lesson(lesson_id, teacher_id, title, description, subject, level, edited_sections) |
| except Exception as e: |
| st.error(f"Update failed: {e}") |
|
|
| if ok: |
| st.success("✅ Lesson updated.") |
| st.session_state.show_edit_lesson = False |
| st.session_state.edit_lesson_id = None |
| st.rerun() |
| else: |
| st.error("Could not update this lesson. Check ownership or backend errors.") |
|
|
| def _edit_quiz_panel(teacher_id: int, quiz_id: int): |
| |
| try: |
| data = (dbapi.get_quiz(quiz_id) if (USE_LOCAL_DB and hasattr(dbapi, "get_quiz")) else api._req("GET", f"/quizzes/{quiz_id}").json()) |
| except Exception as e: |
| st.error(f"Quiz not found: {e}") |
| return |
|
|
| Q = data.get("quiz") |
| raw_items = data.get("items", []) |
| if not Q: |
| st.error("Quiz not found.") |
| return |
|
|
| def _dec(x): |
| if isinstance(x, str): |
| try: |
| return json.loads(x) |
| except Exception: |
| return x |
| return x |
|
|
| items = [] |
| for it in raw_items: |
| opts = _dec(it.get("options")) or [] |
| while len(opts) < 4: |
| opts.append("Option") |
| opts = opts[:4] |
|
|
| ans = _dec(it.get("answer_key")) |
| if isinstance(ans, list) and ans: |
| ans = ans[0] |
| ans = (str(ans) or "A").upper()[:1] |
| if ans not in ("A","B","C","D"): |
| ans = "A" |
|
|
| items.append({ |
| "question": (it.get("question") or "").strip(), |
| "options": opts, |
| "answer_key": ans, |
| "points": int(it.get("points") or 1), |
| }) |
|
|
| key_cnt = f"eq_cnt_{quiz_id}" |
| if key_cnt not in st.session_state: |
| st.session_state[key_cnt] = max(1, len(items) or 5) |
|
|
| st.markdown("### ✏️ Edit Quiz") |
|
|
| with st.form(f"edit_quiz_form_{quiz_id}", clear_on_submit=False): |
| title = st.text_input("Title", value=Q.get("title") or f"Quiz #{quiz_id}") |
|
|
| edited = [] |
| total = st.session_state[key_cnt] |
| for i in range(1, total + 1): |
| it = items[i-1] if i-1 < len(items) else {"question":"", "options":["","","",""], "answer_key":"A", "points":1} |
| with st.expander(f"Question {i}", expanded=(i <= 2)): |
| q = st.text_area(f"Prompt {i}", value=it["question"], key=f"eq_q_{quiz_id}_{i}") |
| cA, cB = st.columns(2) |
| a = cA.text_input(f"Option A", value=it["options"][0], key=f"eq_A_{quiz_id}_{i}") |
| b = cB.text_input(f"Option B", value=it["options"][1], key=f"eq_B_{quiz_id}_{i}") |
| cC, cD = st.columns(2) |
| c = cC.text_input(f"Option C", value=it["options"][2], key=f"eq_C_{quiz_id}_{i}") |
| d = cD.text_input(f"Option D", value=it["options"][3], key=f"eq_D_{quiz_id}_{i}") |
| correct = st.radio("Correct answer", ["A","B","C","D"], |
| index=["A","B","C","D"].index(it["answer_key"]), |
| key=f"eq_ans_{quiz_id}_{i}", horizontal=True) |
| edited.append({"question": q, "options": [a,b,c,d], "answer_key": correct, "points": 1}) |
|
|
| row = st.columns([1,1,6,2,2]) |
| with row[0]: |
| if st.form_submit_button("➕ Add question", type="secondary"): |
| st.session_state[key_cnt] = min(20, st.session_state[key_cnt] + 1) |
| st.rerun() |
| with row[1]: |
| if st.form_submit_button("➖ Remove last", type="secondary", disabled=st.session_state[key_cnt] <= 1): |
| st.session_state[key_cnt] = max(1, st.session_state[key_cnt] - 1) |
| st.rerun() |
|
|
| save = row[3].form_submit_button("💾 Save", type="primary") |
| cancel = row[4].form_submit_button("✖ Cancel", type="secondary") |
|
|
| if cancel: |
| st.session_state.show_edit_quiz = False |
| st.session_state.edit_quiz_id = None |
| st.rerun() |
|
|
| if not save: |
| return |
|
|
| cleaned = [] |
| for it in edited: |
| q = (it["question"] or "").strip() |
| opts = [o for o in it["options"] if (o or "").strip()] |
| if not q or len(opts) < 2: |
| continue |
| while len(opts) < 4: |
| opts.append("Option") |
| cleaned.append({ |
| "question": q, |
| "options": opts[:4], |
| "answer_key": it["answer_key"], |
| "points": 1 |
| }) |
|
|
| if not title or not cleaned: |
| st.error("Title and at least one valid question are required.") |
| return |
|
|
| ok = False |
| try: |
| ok = _update_quiz(quiz_id, teacher_id, title, cleaned, settings={}) |
| except Exception as e: |
| st.error(f"Save failed: {e}") |
|
|
| if ok: |
| st.success("✅ Quiz updated.") |
| st.session_state.show_edit_quiz = False |
| st.session_state.edit_quiz_id = None |
| st.rerun() |
| else: |
| st.error("Could not update this quiz. Check ownership or backend errors.") |
|
|
| |
| def show_page(): |
| user = st.session_state.user |
| teacher_id = user["user_id"] |
|
|
| st.title("📚 Content Management") |
| st.caption("Create and manage custom lessons and quizzes") |
|
|
| |
| lessons = _list_lessons_by_teacher(teacher_id) |
| quizzes = _list_quizzes_by_teacher(teacher_id) |
|
|
| |
| a1, a2, _sp = st.columns([3,3,4]) |
| if a1.button("➕ Create Lesson", use_container_width=True): |
| st.session_state.show_create_lesson = True |
| if a2.button("🏆 Create Quiz", use_container_width=True): |
| st.session_state.show_create_quiz = True |
|
|
| |
| if st.session_state.get("show_create_lesson"): |
| with st.container(border=True): |
| _create_lesson_panel(teacher_id) |
| st.markdown("---") |
|
|
| if st.session_state.get("show_create_quiz"): |
| with st.container(border=True): |
| _create_quiz_panel(teacher_id) |
| st.markdown("---") |
|
|
| |
| if st.session_state.get("show_edit_lesson") and st.session_state.get("edit_lesson_id"): |
| with st.container(border=True): |
| _edit_lesson_panel(teacher_id, st.session_state.edit_lesson_id) |
| st.markdown("---") |
|
|
| if st.session_state.get("show_edit_quiz") and st.session_state.get("edit_quiz_id"): |
| with st.container(border=True): |
| _edit_quiz_panel(teacher_id, st.session_state.edit_quiz_id) |
| st.markdown("---") |
|
|
| |
| tab1, tab2 = st.tabs([f"Custom Lessons ({len(lessons)})", f"Custom Quizzes ({len(quizzes)})"]) |
|
|
| |
| with tab1: |
| if not lessons: |
| st.info("No lessons yet. Use **Create Lesson** above.") |
| else: |
| all_students = _list_all_students_for_teacher(teacher_id) |
| student_options = {f"{s['name']} · {s['email']}": s["user_id"] for s in all_students} |
|
|
| for L in lessons: |
| assignees = _list_assigned_students_for_lesson(L["lesson_id"]) |
| assignee_names = [a.get("name") for a in assignees] |
| created = _fmt_date(L.get("created_at")) |
| count = len(assignees) |
|
|
| with st.container(border=True): |
| c1, c2 = st.columns([8,3]) |
| with c1: |
| st.markdown(f"### {L['title']}") |
| st.caption(L.get("description") or "") |
| st.markdown( |
| _pill((L.get("level") or "beginner").capitalize()) + |
| _pill(L.get("subject","finance")) + |
| _pill(f"{count} student{'s' if count != 1 else ''} assigned") + |
| _pill(f"Created {created}"), |
| unsafe_allow_html=True |
| ) |
| with c2: |
| b1, b2 = st.columns([1,1]) |
| with b1: |
| if st.button("Edit", key=f"edit_{L['lesson_id']}"): |
| st.session_state.edit_lesson_id = L["lesson_id"] |
| st.session_state.show_edit_lesson = True |
| st.rerun() |
| with b2: |
| if st.button("Delete", key=f"del_{L['lesson_id']}"): |
| ok, msg = _delete_lesson(L["lesson_id"], teacher_id) |
| if ok: st.success("Lesson deleted"); st.rerun() |
| else: st.error(msg or "Delete failed") |
|
|
| st.markdown("**Assigned Students:**") |
| if assignee_names: |
| st.markdown(" ".join(_pill(n) for n in assignee_names if n), unsafe_allow_html=True) |
| else: |
| st.caption("No students assigned yet.") |
|
|
| |
| with tab2: |
| if not quizzes: |
| st.info("No quizzes yet. Use **Create Quiz** above.") |
| else: |
| for Q in quizzes: |
| assignees = _list_assigned_students_for_quiz(Q["quiz_id"]) |
| created = _fmt_date(Q.get("created_at")) |
| num_qs = int(Q.get("num_items", 0)) |
|
|
| with st.container(border=True): |
| c1, c2 = st.columns([8,3]) |
| with c1: |
| st.markdown(f"### {Q['title']}") |
| st.caption(f"Lesson: {Q.get('lesson_title','')}") |
| st.markdown( |
| _pill(f"{num_qs} question{'s' if num_qs != 1 else ''}") + |
| _pill(f"{len(assignees)} students assigned") + |
| _pill(f"Created {created}"), |
| unsafe_allow_html=True |
| ) |
| with c2: |
| b1, b2 = st.columns(2) |
| with b1: |
| if st.button("Edit", key=f"editq_{Q['quiz_id']}"): |
| st.session_state.edit_quiz_id = Q["quiz_id"] |
| st.session_state.show_edit_quiz = True |
| st.rerun() |
| with b2: |
| if st.button("Delete", key=f"delq_{Q['quiz_id']}"): |
| ok, msg = _delete_quiz(Q["quiz_id"], teacher_id) |
| if ok: st.success("Quiz deleted"); st.rerun() |
| else: st.error(msg or "Delete failed") |
|
|
| st.markdown("**Assigned Students:**") |
| if assignees: |
| st.markdown(" ".join(_pill(a.get('name')) for a in assignees if a.get('name')), unsafe_allow_html=True) |
| else: |
| st.caption("No students assigned yet.") |
|
|
| with st.expander("View questions", expanded=False): |
| |
| try: |
| data = (dbapi.get_quiz(Q["quiz_id"]) if (USE_LOCAL_DB and hasattr(dbapi, "get_quiz")) |
| else api._req("GET", f"/quizzes/{Q['quiz_id']}").json()) |
| except Exception as e: |
| st.info(f"Could not fetch items: {e}") |
| data = None |
| items = data.get("items", []) if data else [] |
| if not items: |
| st.info("No items found for this quiz.") |
| else: |
| labels = ["A","B","C","D"] |
| for i, it in enumerate(items, start=1): |
| opts = it.get("options") |
| if isinstance(opts, str): |
| try: |
| opts = json.loads(opts) |
| except Exception: |
| opts = [opts] |
| answer = it.get("answer_key") |
| if isinstance(answer, str): |
| try: |
| answer = json.loads(answer) |
| except Exception: |
| pass |
|
|
| st.markdown(f"**Q{i}.** {it.get('question','').strip()}") |
| for j, opt in enumerate((opts or [])[:4]): |
| st.write(f"{labels[j]}) {opt}") |
| ans_text = answer if isinstance(answer, str) else ",".join(answer or []) |
| st.caption(f"Answer: {ans_text}") |
| st.markdown("---") |
|
|