- 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
143 lines
5 KiB
TypeScript
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>
|
|
);
|
|
}
|