first commit
This commit is contained in:
110
app/routes/subjects.tsx
Normal file
110
app/routes/subjects.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { ProtectedRoute } from "~/components/ProtectedRoute";
|
||||
import { SubjectCard } from "~/components/SubjectCard";
|
||||
import { LoadingSpinner } from "~/components/LoadingSpinner";
|
||||
import { Button } from "~/components/Button";
|
||||
import { api } from "~/api/client";
|
||||
import type { Subject, Grade } from "~/types/api";
|
||||
import { calculateSubjectAverage } from "~/utils/gradeCalculations";
|
||||
|
||||
export default function SubjectsList() {
|
||||
const [subjects, setSubjects] = useState<Subject[]>([]);
|
||||
const [grades, setGrades] = useState<Grade[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [subjectsData, gradesData] = await Promise.all([
|
||||
api.getSubjects(),
|
||||
api.getGrades(),
|
||||
]);
|
||||
setSubjects(subjectsData);
|
||||
setGrades(gradesData);
|
||||
} catch (error) {
|
||||
console.error("Failed to load subjects:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (subjectId: string) => {
|
||||
if (!confirm("Are you sure? This will delete all grades for this subject.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteSubject(subjectId);
|
||||
setSubjects(subjects.filter((s) => s._id !== subjectId));
|
||||
setGrades(grades.filter((g) => g.subject_id !== subjectId));
|
||||
} catch (error) {
|
||||
alert("Failed to delete subject");
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<LoadingSpinner size="lg" />
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
||||
// Use the first subject's grading system as the primary system
|
||||
const primaryGradeSystem = subjects[0]?.grade_system || "percentage";
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<Link to="/dashboard" className="text-blue-600 hover:text-blue-700 text-sm">
|
||||
← Back to Dashboard
|
||||
</Link>
|
||||
<h1 className="text-2xl font-bold text-gray-900 mt-1">Manage Subjects</h1>
|
||||
</div>
|
||||
<Link to="/subjects/new">
|
||||
<Button>+ Add Subject</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{subjects.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{subjects.map((subject) => {
|
||||
const subjectGrades = grades.filter((g) => g.subject_id === subject._id);
|
||||
const avgData = calculateSubjectAverage(subject, subjectGrades);
|
||||
return (
|
||||
<SubjectCard
|
||||
key={subject._id}
|
||||
subject={subject}
|
||||
averageGrade={avgData.overallAverage}
|
||||
gradeSystem={primaryGradeSystem}
|
||||
onDelete={() => handleDelete(subject._id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow p-12 text-center">
|
||||
<p className="text-gray-600 mb-4">No subjects yet. Create your first subject!</p>
|
||||
<Link to="/subjects/new">
|
||||
<Button>+ Add Subject</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user