292 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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é - {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>
 | ||
|   );
 | ||
| }
 | 
