feat: implement structured grading system with predefined assessment types

- Add structured grading policy with predefined types (Continuous Assessment, Midterm, Final Test, Exam)
- Replace free-text category input with combobox selection in Grading Policy page
- Update Lesson Editor to use dropdown selector for grading category assignment
- Fix create_grading_category handler to capture organization context
- Fix update_course handler to set audit context in database transaction
- Implement getImageUrl helper for proper asset path resolution
- Add unoptimized prop to organization logo images to bypass Next.js optimization
- Add database migrations for organization_id in content tables
- Seed default tutorial courses for Admin, Instructor, and Student roles
- Fix audit log constraints and content schema issues
This commit is contained in:
2026-01-12 00:52:26 -03:00
parent 3ddcaaaf15
commit 942780db1c
19 changed files with 476 additions and 92 deletions
@@ -193,7 +193,7 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6 border-b border-white/5 pb-8">
<div className="space-y-1">
<div className="flex items-center gap-2 text-[10px] text-blue-500 font-bold uppercase tracking-[0.2em]">
<Link href={`/ courses / ${params.id} `} className="hover:text-white transition-colors">Outline</Link>
<Link href={`/courses/${params.id}`} className="hover:text-white transition-colors">Outline</Link>
<span className="text-gray-700">/</span>
<span>Activity</span>
</div>
@@ -265,28 +265,24 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
{isGraded && (
<>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 animate-in zoom-in-95 duration-300">
{gradingCategories.length === 0 ? (
<div className="col-span-full py-4 text-center border border-dashed border-white/10 rounded-2xl text-xs text-gray-500 italic">
No grading categories defined. <Link href={`/ courses / ${params.id} /grading`} className="text-blue-400 underline ml-1">Go to Grading Policy</Link >
</div >
) : (
gradingCategories.map((cat) => (
<button
key={cat.id}
onClick={() => setSelectedCategoryId(cat.id)}
className={`p-4 rounded-2xl border transition-all text-left group ${selectedCategoryId === cat.id
? "bg-blue-500/10 border-blue-500 text-blue-400"
: "bg-white/5 border-white/10 text-gray-500 hover:border-white/20"
}`}
>
<div className="text-[10px] font-bold uppercase tracking-widest opacity-60 mb-1">Category</div>
<div className="font-bold truncate">{cat.name}</div>
<div className="text-xs mt-2 font-medium opacity-80">{cat.weight}% Weight</div>
</button>
))
)}
</div >
<div className="col-span-full space-y-2">
<span className="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-2 block">Assessment Category</span>
<select
value={selectedCategoryId}
onChange={(e) => setSelectedCategoryId(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-blue-500 transition-all appearance-none font-bold"
>
<option value="" className="bg-gray-900 border-0">Select Category...</option>
{gradingCategories.map((cat) => (
<option key={cat.id} value={cat.id} className="bg-gray-900 border-0">
{cat.name} ({cat.weight}%)
</option>
))}
</select>
<div className="text-[10px] text-gray-500 italic mt-1 pl-1">
Manage categories in <Link href={`/courses/${params.id}/grading`} className="text-blue-400 hover:underline">Grading Policy</Link>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pt-6 border-t border-white/5 animate-in fade-in duration-500">
<div className="space-y-4">