425 lines
8.9 KiB
TypeScript
425 lines
8.9 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import { DateTime, Duration } from "luxon";
|
|
|
|
const BASE_URL = import.meta.env.VITE_API_URL;
|
|
|
|
const makePostRequest = async (
|
|
url: string,
|
|
body: object,
|
|
error = "Une erreur est survenue",
|
|
method = "POST"
|
|
) => {
|
|
const response = await fetch(BASE_URL + url, {
|
|
method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
body: JSON.stringify(body),
|
|
credentials: "include", // Include cookies for authentication
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (!response.ok) throw new Error(data.error || error);
|
|
return data?.data || data;
|
|
};
|
|
|
|
const makeRequest = async (url: string, error = "Une erreur est survenue") => {
|
|
const response = await fetch(BASE_URL + url, { credentials: "include" });
|
|
|
|
const data = await response.json();
|
|
if (!response.ok) throw new Error(data.error || error);
|
|
return data?.data || data;
|
|
};
|
|
|
|
/**
|
|
* === AUTH API ===
|
|
*/
|
|
export const requestLogin = async (email: string, token: string) => {
|
|
return makePostRequest(
|
|
"/auth/request",
|
|
{ email, token },
|
|
"Échec de la demande de connexion"
|
|
);
|
|
};
|
|
|
|
export const verifyOtp = async ({
|
|
otpCode,
|
|
email,
|
|
}: {
|
|
otpCode: string;
|
|
email: string;
|
|
}) => {
|
|
return makePostRequest(
|
|
"/auth/verify",
|
|
{ email, code: otpCode },
|
|
"Code de vérification invalide"
|
|
);
|
|
};
|
|
|
|
export const getClasses = async () => {
|
|
try {
|
|
const res = await fetch("/classes.json");
|
|
return res.json();
|
|
} catch (error) {
|
|
console.error("Error fetching classes:", error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const registerUser = async (
|
|
name: string,
|
|
className: string,
|
|
token: string
|
|
) => {
|
|
return makePostRequest(
|
|
"/auth/register",
|
|
{ name, className, token },
|
|
"Échec de l'inscription"
|
|
);
|
|
};
|
|
|
|
export const getNames = async (className: string) => {
|
|
return makeRequest(
|
|
`/auth/autocomplete?className=${encodeURIComponent(className)}`,
|
|
"Échec de la récupération des noms"
|
|
);
|
|
};
|
|
|
|
/**
|
|
* === USER API ===
|
|
*/
|
|
const fetchUser = async () => {
|
|
return makeRequest(
|
|
"/users/@me",
|
|
"Échec de la récupération des informations utilisateur"
|
|
);
|
|
};
|
|
|
|
const defaultUser = {
|
|
id: 0,
|
|
firstName: "",
|
|
lastName: "",
|
|
fullName: "",
|
|
email: "",
|
|
className: "",
|
|
preferences: [] as UserPreferences,
|
|
};
|
|
|
|
export type User = typeof defaultUser;
|
|
|
|
export const AUTH_ERROR = "Unauthorized access";
|
|
|
|
export const useUser = () => {
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["user"],
|
|
queryFn: fetchUser,
|
|
staleTime: Duration.fromObject({
|
|
minutes: 5, // 5 minutes
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
});
|
|
return {
|
|
user: (data ? Object.assign(defaultUser, data) : defaultUser) as User,
|
|
...props,
|
|
};
|
|
};
|
|
|
|
export const logout = async () => {
|
|
return makePostRequest("/auth/logout", {}, "Échec de la déconnexion", "POST");
|
|
};
|
|
|
|
export const updateUserPreferences = async (
|
|
preferences: { name: string; emoji: string; color: string }[]
|
|
) => {
|
|
return makePostRequest(
|
|
"/users/@me",
|
|
{ preferences },
|
|
"Échec de la mise à jour des préférences utilisateur",
|
|
"POST"
|
|
);
|
|
};
|
|
|
|
interface UserPreference {
|
|
name: string;
|
|
emoji: string;
|
|
color: string;
|
|
}
|
|
export type UserPreferences = UserPreference[];
|
|
|
|
/**
|
|
* === COLLES API ===
|
|
*/
|
|
export const getColles = async (startDate: DateTime) => {
|
|
return makeRequest(
|
|
`/colles?startDate=${startDate.toISODate()}`,
|
|
"Échec de la récupération des colles"
|
|
);
|
|
};
|
|
|
|
export interface Colle {
|
|
id: number;
|
|
date: string; // ISO date string
|
|
subject: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
examiner: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
room: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
student: User;
|
|
grade?: number; // Nullable grade
|
|
bjsecret?: string; // Optional field
|
|
bjid?: string; // Optional field
|
|
content?: string; // Optional field
|
|
comment?: string; // Optional field
|
|
attachments?: Attachment[]; // Optional field, array of attachment URLs
|
|
}
|
|
|
|
interface CollePayload {
|
|
classColles: Colle[];
|
|
studentColles: Colle[];
|
|
favoriteColles: Colle[];
|
|
healthyUntil: Date;
|
|
lastSync: Date;
|
|
}
|
|
|
|
export const useColles = (startDate: DateTime) => {
|
|
// Check if the start date is the current week
|
|
// This is used to determine if the data is "actual" or not
|
|
const isActual = DateTime.now()
|
|
.startOf("week")
|
|
.equals(startDate.startOf("week"));
|
|
|
|
const options = isActual
|
|
? {
|
|
refetchOnWindowFocus: true,
|
|
refetchOnReconnect: true,
|
|
staleTime: 0,
|
|
gcTime: 0,
|
|
}
|
|
: {
|
|
staleTime: Duration.fromObject({
|
|
hours: 1, // 1 hour
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
};
|
|
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["colles", startDate.toISODate()],
|
|
queryFn: () => getColles(startDate),
|
|
...options,
|
|
});
|
|
const mergedData = Object.assign(
|
|
{
|
|
classColles: [],
|
|
studentColles: [],
|
|
favoriteColles: [],
|
|
healthyUntil: new Date(0),
|
|
lastSync: new Date(0),
|
|
},
|
|
data || {}
|
|
) as CollePayload;
|
|
|
|
return {
|
|
...mergedData,
|
|
...props,
|
|
};
|
|
};
|
|
|
|
const fetchColle = async (id: number) => {
|
|
return makeRequest(`/colles/${id}`, "Échec de la récupération de la colle");
|
|
};
|
|
|
|
const defaultColle = {
|
|
id: 0,
|
|
date: "",
|
|
subject: {
|
|
id: 0,
|
|
name: "",
|
|
},
|
|
examiner: {
|
|
id: 0,
|
|
name: "",
|
|
},
|
|
room: {
|
|
id: 0,
|
|
name: "",
|
|
},
|
|
student: defaultUser,
|
|
attachments: [],
|
|
};
|
|
|
|
export const useColle = (id: number) => {
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["colle", id],
|
|
queryFn: () => fetchColle(id),
|
|
staleTime: Duration.fromObject({
|
|
seconds: 30, // 30 seconds
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
});
|
|
return {
|
|
colle: (data || defaultColle) as Colle,
|
|
...props,
|
|
};
|
|
};
|
|
|
|
export const refreshColle = async (id: number) => {
|
|
return makePostRequest(
|
|
`/colles/${id}/refresh`,
|
|
{},
|
|
"Échec de la demande de rafraîchissement de la colle",
|
|
"POST"
|
|
);
|
|
};
|
|
|
|
export interface Attachment {
|
|
path: string;
|
|
name: string;
|
|
}
|
|
|
|
/**
|
|
* === SUBJECTS API ===
|
|
*/
|
|
export const getSubjects = async () => {
|
|
return makeRequest("/subjects", "Échec de la récupération des matières");
|
|
};
|
|
export const useSubjects = () => {
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["subjects"],
|
|
queryFn: getSubjects,
|
|
staleTime: Duration.fromObject({
|
|
hours: 1, // 1 day
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
});
|
|
return {
|
|
subjects: (data as string[]) || [],
|
|
...props,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* === NOTIFICATIONS API ===
|
|
*/
|
|
export const subscribe = async (data: any) => {
|
|
return makePostRequest(
|
|
"/notifications",
|
|
data,
|
|
"Échec de l'abonnement aux notifications"
|
|
);
|
|
};
|
|
|
|
export const unsubscribe = async (id: string) => {
|
|
return makePostRequest(
|
|
`/notifications/${id}/unsubscribe`,
|
|
{},
|
|
"Échec de la désinscription des notifications"
|
|
);
|
|
};
|
|
|
|
export const updateSubscription = async (id: string, events: string[]) => {
|
|
return makePostRequest(
|
|
`/notifications/${id}`,
|
|
{ events },
|
|
"Échec de la mise à jour de l'abonnement aux notifications"
|
|
);
|
|
};
|
|
|
|
export const getNotifications = async () => {
|
|
return makeRequest(
|
|
"/notifications",
|
|
"Échec de la récupération des notifications"
|
|
);
|
|
};
|
|
|
|
export interface Event {
|
|
id: string;
|
|
enabled: boolean;
|
|
}
|
|
|
|
interface Subscription {
|
|
id: string;
|
|
device: string;
|
|
events: Event[];
|
|
enabled: boolean;
|
|
}
|
|
|
|
export const useNotifications = () => {
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["notifications"],
|
|
queryFn: getNotifications,
|
|
staleTime: Duration.fromObject({
|
|
hours: 0, // 1 hour
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
});
|
|
return {
|
|
notifications: (data as Subscription[]) || [],
|
|
...props,
|
|
};
|
|
};
|
|
|
|
export const testNotification = async (id: string) => {
|
|
return makePostRequest(
|
|
`/notifications/${id}/test`,
|
|
{},
|
|
"Échec de l'envoi de la notification de test"
|
|
);
|
|
};
|
|
|
|
/**
|
|
* === GRADES API ===
|
|
*/
|
|
export const getGrades = async (period: string) => {
|
|
return makeRequest(
|
|
`/grades?period=${encodeURIComponent(period)}`,
|
|
"Échec de la récupération des notes"
|
|
);
|
|
};
|
|
export const useGrades = (period: string) => {
|
|
const { data, ...props } = useQuery({
|
|
queryKey: ["grades", period],
|
|
queryFn: () => getGrades(period),
|
|
staleTime: Duration.fromObject({
|
|
hours: 0, // 1 hour
|
|
}).toMillis(),
|
|
gcTime: Duration.fromObject({
|
|
days: 3, // 3 days
|
|
}).toMillis(),
|
|
});
|
|
const grades = data?.grades as PeriodResult[] || [];
|
|
const subjects = data?.subjects as string[] || [];
|
|
return {
|
|
grades,
|
|
subjects,
|
|
...props,
|
|
};
|
|
}
|
|
|
|
interface SubjectPerformance {
|
|
subject: string
|
|
average: number
|
|
}
|
|
|
|
interface PeriodResult {
|
|
period: string
|
|
average: number
|
|
subjectAverages: SubjectPerformance[]
|
|
}
|