espace-paie-odentas/components/staff/StaffAvenantsPageClient.tsx
odentas da17ca6ef2 feat: Amélioration de la page staff/avenants avec pagination et filtres
- Ajout de filtres sophistiqués : organisation, statut, type, signature, élément, dates
- Tri par colonne : date d'effet, date d'avenant, n° avenant, n° contrat
- Pagination avec 25/50/100 éléments par page
- Ordre par défaut : date d'effet décroissant (plus récent en premier)
- Compteur de filtres actifs avec bouton de réinitialisation
- Affichage du matricule salarié, n° avenant et type d'avenant dans le tableau
- Recherche étendue : inclut matricule, production et n° avenant
- Interface cohérente avec les pages staff/contrats et staff/payslips
2025-11-05 18:28:40 +01:00

859 lines
34 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import { useRouter } from "next/navigation";
import { FileText, Plus, Search, Check, X, RefreshCw, Mail, Filter, ChevronLeft, ChevronRight } from "lucide-react";
import { Amendment } from "@/types/amendments";
import { SmartReminderAvenantModal, SmartReminderAvenant, ReminderAction } from "./avenants/SmartReminderAvenantModal";
interface StaffAvenantsPageClientProps {
initialData: Amendment[];
}
export default function StaffAvenantsPageClient({ initialData }: StaffAvenantsPageClientProps) {
const router = useRouter();
const [amendments] = useState<Amendment[]>(initialData);
const [searchTerm, setSearchTerm] = useState("");
const [isRefreshing, setIsRefreshing] = useState(false);
const [selectedAvenantIds, setSelectedAvenantIds] = useState<Set<string>>(new Set());
// Filtres
const [showFilters, setShowFilters] = useState(false);
const [organizationFilter, setOrganizationFilter] = useState<string | null>(null);
const [statutFilter, setStatutFilter] = useState<string | null>(null);
const [typeFilter, setTypeFilter] = useState<string | null>(null);
const [signatureFilter, setSignatureFilter] = useState<string | null>(null);
const [elementFilter, setElementFilter] = useState<string | null>(null);
const [dateEffetFrom, setDateEffetFrom] = useState<string>("");
const [dateEffetTo, setDateEffetTo] = useState<string>("");
// Tri et pagination
const [sortField, setSortField] = useState<"date_effet" | "date_avenant" | "numero_avenant" | "contract_number">("date_effet");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(50);
// Smart reminder modal state
const [showSmartReminderModal, setShowSmartReminderModal] = useState(false);
const [smartReminderAvenants, setSmartReminderAvenants] = useState<SmartReminderAvenant[]>([]);
const [smartReminderProgress, setSmartReminderProgress] = useState<SmartReminderAvenant[]>([]);
const [isLoadingReminders, setIsLoadingReminders] = useState(false);
const handleRefresh = () => {
setIsRefreshing(true);
router.refresh();
setTimeout(() => setIsRefreshing(false), 1000);
};
// Extraire les organisations uniques
const organizations = useMemo(() => {
const orgs = new Set<string>();
amendments.forEach(a => {
if (a.organization_name) orgs.add(a.organization_name);
});
return Array.from(orgs).sort();
}, [amendments]);
// Filtrage et tri
const filteredAndSortedAmendments = useMemo(() => {
let filtered = amendments.filter((amendment) => {
const term = searchTerm.toLowerCase();
const matchesSearch =
amendment.contract_number?.toLowerCase().includes(term) ||
amendment.employee_name?.toLowerCase().includes(term) ||
amendment.employee_matricule?.toLowerCase().includes(term) ||
amendment.organization_name?.toLowerCase().includes(term) ||
amendment.production_name?.toLowerCase().includes(term) ||
amendment.numero_avenant?.toLowerCase().includes(term);
if (!matchesSearch) return false;
// Filtre organisation
if (organizationFilter && amendment.organization_name !== organizationFilter) return false;
// Filtre statut
if (statutFilter && amendment.status !== statutFilter) return false;
// Filtre type
if (typeFilter && amendment.type_avenant !== typeFilter) return false;
// Filtre signature
if (signatureFilter) {
if (signatureFilter === "signed" && amendment.signature_status !== "signed") return false;
if (signatureFilter === "pending" && !["pending_employer", "pending_employee"].includes(amendment.signature_status || "")) return false;
if (signatureFilter === "not_sent" && amendment.signature_status !== "not_sent") return false;
}
// Filtre élément
if (elementFilter) {
if (!amendment.elements || !amendment.elements.includes(elementFilter as any)) return false;
}
// Filtre date d'effet
if (dateEffetFrom && amendment.date_effet && amendment.date_effet < dateEffetFrom) return false;
if (dateEffetTo && amendment.date_effet && amendment.date_effet > dateEffetTo) return false;
return true;
});
// Tri
filtered.sort((a, b) => {
let aVal: any;
let bVal: any;
switch (sortField) {
case "date_effet":
aVal = a.date_effet || "";
bVal = b.date_effet || "";
break;
case "date_avenant":
aVal = a.date_avenant || "";
bVal = b.date_avenant || "";
break;
case "numero_avenant":
aVal = a.numero_avenant || "";
bVal = b.numero_avenant || "";
break;
case "contract_number":
aVal = a.contract_number || "";
bVal = b.contract_number || "";
break;
default:
return 0;
}
if (aVal < bVal) return sortOrder === "asc" ? -1 : 1;
if (aVal > bVal) return sortOrder === "asc" ? 1 : -1;
return 0;
});
return filtered;
}, [amendments, searchTerm, organizationFilter, statutFilter, typeFilter, signatureFilter, elementFilter, dateEffetFrom, dateEffetTo, sortField, sortOrder]);
// Pagination
const totalPages = Math.ceil(filteredAndSortedAmendments.length / itemsPerPage);
const paginatedAmendments = useMemo(() => {
const start = (currentPage - 1) * itemsPerPage;
return filteredAndSortedAmendments.slice(start, start + itemsPerPage);
}, [filteredAndSortedAmendments, currentPage, itemsPerPage]);
// Reset à la page 1 quand les filtres changent
const resetPage = () => setCurrentPage(1);
const formatDate = (dateString?: string) => {
if (!dateString) return "-";
const [y, m, d] = dateString.split("-");
return `${d}/${m}/${y}`;
};
const getStatusBadge = (status: Amendment["status"]) => {
const badges = {
draft: "bg-slate-100 text-slate-700",
pending: "bg-orange-100 text-orange-700",
signed: "bg-green-100 text-green-700",
cancelled: "bg-red-100 text-red-700",
};
const labels = {
draft: "Brouillon",
pending: "En attente",
signed: "Signé",
cancelled: "Annulé",
};
return (
<span className={`px-2 py-1 text-xs font-medium rounded ${badges[status]}`}>
{labels[status]}
</span>
);
};
const getTypeBadge = (type?: string) => {
if (type === "annulation") {
return <span className="px-2 py-1 text-xs font-medium rounded bg-red-100 text-red-700">Annulation</span>;
}
return <span className="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700">Modification</span>;
};
const getSignatureIcons = (signatureStatus?: string) => {
// Déterminer si employeur a signé
const employerSigned = signatureStatus === 'pending_employee' || signatureStatus === 'signed';
// Déterminer si salarié a signé
const employeeSigned = signatureStatus === 'signed';
// Si pas encore envoyé
const notSent = !signatureStatus || signatureStatus === 'not_sent';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col items-center gap-1">
<div className="text-xs font-semibold text-slate-600">E</div>
{notSent ? (
<span className="text-xs text-slate-400"></span>
) : employerSigned ? (
<Check className="w-4 h-4 text-green-600" strokeWidth={3} />
) : (
<X className="w-4 h-4 text-red-600" strokeWidth={3} />
)}
</div>
<div className="flex flex-col items-center gap-1">
<div className="text-xs font-semibold text-slate-600">S</div>
{notSent ? (
<span className="text-xs text-slate-400"></span>
) : employeeSigned ? (
<Check className="w-4 h-4 text-green-600" strokeWidth={3} />
) : (
<X className="w-4 h-4 text-red-600" strokeWidth={3} />
)}
</div>
</div>
);
};
const getElementsLabel = (elements: Amendment["elements"]) => {
const labels = {
objet: "Objet",
duree: "Durée",
lieu_horaire: "Lieu/Horaire",
remuneration: "Rémunération",
};
return elements.map((el) => labels[el]).join(", ");
};
const handleSort = (field: typeof sortField) => {
if (sortField === field) {
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
} else {
setSortField(field);
setSortOrder("desc");
}
};
const getSortIcon = (field: typeof sortField) => {
if (sortField !== field) return null;
return sortOrder === "asc" ? "↑" : "↓";
};
// Fonction pour toggle la sélection de tous les avenants
const toggleSelectAll = () => {
if (selectedAvenantIds.size === paginatedAmendments.length) {
setSelectedAvenantIds(new Set());
} else {
setSelectedAvenantIds(new Set(paginatedAmendments.map(a => a.id).filter(Boolean) as string[]));
}
};
// Fonction pour toggle la sélection d'un avenant
const toggleSelectAvenant = (avenantId: string) => {
setSelectedAvenantIds(prev => {
const newSet = new Set(prev);
if (newSet.has(avenantId)) {
newSet.delete(avenantId);
} else {
newSet.add(avenantId);
}
return newSet;
});
};
// Clear all filters
const clearAllFilters = () => {
setSearchTerm("");
setOrganizationFilter(null);
setStatutFilter(null);
setTypeFilter(null);
setSignatureFilter(null);
setElementFilter(null);
setDateEffetFrom("");
setDateEffetTo("");
resetPage();
};
const activeFiltersCount = [
searchTerm,
organizationFilter,
statutFilter,
typeFilter,
signatureFilter,
elementFilter,
dateEffetFrom,
dateEffetTo
].filter(Boolean).length;
// Fonction pour envoyer les relances intelligentes
const handleSmartReminders = async () => {
if (selectedAvenantIds.size === 0) {
alert("Aucun avenant sélectionné");
return;
}
const selectedAvenants = amendments.filter(a => a.id && selectedAvenantIds.has(a.id));
// Analyser les avenants pour déterminer les actions
const analyzedAvenants: SmartReminderAvenant[] = selectedAvenants.map(avenant => {
// Déterminer qui a signé
const employerSigned = avenant.signature_status === 'pending_employee' || avenant.signature_status === 'signed';
const employeeSigned = avenant.signature_status === 'signed';
let action: ReminderAction;
let reason = '';
// Si déjà complètement signé
if (employerSigned && employeeSigned) {
action = 'already-signed';
reason = 'Avenant déjà signé par les deux parties';
}
// Si employeur n'a pas signé
else if (!employerSigned) {
// Vérifier cooldown (24h)
if (avenant.last_employer_notification_at) {
const lastNotif = new Date(avenant.last_employer_notification_at);
const now = new Date();
const hoursSince = (now.getTime() - lastNotif.getTime()) / (1000 * 60 * 60);
if (hoursSince < 24) {
action = 'skip';
reason = `Dernière relance envoyée il y a ${Math.round(hoursSince)}h (moins de 24h)`;
} else {
action = 'employer';
reason = 'En attente de signature employeur';
}
} else {
action = 'employer';
reason = 'En attente de signature employeur';
}
}
// Si employeur a signé mais pas le salarié
else if (employerSigned && !employeeSigned) {
// Vérifier cooldown (24h)
if (avenant.last_employee_notification_at) {
const lastNotif = new Date(avenant.last_employee_notification_at);
const now = new Date();
const hoursSince = (now.getTime() - lastNotif.getTime()) / (1000 * 60 * 60);
if (hoursSince < 24) {
action = 'skip';
reason = `Dernière relance envoyée il y a ${Math.round(hoursSince)}h (moins de 24h)`;
} else {
action = 'employee';
reason = 'En attente de signature salarié';
}
} else {
action = 'employee';
reason = 'En attente de signature salarié';
}
}
else {
action = 'skip';
reason = 'Statut de signature inconnu';
}
return {
id: avenant.id!,
numero_avenant: avenant.id,
contract_number: avenant.contract_number,
employee_name: avenant.employee_name,
employer_signed: employerSigned,
employee_signed: employeeSigned,
action,
reason
};
});
setSmartReminderAvenants(analyzedAvenants);
setShowSmartReminderModal(true);
};
// Fonction pour confirmer et envoyer les relances
const confirmSmartReminders = async (forceResend: boolean) => {
setIsLoadingReminders(true);
setSmartReminderProgress([...smartReminderAvenants].map(a => ({ ...a, status: 'pending' })));
let successCount = 0;
let errorCount = 0;
for (const avenant of smartReminderAvenants) {
// Déterminer l'action finale
const extended = avenant as SmartReminderAvenant & { forcedAction?: ReminderAction };
let finalAction = avenant.action;
// Si forceResend et que l'avenant était skip pour cooldown, envoyer quand même
if (forceResend && avenant.action === 'skip' && avenant.reason?.includes('moins de 24h')) {
if (!avenant.employer_signed) {
finalAction = 'employer';
extended.forcedAction = 'employer';
} else if (!avenant.employee_signed) {
finalAction = 'employee';
extended.forcedAction = 'employee';
}
}
// Skip si déjà signé ou action skip sans forceResend
if (finalAction === 'already-signed' || (finalAction === 'skip' && !forceResend)) {
continue;
}
// Marquer comme en cours d'envoi
setSmartReminderProgress(prev =>
prev.map(a => a.id === avenant.id ? { ...a, status: 'sending' } : a)
);
try {
// Envoyer la relance selon l'action
let response;
if (finalAction === 'employer') {
response = await fetch(`/api/staff/avenants/${avenant.id}/remind-employer`, {
method: 'POST',
});
} else if (finalAction === 'employee') {
response = await fetch(`/api/staff/avenants/relance-salarie`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ avenantId: avenant.id }),
});
}
if (response && response.ok) {
successCount++;
setSmartReminderProgress(prev =>
prev.map(a => a.id === avenant.id ? { ...a, status: 'success' } : a)
);
} else {
errorCount++;
setSmartReminderProgress(prev =>
prev.map(a => a.id === avenant.id ? { ...a, status: 'error' } : a)
);
}
} catch (error) {
errorCount++;
setSmartReminderProgress(prev =>
prev.map(a => a.id === avenant.id ? { ...a, status: 'error' } : a)
);
}
// Délai entre chaque envoi
await new Promise(resolve => setTimeout(resolve, 500));
}
// Message final
setIsLoadingReminders(false);
if (successCount > 0) {
alert(`${successCount} relance(s) envoyée(s) avec succès`);
}
if (errorCount > 0) {
alert(`${errorCount} erreur(s) lors de l'envoi`);
}
// Déselectionner et rafraîchir
setTimeout(() => {
setSelectedAvenantIds(new Set());
setShowSmartReminderModal(false);
router.refresh();
}, 2000);
};
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-900">Avenants aux contrats</h1>
<p className="text-sm text-slate-600 mt-1">
{filteredAndSortedAmendments.length} avenant{filteredAndSortedAmendments.length > 1 ? "s" : ""}
{activeFiltersCount > 0 && ` (${activeFiltersCount} filtre${activeFiltersCount > 1 ? "s" : ""})`}
</p>
</div>
<div className="flex items-center gap-3">
{selectedAvenantIds.size > 0 && (
<button
onClick={handleSmartReminders}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-sm"
>
<Mail className="h-4 w-4" />
Relances ({selectedAvenantIds.size})
</button>
)}
<button
onClick={handleRefresh}
disabled={isRefreshing}
className="flex items-center gap-2 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 transition-colors disabled:opacity-50"
title="Rafraîchir la liste"
>
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
Rafraîchir
</button>
<button
onClick={() => router.push("/staff/avenants/nouveau")}
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors shadow-sm"
>
<Plus className="h-4 w-4" />
Nouvel avenant
</button>
</div>
</div>
{/* Search and filters bar */}
<div className="bg-white rounded-xl border shadow-sm p-4 space-y-4">
<div className="flex items-center gap-3">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<input
type="text"
placeholder="Rechercher par n° contrat, salarié, matricule, organisation, production, n° avenant..."
value={searchTerm}
onChange={(e) => { setSearchTerm(e.target.value); resetPage(); }}
className="w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors ${
showFilters ? "bg-indigo-50 border-indigo-300 text-indigo-700" : "hover:bg-slate-50"
}`}
>
<Filter className="h-4 w-4" />
Filtres
{activeFiltersCount > 0 && (
<span className="bg-indigo-600 text-white text-xs font-medium px-2 py-0.5 rounded-full">
{activeFiltersCount}
</span>
)}
</button>
{activeFiltersCount > 0 && (
<button
onClick={clearAllFilters}
className="px-4 py-2 text-sm text-slate-600 hover:text-slate-900"
>
Réinitialiser
</button>
)}
</div>
{/* Filters panel */}
{showFilters && (
<div className="border-t pt-4 grid grid-cols-1 md:grid-cols-4 gap-4">
{/* Organisation */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Organisation</label>
<select
value={organizationFilter || ""}
onChange={(e) => { setOrganizationFilter(e.target.value || null); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm bg-white"
>
<option value="">Toutes</option>
{organizations.map(org => (
<option key={org} value={org}>{org}</option>
))}
</select>
</div>
{/* Statut */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Statut</label>
<select
value={statutFilter || ""}
onChange={(e) => { setStatutFilter(e.target.value || null); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm bg-white"
>
<option value="">Tous</option>
<option value="draft">Brouillon</option>
<option value="pending">En attente</option>
<option value="signed">Signé</option>
<option value="cancelled">Annulé</option>
</select>
</div>
{/* Type */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Type</label>
<select
value={typeFilter || ""}
onChange={(e) => { setTypeFilter(e.target.value || null); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm bg-white"
>
<option value="">Tous</option>
<option value="modification">Modification</option>
<option value="annulation">Annulation</option>
</select>
</div>
{/* Signature */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Signature</label>
<select
value={signatureFilter || ""}
onChange={(e) => { setSignatureFilter(e.target.value || null); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm bg-white"
>
<option value="">Tous</option>
<option value="signed">Signé</option>
<option value="pending">En attente</option>
<option value="not_sent">Non envoyé</option>
</select>
</div>
{/* Élément avenant */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Élément</label>
<select
value={elementFilter || ""}
onChange={(e) => { setElementFilter(e.target.value || null); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm bg-white"
>
<option value="">Tous</option>
<option value="objet">Objet</option>
<option value="duree">Durée</option>
<option value="lieu_horaire">Lieu/Horaire</option>
<option value="remuneration">Rémunération</option>
</select>
</div>
{/* Date effet from */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Date effet min</label>
<input
type="date"
value={dateEffetFrom}
onChange={(e) => { setDateEffetFrom(e.target.value); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm"
/>
</div>
{/* Date effet to */}
<div>
<label className="block text-xs font-medium text-slate-700 mb-1">Date effet max</label>
<input
type="date"
value={dateEffetTo}
onChange={(e) => { setDateEffetTo(e.target.value); resetPage(); }}
className="w-full px-3 py-2 border rounded-lg text-sm"
/>
</div>
</div>
)}
</div>
{/* Table */}
{filteredAndSortedAmendments.length === 0 ? (
<div className="bg-white rounded-xl border shadow-sm p-12 text-center">
<FileText className="h-12 w-12 text-slate-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-slate-900 mb-2">
{activeFiltersCount > 0 ? "Aucun résultat" : "Aucun avenant"}
</h3>
<p className="text-sm text-slate-600 mb-6">
{activeFiltersCount > 0
? "Aucun avenant ne correspond à vos critères de recherche."
: "Commencez par créer un nouvel avenant."}
</p>
{activeFiltersCount > 0 ? (
<button
onClick={clearAllFilters}
className="inline-flex items-center gap-2 px-4 py-2 bg-slate-600 text-white rounded-lg hover:bg-slate-700 transition-colors"
>
Réinitialiser les filtres
</button>
) : (
<button
onClick={() => router.push("/staff/avenants/nouveau")}
className="inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
>
<Plus className="h-4 w-4" />
Créer le premier avenant
</button>
)}
</div>
) : (
<>
<div className="bg-white rounded-xl border shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-4 py-3">
<input
type="checkbox"
checked={selectedAvenantIds.size === paginatedAmendments.length && paginatedAmendments.length > 0}
onChange={toggleSelectAll}
className="w-4 h-4 text-indigo-600 border-slate-300 rounded focus:ring-indigo-500"
/>
</th>
<th
className="px-4 py-3 text-left text-xs font-medium text-slate-600 cursor-pointer hover:bg-slate-100"
onClick={() => handleSort("numero_avenant")}
>
N° Avenant {getSortIcon("numero_avenant")}
</th>
<th
className="px-4 py-3 text-left text-xs font-medium text-slate-600 cursor-pointer hover:bg-slate-100"
onClick={() => handleSort("contract_number")}
>
N° Contrat {getSortIcon("contract_number")}
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Salarié
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Organisation
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Type
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Éléments
</th>
<th
className="px-4 py-3 text-left text-xs font-medium text-slate-600 cursor-pointer hover:bg-slate-100"
onClick={() => handleSort("date_effet")}
>
Date d'effet {getSortIcon("date_effet")}
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Signatures
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Statut
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-600">
Actions
</th>
</tr>
</thead>
<tbody className="divide-y">
{paginatedAmendments.map((amendment) => (
<tr
key={amendment.id}
className="hover:bg-slate-50 transition-colors"
>
<td className="px-4 py-3" onClick={(e) => e.stopPropagation()}>
<input
type="checkbox"
checked={amendment.id ? selectedAvenantIds.has(amendment.id) : false}
onChange={() => amendment.id && toggleSelectAvenant(amendment.id)}
className="w-4 h-4 text-indigo-600 border-slate-300 rounded focus:ring-indigo-500"
/>
</td>
<td
className="px-4 py-3 text-sm font-medium text-slate-900 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{amendment.numero_avenant || "-"}
</td>
<td
className="px-4 py-3 text-sm text-slate-700 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{amendment.contract_number || "-"}
</td>
<td
className="px-4 py-3 text-sm text-slate-700 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
<div>{amendment.employee_name || "-"}</div>
{amendment.employee_matricule && (
<div className="text-xs text-slate-500">({amendment.employee_matricule})</div>
)}
</td>
<td
className="px-4 py-3 text-sm text-slate-700 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{amendment.organization_name || "-"}
</td>
<td
className="px-4 py-3 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{getTypeBadge(amendment.type_avenant)}
</td>
<td
className="px-4 py-3 text-sm text-slate-700 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{getElementsLabel(amendment.elements)}
</td>
<td
className="px-4 py-3 text-sm text-slate-700 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{formatDate(amendment.date_effet)}
</td>
<td
className="px-4 py-3 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{getSignatureIcons(amendment.signature_status)}
</td>
<td
className="px-4 py-3 cursor-pointer"
onClick={() => router.push(`/staff/avenants/${amendment.id}`)}
>
{getStatusBadge(amendment.status)}
</td>
<td className="px-4 py-3 text-sm">
<button
onClick={(e) => {
e.stopPropagation();
router.push(`/staff/avenants/${amendment.id}`);
}}
className="text-indigo-600 hover:text-indigo-700 font-medium"
>
Voir
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="bg-white rounded-xl border shadow-sm p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-sm text-slate-600">
Affichage de {(currentPage - 1) * itemsPerPage + 1} à {Math.min(currentPage * itemsPerPage, filteredAndSortedAmendments.length)} sur {filteredAndSortedAmendments.length} avenant{filteredAndSortedAmendments.length > 1 ? "s" : ""}
</span>
<select
value={itemsPerPage}
onChange={(e) => { setItemsPerPage(Number(e.target.value)); setCurrentPage(1); }}
className="px-3 py-1 border rounded-lg text-sm"
>
<option value={25}>25 par page</option>
<option value={50}>50 par page</option>
<option value={100}>100 par page</option>
</select>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="p-2 border rounded-lg hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronLeft className="h-4 w-4" />
</button>
<span className="text-sm text-slate-600 px-3">
Page {currentPage} sur {totalPages}
</span>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="p-2 border rounded-lg hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
</div>
</div>
)}
</>
)}
{/* Smart Reminder Modal */}
<SmartReminderAvenantModal
isOpen={showSmartReminderModal}
onClose={() => setShowSmartReminderModal(false)}
onConfirm={confirmSmartReminders}
isLoading={isLoadingReminders}
avenants={smartReminderAvenants}
progressAvenants={smartReminderProgress.length > 0 ? smartReminderProgress : undefined}
/>
</div>
);
}