150 lines
6.1 KiB
TypeScript
150 lines
6.1 KiB
TypeScript
import type { Grade, Subject } from "~/types/api";
|
|
import { formatGrade } from "~/utils/gradeCalculations";
|
|
|
|
interface GradeTableProps {
|
|
grades: Grade[];
|
|
subject?: Subject;
|
|
onEdit?: (grade: Grade) => void;
|
|
onDelete?: (gradeId: string) => void;
|
|
deletingGradeId?: string | null;
|
|
}
|
|
|
|
export function GradeTable({ grades, subject, onEdit, onDelete, deletingGradeId }: GradeTableProps) {
|
|
if (grades.length === 0) {
|
|
return (
|
|
<div className="text-center py-8 text-gray-500">
|
|
No grades recorded yet. Add your first grade!
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full border-collapse">
|
|
<thead>
|
|
<tr className="bg-gray-50 border-b">
|
|
<th className="text-left py-3 px-4 font-semibold text-gray-700">Date</th>
|
|
<th className="text-left py-3 px-4 font-semibold text-gray-700">Name</th>
|
|
<th className="text-left py-3 px-4 font-semibold text-gray-700">Category</th>
|
|
<th className="text-right py-3 px-4 font-semibold text-gray-700">Grade</th>
|
|
<th className="text-right py-3 px-4 font-semibold text-gray-700">Percentage</th>
|
|
<th className="text-right py-3 px-4 font-semibold text-gray-700">Weight</th>
|
|
<th className="text-right py-3 px-4 font-semibold text-gray-700">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{grades.map((grade) => {
|
|
const percentage = (grade.grade / grade.max_grade) * 100;
|
|
const category = subject?.grading_categories.find(
|
|
(c) => c.name === grade.category_name
|
|
);
|
|
|
|
return (
|
|
<tr key={grade._id} className="border-b hover:bg-gray-50">
|
|
<td className="py-3 px-4 text-sm text-gray-600">
|
|
{new Date(grade.date).toLocaleDateString()}
|
|
</td>
|
|
<td className="py-3 px-4">
|
|
<div className="font-medium">{grade.name || "Unnamed"}</div>
|
|
{grade.notes && (
|
|
<div className="text-sm text-gray-500">{grade.notes}</div>
|
|
)}
|
|
</td>
|
|
<td className="py-3 px-4">
|
|
<span
|
|
className="px-2 py-1 text-xs font-medium rounded-full"
|
|
style={{
|
|
backgroundColor: category?.color || "#e5e7eb",
|
|
color: "#1f2937",
|
|
}}
|
|
>
|
|
{grade.category_name}
|
|
</span>
|
|
</td>
|
|
<td className="py-3 px-4 text-right font-medium">
|
|
{grade.grade} / {grade.max_grade}
|
|
</td>
|
|
<td className="py-3 px-4 text-right font-semibold">
|
|
{formatGrade(percentage)}%
|
|
</td>
|
|
<td className="py-3 px-4 text-right text-sm text-gray-600">
|
|
{grade.weight_in_category}x
|
|
</td>
|
|
<td className="py-3 px-4 text-right">
|
|
<div className="flex justify-end gap-2">
|
|
{onEdit && (
|
|
<button
|
|
onClick={() => onEdit(grade)}
|
|
className="text-blue-500 hover:text-blue-700"
|
|
aria-label="Edit grade"
|
|
>
|
|
<svg
|
|
className="w-4 h-4"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
{onDelete && (
|
|
<button
|
|
onClick={() => onDelete(grade._id)}
|
|
className="text-red-500 hover:text-red-700 disabled:opacity-50"
|
|
aria-label="Delete grade"
|
|
disabled={deletingGradeId === grade._id}
|
|
>
|
|
{deletingGradeId === grade._id ? (
|
|
<svg
|
|
className="w-4 h-4 animate-spin"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
) : (
|
|
<svg
|
|
className="w-4 h-4"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
/>
|
|
</svg>
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|