frontend/app/components/home/index.tsx
Nathan Lamy 52317bdf97
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m16s
fix: ui too large on mobile
2026-03-05 18:08:34 +01:00

325 lines
10 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,
CalendarClock,
} from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Tabs, TabsList, tabsStyle, TabsTrigger } from "~/components/ui/tabs";
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";
import WeekNavigation from "./week-navigation";
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()!);
// Fetch colles from API
const {
studentColles,
classColles,
favoriteColles,
upcomingClassColles,
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 == "all" ? "" : subject);
};
const rawExaminer = query.get("examiner");
const [examinerFilter, setExaminerFilter] = useState<string>(
rawExaminer === "all" ? "" : rawExaminer || ""
);
const setExaminer = (examiner: string) => {
updateQuery("examiner", examiner);
setExaminerFilter(examiner == "all" ? "" : examiner);
};
const rawStudent = query.get("student");
const [studentFilter, setStudentFilter] = useState<string>(
rawStudent === "all" ? "" : rawStudent || ""
);
const setStudent = (student: string) => {
updateQuery("student", student);
setStudentFilter(student == "all" ? "" : student);
};
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.filter(Boolean))];
if (value && !unique.includes(value)) {
unique.push(value);
}
unique.sort((a, b) => a.localeCompare(b));
return unique;
};
const currentClassColles =
activeTab === "upcoming" ? upcomingClassColles : classColles;
const subjects = generateFilter(
currentClassColles.map((colle) => colle.subject?.name),
subjectFilter
);
const examiners = generateFilter(
currentClassColles.map((colle) => colle.examiner?.name),
examinerFilter
);
const students = generateFilter(
upcomingClassColles.map((colle) => colle.student?.fullName),
studentFilter
);
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;
const studentMatch =
activeTab !== "upcoming" || studentFilter === "all" || !studentFilter
? true
: colle.student?.fullName === studentFilter;
return subjectMatch && examinerMatch && studentMatch;
})
.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="you"
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>
<TabsTrigger value="upcoming" className={tabsStyle}>
<CalendarClock className="h-4 w-4" />
À venir
</TabsTrigger>
</TabsList>
</Tabs>
{/* Week Navigation */}
{activeTab !== "upcoming" && (
<WeekNavigation startDate={startDate} setStartDate={setStartDate} />
)}
{/* Filter component */}
<div className="flex gap-2 pb-0 pt-2">
{activeTab !== "upcoming" && (
<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>
{activeTab === "upcoming" && (
<Select value={studentFilter} onValueChange={setStudent}>
<SelectTrigger className="rounded-full data-[placeholder]:text-primary">
<SelectValue placeholder="Élève" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Tous</SelectItem>
{students.map((student) => (
<SelectItem key={student} value={student}>
{student}
</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}
/>
)}
{/* Upcoming Colles Tab */}
{activeTab === "upcoming" && (
<TabContent
tabTitle="Toutes les colles à venir"
emptyCollesText="Aucune colle à venir n'a été trouvée."
isLoading={isLoading}
isSorted={sorted === "desc"}
colles={applyFilters(upcomingClassColles)}
preferences={user.preferences}
/>
)}
{/* Bottom Navigation for Mobile */}
<BottomNavigation activeId="colles" />
</div>
</MainLayout>
);
}