- Ajout détails cachets/répétitions/heures au modal ContractDetails - Card verte avec validation quand tous les contrats ont une fiche de paie - Système complet de création de fiches de paie avec recherche et vérification - Modal liste des contrats sans paie avec création directe - Amélioration édition dates dans PayslipDetailsModal - Optimisation recherche contrats (ordre des filtres) - Augmentation limite pagination ContractsGrid à 200 - Ajout logs debug génération PDF logo - Script SQL vérification cohérence structure/organisation
194 lines
7 KiB
TypeScript
194 lines
7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { X, FileText } from "lucide-react";
|
|
|
|
interface MissingContract {
|
|
id: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
type_de_contrat: string;
|
|
n_objet?: string;
|
|
reference?: string;
|
|
employee_name?: string;
|
|
profession?: string;
|
|
production_name?: string;
|
|
}
|
|
|
|
interface MissingPayslipsModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
structureId: string;
|
|
periodFrom: string;
|
|
periodTo: string;
|
|
onContractSelect: (contractId: string) => void;
|
|
}
|
|
|
|
export default function MissingPayslipsModal({
|
|
isOpen,
|
|
onClose,
|
|
structureId,
|
|
periodFrom,
|
|
periodTo,
|
|
onContractSelect,
|
|
}: MissingPayslipsModalProps) {
|
|
const [contracts, setContracts] = useState<MissingContract[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const fetchMissingContracts = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const params = new URLSearchParams({
|
|
structure: structureId,
|
|
period_from: periodFrom,
|
|
period_to: periodTo,
|
|
});
|
|
|
|
const response = await fetch(`/api/staff/payslips/missing-stats?${params}`);
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.missing_contracts) {
|
|
setContracts(data.missing_contracts);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching missing contracts:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchMissingContracts();
|
|
}, [isOpen, structureId, periodFrom, periodTo]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return new Date(dateString).toLocaleDateString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
const handleContractClick = (contractId: string) => {
|
|
onContractSelect(contractId);
|
|
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-4xl 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">
|
|
Contrats sans fiche de 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>
|
|
) : contracts.length === 0 ? (
|
|
<div className="text-center py-12">
|
|
<p className="text-slate-600">Aucun contrat sans fiche de paie</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{contracts.map((contract) => {
|
|
const startDate = new Date(contract.start_date);
|
|
const endDate = new Date(contract.end_date);
|
|
const isMultiMonth = startDate.getMonth() !== endDate.getMonth() ||
|
|
startDate.getFullYear() !== endDate.getFullYear();
|
|
|
|
return (
|
|
<button
|
|
key={contract.id}
|
|
onClick={() => handleContractClick(contract.id)}
|
|
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-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<FileText className="w-4 h-4 text-slate-400 group-hover:text-indigo-600" />
|
|
<span className="font-medium text-slate-900">
|
|
{contract.reference || 'Référence non définie'}
|
|
</span>
|
|
<span className={`text-xs px-2 py-0.5 rounded ${
|
|
isMultiMonth
|
|
? 'bg-purple-100 text-purple-700'
|
|
: 'bg-blue-100 text-blue-700'
|
|
}`}>
|
|
{isMultiMonth ? 'Multi-mois' : 'Mono-mois'}
|
|
</span>
|
|
<span className="text-xs px-2 py-0.5 rounded bg-slate-100 text-slate-700">
|
|
{contract.type_de_contrat}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="text-sm text-slate-600">
|
|
<span className="font-medium">Période :</span> {formatDate(contract.start_date)} → {formatDate(contract.end_date)}
|
|
</div>
|
|
|
|
{contract.employee_name && (
|
|
<div className="text-sm text-slate-600 mt-1">
|
|
<span className="font-medium">Salarié :</span> {contract.employee_name}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-4 mt-1 text-sm text-slate-600">
|
|
{contract.profession && (
|
|
<div>
|
|
<span className="font-medium">Profession :</span> {contract.profession}
|
|
</div>
|
|
)}
|
|
{contract.production_name && (
|
|
<div>
|
|
<span className="font-medium">Production :</span> {contract.production_name}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="ml-4">
|
|
<span className="text-sm text-indigo-600 group-hover:text-indigo-700 font-medium">
|
|
Créer une paie →
|
|
</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">
|
|
{contracts.length} contrat{contracts.length > 1 ? 's' : ''} sans fiche de paie
|
|
</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>
|
|
);
|
|
}
|