221 lines
8.6 KiB
TypeScript
221 lines
8.6 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 justify-between items-center">
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handlePrevPage}
|
|
disabled={currentPage === 1 || isLoading}
|
|
>
|
|
← Précédent
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleNextPage}
|
|
disabled={!hasMore || isLoading}
|
|
>
|
|
Suivant →
|
|
</Button>
|
|
</div>
|
|
<Button asChild variant="secondary" size="sm">
|
|
<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}`;
|
|
}
|