espace-paie-odentas/app/(app)/page.tsx
2025-10-15 00:40:57 +02:00

223 lines
8.8 KiB
TypeScript

// app/(app)/page.tsx
"use client";
import { useState } from "react";
import { useContrats, type Contrat } from "@/hooks/useContrats";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/lib/fetcher";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { usePageTitle } from "@/hooks/usePageTitle";
type ClientInfo = {
id: string;
name: string;
api_name?: string;
user?: {
id: string;
email: string;
display_name?: string;
first_name?: string;
} | null;
} | null;
export default function Dashboard() {
const [currentPage, setCurrentPage] = useState(1);
// Définir le titre de la page
usePageTitle("Tableau de bord");
const { data, isLoading, isError } = useContrats({
// Pas de restriction de régime - récupère tous les contrats
status: "en_cours",
page: currentPage,
limit: 5,
});
const items = data?.items ?? [];
const hasMore = data?.hasMore ?? false;
const total = data?.total ?? 0;
// Récupérer les infos utilisateur pour personnaliser l'accueil
const { data: clientInfo } = useQuery({
queryKey: ["me-info"],
queryFn: async () => {
try {
const res = await fetch("/api/me", {
cache: "no-store",
headers: { Accept: "application/json" },
credentials: "include"
});
if (!res.ok) return null;
const me = await res.json();
return {
id: me.active_org_id || null,
name: me.active_org_name || "Organisation",
api_name: me.active_org_api_name,
user: me.user || null,
} as ClientInfo;
} catch {
return null;
}
},
staleTime: 30_000,
});
const rawFirstName = clientInfo?.user?.first_name || clientInfo?.user?.display_name?.split(' ')[0] || null;
const userFirstName = rawFirstName
? rawFirstName.trim().replace(/^./, (ch) => ch.toLocaleUpperCase('fr-FR'))
: null;
const count = items.length;
const countLabel = total === 0
? "Vous n'avez aucun contrat en cours"
: total === 1
? "Vous avez 1 contrat en cours"
: count === total
? `Vous avez ${total} contrat${total > 1 ? 's' : ''} en cours`
: `Vous avez ${total} contrats en cours • ${count} affiché${count > 1 ? 's' : ''} sur cette page`;
const handlePrevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
const handleNextPage = () => {
if (hasMore) {
setCurrentPage(currentPage + 1);
}
};
return (
<div className="space-y-6">
<section className="rounded-2xl border bg-white p-6">
<h1 className="text-2xl font-semibold">Bonjour{userFirstName ? ` ${userFirstName}` : ''} 👋</h1>
<p className="text-sm text-slate-600 mt-2">Voici un aperçu rapide de vos contrats et actions à venir.</p>
</section>
<section className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="rounded-2xl border bg-white p-6 space-y-4">
<h2 className="text-xl font-semibold">Vos contrats en cours</h2>
<p className="text-sm text-slate-600">
{countLabel}
</p>
{isLoading && <div className="text-sm text-slate-500">Chargement</div>}
{isError && <div className="text-sm text-rose-500">Erreur de chargement</div>}
{items.map((c: Contrat) => (
<Link
key={c.id}
href={c.regime === 'RG' ? `/contrats-rg/${c.id}` : `/contrats/${c.id}`}
className="block p-4 border rounded-xl bg-gray-50 hover:shadow-md transition-shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<h3 className="text-lg font-semibold">{c.salarie_nom}</h3>
<div className="flex flex-wrap items-center gap-2 mt-1">
<span className="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{c.profession}
</span>
<span className="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600">
Réf. {c.reference}
</span>
{c.regime && (
<span className={`inline-block px-2 py-0.5 rounded-full text-xs font-medium ${
c.regime === 'RG'
? 'bg-emerald-100 text-emerald-800'
: 'bg-purple-100 text-purple-800'
}`}>
{c.regime === 'RG' ? 'Régime Général' : 'CDDU'}
</span>
)}
</div>
<div className="flex flex-col sm:flex-row gap-1 sm:gap-4 mt-2">
<span className="flex items-center text-sm">
{/* icône calendrier */}
<svg className="w-4 h-4 mr-1" viewBox="0 0 20 20"><rect x="3" y="4" width="14" height="13" rx="2"/><path d="M16 7H4"/><path d="M7 2v2M13 2v2"/></svg>
Début : {formatFR(c.date_debut)}
</span>
<span className="flex items-center text-sm">
<svg className="w-4 h-4 mr-1" viewBox="0 0 20 20"><rect x="3" y="4" width="14" height="13" rx="2"/><path d="M16 7H4"/><path d="M7 2v2M13 2v2"/></svg>
{c.date_fin === '2099-01-01' ? (
<span className="italic text-emerald-600">CDI en cours</span>
) : (
<>Fin : {formatFR(c.date_fin)}</>
)}
</span>
</div>
</Link>
))}
<div className="mt-4 flex flex-col gap-2">
<div className="flex gap-2 justify-between sm:justify-start">
<Button
variant="outline"
size="sm"
onClick={handlePrevPage}
disabled={currentPage === 1 || isLoading}
className="flex-1 sm:flex-none whitespace-nowrap"
>
Préc.
</Button>
<Button
variant="outline"
size="sm"
onClick={handleNextPage}
disabled={!hasMore || isLoading}
className="flex-1 sm:flex-none whitespace-nowrap"
>
Suiv.
</Button>
</div>
<Button asChild variant="secondary" size="sm" className="w-full sm:w-auto sm:self-start">
<Link href="/contrats/">Voir tous les contrats</Link>
</Button>
</div>
</div>
<div className="rounded-2xl border bg-white p-6">
<h2 className="text-xl font-semibold mb-4">Vos notifications</h2>
<div className="relative p-6 rounded-xl bg-gradient-to-br from-blue-50 to-indigo-50 border border-blue-100 overflow-hidden">
{/* Icône décorative en arrière-plan */}
<div className="absolute top-4 right-4 opacity-10">
<svg className="w-12 h-12 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2L13 8h6l-5 4 2 6-6-4-6 4 2-6-5-4h6l3-6z"/>
</svg>
</div>
{/* Contenu principal */}
<div className="relative">
<div className="flex items-center gap-3 mb-3">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-5 5v-5zM4 3h11v6H4V3zM4 13h7v7H4v-7z" />
</svg>
</div>
<div>
<h3 className="font-semibold text-slate-900">Bientôt disponible</h3>
<p className="text-sm text-slate-600">Fonctionnalité en développement</p>
</div>
</div>
<p className="text-sm text-slate-700 leading-relaxed">
Le système de notifications vous permettra de recevoir des alertes importantes concernant vos contrats, échéances et documents à traiter, directement dans votre Espace Paie.
</p>
<div className="mt-4 flex items-center gap-2 text-xs text-blue-600">
<div className="w-2 h-2 rounded-full bg-blue-400 animate-pulse"></div>
<span>Cette fonctionnalité sera lancée prochainement</span>
</div>
</div>
</div>
</div>
</section>
</div>
);
}
function formatFR(iso: string){
const d = new Date(iso); if (isNaN(d.getTime())) return "";
const dd = String(d.getDate()).padStart(2,'0');
const mm = String(d.getMonth()+1).padStart(2,'0');
const yyyy = d.getFullYear();
return `${dd}/${mm}/${yyyy}`;
}