first commit
This commit is contained in:
114
app/components/SubjectCard.tsx
Normal file
114
app/components/SubjectCard.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { Subject } from "~/types/api";
|
||||
import { Link } from "react-router";
|
||||
import { formatGradeBySystem, type GradeSystem } from "~/utils/gradeSystems";
|
||||
import { calculateTargetProgress, getTargetStatusBgColor } from "~/utils/gradeCalculations";
|
||||
|
||||
interface SubjectCardProps {
|
||||
subject: Subject;
|
||||
averageGrade?: number;
|
||||
calculatedAverage?: number;
|
||||
gradeSystem?: string;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export function SubjectCard({ subject, averageGrade, calculatedAverage, gradeSystem, onDelete }: SubjectCardProps) {
|
||||
// Use provided gradeSystem, fallback to subject's own system
|
||||
const displaySystem = (gradeSystem || subject.grade_system || "percentage") as GradeSystem;
|
||||
|
||||
// Calculate target progress if grade is available
|
||||
const targetProgress = averageGrade !== undefined ? calculateTargetProgress(subject.target_grade ?? averageGrade, subject) : null;
|
||||
const hasTaget = targetProgress && targetProgress.status !== "no-target";
|
||||
|
||||
const isOverridden = calculatedAverage !== undefined && averageGrade !== undefined && Math.abs(calculatedAverage - averageGrade) > 0.01;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`p-6 rounded-lg border-2 hover:shadow-lg transition-shadow ${hasTaget ? getTargetStatusBgColor(targetProgress.status) : ""}`}
|
||||
style={{ borderColor: subject.color || "#3b82f6" }}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<Link to={`/subjects/${subject._id}`} className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-gray-900 hover:text-blue-600">
|
||||
{subject.name}
|
||||
</h3>
|
||||
{subject.teacher && (
|
||||
<p className="text-sm text-gray-600 mt-1">{subject.teacher}</p>
|
||||
)}
|
||||
</Link>
|
||||
{onDelete && (
|
||||
<button
|
||||
onClick={onDelete}
|
||||
className="text-red-500 hover:text-red-700 ml-2"
|
||||
aria-label="Delete subject"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
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>
|
||||
|
||||
{averageGrade !== undefined && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-3xl font-bold" style={{ color: subject.color || "#3b82f6" }}>
|
||||
{formatGradeBySystem(averageGrade, displaySystem, 1)}
|
||||
{isOverridden && (
|
||||
<span className="text-gray-500 text-lg font-normal ml-2">
|
||||
({formatGradeBySystem(calculatedAverage!, displaySystem, 1)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">Current Average</p>
|
||||
</div>
|
||||
{hasTaget && (
|
||||
<div className="text-right">
|
||||
<div className="text-sm text-gray-600">Target: {formatGradeBySystem(targetProgress.targetPercentage, displaySystem, 1)}</div>
|
||||
<div className="text-xs font-semibold mt-1">
|
||||
{targetProgress.status === "above" && (
|
||||
<span className="text-green-700">✓ Above Target</span>
|
||||
)}
|
||||
{targetProgress.status === "near" && (
|
||||
<span className="text-yellow-700">→ Near Target</span>
|
||||
)}
|
||||
{targetProgress.status === "below" && (
|
||||
<span className="text-red-700">↓ Below Target</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-700">Grading Categories:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{subject.grading_categories.map((category, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 text-xs font-medium rounded-full"
|
||||
style={{
|
||||
backgroundColor: category.color || "#e5e7eb",
|
||||
color: "#1f2937",
|
||||
}}
|
||||
>
|
||||
{category.name} ({category.weight}%)
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user