Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 1x 9x 2x 7x 13x 37x | /**
* EI-3182 — RubricTable
*
* Presentational component that renders a structured assignment rubric.
* Accepts the canonical rubric shape:
* { criteria: [ { name: string, levels: [ { label: string, points: number, description?: string } ] } ] }
*
* Renders nothing when rubric is absent or has no criteria rows.
*
* Developer: Allan Niñal
*/
import React from "react";
import {
Box,
Card,
CardContent,
Chip,
Divider,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import GradingIcon from "@mui/icons-material/Grading";
/**
* @param {{ rubric: import("../types").Rubric | null | undefined }} props
*/
const RubricTable = ({ rubric }) => {
if (!rubric || !Array.isArray(rubric.criteria) || rubric.criteria.length === 0) {
return null;
}
return (
<Card elevation={2} id="student-assignment-rubric-card" sx={{ mb: 3 }} data-testid="rubric-card">
<CardContent>
<Box sx={{ display: "flex", alignItems: "center", mb: 2 }}>
<GradingIcon sx={{ fontSize: 30, color: "primary.main", mr: 1.5 }} />
<Typography variant="h6" fontWeight={600}>
Rubric
</Typography>
</Box>
<Divider sx={{ mb: 2 }} />
{rubric.criteria.map((criterion, criterionIndex) => (
<Box key={criterionIndex} sx={{ mb: criterionIndex < rubric.criteria.length - 1 ? 3 : 0 }}>
<Typography
variant="subtitle1"
fontWeight={600}
sx={{ mb: 1 }}
data-testid={`rubric-criterion-name-${criterionIndex}`}
>
{criterion.name}
</Typography>
<TableContainer>
<Table
size="small"
aria-label={`Rubric criteria: ${criterion.name}`}
data-testid={`rubric-criterion-table-${criterionIndex}`}
>
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 600, width: 120 }}>Level</TableCell>
<TableCell sx={{ fontWeight: 600, width: 80 }}>Points</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Description</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Array.isArray(criterion.levels) &&
criterion.levels.map((level, levelIndex) => (
<TableRow
key={levelIndex}
data-testid={`rubric-level-row-${criterionIndex}-${levelIndex}`}
sx={{
"&:last-child td, &:last-child th": { border: 0 },
}}
>
<TableCell>
<Chip
label={level.label}
size="small"
color="primary"
variant="outlined"
/>
</TableCell>
<TableCell>
<Typography variant="body2" fontWeight={500}>
{level.points}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="text.secondary">
{level.description ?? ""}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
{criterionIndex < rubric.criteria.length - 1 && <Divider sx={{ mt: 2 }} />}
</Box>
))}
</CardContent>
</Card>
);
};
export default RubricTable;
|