frontend/app/lib/utils.ts
Nathan Lamy 4aebc41e2b
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m17s
feat: add syllabus
2025-12-23 11:59:01 +01:00

199 lines
6.8 KiB
TypeScript

import { type ClassValue, clsx } from "clsx";
import { del } from "idb-keyval";
import { DateTime } from "luxon";
import type { NavigateFunction } from "react-router";
import { twMerge } from "tailwind-merge";
import { CACHE_KEY } from "./client";
import type { User, UserPreferences } from "./api";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* === STRING UTILS ===
*/
export function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function titleCase(str: string) {
return str.replace(
/\w\S*/g,
(text) => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()
);
}
/**
* === NAVIGATION UTILS ===
*/
// Force a reload of the page
export function forceReload() {
window.location.reload();
}
export function goBack(navigate: NavigateFunction) {
let canGoBack = false;
// Check if there is a history stack to go back to
try {
canGoBack = window.history.length > 1;
} catch (e) {
canGoBack = false;
}
if (!canGoBack) {
return navigate("/");
} else {
return navigate(-1);
}
}
/**
* === COLLES UTILS ===
*/
export const formatDate = (date: string) => {
const dt = DateTime.fromISO(date).setLocale("fr");
const str = dt.toLocaleString({
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
});
return titleCase(str);
};
export const formatTime = (date: string) => {
const dt = DateTime.fromISO(date).setLocale("fr");
return (
dt
.toLocaleString({
hour: "2-digit",
minute: "2-digit",
})
?.replace(":", "h") || "N/A"
);
};
export const formatGrade = (grade?: number) => {
if (grade === undefined || grade === null || grade < 0 || grade > 20)
return "N/A";
const rounded = Math.round(grade * 10) / 10;
const str =
rounded % 1 === 0
? rounded.toFixed(0) // no decimals if .0
: rounded.toFixed(1); // one decimal otherwise
return str.replace(".", ",").padStart(2, "0"); // pad with zero if needed
};
// === COLORS UTILS ===
export const colors = [
"red",
"orange",
"amber",
"yellow",
"lime",
"green",
"emerald",
"teal",
"cyan",
"sky",
"blue",
"indigo",
"violet",
"purple",
"fuchsia",
"pink",
"rose",
"slate",
"zinc",
];
export const getColorClass = (colorCode: string) =>
`bg-${colorCode}-100 text-${colorCode}-800 dark:bg-${colorCode}-300 dark:text-${colorCode}-900 hover:bg-${colorCode}-100 dark:hover:bg-${colorCode}-300`;
// Used for Tailwind CSS to generate the classes
export const classNames = [
[
"bg-red-100 text-red-800 dark:bg-red-300 dark:text-red-900 hover:bg-red-100 dark:hover:bg-red-300",
"bg-orange-100 text-orange-800 dark:bg-orange-300 dark:text-orange-900 hover:bg-orange-100 dark:hover:bg-orange-300",
"bg-amber-100 text-amber-800 dark:bg-amber-300 dark:text-amber-900 hover:bg-amber-100 dark:hover:bg-amber-300",
"bg-yellow-100 text-yellow-800 dark:bg-yellow-300 dark:text-yellow-900 hover:bg-yellow-100 dark:hover:bg-yellow-300",
"bg-lime-100 text-lime-800 dark:bg-lime-300 dark:text-lime-900 hover:bg-lime-100 dark:hover:bg-lime-300",
"bg-green-100 text-green-800 dark:bg-green-300 dark:text-green-900 hover:bg-green-100 dark:hover:bg-green-300",
"bg-emerald-100 text-emerald-800 dark:bg-emerald-300 dark:text-emerald-900 hover:bg-emerald-100 dark:hover:bg-emerald-300",
"bg-teal-100 text-teal-800 dark:bg-teal-300 dark:text-teal-900 hover:bg-teal-100 dark:hover:bg-teal-300",
"bg-cyan-100 text-cyan-800 dark:bg-cyan-300 dark:text-cyan-900 hover:bg-cyan-100 dark:hover:bg-cyan-300",
"bg-sky-100 text-sky-800 dark:bg-sky-300 dark:text-sky-900 hover:bg-sky-100 dark:hover:bg-sky-300",
"bg-blue-100 text-blue-800 dark:bg-blue-300 dark:text-blue-900 hover:bg-blue-100 dark:hover:bg-blue-300",
"bg-indigo-100 text-indigo-800 dark:bg-indigo-300 dark:text-indigo-900 hover:bg-indigo-100 dark:hover:bg-indigo-300",
"bg-violet-100 text-violet-800 dark:bg-violet-300 dark:text-violet-900 hover:bg-violet-100 dark:hover:bg-violet-300",
"bg-purple-100 text-purple-800 dark:bg-purple-300 dark:text-purple-900 hover:bg-purple-100 dark:hover:bg-purple-300",
"bg-fuchsia-100 text-fuchsia-800 dark:bg-fuchsia-300 dark:text-fuchsia-900 hover:bg-fuchsia-100 dark:hover:bg-fuchsia-300",
"bg-pink-100 text-pink-800 dark:bg-pink-300 dark:text-pink-900 hover:bg-pink-100 dark:hover:bg-pink-300",
"bg-rose-100 text-rose-800 dark:bg-rose-300 dark:text-rose-900 hover:bg-rose-100 dark:hover:bg-rose-300",
"bg-slate-100 text-slate-800 dark:bg-slate-300 dark:text-slate-900 hover:bg-slate-100 dark:hover:bg-slate-300",
"bg-zinc-100 text-zinc-800 dark:bg-zinc-300 dark:text-zinc-900 hover:bg-zinc-100 dark:hover:bg-zinc-300",
"bg-gray-100 text-gray-800 dark:bg-gray-300 dark:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-300",
],
];
export const DEFAULT_COLOR = "blue"; // Default color for subjects
export const DEFAULT_EMOJI = "📚"; // Default emoji for subjects
export const getSubjectEmoji = (subject: string, pref: UserPreferences) => {
const preference = pref.find((p) => p.name === subject);
return preference ? preference.emoji : DEFAULT_EMOJI;
};
export const getSubjectColor = (subject: string, pref: UserPreferences) => {
const preference = pref.find((p) => p.name === subject);
return preference ? preference.color : DEFAULT_COLOR;
};
// === DEBUG UTILS ===
export const clearCache = () => del(CACHE_KEY);
// === MP* COLLES SYLLABUS CONSTANTS ===
export function getColleWeek(date: Date) {
// Define week ranges [start, end, week_number]
const weeks = [
["15/09/2025", "20/09/2025", 0],
["22/09/2025", "27/09/2025", 1],
["29/09/2025", "04/10/2025", 2],
["06/10/2025", "11/10/2025", 3],
["13/10/2025", "18/10/2025", 4],
["03/11/2025", "08/11/2025", 5],
["10/11/2025", "15/11/2025", 6],
["17/11/2025", "22/11/2025", 7],
["24/11/2025", "29/11/2025", 8],
["01/12/2025", "06/12/2025", 9],
["08/12/2025", "13/12/2025", 10],
["15/12/2025", "20/12/2025", 11],
["05/01/2026", "10/01/2026", 12],
["12/01/2026", "17/01/2026", 13],
["19/01/2026", "24/01/2026", 14],
["26/01/2026", "31/01/2026", 15],
["02/02/2026", "07/02/2026", 16],
["09/02/2026", "14/02/2026", 17],
["09/03/2026", "14/03/2026", 18],
["16/03/2026", "21/03/2026", 19],
] as [string, string, number][];
// Helper function to parse DD/MM/YYYY string to Date
function parseDate(dateStr: string): Date {
const [day, month, year] = dateStr.split("/").map(Number);
return new Date(year, month - 1, day);
}
// Check which week the date falls into
for (const [startStr, endStr, weekNum] of weeks) {
const startDate = parseDate(startStr);
const endDate = parseDate(endStr);
if (date >= startDate && date <= endDate) {
return weekNum;
}
}
return null;
}