# phase/Teacher_view/classmanage.py import os import streamlit as st from utils import db as dbapi import utils.api as api # backend Space client # When DISABLE_DB=1 (default), skip direct MySQL and use backend APIs USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" def _metric_card(label: str, value: str, caption: str = ""): st.markdown( f"""
{value}
{label}
{caption}
""", unsafe_allow_html=True, ) def _prefer_db(db_name: str, api_func, default, *args, **kwargs): """ Try local DB function if enabled & present; else call backend API; else return default. """ if USE_LOCAL_DB and hasattr(dbapi, db_name): try: return getattr(dbapi, db_name)(*args, **kwargs) except Exception as e: st.warning(f"DB call {db_name} failed; falling back to backend. ({e})") try: return api_func(*args, **kwargs) except Exception as e: st.error(f"Backend call failed: {e}") return default def show_page(): user = st.session_state.user teacher_id = user["user_id"] st.title("๐Ÿ“š Classroom Management") st.caption("Manage all your classrooms and students") # -------- Create Classroom -------- with st.expander("โž• Create Classroom", expanded=False): new_name = st.text_input("Classroom Name", key="new_classroom_name") if st.button("Create Classroom"): name = new_name.strip() if not name: st.error("Enter a real name, not whitespace.") else: out = _prefer_db( "create_class", lambda tid, n: api.create_class(tid, n), None, teacher_id, # positional arg name, # positional arg ) if out: st.session_state.selected_class_id = out.get("class_id") or out.get("id") st.success(f'Classroom "{name}" created with code: {out.get("code","โ€”")}') st.rerun() else: st.error("Could not create classroom (no response).") # -------- Load classes for this teacher -------- classes = _prefer_db( "list_classes_by_teacher", lambda tid: api.list_classes_by_teacher(tid), [], teacher_id, # positional ) if not classes: st.info("No classrooms yet. Create one above, then share the code.") return # Picker st.subheader("Your Classrooms") options = {f"{c.get('name','(unnamed)')} (Code: {c.get('code','')})": c for c in classes} selected_label = st.selectbox("Select a classroom", list(options.keys())) selected = options[selected_label] class_id = selected.get("class_id") or selected.get("id") st.markdown("---") st.header(selected.get("name", "Classroom")) # -------- Code stripe -------- st.subheader("Class Code") c1, c2, c3 = st.columns([3, 1, 1]) with c1: st.markdown(f"**`{selected.get('code', 'UNKNOWN')}`**") with c2: if st.button("๐Ÿ“‹ Copy Code"): st.toast("Code is shown above. Copy it.") with c3: st.button("๐Ÿ—‘๏ธ Delete Class", disabled=True, help="Soft-delete coming later") # -------- Tabs -------- tab_students, tab_content, tab_analytics = st.tabs(["๐Ÿ‘ฅ Students", "๐Ÿ“˜ Content", "๐Ÿ“Š Analytics"]) # ============== Students tab ============== with tab_students: q = st.text_input("Search students by name or email", "") roster = _prefer_db( "list_students_in_class", lambda cid: api.list_students_in_class(cid), [], class_id, # positional ) # simple filter if q.strip(): ql = q.lower() roster = [r for r in roster if ql in (r.get("name","").lower()) or ql in (r.get("email","").lower())] st.caption(f"{len(roster)} Students Found") if not roster: st.info("No students in this class yet.") else: for s in roster: st.subheader(f"๐Ÿ‘ค {s.get('name','(unknown)')}") st.caption(s.get("email","โ€”")) joined = s.get("joined_at") or s.get("created_at") st.caption(f"๐Ÿ“… Joined: {str(joined)[:10] if joined else 'โ€”'}") st.progress(0.0) # placeholder bar cols = st.columns(3) level_slug = (s.get("level_slug") or s.get("level") or "beginner") try: level_label = level_slug.capitalize() if isinstance(level_slug, str) else str(level_slug) except Exception: level_label = "โ€”" cols[0].metric("โญ Level", level_label) cols[1].metric("๐Ÿ“Š Avg Score", "โ€”") cols[2].metric("๐Ÿ”ฅ Streak", "โ€”") st.markdown("---") # ============== Content tab ============== with tab_content: counts = _prefer_db( "class_content_counts", lambda cid: api.class_content_counts(cid), {"lessons": 0, "quizzes": 0}, class_id, # positional ) left, right = st.columns(2) with left: _metric_card("๐Ÿ“– Custom Lessons", str(counts.get("lessons", 0)), "Lessons created for this classroom") with right: _metric_card("๐Ÿ† Custom Quizzes", str(counts.get("quizzes", 0)), "Quizzes created for this classroom") assigs = _prefer_db( "list_class_assignments", lambda cid: api.list_class_assignments(cid), [], class_id, # positional ) if assigs: st.markdown("#### Assigned items") for a in assigs: has_quiz = " + Quiz" if a.get("quiz_id") else "" st.markdown(f"- **{a.get('title','Untitled')}** ยท {a.get('subject','โ€”')} ยท {a.get('level','โ€”')}{has_quiz}") # ============== Analytics tab ============== with tab_analytics: stats = _prefer_db( "class_analytics", lambda cid: api.class_analytics(cid), {"class_avg": 0.0, "total_xp": 0, "lessons_completed": 0}, class_id, # positional ) class_avg_pct = round(float(stats.get("class_avg", 0)) * 100) if stats.get("class_avg") is not None else 0 total_xp = stats.get("total_xp", 0) lessons_completed = stats.get("lessons_completed", 0) g1, g2, g3 = st.columns(3) with g1: _metric_card("๐Ÿ“Š Class Average", f"{class_avg_pct}%", "Average quiz performance") with g2: _metric_card("๐Ÿช™ Total XP", f"{total_xp}", "Combined XP earned") with g3: _metric_card("๐Ÿ“˜ Lessons Completed", f"{lessons_completed}", "Total lessons completed")