FInFront / phase /Teacher_view /classmanage.py
lanna_lalala;-
added folders
0aa6283
# 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"""
<div class="metric-card">
<div class="metric-value">{value}</div>
<div class="metric-label">{label}</div>
<div class="metric-caption">{caption}</div>
</div>
""",
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")