178 lines
5.6 KiB
TypeScript
178 lines
5.6 KiB
TypeScript
import type React from "react";
|
|
import type { Colle } from "~/lib/api";
|
|
|
|
import { DateTime } from "luxon";
|
|
import { Link, useNavigate } from "react-router";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardFooter,
|
|
} from "~/components/ui/card";
|
|
import { User, UserCheck, Paperclip, Star, MapPinHouse } from "lucide-react";
|
|
import { Badge } from "~/components/ui/badge";
|
|
import { Button } from "~/components/ui/button";
|
|
import { titleCase } from "~/lib/utils";
|
|
|
|
// TODO: Preferences for subject colors
|
|
const getSubjectColor = (_: string) => {
|
|
// Mock placeholder function
|
|
return "bg-blue-100 text-blue-800"; // Default color
|
|
};
|
|
const getSubjectEmoji = (_: string) => {
|
|
// Mock placeholder function
|
|
return "📚"; // Default emoji
|
|
};
|
|
|
|
type ColleCardProps = {
|
|
colle: Colle;
|
|
onToggleFavorite: (id: number, favorite: boolean) => void;
|
|
beforeClick: () => void;
|
|
isFavorite: boolean;
|
|
};
|
|
|
|
export default function ColleCard({
|
|
colle,
|
|
onToggleFavorite,
|
|
beforeClick,
|
|
isFavorite,
|
|
}: ColleCardProps) {
|
|
const navigate = useNavigate();
|
|
|
|
// TODO: Remove this if scroll restoration is not needed (test first)
|
|
const handleCardClick = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
beforeClick();
|
|
setTimeout(() => navigate(`/colles/${colle.id}`), 100);
|
|
};
|
|
|
|
// TODO: Favorites
|
|
const handleToggleFavorite = (e: React.MouseEvent) => {
|
|
e.stopPropagation(); // Prevent card click
|
|
e.preventDefault();
|
|
const newValue = !isFavorite;
|
|
onToggleFavorite(colle.id, newValue);
|
|
};
|
|
|
|
const subjectColor = getSubjectColor(colle.subject.name);
|
|
const subjectEmoji = getSubjectEmoji(colle.subject.name);
|
|
|
|
return (
|
|
<Link to={`/colles/${colle.id}`} onClick={handleCardClick}>
|
|
<Card
|
|
id={`colle-${colle.id}`}
|
|
className="h-full cursor-pointer hover:shadow-md transition-shadow border-primary"
|
|
>
|
|
<CardHeader className="pb-0 pt-3 px-4 flex flex-row items-center justify-between">
|
|
<div>
|
|
<div className="font-medium">{formatDate(colle.date)}</div>
|
|
<div className="font-normal text-sm text-muted-foreground">
|
|
{formatTime(colle.date)}
|
|
</div>
|
|
</div>
|
|
{colle.grade && (
|
|
<div className="flex items-center gap-2 h-full mb-3">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className={`h-8 w-8 ${
|
|
isFavorite ? "text-yellow-500" : "text-muted-foreground"
|
|
}`}
|
|
onClick={handleToggleFavorite}
|
|
>
|
|
<Star
|
|
className={`h-5 w-5 ${isFavorite ? "fill-yellow-500" : ""}`}
|
|
/>
|
|
<span className="sr-only">Ajouter aux favoris</span>
|
|
</Button>
|
|
<div
|
|
className={`px-2 py-1 rounded-md text-sm font-medium ${subjectColor}`}
|
|
>
|
|
{formatGrade(colle.grade)}/20
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardHeader>
|
|
|
|
<CardContent className="pb-0 pt-0 px-4" onClick={handleCardClick}>
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">{colle.student.fullName}</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<UserCheck className="h-4 w-4 text-muted-foreground" />
|
|
<span>{colle.examiner.name}</span>
|
|
</div>
|
|
|
|
{colle.room && (
|
|
<div className="flex items-center gap-2">
|
|
<MapPinHouse className="h-4 w-4 text-muted-foreground" />
|
|
<span>{colle.room.name}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
|
|
<CardFooter className="pt-1 pb-3 px-4 flex flex-col items-start">
|
|
<div className="w-full flex justify-between items-center">
|
|
<div className="flex items-center gap-1">
|
|
<Badge className={subjectColor}>
|
|
{colle.subject.name + " " + subjectEmoji}
|
|
</Badge>
|
|
{isFavorite && (
|
|
<Badge variant="secondary">
|
|
<Star className="h-3 w-3 fill-yellow-500 text-yellow-500 mr-1" />
|
|
Favori
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
{/* TODO: Attachments */}
|
|
{colle.attachmentsCount > 0 && (
|
|
<div className="flex items-center gap-1 text-muted-foreground">
|
|
<Paperclip className="h-3.5 w-3.5" />
|
|
<span className="text-xs">{colle.attachmentsCount}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardFooter>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
const formatDate = (date: string) => {
|
|
const dt = DateTime.fromISO(date).setLocale("fr");
|
|
const str = dt.toLocaleString({
|
|
weekday: "long",
|
|
day: "numeric",
|
|
month: "long",
|
|
year: "numeric",
|
|
});
|
|
return titleCase(str);
|
|
};
|
|
|
|
const formatTime = (date: string) => {
|
|
const dt = DateTime.fromISO(date).setLocale("fr");
|
|
return dt.toLocaleString({
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
};
|
|
|
|
const formatGrade = (grade?: number) => {
|
|
if (grade === undefined || grade === null || grade < 0 || grade > 20)
|
|
return "N/A";
|
|
|
|
const rounded = Math.round(grade * 10) / 10;
|
|
const str =
|
|
rounded % 1 === 0
|
|
? rounded.toFixed(0) // no decimals if .0
|
|
: rounded.toFixed(1); // one decimal otherwise
|
|
|
|
return str.replace(".", ",").padStart(2, "0"); // pad with zero if needed
|
|
};
|