feat: add last sync and health

This commit is contained in:
Nathan Lamy 2025-08-19 19:32:03 +02:00
parent 74f31a723a
commit a89546c54e
7 changed files with 170 additions and 146 deletions

View file

@ -24,6 +24,9 @@ import Error from "~/components/error";
import { useSearchParams } from "react-router"; import { useSearchParams } from "react-router";
import { useColles, type User } from "~/lib/api"; import { useColles, type User } from "~/lib/api";
import TabContent from "~/components/home/tab-content"; 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 }) { export default function Home({ user }: { user: User }) {
// Handle query parameters // Handle query parameters
@ -62,8 +65,15 @@ export default function Home({ user }: { user: User }) {
}; };
// Fetch colles from API // Fetch colles from API
const { studentColles, classColles, favoriteColles, error, isLoading } = const {
useColles(startDate); studentColles,
classColles,
favoriteColles,
healthyUntil,
lastSync,
error,
isLoading,
} = useColles(startDate);
// Error handling (after all hooks) // Error handling (after all hooks)
if (error) if (error)
@ -143,6 +153,16 @@ export default function Home({ user }: { user: User }) {
}; };
return ( return (
<MainLayout
header={
<>
<h1 className="text-2xl font-bold" onClick={forceReload}>
Khollis&eacute; - {user.className}
</h1>
<SyncButton healthyUntil={healthyUntil} lastSync={lastSync} />
</>
}
>
<div className="space-y-6 pb-20 md:pb-0"> <div className="space-y-6 pb-20 md:pb-0">
{/* Tabs */} {/* Tabs */}
<Tabs <Tabs
@ -267,5 +287,6 @@ export default function Home({ user }: { user: User }) {
{/* Bottom Navigation for Mobile */} {/* Bottom Navigation for Mobile */}
<BottomNavigation activeId="colles" /> <BottomNavigation activeId="colles" />
</div> </div>
</MainLayout>
); );
} }

View file

@ -8,10 +8,18 @@ import {
import { Cloud, CloudOff, Wifi, WifiOff } from "lucide-react"; import { Cloud, CloudOff, Wifi, WifiOff } from "lucide-react";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
export function SyncButton() { export function SyncButton({
const [isSync, setIsSync] = useState(true); healthyUntil,
lastSync,
}: {
healthyUntil: Date;
lastSync: Date;
}) {
const healthyUntilDate = new Date(healthyUntil) || new Date(0);
const lastSyncDate = new Date(lastSync) || new Date(0);
// Determine if the sync is healthy based on the healthyUntil date
const isSync = healthyUntilDate > new Date();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const lastSync = new Date(); // TODO: Replace with actual last sync date
const getIcon = () => { const getIcon = () => {
if (isSync) return <Cloud className="w-6! h-6!" />; if (isSync) return <Cloud className="w-6! h-6!" />;
@ -28,11 +36,12 @@ export function SyncButton() {
const now = new Date(); const now = new Date();
const diff = now.getTime() - date.getTime(); const diff = now.getTime() - date.getTime();
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(diff / 60000); const minutes = Math.floor(diff / 60000);
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
if (minutes < 1) return "A l'instant"; if (minutes < 1) return `il y a ${seconds}s`;
if (minutes < 60) return `il y a ${minutes}m`; if (minutes < 60) return `il y a ${minutes}m`;
if (hours < 24) return `il y a ${hours}h`; if (hours < 24) return `il y a ${hours}h`;
return `il y a ${days}j`; return `il y a ${days}j`;
@ -61,26 +70,26 @@ export function SyncButton() {
) : ( ) : (
<WifiOff className="h-4 w-4" /> <WifiOff className="h-4 w-4" />
)} )}
<span className="font-medium"> <span className="font-medium">Status</span>
Status
</span>
</div> </div>
<div <div
className={cn( className={cn(
"ml-auto px-2 py-1 rounded-full text-xs font-medium", "ml-auto px-2 py-1 rounded-full text-xs font-medium",
isSync isSync
? "bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400" ? "bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400"
: "bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400" : "bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400",
)} )}
> >
{isSync ? "Connecté" : "Erreur"} {isSync ? "Connecté" : "Hors ligne"}
</div> </div>
</div> </div>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Dernière mis à jour :</span> <span className="text-muted-foreground">
<span>{formatLastSync(lastSync)}</span> Dernière mis à jour :
</span>
<span>{formatLastSync(lastSyncDate)}</span>
</div> </div>
</div> </div>
{/* TODO: {/* TODO:

View file

@ -51,18 +51,18 @@ export function AuthLayout({
export function MainLayout({ export function MainLayout({
children, children,
page header
}: { }: {
children: React.ReactNode; children: React.ReactNode;
page: React.ReactNode; header: React.ReactNode;
}) { }) {
return ( return (
<main className="container mx-auto py-8 px-4"> <main className="container mx-auto py-8 px-4">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
{children} {header}
</div> </div>
{page} {children}
</main> </main>
); );
} }

View file

@ -189,6 +189,8 @@ interface CollePayload {
classColles: Colle[]; classColles: Colle[];
studentColles: Colle[]; studentColles: Colle[];
favoriteColles: Colle[]; favoriteColles: Colle[];
healthyUntil: Date;
lastSync: Date;
} }
export const useColles = (startDate: DateTime) => { export const useColles = (startDate: DateTime) => {
@ -206,7 +208,9 @@ export const useColles = (startDate: DateTime) => {
gcTime: 0, gcTime: 0,
} }
: { : {
staleTime: 0, staleTime: Duration.fromObject({
hours: 1, // 1 hour
}).toMillis(),
gcTime: Duration.fromObject({ gcTime: Duration.fromObject({
days: 3, // 3 days days: 3, // 3 days
}).toMillis(), }).toMillis(),
@ -222,6 +226,8 @@ export const useColles = (startDate: DateTime) => {
classColles: [], classColles: [],
studentColles: [], studentColles: [],
favoriteColles: [], favoriteColles: [],
healthyUntil: new Date(0),
lastSync: new Date(0),
}, },
data || {} data || {}
) as CollePayload; ) as CollePayload;

View file

@ -16,13 +16,11 @@ import {
ChevronUp, ChevronUp,
ChevronDown, ChevronDown,
MapPinHouse, MapPinHouse,
Users,
RefreshCw, RefreshCw,
Share2, Share2,
ExternalLink, ExternalLink,
} from "lucide-react"; } from "lucide-react";
import { Separator } from "~/components/ui/separator"; import { Separator } from "~/components/ui/separator";
import UserDropdown from "~/components/user-dropdown";
import ColleDetailsSkeleton from "~/components/details/skeleton-details"; import ColleDetailsSkeleton from "~/components/details/skeleton-details";
import AttachmentItem from "~/components/details/attachment"; import AttachmentItem from "~/components/details/attachment";
import Error from "~/components/error"; import Error from "~/components/error";
@ -134,9 +132,6 @@ export default function ColleDetailPage() {
<ArrowLeft className="h-4 w-4 mr-2" /> <ArrowLeft className="h-4 w-4 mr-2" />
Retour Retour
</Button> </Button>
<div className="hidden md:block">
<UserDropdown user={user} />
</div>
<div className="flex md:hidden items-center gap-2"> <div className="flex md:hidden items-center gap-2">
<Button <Button
variant="outline" variant="outline"
@ -150,7 +145,6 @@ export default function ColleDetailPage() {
/> />
<span className="sr-only">Recharger</span> <span className="sr-only">Recharger</span>
</Button> </Button>
<UserDropdown user={user} />
</div> </div>
</div> </div>

View file

@ -2,10 +2,7 @@ import { Navigate } from "react-router";
import Error from "~/components/error"; import Error from "~/components/error";
import HomePage from "~/components/home"; import HomePage from "~/components/home";
import Loader from "~/components/loader"; import Loader from "~/components/loader";
import { SyncButton } from "~/components/sync-status";
import { MainLayout } from "~/layout";
import { AUTH_ERROR, useUser } from "~/lib/api"; import { AUTH_ERROR, useUser } from "~/lib/api";
import { forceReload } from "~/lib/utils";
export default function Home() { export default function Home() {
const { user, isLoading, error } = useUser(); const { user, isLoading, error } = useUser();
@ -20,12 +17,5 @@ export default function Home() {
return <Error message={error.message} />; return <Error message={error.message} />;
} }
return ( return <HomePage user={user} />;
<MainLayout page={<HomePage user={user} />}>
<h1 className="text-2xl font-bold" onClick={forceReload}>
Khollis&eacute; - {user.className}
</h1>
<SyncButton />
</MainLayout>
);
} }

View file

@ -20,10 +20,14 @@ export default function Home() {
} }
return ( return (
<MainLayout page={<SettingsPage user={user} />}> <MainLayout
header={
<h1 className="text-2xl font-bold" onClick={forceReload}> <h1 className="text-2xl font-bold" onClick={forceReload}>
Khollis&eacute; - {user.className} Khollis&eacute; - {user.className}
</h1> </h1>
}
>
<SettingsPage user={user} />
</MainLayout> </MainLayout>
); );
} }