Student Gradebook Table
blockeducationAcademic 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.
Installation
$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/student-gradebook-table.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/student-gradebook-table.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/student-gradebook-table.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/student-gradebook-table.jsonNamed registry:
npx shadcn-vue@latest add @uipkge/student-gradebook-tableInstalls to:app/components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
StudentGradeRecordinterface StudentGradeRecord {
id: string
name: string
studentId: string
homework: string
midterm: string
labProjects: string
finalProject: string
overallGrade: string
letterGrade: LetterGrade
attendanceRate: string
absences: number
}npm dependencies
Files installed (1)
app/components/blocks/StudentGradebookTable.vue11 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { CalendarCheck, ClipboardCheck, Download, GraduationCap, Mail, MoreHorizontal, Pencil, Plus, User, Users, } from 'lucide-vue-next' 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 props = defineProps<{ class?: HTMLAttributes['class'] }>() 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, { class: string }> = { 'A+': { class: 'bg-success/10 text-success border-success/20 font-semibold', }, A: { class: 'bg-success/10 text-success border-success/20 font-semibold', }, 'A-': { class: 'bg-success/10 text-success border-success/20 font-semibold', }, 'B+': { class: 'bg-info/10 text-info border-info/20 font-semibold', }, B: { class: 'bg-warning/10 text-warning border-warning/20 font-semibold', }, 'B-': { class: '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() } </script> <template> <div data-slot="student-gradebook-table" :class="cn('w-full space-y-6', props.class)"> <!-- Header --> <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> <div> <div class="flex flex-wrap items-center gap-2.5"> <h2 class="text-2xl font-bold tracking-tight">CS 301: Advanced Software Systems & Compilers</h2> <Badge variant="outline" class="font-mono text-xs">CS-301-B</Badge> </div> <div class="text-muted-foreground mt-1.5 flex flex-wrap items-center gap-2 text-sm"> <span>Fall 2026 · Section B</span> <span class="text-foreground inline-flex items-center gap-1 font-medium"> Class Average: <span class="tabular-nums">88.4%</span> · Grade B+ </span> </div> </div> <div class="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 class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <Card v-for="metric in metrics" :key="metric.title" class="shadow-xs"> <CardContent class="space-y-2 p-5"> <div class="flex items-center justify-between"> <p class="text-muted-foreground text-sm font-medium">{{ metric.title }}</p> <div :class="cn('flex size-8 items-center justify-center rounded-md', metric.iconClass)"> <component :is="metric.icon" aria-hidden="true" class="size-4" /> </div> </div> <div> <p :class="cn('text-2xl font-bold tracking-tight tabular-nums', metric.valueClass)"> {{ metric.value }} </p> <p class="text-muted-foreground mt-1 text-xs">{{ metric.description }}</p> </div> </CardContent> </Card> </div> <!-- Gradebook Table --> <div class="bg-card overflow-x-auto rounded-lg border shadow-xs"> <Table> <TableHeader> <TableRow> <TableHead>Student</TableHead> <TableHead class="text-right">Homework (25%)</TableHead> <TableHead class="text-right">Midterm (30%)</TableHead> <TableHead class="text-right">Lab Projects (25%)</TableHead> <TableHead class="text-right">Final Project (20%)</TableHead> <TableHead class="text-right font-semibold">Overall Grade</TableHead> <TableHead class="text-center">Letter Grade</TableHead> <TableHead>Attendance</TableHead> <TableHead class="w-12 text-right"> <span class="sr-only">Actions</span> </TableHead> </TableRow> </TableHeader> <TableBody> <TableRow v-for="student in students" :key="student.id" class="hover:bg-muted/50"> <TableCell> <div class="flex items-center gap-3"> <Avatar class="size-8"> <AvatarFallback class="text-xs font-medium">{{ initials(student.name) }}</AvatarFallback> </Avatar> <div class="min-w-0"> <p class="truncate text-sm font-semibold">{{ student.name }}</p> <p class="text-muted-foreground font-mono text-xs">{{ student.studentId }}</p> </div> </div> </TableCell> <TableCell class="text-right font-medium tabular-nums">{{ student.homework }}</TableCell> <TableCell class="text-right font-medium tabular-nums">{{ student.midterm }}</TableCell> <TableCell class="text-right font-medium tabular-nums">{{ student.labProjects }}</TableCell> <TableCell class="text-right font-medium tabular-nums">{{ student.finalProject }}</TableCell> <TableCell class="text-foreground text-right font-bold tabular-nums">{{ student.overallGrade }}</TableCell> <TableCell class="text-center"> <Badge variant="outline" :class="cn('font-semibold', gradeBadgeConfig[student.letterGrade].class)"> {{ student.letterGrade }} </Badge> </TableCell> <TableCell> <div class="inline-flex items-center gap-1.5 text-xs"> <span class="inline-block size-1.5 rounded-full" :class="student.absences === 0 ? 'bg-success' : 'bg-warning'" aria-hidden="true" /> <span class="text-foreground font-medium tabular-nums">{{ student.attendanceRate }}</span> <span class="text-muted-foreground"> · {{ student.absences === 0 ? '0 absences' : `${student.absences} absence${student.absences > 1 ? 's' : ''}` }} </span> </div> </TableCell> <TableCell class="text-right"> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="ghost" size="icon-sm" class="text-muted-foreground"> <MoreHorizontal aria-hidden="true" /> <span class="sr-only">Open actions for {{ student.name }}</span> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" class="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 class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <p class="text-muted-foreground text-sm"> Showing <span class="text-foreground font-medium">6</span> of <span class="text-foreground font-medium">28</span> enrolled students </p> <div class="flex items-center gap-2"> <Button variant="outline" size="sm" disabled> Previous </Button> <Button variant="outline" size="sm"> Next </Button> </div> </div> </div> </template>
Raw manifest:https://uipkge.dev/r/vue/student-gradebook-table.json