Розроблення системи домашніх завдань у LMS
Система домашніх завдань — для завдань з відправленням файлів, дедлайнів, обробки запізнілих відправлень, рубрик оцінювання та зворотного зв'язку. Викладачі створюють шаблони завдань, учні відправляють роботи, викладачі оцінюють з коментарями.
Модель завдання
interface Assignment {
id: string;
lessonId: string;
title: string;
description: string;
dueDate: Date;
maxScore: number;
rubric?: Array<{ criterion: string; points: number }>;
allowLateSubmission: boolean;
lateDeductionPercent?: number;
}
interface Submission {
id: string;
assignmentId: string;
userId: string;
files: Array<{ name: string; url: string; size: number }>;
submittedAt: Date;
isLate: boolean;
grade?: number;
feedback?: string;
rubricScores?: Record<string, number>;
}
API створення завдання
app.post('/api/assignments', authenticate, async (req, res) => {
const { lessonId, title, description, dueDate, maxScore, rubric } = req.body;
const assignment = await db.assignments.create({
lessonId,
title,
description,
dueDate: new Date(dueDate),
maxScore,
rubric: rubric || [],
createdBy: req.user.id,
});
res.json(assignment);
});
// Відправлення учня
app.post('/api/assignments/:assignmentId/submit', authenticate, upload.array('files'), async (req, res) => {
const assignment = await db.assignments.findById(req.params.assignmentId);
const existingSubmission = await db.submissions.findByUserAndAssignment(
req.user.id, req.params.assignmentId
);
if (existingSubmission && !assignment.allowResubmission) {
return res.status(409).json({ error: 'Already submitted' });
}
const now = new Date();
const isLate = now > new Date(assignment.dueDate);
const files = await Promise.all(
req.files?.map(async (f) => ({
name: f.originalname,
url: await uploadToStorage(f),
size: f.size,
})) || []
);
const submission = await db.submissions.create({
assignmentId: req.params.assignmentId,
userId: req.user.id,
files,
submittedAt: now,
isLate,
});
res.json(submission);
});
Інтерфейс оцінювання
function GradingPanel({ submission, assignment, onSave }) {
const [rubricScores, setRubricScores] = useState<Record<string, number>>({});
const [feedback, setFeedback] = useState(submission.feedback || '');
const [grade, setGrade] = useState(submission.grade || 0);
const handleSave = async () => {
await fetch(`/api/submissions/${submission.id}/grade`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grade,
rubricScores,
feedback,
}),
});
onSave?.();
};
return (
<div className="max-w-2xl mx-auto p-6 space-y-6">
<div>
<h3 className="font-semibold mb-4">Файли відправлення</h3>
{submission.files.map(f => (
<a key={f.url} href={f.url} className="block text-blue-600 hover:underline">
{f.name}
</a>
))}
</div>
{assignment.rubric?.length > 0 && (
<div>
<h3 className="font-semibold mb-4">Рубрика</h3>
{assignment.rubric.map(criterion => (
<div key={criterion.criterion} className="mb-3">
<label className="text-sm text-gray-600">{criterion.criterion} (макс {criterion.points})</label>
<input
type="number"
min="0"
max={criterion.points}
value={rubricScores[criterion.criterion] || 0}
onChange={(e) => setRubricScores({
...rubricScores,
[criterion.criterion]: Number(e.target.value),
})}
className="w-24 border rounded px-2 py-1"
/>
</div>
))}
</div>
)}
<div>
<label className="block text-sm font-medium mb-2">Заключна оцінка</label>
<input
type="number"
min="0"
max={assignment.maxScore}
value={grade}
onChange={(e) => setGrade(Number(e.target.value))}
className="w-24 border rounded px-2 py-1"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Зворотний зв'язок</label>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
rows={6}
className="w-full border rounded px-3 py-2"
/>
</div>
<button
onClick={handleSave}
className="w-full bg-blue-600 text-white rounded-lg py-2 font-medium hover:bg-blue-700"
>
Зберегти оцінку
</button>
</div>
);
}
Обробка запізнілих відправлень
async function calculateGrade(submission: Submission, assignment: Assignment): Promise<number> {
let finalGrade = submission.grade || 0;
if (submission.isLate && assignment.lateDeductionPercent) {
finalGrade = finalGrade * (1 - assignment.lateDeductionPercent / 100);
}
return Math.round(finalGrade * 100) / 100;
}
Строки виконання
Базова відправка завдання та оцінювання — 1 тиждень. З рубриками, штрафами за запізнення та детальним зворотним зв'язком — 2–3 тижні.







