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 uipkge-ng@latest add student-gradebook-table$npx uipkge-ng@latest add student-gradebook-table$yarn dlx uipkge-ng@latest add student-gradebook-table$bunx uipkge-ng@latest add student-gradebook-tableManifest:
https://uipkge.dev/r/angular/student-gradebook-table.jsonInstalls to:src/app/components/blocks/student-gradebook-table/Variants
Loading interactive previews…
Loading interactive previews…
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 (2)
src/app/components/blocks/student-gradebook-table/student-gradebook-table.component.ts13.7 kB
// Academic gradebook ledger with course header, weighted assignment categories, class GPA // and enrollment metric cards, overall percentage, letter grade badges, attendance tags, and // student action menus. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { CalendarCheck, ClipboardCheck, Download, GraduationCap, LucideAngularModule, Mail, MoreHorizontal, Pencil, Plus, User, Users, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' 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, { 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): string { return name .split(' ') .map((part) => part[0]) .slice(0, 2) .join('') .toUpperCase() } @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-student-gradebook-table, [ui-student-gradebook-table]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiAvatarComponent, UiAvatarFallbackComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, UiTableComponent, UiTableBodyComponent, UiTableCellComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="student-gradebook-table" [class]="rootClass"> <!-- 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> <span ui-badge variant="outline" class="font-mono text-xs">CS-301-B</span> </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 ui-button aria-label="Download attachment" variant="outline"> <lucide-icon [img]="Download" class="size-4" /> Export Grades CSV </button> <button ui-button> <lucide-icon [img]="Plus" class="size-4" /> 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"> @for (metric of metrics; track metric.title) { <div ui-card class="shadow-xs"> <div ui-card-content 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]="metricIconClass(metric.iconClass)"> <lucide-icon [img]="metric.icon" class="size-4" /> </div> </div> <div> <p [class]="metricValueClass(metric.valueClass)">{{ metric.value }}</p> <p class="text-muted-foreground mt-1 text-xs">{{ metric.description }}</p> </div> </div> </div> } </div> <!-- Gradebook Table --> <div class="bg-card overflow-x-auto rounded-lg border shadow-xs"> <table ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head>Student</th> <th ui-table-head class="text-right">Homework (25%)</th> <th ui-table-head class="text-right">Midterm (30%)</th> <th ui-table-head class="text-right">Lab Projects (25%)</th> <th ui-table-head class="text-right">Final Project (20%)</th> <th ui-table-head class="text-right font-semibold">Overall Grade</th> <th ui-table-head class="text-center">Letter Grade</th> <th ui-table-head>Attendance</th> <th ui-table-head class="w-12 text-right"> <span class="sr-only">Actions</span> </th> </tr> </thead> <tbody ui-table-body> @for (student of students; track student.id) { <tr ui-table-row class="hover:bg-muted/50"> <td ui-table-cell> <div class="flex items-center gap-3"> <ui-avatar class="size-8"> <ui-avatar-fallback class="text-xs font-medium">{{ initials(student.name) }}</ui-avatar-fallback> </ui-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> </td> <td ui-table-cell class="text-right font-medium tabular-nums">{{ student.homework }}</td> <td ui-table-cell class="text-right font-medium tabular-nums">{{ student.midterm }}</td> <td ui-table-cell class="text-right font-medium tabular-nums">{{ student.labProjects }}</td> <td ui-table-cell class="text-right font-medium tabular-nums">{{ student.finalProject }}</td> <td ui-table-cell class="text-foreground text-right font-bold tabular-nums"> {{ student.overallGrade }} </td> <td ui-table-cell class="text-center"> <span ui-badge variant="outline" [class]="gradeBadgeClass(student.letterGrade)"> {{ student.letterGrade }} </span> </td> <td ui-table-cell> <div class="inline-flex items-center gap-1.5 text-xs"> <span [class]="attendanceDotClass(student.absences)" aria-hidden="true"></span> <span class="text-foreground font-medium tabular-nums">{{ student.attendanceRate }}</span> <span class="text-muted-foreground">· {{ absenceLabel(student.absences) }}</span> </div> </td> <td ui-table-cell class="text-right"> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="ghost" size="icon-sm" class="text-muted-foreground" > <lucide-icon [img]="MoreHorizontal" class="size-4" /> <span class="sr-only">Open actions for {{ student.name }}</span> </button> <ui-dropdown-menu-content align="end" class="w-48"> <ui-dropdown-menu-item> <lucide-icon [img]="Pencil" class="size-4" /> <span>Edit Grades</span> </ui-dropdown-menu-item> <ui-dropdown-menu-item> <lucide-icon [img]="User" class="size-4" /> <span>View Student Profile</span> </ui-dropdown-menu-item> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item> <lucide-icon [img]="Mail" class="size-4" /> <span>Send Email Alert</span> </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </td> </tr> } </tbody> </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 ui-button variant="outline" size="sm" disabled>Previous</button> <button ui-button variant="outline" size="sm">Next</button> </div> </div> </div> `, }) export class UiStudentGradebookTableComponent { protected readonly Download = Download protected readonly Mail = Mail protected readonly MoreHorizontal = MoreHorizontal protected readonly Pencil = Pencil protected readonly Plus = Plus protected readonly User = User protected readonly metrics = metrics protected readonly students = students @Input('class') className?: string get rootClass(): string { return cn('w-full space-y-6', this.className) } metricIconClass(iconClass: string): string { return cn('flex size-8 items-center justify-center rounded-md', iconClass) } metricValueClass(valueClass: string): string { return cn('text-2xl font-bold tracking-tight tabular-nums', valueClass) } gradeBadgeClass(grade: LetterGrade): string { return cn('font-semibold', gradeBadgeConfig[grade].class) } attendanceDotClass(absences: number): string { return cn('inline-block size-1.5 rounded-full', absences === 0 ? 'bg-success' : 'bg-warning') } absenceLabel(absences: number): string { if (absences === 0) return '0 absences' return `${absences} absence${absences > 1 ? 's' : ''}` } initials(name: string): string { return initials(name) } }src/app/components/blocks/student-gradebook-table/index.ts0.1 kB
export { UiStudentGradebookTableComponent, type LetterGrade, type StudentGradeRecord, } from './student-gradebook-table.component'
Raw manifest:https://uipkge.dev/r/angular/student-gradebook-table.json