diff --git a/app/app.css b/app/app.css
index c4fe699..0885b4f 100644
--- a/app/app.css
+++ b/app/app.css
@@ -153,3 +153,26 @@ body {
[data-sonner-toaster][data-sonner-theme='dark'] [data-description] {
color: var(--primary) !important;
}
+
+:root {
+ --color-red-800: theme('colors.red.800');
+ --color-orange-800: theme('colors.orange.800');
+ --color-amber-800: theme('colors.amber.800');
+ --color-yellow-800: theme('colors.yellow.800');
+ --color-lime-800: theme('colors.lime.800');
+ --color-green-800: theme('colors.green.800');
+ --color-emerald-800: theme('colors.emerald.800');
+ --color-teal-800: theme('colors.teal.800');
+ --color-cyan-800: theme('colors.cyan.800');
+ --color-sky-800: theme('colors.sky.800');
+ --color-blue-800: theme('colors.blue.800');
+ --color-indigo-800: theme('colors.indigo.800');
+ --color-violet-800: theme('colors.violet.800');
+ --color-purple-800: theme('colors.purple.800');
+ --color-fuchsia-800: theme('colors.fuchsia.800');
+ --color-pink-800: theme('colors.pink.800');
+ --color-rose-800: theme('colors.rose.800');
+ --color-slate-800: theme('colors.slate.800');
+ --color-gray-800: theme('colors.gray.800');
+ --color-zinc-800: theme('colors.zinc.800');
+}
diff --git a/app/components/details/attachment.tsx b/app/components/details/attachment.tsx
index de4823e..bae3ba0 100644
--- a/app/components/details/attachment.tsx
+++ b/app/components/details/attachment.tsx
@@ -8,7 +8,7 @@ export default function AttachmentItem({ attachment }: { attachment: Attachment
target="_blank"
className="flex items-center gap-2 p-3 border rounded-md hover:bg-muted transition-colors cursor-pointer"
>
- {getIcon(attachment.name)}
+ {getIcon(attachment.path)}
{attachment.name}
diff --git a/app/components/grades/inde.tsx b/app/components/grades/inde.tsx
index a98cc32..3c08c91 100644
--- a/app/components/grades/inde.tsx
+++ b/app/components/grades/inde.tsx
@@ -320,9 +320,6 @@ export default function SchoolStatsPage() {
diff --git a/app/components/grades/index.tsx b/app/components/grades/index.tsx
index dcef0d9..f9e1004 100644
--- a/app/components/grades/index.tsx
+++ b/app/components/grades/index.tsx
@@ -7,50 +7,131 @@ import {
XAxis,
YAxis,
} from "recharts";
-import { useGrades, type User } from "~/lib/api";
+import { useAverages, useGrades, type User } from "~/lib/api";
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "../ui/chart";
-import { chartConfig, gradesTrendData } from "./inde";
+import { Award } from "lucide-react";
+import { getSubjectColor, getSubjectEmoji } from "~/lib/utils";
+import { useEffect, useState } from "react";
export default function GradesPage({ user }: { user: User }) {
// TODO: Error handling, loading state
- const { grades, subjects, isLoading, isError } = useGrades("YEAR")
+ const { grades, subjects, isLoading, isError } = useGrades("YEAR");
+ const {
+ subjectAverages,
+ globalAverage,
+ isLoading: loading,
+ isError: error,
+ } = useAverages("YEAR");
+
+ const [hiddenSubjects, setHiddenSubjects] = useState
([]);
+ const toggleSubjectVisibility = (subject: string) => {
+ setHiddenSubjects((prev) =>
+ prev.includes(subject)
+ ? prev.filter((s) => s !== subject)
+ : [...prev, subject]
+ );
+ };
+
+ const [min, setMin] = useState(0);
+ const [max, setMax] = useState(20);
+ useEffect(() => {
+ if (grades.length > 0) {
+ const filteredGrades = grades.map((o) =>
+ Object.entries(o)
+ .filter(([key, value]) => {
+ return !hiddenSubjects.includes(key) && !isNaN(parseFloat(value));
+ })
+ .map(([_, v]) => parseFloat(v))
+ .filter(Boolean)
+ );
+ setMin(getMin(filteredGrades));
+ setMax(getMax(filteredGrades));
+ }
+ }, [grades, hiddenSubjects]);
return (
-
+
{/* Tabs */}
+ {/* Global average */}
+
+
+
+ Moyenne générale
+
+
+
+
{globalAverage} / 20
+
+
{/* Charts - Mobile First Layout */}
{/* Grade Trends */}
-
Grade Trends
+
+ Votre progression
+
- performance across subjects
+ Suivez vos notes au fil du temps
-
-
-
-
-
-
- {/* TODO: Custom domain */}
-
- } />
-
- {subjects.map((subject, index) => (
-
- ))}
-
-
+
+
+
+
+
+ {/* TODO: Custom domain */}
+
+ } />
+
@@ -58,3 +139,21 @@ export default function GradesPage({ user }: { user: User }) {
);
}
+
+function getMin(grades: number[][]) {
+ return Math.max(
+ Math.round(
+ Math.min(...grades.map((g) => Math.min(...g)).filter(Boolean)) - 1
+ ),
+ 0
+ );
+}
+
+function getMax(grades: number[][]) {
+ return Math.min(
+ Math.round(
+ Math.max(...grades.map((g) => Math.max(...g)).filter(Boolean)) + 1
+ ),
+ 20
+ );
+}
diff --git a/app/lib/api.ts b/app/lib/api.ts
index 70eaf8b..082b643 100644
--- a/app/lib/api.ts
+++ b/app/lib/api.ts
@@ -404,22 +404,47 @@ export const useGrades = (period: string) => {
days: 3, // 3 days
}).toMillis(),
});
- const grades = data?.grades as PeriodResult[] || [];
- const subjects = data?.subjects as string[] || [];
+ const grades = (data?.grades as PeriodResult[]) || [];
+ const subjects = (data?.subjects as string[]) || [];
return {
grades,
subjects,
...props,
};
-}
+};
interface SubjectPerformance {
- subject: string
- average: number
+ subject: string;
+ average: number;
}
interface PeriodResult {
- period: string
- average: number
- subjectAverages: SubjectPerformance[]
+ period: string;
+ average: number;
}
+
+export const getAverages = async (period: string) => {
+ return makeRequest(
+ `/averages?period=${encodeURIComponent(period)}`,
+ "Échec de la récupération des moyennes"
+ );
+};
+export const useAverages = (period: string) => {
+ const { data, ...props } = useQuery({
+ queryKey: ["averages", period],
+ queryFn: () => getAverages(period),
+ staleTime: Duration.fromObject({
+ hours: 0, // 1 hour
+ }).toMillis(),
+ gcTime: Duration.fromObject({
+ days: 3, // 3 days
+ }).toMillis(),
+ });
+ const subjectAverages = (data?.subjectAverages as SubjectPerformance[]) || [];
+ const globalAverage = data?.globalAverage || 0;
+ return {
+ subjectAverages,
+ globalAverage,
+ ...props,
+ };
+};