feat(classes): optimize teacher dashboard ui and implement grade management

This commit is contained in:
SpecialX
2026-01-14 13:59:11 +08:00
parent ade8d4346c
commit 9bfc621d3f
104 changed files with 12793 additions and 2309 deletions

View File

@@ -122,6 +122,20 @@ export const getTeacherClasses = cache(async (params?: { teacherId?: string }):
const rows = await (async () => {
try {
const ownedIds = await db
.select({ id: classes.id })
.from(classes)
.where(eq(classes.teacherId, teacherId))
const enrolledIds = await db
.select({ id: classEnrollments.classId })
.from(classEnrollments)
.where(and(eq(classEnrollments.studentId, teacherId), eq(classEnrollments.status, "active")))
const allIds = Array.from(new Set([...ownedIds.map((x) => x.id), ...enrolledIds.map((x) => x.id)]))
if (allIds.length === 0) return []
return await db
.select({
id: classes.id,
@@ -135,26 +149,11 @@ export const getTeacherClasses = cache(async (params?: { teacherId?: string }):
})
.from(classes)
.leftJoin(classEnrollments, eq(classEnrollments.classId, classes.id))
.where(eq(classes.teacherId, teacherId))
.where(inArray(classes.id, allIds))
.groupBy(classes.id, classes.schoolName, classes.name, classes.grade, classes.homeroom, classes.room, classes.invitationCode)
.orderBy(asc(classes.schoolName), asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
} catch {
return await db
.select({
id: classes.id,
schoolName: sql<string | null>`NULL`.as("schoolName"),
name: classes.name,
grade: classes.grade,
homeroom: classes.homeroom,
room: classes.room,
invitationCode: sql<string | null>`NULL`.as("invitationCode"),
studentCount: sql<number>`COALESCE(SUM(CASE WHEN ${classEnrollments.status} = 'active' THEN 1 ELSE 0 END), 0)`,
})
.from(classes)
.leftJoin(classEnrollments, eq(classEnrollments.classId, classes.id))
.where(eq(classes.teacherId, teacherId))
.groupBy(classes.id, classes.name, classes.grade, classes.homeroom, classes.room)
.orderBy(asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
return []
}
})()
@@ -331,6 +330,143 @@ export const getAdminClasses = cache(async (): Promise<AdminClassListItem[]> =>
return list
})
export const getGradeManagedClasses = cache(async (userId: string): Promise<AdminClassListItem[]> => {
const managedGradeIds = await db
.select({ id: grades.id })
.from(grades)
.where(or(eq(grades.gradeHeadId, userId), eq(grades.teachingHeadId, userId)))
if (managedGradeIds.length === 0) return []
const gradeIds = managedGradeIds.map((g) => g.id)
const [rows, subjectRows] = await Promise.all([
(async () => {
try {
return await db
.select({
id: classes.id,
schoolName: classes.schoolName,
schoolId: classes.schoolId,
name: classes.name,
grade: classes.grade,
gradeId: classes.gradeId,
homeroom: classes.homeroom,
room: classes.room,
invitationCode: classes.invitationCode,
teacherId: users.id,
teacherName: users.name,
teacherEmail: users.email,
studentCount: sql<number>`COALESCE(SUM(CASE WHEN ${classEnrollments.status} = 'active' THEN 1 ELSE 0 END), 0)`,
createdAt: classes.createdAt,
updatedAt: classes.updatedAt,
})
.from(classes)
.innerJoin(users, eq(users.id, classes.teacherId))
.leftJoin(classEnrollments, eq(classEnrollments.classId, classes.id))
.where(inArray(classes.gradeId, gradeIds))
.groupBy(
classes.id,
classes.schoolName,
classes.schoolId,
classes.name,
classes.grade,
classes.gradeId,
classes.homeroom,
classes.room,
classes.invitationCode,
users.id,
users.name,
users.email,
classes.createdAt,
classes.updatedAt
)
.orderBy(
asc(classes.schoolName),
asc(classes.grade),
asc(classes.name),
asc(classes.homeroom),
asc(classes.room)
)
} catch {
return []
}
})(),
db
.select({
classId: classSubjectTeachers.classId,
subject: classSubjectTeachers.subject,
teacherId: users.id,
teacherName: users.name,
teacherEmail: users.email,
})
.from(classSubjectTeachers)
.innerJoin(classes, eq(classes.id, classSubjectTeachers.classId))
.leftJoin(users, eq(users.id, classSubjectTeachers.teacherId))
.where(inArray(classes.gradeId, gradeIds))
.orderBy(asc(classSubjectTeachers.classId), asc(classSubjectTeachers.subject)),
])
const subjectsByClassId = new Map<string, Map<ClassSubject, TeacherOption | null>>()
for (const r of subjectRows) {
const subject = r.subject as ClassSubject
if (!DEFAULT_CLASS_SUBJECTS.includes(subject)) continue
const teacher =
typeof r.teacherId === "string" && r.teacherId.length > 0
? { id: r.teacherId, name: r.teacherName ?? "Unnamed", email: r.teacherEmail ?? "" }
: null
const bySubject = subjectsByClassId.get(r.classId) ?? new Map<ClassSubject, TeacherOption | null>()
bySubject.set(subject, teacher)
subjectsByClassId.set(r.classId, bySubject)
}
const list = rows.map((r) => {
const bySubject = subjectsByClassId.get(r.id)
const subjectTeachers: ClassSubjectTeacherAssignment[] = DEFAULT_CLASS_SUBJECTS.map((subject) => ({
subject,
teacher: bySubject?.get(subject) ?? null,
}))
return {
id: r.id,
schoolName: r.schoolName,
schoolId: r.schoolId,
name: r.name,
grade: r.grade,
gradeId: r.gradeId,
homeroom: r.homeroom,
room: r.room,
invitationCode: r.invitationCode ?? null,
teacher: {
id: r.teacherId,
name: r.teacherName ?? "Unnamed",
email: r.teacherEmail,
},
subjectTeachers,
studentCount: Number(r.studentCount ?? 0),
createdAt: r.createdAt.toISOString(),
updatedAt: r.updatedAt.toISOString(),
}
})
list.sort(compareClassLike)
return list
})
export const getManagedGrades = cache(async (userId: string) => {
return await db
.select({
id: grades.id,
name: grades.name,
schoolId: grades.schoolId,
schoolName: schools.name,
})
.from(grades)
.innerJoin(schools, eq(schools.id, grades.schoolId))
.where(or(eq(grades.gradeHeadId, userId), eq(grades.teachingHeadId, userId)))
.orderBy(asc(schools.name), asc(grades.name))
})
export const getStudentClasses = cache(async (studentId: string): Promise<StudentEnrolledClass[]> => {
const id = studentId.trim()
if (!id) return []
@@ -345,9 +481,12 @@ export const getStudentClasses = cache(async (studentId: string): Promise<Studen
grade: classes.grade,
homeroom: classes.homeroom,
room: classes.room,
teacherName: users.name,
teacherEmail: users.email,
})
.from(classEnrollments)
.innerJoin(classes, eq(classes.id, classEnrollments.classId))
.leftJoin(users, eq(users.id, classes.teacherId))
.where(and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active")))
.orderBy(asc(classes.schoolName), asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
} catch {
@@ -359,9 +498,12 @@ export const getStudentClasses = cache(async (studentId: string): Promise<Studen
grade: classes.grade,
homeroom: classes.homeroom,
room: classes.room,
teacherName: users.name,
teacherEmail: users.email,
})
.from(classEnrollments)
.innerJoin(classes, eq(classes.id, classEnrollments.classId))
.leftJoin(users, eq(users.id, classes.teacherId))
.where(and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active")))
.orderBy(asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
}
@@ -374,6 +516,8 @@ export const getStudentClasses = cache(async (studentId: string): Promise<Studen
grade: r.grade,
homeroom: r.homeroom,
room: r.room,
teacherName: r.teacherName,
teacherEmail: r.teacherEmail,
}))
list.sort(compareClassLike)
@@ -414,12 +558,13 @@ export const getStudentSchedule = cache(async (studentId: string): Promise<Stude
})
export const getClassStudents = cache(
async (params?: { classId?: string; q?: string; teacherId?: string }): Promise<ClassStudent[]> => {
async (params?: { classId?: string; q?: string; status?: string; teacherId?: string }): Promise<ClassStudent[]> => {
const teacherId = params?.teacherId ?? (await getDefaultTeacherId())
if (!teacherId) return []
const classId = params?.classId?.trim()
const q = params?.q?.trim().toLowerCase()
const status = params?.status?.trim().toLowerCase()
const conditions: SQL[] = [eq(classes.teacherId, teacherId)]
@@ -427,6 +572,10 @@ export const getClassStudents = cache(
conditions.push(eq(classes.id, classId))
}
if (status === "active" || status === "inactive") {
conditions.push(eq(classEnrollments.status, status))
}
if (q && q.length > 0) {
const needle = `%${q}%`
conditions.push(
@@ -439,9 +588,12 @@ export const getClassStudents = cache(
id: users.id,
name: users.name,
email: users.email,
image: users.image,
gender: users.gender,
classId: classes.id,
className: classes.name,
status: classEnrollments.status,
joinedAt: classEnrollments.createdAt,
})
.from(classEnrollments)
.innerJoin(classes, eq(classes.id, classEnrollments.classId))
@@ -453,9 +605,12 @@ export const getClassStudents = cache(
id: r.id,
name: r.name ?? "Unnamed",
email: r.email,
image: r.image,
gender: r.gender,
classId: r.classId,
className: r.className,
status: r.status,
joinedAt: r.joinedAt,
}))
}
)