# phase/Student_view/teacherlink.py import os import streamlit as st from utils import db as dbapi import utils.api as api # <-- backend Space client USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" # DB only when DISABLE_DB=0 def load_css(file_name: str): try: with open(file_name, "r", encoding="utf-8") as f: st.markdown(f"", unsafe_allow_html=True) except FileNotFoundError: pass def _progress_0_1(v): try: f = float(v) except Exception: return 0.0 # accept 0..1 or 0..100 return max(0.0, min(1.0, f if f <= 1.0 else f / 100.0)) # --- Small wrappers to switch between DB and Backend --- def _join_class_by_code(student_id: int, code: str): if USE_LOCAL_DB and hasattr(dbapi, "join_class_by_code"): return dbapi.join_class_by_code(student_id, code) return api.join_class_by_code(student_id, code) def _list_classes_for_student(student_id: int): if USE_LOCAL_DB and hasattr(dbapi, "list_classes_for_student"): return dbapi.list_classes_for_student(student_id) try: return api.list_classes_for_student(student_id) except Exception: return [] def _class_content_counts(class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "class_content_counts"): return dbapi.class_content_counts(class_id) try: return api.class_content_counts(class_id) except Exception: return {"lessons": 0, "quizzes": 0} def _student_class_progress(student_id: int, class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "student_class_progress"): return dbapi.student_class_progress(student_id, class_id) try: return api.student_class_progress(student_id, class_id) except Exception: return { "overall_progress": 0, "lessons_completed": 0, "total_assigned_lessons": 0, "avg_score": 0, } def _leave_class(student_id: int, class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "leave_class"): return dbapi.leave_class(student_id, class_id) return api.leave_class(student_id, class_id) def _student_assignments_for_class(student_id: int, class_id: int): if USE_LOCAL_DB and hasattr(dbapi, "student_assignments_for_class"): return dbapi.student_assignments_for_class(student_id, class_id) try: return api.student_assignments_for_class(student_id, class_id) except Exception: return [] def _mark_assignment_started(student_id: int, assignment_id: int): if USE_LOCAL_DB and hasattr(dbapi, "mark_assignment_started"): return dbapi.mark_assignment_started(student_id, assignment_id) return api.mark_assignment_started(student_id, assignment_id) def _set_assignment_progress(student_id: int, assignment_id: int, current_pos: int = 1, progress: float = 0.05): # optional nudge so the bar is not 0 if USE_LOCAL_DB and hasattr(dbapi, "set_assignment_progress"): return dbapi.set_assignment_progress(student_id, assignment_id, current_pos, progress) return api.set_assignment_progress(student_id, assignment_id, current_pos, progress) # --- UI --- def show_code(): load_css(os.path.join("assets", "styles.css")) if "user" not in st.session_state or not st.session_state.user: st.error("Please log in as a student.") return if st.session_state.user["role"] != "Student": st.error("This page is for students.") return student_id = st.session_state.user["user_id"] st.markdown("## πŸ‘₯ Join a Class") st.caption("Enter class code from your teacher") raw = st.text_input( label="Class Code", placeholder="e.g. FIN5A2024", key="class_code_input", label_visibility="collapsed" ) # custom button style st.markdown( """ """, unsafe_allow_html=True, ) if st.button("Join Class", key="join_class_btn"): code = (raw or "").strip().upper() if not code: st.error("Enter a class code.") else: try: _join_class_by_code(student_id, code) st.success("πŸŽ‰ Joined the class!") st.rerun() except ValueError as e: st.error(str(e)) except Exception as e: st.error(f"Could not join class: {e}") st.markdown("---") st.markdown("## Your Classes") classes = _list_classes_for_student(student_id) if not classes: st.info("You haven’t joined any classes yet. Ask your teacher for a class code.") return # one card per class for c in classes: class_id = c["class_id"] counts = _class_content_counts(class_id) # lessons/quizzes count prog = _student_class_progress(student_id, class_id) st.markdown(f"### {c.get('name', 'Untitled Class')}") st.caption( f"Teacher: {c.get('teacher_name','β€”')} β€’ " f"Code: {c.get('code','β€”')} β€’ " f"Joined: {str(c.get('joined_at',''))[:10] if c.get('joined_at') else 'β€”'}" ) st.progress(_progress_0_1(prog.get("overall_progress", 0))) avg_pct = int(round(100 * _progress_0_1(prog.get("avg_score", 0)))) st.caption( f"{prog.get('lessons_completed', 0)}/{prog.get('total_assigned_lessons', 0)} lessons completed β€’ " f"Avg quiz: {avg_pct}%" ) # top metrics m1, m2, m3, m4 = st.columns(4) m1.metric("Lessons", counts.get("lessons", 0)) m2.metric("Quizzes", counts.get("quizzes", 0)) m3.metric("Overall", f"{int(round(100 * _progress_0_1(prog.get('overall_progress', 0))))}%") m4.metric("Avg Quiz", f"{avg_pct}%") # Leave class leave_col, _ = st.columns([1, 3]) with leave_col: if st.button("πŸšͺ Leave Class", key=f"leave_{class_id}"): try: _leave_class(student_id, class_id) st.toast("Left class.", icon="πŸ‘‹") st.rerun() except Exception as e: st.error(f"Could not leave class: {e}") # Assignments for THIS class with THIS student's progress st.markdown("#### Teacher Lessons & Quizzes") rows = _student_assignments_for_class(student_id, class_id) if not rows: st.info("No assignments yet.") else: lessons_tab, quizzes_tab = st.tabs(["πŸ“˜ Lessons", "πŸ† Quizzes"]) with lessons_tab: for r in rows: if r.get("lesson_id") is None: continue status = r.get("status") or "not_started" pos = r.get("current_pos") or 0 # if backend returns explicit progress % or 0..1, keep it sane: pct = r.get("progress") if pct is None: # fallback: estimate from position pct = 1.0 if status == "completed" else min(0.95, float(pos or 0) * 0.1) st.subheader(r.get("title", "Untitled")) due = r.get("due_at") due_txt = f"Due: {str(due)[:10]}" if due else "β€”" st.caption(f"{r.get('subject','General')} β€’ {r.get('level','Beginner')} β€’ {due_txt}") st.progress(_progress_0_1(pct)) c1, c2 = st.columns(2) with c1: if st.button("▢️ Start Lesson", key=f"start_lesson_{r.get('assignment_id')}"): lesson_id = int(r.get("lesson_id")) assignment_id = int(r.get("assignment_id")) try: _mark_assignment_started(student_id, assignment_id) _set_assignment_progress(student_id, assignment_id, current_pos=1, progress=0.05) except Exception as e: st.warning(f"Could not mark as started yet. Continuing anyway. {e}") st.session_state.selected_lesson = lesson_id st.session_state.selected_assignment = assignment_id st.session_state.lesson_route = { "source": "teacher", "lesson_id": lesson_id, "assignment_id": assignment_id, } st.session_state.current_page = "Lessons" st.rerun() with c2: st.write(f"Status: **{status}**") with quizzes_tab: any_quiz = False for r in rows: quiz_id = r.get("quiz_id") if not quiz_id: continue any_quiz = True st.subheader(r.get("title", "Untitled")) score, total = r.get("score"), r.get("total") if score is not None and total: try: pct = int(round(100 * float(score) / float(total))) st.caption(f"Last score: {pct}%") except Exception: st.caption("Last score: β€”") else: st.caption("No submission yet") # pass quiz & assignment to the Quiz page if st.button("πŸ“ Start Quiz", key=f"start_quiz_{class_id}_{quiz_id}"): st.session_state.selected_quiz = quiz_id st.session_state.current_assignment = r.get("assignment_id") st.session_state.current_page = "Quiz" st.rerun() if not any_quiz: st.info("No quizzes yet for this class.") st.markdown("---")