espace-paie-odentas/components/staff/MissingPayslipsOrganizationsModal.tsx
odentas ad2a9c6b7d feat: Amélioration modale création fiche de paie et page staff/contrats
- Ajout profession et dates de contrat dans la modale de création de fiche de paie
- Pré-remplissage automatique des dates et salaire brut pour contrats mono-mois
- Exclusion des contrats annulés des statistiques et recherches
- Suppression titre page staff/contrats et mise en pleine largeur des filtres
- Ajout route API pour organisations avec contrats sans paie
2025-11-28 00:11:52 +01:00

143 lines
5 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { X, Building2 } from "lucide-react";
interface OrganizationWithMissing {
organization_id: string;
organization_name: string;
missing_count: number;
}
interface MissingPayslipsOrganizationsModalProps {
isOpen: boolean;
onClose: () => void;
periodFrom: string;
periodTo: string;
onOrganizationSelect: (orgId: string) => void;
}
export default function MissingPayslipsOrganizationsModal({
isOpen,
onClose,
periodFrom,
periodTo,
onOrganizationSelect,
}: MissingPayslipsOrganizationsModalProps) {
const [organizations, setOrganizations] = useState<OrganizationWithMissing[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [totalMissing, setTotalMissing] = useState(0);
useEffect(() => {
if (!isOpen) return;
const fetchOrganizations = async () => {
setIsLoading(true);
try {
const params = new URLSearchParams({
period_from: periodFrom,
period_to: periodTo,
});
const response = await fetch(`/api/staff/payslips/missing-organizations?${params}`);
const data = await response.json();
if (response.ok && data.organizations) {
setOrganizations(data.organizations);
setTotalMissing(data.total_missing_contracts || 0);
}
} catch (error) {
console.error("Error fetching organizations:", error);
} finally {
setIsLoading(false);
}
};
fetchOrganizations();
}, [isOpen, periodFrom, periodTo]);
if (!isOpen) return null;
const handleOrganizationClick = (orgName: string) => {
onOrganizationSelect(orgName);
onClose();
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white rounded-lg shadow-xl w-full max-w-3xl max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b">
<h2 className="text-xl font-semibold text-slate-900">
Organisations avec contrats sans paie
</h2>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-600 transition-colors"
>
<X className="w-6 h-6" />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6">
{isLoading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-600 border-t-transparent"></div>
</div>
) : organizations.length === 0 ? (
<div className="text-center py-12">
<p className="text-slate-600">Aucune organisation avec des contrats sans paie</p>
</div>
) : (
<div className="space-y-3">
{organizations.map((org) => (
<button
key={org.organization_id}
onClick={() => handleOrganizationClick(org.organization_name)}
className="w-full text-left p-4 border rounded-lg hover:bg-slate-50 hover:border-indigo-300 transition-colors group"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-indigo-100 rounded-lg group-hover:bg-indigo-200 transition-colors">
<Building2 className="w-5 h-5 text-indigo-600" />
</div>
<div>
<div className="font-medium text-slate-900">
{org.organization_name}
</div>
<div className="text-sm text-slate-600 mt-0.5">
{org.missing_count} contrat{org.missing_count > 1 ? 's' : ''} sans paie
</div>
</div>
</div>
<div className="ml-4">
<span className="text-sm text-indigo-600 group-hover:text-indigo-700 font-medium">
Voir les contrats
</span>
</div>
</div>
</button>
))}
</div>
)}
</div>
{/* Footer */}
<div className="p-6 border-t bg-slate-50">
<div className="flex items-center justify-between">
<p className="text-sm text-slate-600">
{organizations.length} organisation{organizations.length > 1 ? 's' : ''} · {totalMissing} contrat{totalMissing > 1 ? 's' : ''} sans paie au total
</p>
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50"
>
Fermer
</button>
</div>
</div>
</div>
</div>
);
}