
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
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 React ->$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.jsonnpx shadcn-vue@latest add @uipkge/student-gradebook-tableInstalls to:app/components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
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
}<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