frontend/app/components/home/index.tsx
2025-08-19 19:32:03 +02:00

292 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { DateTime } from "luxon";
import { useState } from "react";
import { Button } from "~/components/ui/button";
import {
ChevronLeft,
ChevronRight,
Star,
Users,
UserIcon,
SortAsc,
SortDesc,
} from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Tabs, TabsList, tabsStyle, TabsTrigger } from "~/components/ui/tabs";
import DatePickerWithRange from "~/components/home/date-picker";
import BottomNavigation from "~/components/bottom-nav";
import Error from "~/components/error";
import { useSearchParams } from "react-router";
import { useColles, type User } from "~/lib/api";
import TabContent from "~/components/home/tab-content";
import { MainLayout } from "~/layout";
import { forceReload } from "~/lib/utils";
import { SyncButton } from "../sync-status";
export default function Home({ user }: { user: User }) {
// Handle query parameters
const [query, setQuery] = useSearchParams();
const updateQuery = (key: string, value: string) => {
if (query.get(key) !== value) {
setQuery((prev) => {
const newQuery = new URLSearchParams(prev);
newQuery.set(key, value);
return newQuery;
});
}
};
// Tabs
const activeTab = query.get("view") || "you";
const setActiveTab = (tab: string) => updateQuery("view", tab);
// Date
const rawStartDate = query.get("start");
const startDate = rawStartDate
? DateTime.fromISO(rawStartDate, { zone: "local" })
: DateTime.now().startOf("week");
const setStartDate = (date: DateTime) =>
updateQuery("start", date.startOf("week").toISODate()!);
const handlePreviousWeek = () => {
const previousWeek = startDate.minus({ weeks: 1 });
setStartDate(previousWeek);
};
const handleNextWeek = () => {
const nextWeek = startDate.plus({ weeks: 1 });
setStartDate(nextWeek);
};
// Fetch colles from API
const {
studentColles,
classColles,
favoriteColles,
healthyUntil,
lastSync,
error,
isLoading,
} = useColles(startDate);
// Error handling (after all hooks)
if (error)
return (
<Error
title="Impossible de charger les colles"
message={error?.toString()}
code={500}
description="Une erreur s'est produite lors du chargement de la liste des colles."
/>
);
// TODO: FAVORITES
const useToggleStar = (auth: any) => {};
// Filter state
const rawSubject = query.get("subject");
const [subjectFilter, setSubjectFilter] = useState<string>(
rawSubject === "all" ? "" : rawSubject || ""
);
const setSubject = (subject: string) => {
updateQuery("subject", subject);
setSubjectFilter(subject);
};
const rawExaminer = query.get("examiner");
const [examinerFilter, setExaminerFilter] = useState<string>(
rawExaminer === "all" ? "" : rawExaminer || ""
);
const setExaminer = (examiner: string) => {
updateQuery("examiner", examiner);
setExaminerFilter(examiner);
};
const [sorted, setSort] = useState<string>(query.get("sort") || "desc");
const toggleSort = () => {
const newSort = sorted === "asc" ? "desc" : "asc";
updateQuery("sort", newSort);
setSort(newSort);
};
const generateFilter = (arr: string[], value: string) => {
const unique = [...new Set(arr)];
if (value && !unique.includes(value)) {
unique.push(value);
}
unique.sort((a, b) => a.localeCompare(b));
return unique;
};
const subjects = generateFilter(
classColles.map((colle) => colle.subject?.name),
subjectFilter
);
const examiners = generateFilter(
classColles.map((colle) => colle.examiner?.name),
examinerFilter
);
const applyFilters = (colles: any[]) => {
return colles
.filter((colle) => {
const subjectMatch =
subjectFilter === "all" || !subjectFilter
? true
: colle.subject?.name === subjectFilter;
const examinerMatch =
examinerFilter === "all" || !examinerFilter
? true
: colle.examiner?.name === examinerFilter;
return subjectMatch && examinerMatch;
})
.sort((a, b) => {
if (sorted === "asc") {
return a.date.localeCompare(b.date);
} else {
return b.date.localeCompare(a.date);
}
});
};
return (
<MainLayout
header={
<>
<h1 className="text-2xl font-bold" onClick={forceReload}>
Khollis&eacute; - {user.className}
</h1>
<SyncButton healthyUntil={healthyUntil} lastSync={lastSync} />
</>
}
>
<div className="space-y-6 pb-20 md:pb-0">
{/* Tabs */}
<Tabs
defaultValue="all"
value={activeTab}
onValueChange={setActiveTab}
className="max-w-md w-full"
>
<TabsList className="w-full p-0 bg-background justify-start border-b rounded-none">
<TabsTrigger value="you" className={tabsStyle}>
<UserIcon className="h-4 w-4" />
Vous
</TabsTrigger>
{/* <TabsTrigger value="favorites" className={tabsStyle}>
<Star className="h-4 w-4" />
Favoris
</TabsTrigger> */}
<TabsTrigger value="class" className={tabsStyle}>
<Users className="h-4 w-4" />
Classe
</TabsTrigger>
</TabsList>
</Tabs>
{/* Week Navigation */}
<div className="mb-0">
<div className="flex flex-row items-center justify-between gap-2">
<Button variant="outline" size="sm" onClick={handlePreviousWeek}>
<ChevronLeft className="h-10 w-10" />
</Button>
<div className="flex-1">
<DatePickerWithRange
startDate={startDate}
setStartDate={setStartDate}
/>
</div>
<Button variant="outline" size="sm" onClick={handleNextWeek}>
<ChevronRight className="h-10 w-10" />
</Button>
</div>
</div>
{/* Filter component */}
<div className="flex gap-2 pb-0 pt-2">
<Select value={subjectFilter} onValueChange={setSubject}>
<SelectTrigger className="rounded-full data-[placeholder]:text-primary">
<SelectValue placeholder="Matière" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toutes</SelectItem>
{subjects.map((subject) => (
<SelectItem key={subject} value={subject}>
{subject}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={examinerFilter} onValueChange={setExaminer}>
<SelectTrigger className="rounded-full data-[placeholder]:text-primary">
<SelectValue placeholder="Colleur" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Tous</SelectItem>
{examiners.map((examiner) => (
<SelectItem key={examiner} value={examiner}>
{examiner}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="outline"
size="sm"
className="rounded-full dark:bg-input/30 text-primary font-normal"
onClick={toggleSort}
>
{sorted == "asc" ? (
<SortAsc className="h-5 w-5" />
) : (
<SortDesc className="h-5 w-5" />
)}
Trier
</Button>
</div>
{/* Your Colles Tab */}
{activeTab === "you" && (
<TabContent
tabTitle="Vos colles"
emptyCollesText="Vous n'avez pas encore de colle cette semaine."
isLoading={isLoading}
isSorted={sorted === "desc"}
colles={applyFilters(studentColles)}
preferences={user.preferences}
/>
)}
{/* Favorites Tab
{activeTab === "favorites" && (
<TabContent
tabTitle="Vos favoris"
emptyCollesText="Vous n'avez pas encore de colle favorite, cliquez sur l'étoile pour ajouter une colle à vos favoris."
isLoading={isLoading}
colles={favoriteColles}
/>
)} */}
{/* Class Colles Tab */}
{activeTab === "class" && (
<TabContent
tabTitle="Les colles de la classe"
emptyCollesText="Aucune colle trouvée."
isLoading={isLoading}
isSorted={sorted === "desc"}
colles={applyFilters(classColles)}
preferences={user.preferences}
/>
)}
{/* Bottom Navigation for Mobile */}
<BottomNavigation activeId="colles" />
</div>
</MainLayout>
);
}