Building Homework Review System in LMS
Homework review system extends grading with peer review, multi-step workflow (student submission → peer review → instructor review), detailed rubric-based feedback, and revision history. Supports both synchronous instructor reviews and asynchronous peer feedback.
Submission and Review Workflow
interface Submission {
id: string;
assignmentId: string;
userId: string;
content: string;
files: Array<{ url: string }>;
submittedAt: Date;
status: 'draft' | 'submitted' | 'in_review' | 'approved' | 'needs_revision';
revisionCount: number;
}
interface Review {
id: string;
submissionId: string;
reviewerId: string;
type: 'peer' | 'instructor';
rubricScores: Record<string, number>;
overallFeedback: string;
comments: Array<{ line: number; text: string; severity: 'info' | 'warning' | 'error' }>;
createdAt: Date;
approved: boolean;
}
Peer Review Assignment
app.post('/api/assignments/:assignmentId/assign-peer-reviews', authenticate, async (req, res) => {
const assignment = await db.assignments.findById(req.params.assignmentId);
const submissions = await db.submissions.findByAssignment(req.params.assignmentId);
const reviewsPerSubmission = 2; // Each student gets 2 peer reviews
for (const submission of submissions) {
const otherSubmissions = submissions.filter(s => s.userId !== submission.userId);
const randomReviewers = otherSubmissions
.sort(() => Math.random() - 0.5)
.slice(0, reviewsPerSubmission);
for (const reviewer of randomReviewers) {
await db.peerReviews.create({
submissionId: submission.id,
reviewerId: reviewer.userId,
assignmentId: req.params.assignmentId,
status: 'pending',
});
}
}
res.json({ ok: true });
});
// Get assigned peer reviews
app.get('/api/my-peer-reviews', authenticate, async (req, res) => {
const reviews = await db.peerReviews.findByReviewer(req.user.id);
const populated = await Promise.all(
reviews.map(async (r) => ({
...r,
submission: await db.submissions.findById(r.submissionId),
assignment: await db.assignments.findById(r.assignmentId),
}))
);
res.json(populated);
});
Inline Comments and Feedback
function SubmissionReview({ submission, reviewRubric, onSave }) {
const [lineComments, setLineComments] = useState<Map<number, string>>(new Map());
const [rubricScores, setRubricScores] = useState<Record<string, number>>({});
const [overallFeedback, setOverallFeedback] = useState('');
const lines = submission.content.split('\n');
const handleAddComment = (lineNum: number, text: string) => {
setLineComments(new Map(lineComments).set(lineNum, text));
};
const handleSave = async () => {
await fetch(`/api/submissions/${submission.id}/review`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rubricScores,
lineComments: Array.from(lineComments.entries()).map(([line, text]) => ({
line, text,
})),
overallFeedback,
}),
});
onSave?.();
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
<div className="bg-gray-100 p-4 rounded-lg space-y-2 font-mono text-sm">
{lines.map((line, i) => (
<div key={i} className="relative group flex gap-2">
<span className="text-gray-500 w-8 text-right">{i + 1}</span>
<span className="flex-1">{line}</span>
<button
onClick={() => {
const comment = prompt('Add comment');
if (comment) handleAddComment(i, comment);
}}
className="opacity-0 group-hover:opacity-100 text-xs text-blue-600 hover:underline"
>
Comment
</button>
{lineComments.has(i) && (
<div className="absolute left-0 top-full mt-1 bg-yellow-100 p-2 rounded text-xs">
{lineComments.get(i)}
</div>
)}
</div>
))}
</div>
<div>
<h3 className="font-semibold mb-4">Rubric Evaluation</h3>
{reviewRubric.map((criterion) => (
<div key={criterion.id} className="mb-3">
<label className="text-sm">{criterion.name}</label>
<select
value={rubricScores[criterion.id] || 0}
onChange={(e) => setRubricScores({
...rubricScores,
[criterion.id]: Number(e.target.value),
})}
className="border rounded px-2 py-1"
>
{[...Array(criterion.points + 1)].map((_, i) => (
<option key={i} value={i}>{i}/{criterion.points}</option>
))}
</select>
</div>
))}
</div>
<div>
<label className="block text-sm font-medium mb-2">Overall Feedback</label>
<textarea
value={overallFeedback}
onChange={(e) => setOverallFeedback(e.target.value)}
rows={6}
className="w-full border rounded px-3 py-2"
placeholder="Provide constructive feedback..."
/>
</div>
<button
onClick={handleSave}
className="w-full bg-green-600 text-white rounded-lg py-2 font-medium"
>
Submit Review
</button>
</div>
);
}
Timeframe
Basic peer review system with rubric scores — 1.5 weeks. With line comments, revision tracking, and multi-step workflow — 2.5–3 weeks.







