lomda-hub/frontend/src/pages/CoursesPage.tsx
2026-02-04 16:50:33 +02:00

64 lines
2.9 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import { Box, Button, Card, CardContent, Chip, LinearProgress, Stack, Typography } from "@mui/material";
import { listCourses, enroll, getProgress } from "../api/client";
import { CourseOut, ProgressOut } from "../api/types";
export default function CoursesPage() {
const [courses, setCourses] = useState<CourseOut[]>([]);
const [progress, setProgress] = useState<ProgressOut | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
listCourses().then(setCourses).catch((err) => setError(err.message));
getProgress().then(setProgress).catch(() => null);
}, []);
const progressMap = useMemo(() => {
const map: Record<number, { percent: number; status: string }> = {};
progress?.courses.forEach((c) => {
map[c.course_id] = { percent: c.percent, status: c.status };
});
return map;
}, [progress]);
return (
<Box>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 3 }}>
<Typography variant="h4">Courses</Typography>
{progress && <Chip label="Progress tracked" color="primary" variant="outlined" />}
</Stack>
{error && <Typography color="error">{error}</Typography>}
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 2 }}>
{courses.map((course) => {
const info = progressMap[course.id] || { percent: 0, status: "" };
const isCompleted = info.percent >= 100 || info.status === "passed";
return (
<Card key={course.id} sx={{ height: "100%" }}>
<CardContent>
<Stack spacing={1.5}>
<Typography variant="h6">{course.title}</Typography>
<Typography variant="body2" color="text.secondary">{course.description}</Typography>
<LinearProgress variant="determinate" value={info.percent} />
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="caption">{Math.round(info.percent)}% complete</Typography>
{isCompleted && <Chip label="Completed" color="success" size="small" />}
</Stack>
<Stack direction="row" spacing={1}>
<Button component={RouterLink} to={`/courses/${course.id}`} variant="contained">Open</Button>
<Button variant="outlined" onClick={async () => {
await enroll(course.id);
const updated = await getProgress();
setProgress(updated);
}}>Enroll</Button>
</Stack>
</Stack>
</CardContent>
</Card>
);
})}
</Box>
</Box>
);
}