# phase/Teacher_view/studentlist.py import os import streamlit as st from utils import db as dbapi import utils.api as api # backend Space client # Use local DB only when DISABLE_DB != "1" USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" # ---------- tiny helpers ---------- def _avatar(name: str) -> str: return "๐งโ๐" if hash(name) % 2 else "๐ฉโ๐" def _avg_pct_from_row(r) -> int: """ Accepts either: - r['avg_pct'] in [0, 100] - r['avg_score'] in [0, 1] or [0, 100] Returns an int 0..100. """ v = r.get("avg_pct", r.get("avg_score", 0)) or 0 try: f = float(v) if f <= 1.0: # treat as 0..1 f *= 100.0 return max(0, min(100, int(round(f)))) except Exception: return 0 def _level_from_xp(total_xp: int) -> int: try: xp = int(total_xp or 0) except Exception: xp = 0 return 1 + xp // 500 def _report_text(r, level, avg_pct): return ( "STUDENT PROGRESS REPORT\n" "======================\n" f"Student: {r.get('name','')}\n" f"Email: {r.get('email','')}\n" f"Joined: {str(r.get('joined_at',''))[:10]}\n\n" "PROGRESS OVERVIEW\n" "-----------------\n" f"Lessons Completed: {int(r.get('lessons_completed') or 0)}/{int(r.get('total_assigned_lessons') or 0)}\n" f"Average Quiz Score: {avg_pct}%\n" f"Total XP: {int(r.get('total_xp') or 0)}\n" f"Current Level: {level}\n" f"Study Streak: {int(r.get('streak_days') or 0)} days\n" ) ROW_CSS = """ """ # ---------- data access (DB or Backend) ---------- @st.cache_data(show_spinner=False, ttl=30) 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) or [] try: return api.list_classes_by_teacher(teacher_id) or [] except Exception: return [] @st.cache_data(show_spinner=False, ttl=30) def _get_class(class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "get_class"): return dbapi.get_class(class_id) or {} try: return api.get_class(class_id) or {} except Exception: return {} @st.cache_data(show_spinner=False, ttl=30) def _class_student_metrics(class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "class_student_metrics"): return dbapi.class_student_metrics(class_id) or [] try: return api.class_student_metrics(class_id) or [] except Exception: return [] @st.cache_data(show_spinner=False, ttl=30) def _list_assignments_for_student(student_id: int): if USE_LOCAL_DB and hasattr(dbapi, "list_assignments_for_student"): return dbapi.list_assignments_for_student(student_id) or [] try: return api.list_assignments_for_student(student_id) or [] except Exception: return [] # ---------- page ---------- def show_page(): st.title("๐ Student Management") st.caption("Monitor and manage your students' progress") st.markdown(ROW_CSS, unsafe_allow_html=True) teacher = st.session_state.get("user") if not teacher: st.error("Please log in.") return teacher_id = teacher["user_id"] classes = _list_classes_by_teacher(teacher_id) if not classes: st.info("No classes yet. Create one in Classroom Management.") return # class selector idx = st.selectbox( "Choose a class", list(range(len(classes))), index=0, format_func=lambda i: f"{classes[i].get('name','(unnamed)')}" ) selected = classes[idx] class_id = selected.get("class_id") or selected.get("id") # be tolerant to backend naming if class_id is None: st.error("Selected class is missing an ID.") return code_row = _get_class(class_id) # get students before drawing chips rows = _class_student_metrics(class_id) # code + student chip row chip1, chip2 = st.columns([1, 1]) with chip1: st.markdown( f'