
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 Vue ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/student-gradebook-table.json$npx shadcn@latest add https://uipkge.dev/r/react/student-gradebook-table.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/student-gradebook-table.json$bunx shadcn@latest add https://uipkge.dev/r/react/student-gradebook-table.jsonnpx shadcn@latest add @uipkge-react/student-gradebook-tableInstalls to:components/blocks/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
}'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 & 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