diff --git a/routers/database/tools/graph_tree_router.py b/routers/database/tools/graph_tree_router.py index 979f87f..19a8b5f 100644 --- a/routers/database/tools/graph_tree_router.py +++ b/routers/database/tools/graph_tree_router.py @@ -166,7 +166,9 @@ def _query_month_days(month_uuid: str) -> List[Dict]: return [] -def _query_teacher_classes(user_id: str, institute_id: str, institute_db: str) -> List[Dict]: +def _query_teacher_classes( + user_id: str, institute_id: str, institute_db: str, section_id: str = "" +) -> List[Dict]: """Query classes for a teacher or student from Supabase (source of truth).""" try: sb = _sb() @@ -192,7 +194,7 @@ def _query_teacher_classes(user_id: str, institute_id: str, institute_db: str) - return [] classes = ( sb.supabase.table("classes") - .select("id, name, code, subject") + .select("id, name, class_code, subject") .in_("id", all_ids) .eq("institute_id", institute_id) .eq("is_active", True) @@ -203,15 +205,24 @@ def _query_teacher_classes(user_id: str, institute_id: str, institute_db: str) - result = [] for c in classes: role = "teacher" if c["id"] in teacher_class_ids else "student" - result.append({ + label = c.get("class_code") or c.get("name") or "Class" + node: Dict = { "neo4j_node_id": c["id"], - "label": c.get("name") or "Class", + "label": label, "node_type": "SubjectClass", "neo4j_db_name": institute_db, "is_section": False, "has_children": True, - "neo4j_props": {"role": role, "subject": c.get("subject") or ""}, - }) + "neo4j_props": { + "role": role, + "subject": c.get("subject") or "", + "name": c.get("name") or "", + "class_code": c.get("class_code") or "", + }, + } + if section_id: + node["section_id"] = section_id + result.append(node) return result except Exception as e: logger.warning(f"Could not query classes for user {user_id}: {e}") @@ -278,7 +289,7 @@ def _build_timetable_section(user_id: str, institute_id: Optional[str], institut tt = rec["tt"] tt_uuid = tt["uuid_string"] # Load classes from Supabase (source of truth) - classes = _query_teacher_classes(user_id, institute_id, institute_db) + classes = _query_teacher_classes(user_id, institute_id, institute_db, section_id="timetable") return { **_section("timetable", "My Timetable", institute_db, "populated", has_children=True, children=classes if classes else None), @@ -296,7 +307,7 @@ def _build_classes_section(user_id: str, institute_id: Optional[str], institute_ if not institute_db or not institute_id: return _section("classes", "My Classes", "", "no_school") - classes = _query_teacher_classes(user_id, institute_id, institute_db) + classes = _query_teacher_classes(user_id, institute_id, institute_db, section_id="classes") if classes: return _section("classes", "My Classes", institute_db, "populated", has_children=True, children=classes) @@ -451,6 +462,84 @@ def _get_children_for_node( logger.warning(f"TeacherTimetable term children failed: {e}") return [] + # SubjectClass — expand to taught lessons (timetable context) or members (classes context) + if node_type == "SubjectClass": + class_id = neo4j_node_id # Supabase class UUID + try: + sb = _sb() + if section_id == "timetable" and user_email: + # Resolve teacher profile_id from email + prof = sb.supabase.table("profiles").select("id").eq("email", user_email).single().execute() + teacher_id = (prof.data or {}).get("id") + if teacher_id: + lessons = ( + sb.supabase.table("taught_lessons") + .select("id, date, period_code, class_name, subject") + .eq("class_id", class_id) + .eq("teacher_profile_id", teacher_id) + .order("date") + .order("period_code") + .execute() + .data or [] + ) + return [ + { + "neo4j_node_id": tl["id"], + "label": "{} {}".format( + tl.get("period_code") or "", + tl.get("date") or "" + ).strip(), + "node_type": "TaughtLesson", + "neo4j_db_name": neo4j_db_name, + "is_section": False, + "has_children": False, + "section_id": "timetable", + } + for tl in lessons + ] + return [] + if section_id == "classes": + # Class members: students enrolled in this class + members = ( + sb.supabase.table("class_students") + .select("student_id, status, enrolled_at") + .eq("class_id", class_id) + .order("enrolled_at") + .execute() + .data or [] + ) + if not members: + return [] + student_ids = [m["student_id"] for m in members] + status_map = {m["student_id"]: m["status"] for m in members} + profiles = ( + sb.supabase.table("profiles") + .select("id, email, first_name, last_name, user_type") + .in_("id", student_ids) + .order("last_name") + .execute() + .data or [] + ) + return [ + { + "neo4j_node_id": p["id"], + "label": "{} {} ({})".format( + p.get("first_name") or "", + p.get("last_name") or "", + status_map.get(p["id"], ""), + ).strip(), + "node_type": "Student", + "neo4j_db_name": neo4j_db_name, + "is_section": False, + "has_children": False, + "section_id": "classes", + } + for p in profiles + ] + except Exception as e: + logger.warning(f"SubjectClass children failed: {e}") + return [] + # Section containers that need lazy loading if node_type == "Section": if section_id == "timetable" and neo4j_db_name: