frontend/app/lib/utils.ts
2025-08-19 16:58:12 +02:00

154 lines
5.4 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);