성적 관리 시스템

선생님은 모든 학생의 과제 및 퀴즈 성적을 한눈에 볼 수 있는 종합 성적표를 확인할 수 있습니다.

개요

주요 기능

1. 종합 성적표

테이블 구조

학생 정보

점수 셀

통계 열

2. 가로 스크롤

과제/퀴즈가 많을 경우 가로 스크롤 가능:

3. 정렬 기능

헤더 클릭으로 정렬:

4. 필터링

API 엔드포인트

GET /api/teacher/grades?community=slug

전체 학생의 성적 조회

Response:

{ "students": [ { "memberId": 1, "userId": 10, "name": "김철수", "avatar": "/avatars/user1.jpg", "level": 5, "points": 1250, "assignmentScores": [ { "assignmentId": 1, "title": "한국어 작문 1", "score": 85, "maxScore": 100 }, { "assignmentId": 2, "title": "한국어 작문 2", "score": null, "maxScore": 100 } ], "quizScores": [ { "quizId": 1, "title": "TOPIK 모의고사 1", "score": 82, "maxScore": 100 } ], "stats": { "completedAssignments": 1, "totalAssignments": 2, "assignmentAverage": 85, "completedQuizzes": 1, "totalQuizzes": 1, "quizAverage": 82, "overallAverage": 83.5 } } ], "assignments": [ { "id": "1", "title": "한국어 작문 1", "maxScore": 100 } ], "quizzes": [ { "id": "1", "title": "TOPIK 모의고사 1", "maxScore": 100 } ] }

데이터 계산 로직

과제 평균 계산

const completedAssignments = assignmentScores.filter((a) => a.score !== null).length; const totalAssignmentScore = assignmentScores.reduce( (sum, a) => sum + (a.score ?? 0), 0 ); const assignmentAverage = completedAssignments > 0 ? Math.round(totalAssignmentScore / completedAssignments) : 0;

퀴즈 평균 계산

const completedQuizzes = quizScores.filter((q) => q.score !== null).length; const totalQuizScore = quizScores.reduce((sum, q) => sum + (q.score ?? 0), 0); const quizAverage = completedQuizzes > 0 ? Math.round(totalQuizScore / completedQuizzes) : 0;

종합 평균 계산

const overallAverage = completedAssignments + completedQuizzes > 0 ? Math.round( ((totalAssignmentScore + totalQuizScore) / (completedAssignments + completedQuizzes)) * 100 ) / 100 : 0;

점수 우선순위

과제 점수는 수동 점수 우선, AI 점수 차선:

const score = submission?.manualScore ?? submission?.score ?? null;

UI 컴포넌트

성적표 테이블

<Table> <TableHeader> <TableRow> <TableHead className="sticky left-0">학생</TableHead> {assignments.map(a => ( <TableHead key={a.id}>{a.title}</TableHead> ))} {quizzes.map(q => ( <TableHead key={q.id}>{q.title}</TableHead> ))} <TableHead className="sticky right-0">평균</TableHead> </TableRow> </TableHeader> <TableBody> {students.map(student => ( <TableRow key={student.memberId}> <TableCell className="sticky left-0"> <StudentInfo student={student} /> </TableCell> {student.assignmentScores.map(score => ( <TableCell key={score.assignmentId}> <ScoreCell score={score} /> </TableCell> ))} {student.quizScores.map(score => ( <TableCell key={score.quizId}> <ScoreCell score={score} /> </TableCell> ))} <TableCell className="sticky right-0"> <Stats stats={student.stats} /> </TableCell> </TableRow> ))} </TableBody> </Table>

점수 셀 색상 코딩

const getScoreColor = (score: number | null, maxScore: number) => { if (score === null) return "text-gray-400"; const percentage = (score / maxScore) * 100; if (percentage >= 90) return "text-emerald-600 font-semibold"; if (percentage >= 80) return "text-blue-600"; if (percentage >= 70) return "text-amber-600"; return "text-red-600"; };

학생 정보 셀

<div className="flex items-center gap-3"> <Avatar src={student.avatar} className="w-8 h-8" /> <div> <div className="font-medium">{student.name}</div> <div className="text-xs text-muted-foreground"> Lv.{student.level} · {student.points}pts </div> </div> </div>

색상 코딩 시스템

점수 색상

배경 색상 (Optional)

사용 시나리오

학기말 성적 산출

  1. 성적표 페이지 접속
  2. 전체 학생의 과제/퀴즈 점수 확인
  3. 종합 평균 열로 최종 성적 산출
  4. Excel 내보내기 (향후 기능)
  5. 학교 시스템에 입력

미제출 학생 파악

  1. 성적표에서 "-" 표시 확인
  2. 미제출 학생에게 독촉 메시지 발송
  3. 과제 마감 연장 여부 결정

저성취 학생 지원

  1. 빨간색/노란색 점수가 많은 학생 확인
  2. 개별 학생 진도 페이지로 이동
  3. 구체적인 학습 패턴 분석
  4. 보충 수업 또는 추가 자료 제공

수업 평가

  1. 특정 과제/퀴즈 열 확인
  2. 전체 학생의 평균 점수 계산
  3. 난이도 적정성 판단
  4. 다음 수업 계획에 반영

내보내기 기능 (향후 구현)

Excel 내보내기

const exportToExcel = () => { const data = students.map(student => ({ 이름: student.name, 레벨: student.level, 포인트: student.points, ...student.assignmentScores.reduce((acc, a) => ({ ...acc, [a.title]: a.score ?? "-" }), {}), ...student.quizScores.reduce((acc, q) => ({ ...acc, [q.title]: q.score ?? "-" }), {}), "과제 평균": student.stats.assignmentAverage, "퀴즈 평균": student.stats.quizAverage, "종합 평균": student.stats.overallAverage, })); // Export using xlsx library };

PDF 내보내기

const exportToPDF = () => { // Generate PDF using jsPDF or similar library };

권한 관리

Owner & Admin

Student

성능 최적화

데이터 로딩

쿼리 최적화

// 필요한 데이터만 조회 const allAssignments = await db .select({ id: assignments.id, title: assignments.title, maxScore: assignments.maxScore, }) .from(assignments) .where(eq(assignments.communityId, communityId)); // 모든 제출 데이터를 한 번에 조회 (N+1 문제 방지) const allSubmissions = await db .select({ memberId: assignmentSubmissions.memberId, assignmentId: assignmentSubmissions.assignmentId, score: assignmentSubmissions.score, manualScore: assignmentSubmissions.manualScore, }) .from(assignmentSubmissions) .leftJoin(assignments, eq(assignmentSubmissions.assignmentId, assignments.id)) .where(eq(assignments.communityId, communityId));

메모리 최적화

캐싱

확장 가능성

향후 추가 기능

트러블슈팅

성적표가 로딩되지 않는 경우

점수가 표시되지 않는 경우

평균 점수가 이상한 경우

테이블이 깨지는 경우