import { useEffect, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Plus, Search, Trash2, Pencil, Users, GraduationCap, BookOpen, Layers, UserPlus, UserMinus, Loader2, Sparkles, CheckCircle2, Circle, Settings2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { classroomsService } from "@/services/classrooms.service"; import { useStudents, useTeachers, useCourses } from "@/hooks/queries"; import { lmsService } from "@/services"; import { useToast } from "@/hooks/use-toast"; import { useAuth } from "@/contexts/AuthContext"; import type { Classroom, LmsBranch } from "@/types"; type ClassroomForm = { name: string; code: string; capacity: string; notes: string; branch_id: string; active: boolean; }; const EMPTY_FORM: ClassroomForm = { name: "", code: "", capacity: "30", notes: "", branch_id: "", active: true, }; export default function ClassroomsPage() { const { toast } = useToast(); const qc = useQueryClient(); const { selectedEntity } = useAuth(); const [search, setSearch] = useState(""); const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [form, setForm] = useState(EMPTY_FORM); const [activeId, setActiveId] = useState(null); // Reference data const studentsQ = useStudents({ size: 500 }); const teachersQ = useTeachers({ size: 500 }); const coursesQ = useCourses({ size: 500 }); const branchesQ = useQuery({ queryKey: ["lms-branches", selectedEntity?.id ?? 0], queryFn: async () => (await lmsService.listBranches({ size: 200, active: true })).items, }); const classroomsQ = useQuery({ queryKey: ["lms-classrooms", selectedEntity?.id ?? 0, search], queryFn: async () => (await classroomsService.list({ size: 200, search: search.trim() || undefined })).items, }); const detailQ = useQuery({ queryKey: ["lms-classroom-detail", activeId], queryFn: () => classroomsService.getById(activeId as number), enabled: !!activeId, }); // ── Mutations ────────────────────────────────────────────────────── const createMut = useMutation({ mutationFn: () => classroomsService.create({ name: form.name, code: form.code || undefined, capacity: Number(form.capacity || 30), notes: form.notes || undefined, branch_id: form.branch_id ? Number(form.branch_id) : null, active: form.active, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); setCreateOpen(false); setForm(EMPTY_FORM); toast({ title: "Classroom created" }); }, onError: (e: Error) => toast({ title: "Error", description: e.message, variant: "destructive" }), }); const updateMut = useMutation({ mutationFn: () => classroomsService.update(editing!.id, { name: form.name, code: form.code || undefined, capacity: Number(form.capacity || 30), notes: form.notes || undefined, branch_id: form.branch_id ? Number(form.branch_id) : null, active: form.active, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); setEditing(null); setForm(EMPTY_FORM); toast({ title: "Classroom updated" }); }, onError: (e: Error) => toast({ title: "Error", description: e.message, variant: "destructive" }), }); const deleteMut = useMutation({ mutationFn: (id: number) => classroomsService.delete(id), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); if (activeId) setActiveId(null); toast({ title: "Classroom deleted" }); }, onError: (e: Error) => toast({ title: "Error", description: e.message, variant: "destructive" }), }); const setStudentsMut = useMutation({ mutationFn: ({ id, ids }: { id: number; ids: number[] }) => classroomsService.setStudents(id, ids, "set"), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); toast({ title: "Roster updated" }); }, }); const setTeachersMut = useMutation({ mutationFn: ({ id, ids }: { id: number; ids: number[] }) => classroomsService.setTeachers(id, ids, "set"), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); toast({ title: "Homeroom teachers updated" }); }, }); const assignCourseMut = useMutation({ mutationFn: ({ id, payload, }: { id: number; payload: { course_id: number; section_id?: number; term_key?: string }; }) => classroomsService.assignCourse(id, payload), onSuccess: (res) => { qc.invalidateQueries({ queryKey: ["lms-classrooms"] }); qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); qc.invalidateQueries({ queryKey: ["lms", "batches"] }); toast({ title: "Course assigned", description: `Batch "${res.batch.name}" — ${res.batch.student_count} student(s) enrolled`, }); }, onError: (e: Error) => toast({ title: "Error", description: e.message, variant: "destructive" }), }); const detachCourseMut = useMutation({ mutationFn: ({ id, courseId }: { id: number; courseId: number }) => classroomsService.detachCourse(id, courseId), onSuccess: () => { qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); toast({ title: "Course detached", description: "Existing batch & enrollments are kept." }); }, }); // ── Derived data ────────────────────────────────────────────────── const items = classroomsQ.data ?? []; const branches = branchesQ.data ?? []; const allStudents = studentsQ.data?.items ?? []; const allTeachers = teachersQ.data?.items ?? []; const allCourses = coursesQ.data?.items ?? []; const detail = detailQ.data; const filtered = useMemo(() => { const q = search.toLowerCase(); return items.filter( (c) => c.name.toLowerCase().includes(q) || c.code.toLowerCase().includes(q) || (c.branch_name || "").toLowerCase().includes(q), ); }, [items, search]); function startEdit(c: Classroom) { setEditing(c); setForm({ name: c.name, code: c.code, capacity: String(c.capacity || 30), notes: c.notes || "", branch_id: c.branch_id ? String(c.branch_id) : "", active: c.active, }); } return (

Classrooms

Homerooms — assign students, teachers and courses. Assigning a course auto-creates a batch and enrolls the classroom roster.

{/* Left list */}
setSearch(e.target.value)} placeholder="Search classrooms…" className="pl-9" />
{classroomsQ.isLoading ? (
) : filtered.length === 0 ? (
No classrooms yet.
) : (
{filtered.map((c) => ( ))}
)}
{/* Right detail */} {!activeId ? (
Select a classroom on the left to manage its roster, teachers, courses and batches.
) : detailQ.isLoading || !detail ? (
) : (

{detail.name}

{detail.code} {!detail.active && Inactive}
{detail.branch_name ? `Branch: ${detail.branch_name} · ` : ""} Capacity: {detail.capacity}
0} hasTeachers={detail.teacher_count > 0} hasCourses={detail.course_count > 0} hasBatches={detail.batch_count > 0} /> 1. Roster {detail.student_count} 2. Faculty {detail.teacher_count} 3. Courses {detail.course_count} 4. Batches {detail.batch_count} {/* Roster ------------------------------------------ */} setStudentsMut.mutate({ id: detail.id, ids })} saving={setStudentsMut.isPending} /> {/* Faculty ----------------------------------------- */} setTeachersMut.mutate({ id: detail.id, ids })} saving={setTeachersMut.isPending} /> {/* Courses ----------------------------------------- */} assignCourseMut.mutate({ id: detail.id, payload }) } onDetach={(courseId) => detachCourseMut.mutate({ id: detail.id, courseId }) } assigning={assignCourseMut.isPending} /> {/* Batches ----------------------------------------- */} { qc.invalidateQueries({ queryKey: ["lms-classroom-detail"] }); qc.invalidateQueries({ queryKey: ["lms", "batches"] }); }} />
)}
{/* Create / Edit dialog */} { if (!v) { setCreateOpen(false); setEditing(null); setForm(EMPTY_FORM); } }} > {editing ? "Edit Classroom" : "Create Classroom"} A classroom is a homeroom: a physical room hosting a group of students, with homeroom teachers and the courses they study.
setForm((f) => ({ ...f, name: e.target.value }))} placeholder="e.g. Grade 7-A" />
setForm((f) => ({ ...f, code: e.target.value }))} placeholder="G7A" />
setForm((f) => ({ ...f, capacity: e.target.value }))} />