UIPackage
Menu

Framework

Change language

Boilerplate repo

Student Gradebook Table

blockeducation

Academic gradebook ledger with course header, weighted assignment categories (homework, midterm, labs, final project), class GPA & enrollment metric cards, overall percentage, letter grade badges, attendance tags, and student action menus.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/student-gradebook-table.json
Named registry:npx shadcn@latest add @uipkge-react/student-gradebook-tableInstalls to:components/blocks/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

StudentGradeRecord
interface StudentGradeRecord {
  id: string
  name: string
  studentId: string
  homework: string
  midterm: string
  labProjects: string
  finalProject: string
  overallGrade: string
  letterGrade: LetterGrade
  attendanceRate: string
  absences: number
}

Files installed (1)

  • components/blocks/StudentGradebookTable.tsx11.9 kB
    'use client'
    
    import {
      CalendarCheck,
      ClipboardCheck,
      Download,
      GraduationCap,
      Mail,
      MoreHorizontal,
      Pencil,
      Plus,
      User,
      Users,
    } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Avatar, AvatarFallback } from '@/components/ui/avatar'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent } from '@/components/ui/card'
    import {
      DropdownMenu,
      DropdownMenuContent,
      DropdownMenuItem,
      DropdownMenuSeparator,
      DropdownMenuTrigger,
    } from '@/components/ui/dropdown-menu'
    import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
    
    export type LetterGrade = 'A+' | 'A' | 'A-' | 'B+' | 'B' | 'B-'
    
    export interface StudentGradeRecord {
      id: string
      name: string
      studentId: string
      homework: string
      midterm: string
      labProjects: string
      finalProject: string
      overallGrade: string
      letterGrade: LetterGrade
      attendanceRate: string
      absences: number
    }
    
    const metrics = [
      {
        title: 'Class GPA',
        value: '3.62 / 4.0',
        description: '+0.18 vs departmental target',
        icon: GraduationCap,
        iconClass: 'bg-success/10 text-success',
        valueClass: 'text-success',
      },
      {
        title: 'Total Enrolled Students',
        value: '28 students',
        description: '100% active roster',
        icon: Users,
        iconClass: 'bg-info/10 text-info',
        valueClass: 'text-foreground',
      },
      {
        title: 'Assignments Graded',
        value: '12 / 14 assignments',
        description: '2 pending final submissions',
        icon: ClipboardCheck,
        iconClass: 'bg-chart-1/10 text-chart-1',
        valueClass: 'text-foreground',
      },
      {
        title: 'Class Attendance Rate',
        value: '96.2% average',
        description: '36 lectures & labs logged',
        icon: CalendarCheck,
        iconClass: 'bg-warning/10 text-warning',
        valueClass: 'text-foreground',
      },
    ]
    
    const gradeBadgeConfig: Record<LetterGrade, { className: string }> = {
      'A+': {
        className: 'bg-success/10 text-success border-success/20 font-semibold',
      },
      A: {
        className: 'bg-success/10 text-success border-success/20 font-semibold',
      },
      'A-': {
        className: 'bg-success/10 text-success border-success/20 font-semibold',
      },
      'B+': {
        className: 'bg-info/10 text-info border-info/20 font-semibold',
      },
      B: {
        className: 'bg-warning/10 text-warning border-warning/20 font-semibold',
      },
      'B-': {
        className: 'bg-warning/10 text-warning border-warning/20 font-semibold',
      },
    }
    
    const students: StudentGradeRecord[] = [
      {
        id: 's1',
        name: 'Elena Rostova',
        studentId: '#STU-84920',
        homework: '95.0%',
        midterm: '92.0%',
        labProjects: '98.0%',
        finalProject: '94.0%',
        overallGrade: '94.5%',
        letterGrade: 'A',
        attendanceRate: '100%',
        absences: 0,
      },
      {
        id: 's2',
        name: 'David Chen',
        studentId: '#STU-84921',
        homework: '88.0%',
        midterm: '91.0%',
        labProjects: '86.0%',
        finalProject: '89.0%',
        overallGrade: '88.6%',
        letterGrade: 'B+',
        attendanceRate: '96%',
        absences: 1,
      },
      {
        id: 's3',
        name: 'Sarah Jenkins',
        studentId: '#STU-84922',
        homework: '98.0%',
        midterm: '96.0%',
        labProjects: '100.0%',
        finalProject: '95.0%',
        overallGrade: '97.1%',
        letterGrade: 'A',
        attendanceRate: '100%',
        absences: 0,
      },
      {
        id: 's4',
        name: 'Marcus Vance',
        studentId: '#STU-84923',
        homework: '82.0%',
        midterm: '79.0%',
        labProjects: '85.0%',
        finalProject: '80.0%',
        overallGrade: '81.4%',
        letterGrade: 'B',
        attendanceRate: '92%',
        absences: 2,
      },
      {
        id: 's5',
        name: 'Sofia Rossi',
        studentId: '#STU-84924',
        homework: '91.0%',
        midterm: '88.0%',
        labProjects: '94.0%',
        finalProject: '90.0%',
        overallGrade: '90.6%',
        letterGrade: 'A-',
        attendanceRate: '98%',
        absences: 1,
      },
      {
        id: 's6',
        name: 'Alex Rivera',
        studentId: '#STU-84925',
        homework: '84.0%',
        midterm: '86.0%',
        labProjects: '82.0%',
        finalProject: '85.0%',
        overallGrade: '84.3%',
        letterGrade: 'B',
        attendanceRate: '94%',
        absences: 2,
      },
    ]
    
    function initials(name: string) {
      return name
        .split(' ')
        .map((part) => part[0])
        .slice(0, 2)
        .join('')
        .toUpperCase()
    }
    
    export function StudentGradebookTable({ className }: { className?: string }) {
      return (
        <div data-slot="student-gradebook-table" className={cn('w-full space-y-6', className)}>
          {/* Header */}
          <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <div>
              <div className="flex flex-wrap items-center gap-2.5">
                <h2 className="text-2xl font-bold tracking-tight">CS 301: Advanced Software Systems &amp; Compilers</h2>
                <Badge variant="outline" className="font-mono text-xs">
                  CS-301-B
                </Badge>
              </div>
              <div className="text-muted-foreground mt-1.5 flex flex-wrap items-center gap-2 text-sm">
                <span>Fall 2026 · Section B</span>
                <span className="text-foreground inline-flex items-center gap-1 font-medium">
                  Class Average: <span className="tabular-nums">88.4%</span> · Grade B+
                </span>
              </div>
            </div>
            <div className="flex flex-wrap items-center gap-2.5">
              <Button aria-label="Download attachment" variant="outline">
                <Download aria-hidden="true" />
                Export Grades CSV
              </Button>
              <Button>
                <Plus aria-hidden="true" />
                New Grade Entry
              </Button>
            </div>
          </div>
    
          {/* 4 Class Overview Metric Cards */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            {metrics.map((metric) => {
              const Icon = metric.icon
              return (
                <Card key={metric.title} className="shadow-xs">
                  <CardContent className="space-y-2 p-5">
                    <div className="flex items-center justify-between">
                      <p className="text-muted-foreground text-sm font-medium">{metric.title}</p>
                      <div className={cn('flex size-8 items-center justify-center rounded-md', metric.iconClass)}>
                        <Icon aria-hidden="true" className="size-4" />
                      </div>
                    </div>
                    <div>
                      <p className={cn('text-2xl font-bold tracking-tight tabular-nums', metric.valueClass)}>
                        {metric.value}
                      </p>
                      <p className="text-muted-foreground mt-1 text-xs">{metric.description}</p>
                    </div>
                  </CardContent>
                </Card>
              )
            })}
          </div>
    
          {/* Gradebook Table */}
          <div className="bg-card overflow-x-auto rounded-lg border shadow-xs">
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Student</TableHead>
                  <TableHead className="text-right">Homework (25%)</TableHead>
                  <TableHead className="text-right">Midterm (30%)</TableHead>
                  <TableHead className="text-right">Lab Projects (25%)</TableHead>
                  <TableHead className="text-right">Final Project (20%)</TableHead>
                  <TableHead className="text-right font-semibold">Overall Grade</TableHead>
                  <TableHead className="text-center">Letter Grade</TableHead>
                  <TableHead>Attendance</TableHead>
                  <TableHead className="w-12 text-right">
                    <span className="sr-only">Actions</span>
                  </TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {students.map((student) => (
                  <TableRow key={student.id} className="hover:bg-muted/50">
                    <TableCell>
                      <div className="flex items-center gap-3">
                        <Avatar className="size-8">
                          <AvatarFallback className="text-xs font-medium">{initials(student.name)}</AvatarFallback>
                        </Avatar>
                        <div className="min-w-0">
                          <p className="truncate text-sm font-semibold">{student.name}</p>
                          <p className="text-muted-foreground font-mono text-xs">{student.studentId}</p>
                        </div>
                      </div>
                    </TableCell>
                    <TableCell className="text-right font-medium tabular-nums">{student.homework}</TableCell>
                    <TableCell className="text-right font-medium tabular-nums">{student.midterm}</TableCell>
                    <TableCell className="text-right font-medium tabular-nums">{student.labProjects}</TableCell>
                    <TableCell className="text-right font-medium tabular-nums">{student.finalProject}</TableCell>
                    <TableCell className="text-foreground text-right font-bold tabular-nums">
                      {student.overallGrade}
                    </TableCell>
                    <TableCell className="text-center">
                      <Badge
                        variant="outline"
                        className={cn('font-semibold', gradeBadgeConfig[student.letterGrade].className)}
                      >
                        {student.letterGrade}
                      </Badge>
                    </TableCell>
                    <TableCell>
                      <div className="inline-flex items-center gap-1.5 text-xs">
                        <span
                          className={cn(
                            'inline-block size-1.5 rounded-full',
                            student.absences === 0 ? 'bg-success' : 'bg-warning',
                          )}
                          aria-hidden="true"
                        />
                        <span className="text-foreground font-medium tabular-nums">{student.attendanceRate}</span>
                        <span className="text-muted-foreground">
                          ·{' '}
                          {student.absences === 0
                            ? '0 absences'
                            : `${student.absences} absence${student.absences > 1 ? 's' : ''}`}
                        </span>
                      </div>
                    </TableCell>
                    <TableCell className="text-right">
                      <DropdownMenu>
                        <DropdownMenuTrigger asChild>
                          <Button variant="ghost" size="icon-sm" className="text-muted-foreground">
                            <MoreHorizontal aria-hidden="true" />
                            <span className="sr-only">Open actions for {student.name}</span>
                          </Button>
                        </DropdownMenuTrigger>
                        <DropdownMenuContent align="end" className="w-48">
                          <DropdownMenuItem>
                            <Pencil aria-hidden="true" />
                            Edit Grades
                          </DropdownMenuItem>
                          <DropdownMenuItem>
                            <User aria-hidden="true" />
                            View Student Profile
                          </DropdownMenuItem>
                          <DropdownMenuSeparator />
                          <DropdownMenuItem>
                            <Mail aria-hidden="true" />
                            Send Email Alert
                          </DropdownMenuItem>
                        </DropdownMenuContent>
                      </DropdownMenu>
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </div>
    
          {/* Table Footer / Pagination */}
          <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
            <p className="text-muted-foreground text-sm">
              Showing <span className="text-foreground font-medium">6</span> of{' '}
              <span className="text-foreground font-medium">28</span> enrolled students
            </p>
            <div className="flex items-center gap-2">
              <Button variant="outline" size="sm" disabled>
                Previous
              </Button>
              <Button variant="outline" size="sm">
                Next
              </Button>
            </div>
          </div>
        </div>
      )
    }
    

Raw manifest:https://uipkge.dev/r/react/student-gradebook-table.json