feat: Ajouter filtre multi-mois pour les contrats CDDU

- Ajouter un bouton de filtre rapide 'Multi-mois' dans la page staff/contrats
- Le filtre affiche uniquement les contrats CDDU dont la date de début et la date de fin sont sur des mois calendaires différents
- Ajout d'un compteur en temps réel pour afficher le nombre de contrats multi-mois
- Le filtre est persisté dans le localStorage comme les autres filtres
- Utilise une icône Calendar de Lucide React et une couleur violette (purple)
This commit is contained in:
odentas 2025-12-04 21:11:06 +01:00
parent b89d26d26e
commit 18edf3b9b0
2 changed files with 113 additions and 6 deletions

View file

@ -226,11 +226,13 @@ export type ContractsGridHandle = {
quickFilterPaieATraiterToutes: () => void;
quickFilterNonSignesDateProche: () => void;
quickFilterContratsEnCours: () => void;
quickFilterMultiMois: () => void;
getCountDpaeAFaire: () => number | null;
getCountContratsAFaireMois: () => number | null;
getCountPaieATraiterMoisDernier: () => number | null;
getCountPaieATraiterToutes: () => number | null;
getCountNonSignesDateProche: () => number | null;
getCountMultiMois: () => number | null;
};
function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract[]; activeOrgId?: string | null }, ref: React.ForwardedRef<ContractsGridHandle>) {
@ -278,6 +280,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
const [etatPaieFilter, setEtatPaieFilter] = useState<string | null>(savedFilters?.etatPaieFilter || null);
const [dpaeFilter, setDpaeFilter] = useState<string | null>(savedFilters?.dpaeFilter || null);
const [signatureFilter, setSignatureFilter] = useState<string | null>(savedFilters?.signatureFilter || null);
const [multiMoisFilter, setMultiMoisFilter] = useState<boolean>(savedFilters?.multiMoisFilter || false);
const [startFrom, setStartFrom] = useState<string | null>(savedFilters?.startFrom || null);
const [startTo, setStartTo] = useState<string | null>(savedFilters?.startTo || null);
const [endFrom, setEndFrom] = useState<string | null>(savedFilters?.endFrom || null);
@ -314,6 +317,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
const [countPaieATraiterMoisDernier, setCountPaieATraiterMoisDernier] = useState<number | null>(null);
const [countPaieATraiterToutes, setCountPaieATraiterToutes] = useState<number | null>(null);
const [countContratsNonSignesDateProche, setCountContratsNonSignesDateProche] = useState<number | null>(null);
const [countMultiMois, setCountMultiMois] = useState<number | null>(null);
// PDF generation state
const [isGeneratingPdfs, setIsGeneratingPdfs] = useState(false);
@ -494,6 +498,26 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
setSortOrder('asc');
};
const applyQuickFilterMultiMois = () => {
// Contrats multi-mois : CDDU dont la date de début et la date de fin ne sont pas sur le même mois calendaire
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter('CDDU'); // Uniquement les CDDU
setProductionFilter(null); // Reset production
setSignatureFilter(null); // Tous les contrats
setEtatPaieFilter(null); // Reset état paie
setEtatContratFilters(new Set()); // Reset état contrat
setDpaeFilter(null); // Reset DPAE
setMultiMoisFilter(true); // Activer le filtre multi-mois
setStartFrom(null);
setStartTo(null);
setEndFrom(null);
setEndTo(null);
setSortField('start_date');
setSortOrder('desc');
};
// Expose imperative handlers to parent wrappers
useImperativeHandle(ref, () => ({
quickFilterDpaeAFaire: applyQuickFilterDpaeAFaire,
@ -502,12 +526,14 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
quickFilterPaieATraiterToutes: applyQuickFilterPaieATraiterToutes,
quickFilterNonSignesDateProche: applyQuickFilterNonSignesDateProche,
quickFilterContratsEnCours: applyQuickFilterContratsEnCours,
quickFilterMultiMois: applyQuickFilterMultiMois,
getCountDpaeAFaire: () => countDpaeAFaire,
getCountContratsAFaireMois: () => countContratsAFaireMois,
getCountPaieATraiterMoisDernier: () => countPaieATraiterMoisDernier,
getCountPaieATraiterToutes: () => countPaieATraiterToutes,
getCountNonSignesDateProche: () => countContratsNonSignesDateProche,
}), [applyQuickFilterDpaeAFaire, applyQuickFilterContratsAFaireMois, applyQuickFilterPaieATraiterMoisDernier, applyQuickFilterPaieATraiterToutes, applyQuickFilterNonSignesDateProche, applyQuickFilterContratsEnCours, countDpaeAFaire, countContratsAFaireMois, countPaieATraiterMoisDernier, countPaieATraiterToutes, countContratsNonSignesDateProche]);
getCountMultiMois: () => countMultiMois,
}), [applyQuickFilterDpaeAFaire, applyQuickFilterContratsAFaireMois, applyQuickFilterPaieATraiterMoisDernier, applyQuickFilterPaieATraiterToutes, applyQuickFilterNonSignesDateProche, applyQuickFilterContratsEnCours, applyQuickFilterMultiMois, countDpaeAFaire, countContratsAFaireMois, countPaieATraiterMoisDernier, countPaieATraiterToutes, countContratsNonSignesDateProche, countMultiMois]);
// Save filters to localStorage whenever they change
useEffect(() => {
@ -520,6 +546,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
etatPaieFilter,
dpaeFilter,
signatureFilter,
multiMoisFilter,
startFrom,
startTo,
endFrom,
@ -529,7 +556,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
showFilters
};
saveFiltersToStorage(filters);
}, [q, structureFilter, typeFilter, productionFilter, etatContratFilters, etatPaieFilter, dpaeFilter, signatureFilter, startFrom, startTo, endFrom, endTo, sortField, sortOrder, showFilters]);
}, [q, structureFilter, typeFilter, productionFilter, etatContratFilters, etatPaieFilter, dpaeFilter, signatureFilter, multiMoisFilter, startFrom, startTo, endFrom, endTo, sortField, sortOrder, showFilters]);
// Réinitialiser le filtre de production quand la structure change
useEffect(() => {
@ -733,16 +760,37 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
// Apply local sorting when using initial data
const sortedRows = useMemo(() => {
const noFilters = !q && !structureFilter && !typeFilter && !productionFilter && etatContratFilters.size === 0 && !etatPaieFilter && !dpaeFilter && !signatureFilter && !startFrom && !startTo && !endFrom && !endTo;
const noFilters = !q && !structureFilter && !typeFilter && !productionFilter && etatContratFilters.size === 0 && !etatPaieFilter && !dpaeFilter && !signatureFilter && !startFrom && !startTo && !endFrom && !endTo && !multiMoisFilter;
let filteredRows = rows;
// Appliquer le filtre multi-mois côté client si activé
if (multiMoisFilter) {
filteredRows = rows.filter(contract => {
if (!contract.start_date || !contract.end_date) return false;
const startDate = new Date(contract.start_date);
const endDate = new Date(contract.end_date);
// Vérifier si les mois ou les années sont différents
return startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear();
});
}
if (noFilters) {
// Utiliser le tri local pour les données initiales
return sortContractsLocally(rows, sortField, sortOrder);
return sortContractsLocally(filteredRows, sortField, sortOrder);
}
// Pour les données filtrées, utiliser les données telles qu'elles viennent du serveur
return rows;
}, [rows, sortField, sortOrder, q, structureFilter, typeFilter, productionFilter, etatContratFilters, etatPaieFilter, dpaeFilter, signatureFilter, startFrom, startTo, endFrom, endTo]);
// mais appliquer le tri local car nous avons potentiellement filtré côté client
if (multiMoisFilter) {
return sortContractsLocally(filteredRows, sortField, sortOrder);
}
return filteredRows;
}, [rows, sortField, sortOrder, q, structureFilter, typeFilter, productionFilter, etatContratFilters, etatPaieFilter, dpaeFilter, signatureFilter, startFrom, startTo, endFrom, endTo, multiMoisFilter]);
// Selection functions (updated to use sortedRows for selection)
const toggleSelectAll = () => {
@ -1558,6 +1606,26 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
calculateCounts();
}, []);
// Calculate multi-mois count from client-side data (separate useEffect)
useEffect(() => {
const cdduContracts = rows.filter(contract =>
contract.type_de_contrat === 'CDDU' || contract.type_de_contrat === 'CDDU_MONO' || contract.type_de_contrat === 'CDDU_MULTI'
);
const multiMoisCount = cdduContracts.filter(contract => {
if (!contract.start_date || !contract.end_date) return false;
const startDate = new Date(contract.start_date);
const endDate = new Date(contract.end_date);
// Vérifier si les mois ou les années sont différents
return startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear();
}).length;
setCountMultiMois(multiMoisCount);
}, [rows]);
// Fonction pour relancer un salarié spécifique
const handleReminderClick = async (contract: any) => {
setSelectedContractForReminder(contract);

View file

@ -13,6 +13,7 @@ export default function StaffContractsPageClient({ initialData, activeOrgId }: {
paieATraiterMoisDernier: null as number | null,
paieATraiterToutes: null as number | null,
nonSignesDateProche: null as number | null,
multiMois: null as number | null,
});
// Check localStorage to detect which filter is active
@ -125,6 +126,15 @@ export default function StaffContractsPageClient({ initialData, activeOrgId }: {
setActiveFilter('contrats-en-cours');
return;
}
// Check for Multi-mois filter
if (filters.multiMoisFilter === true &&
filters.typeFilter === 'CDDU' &&
filters.sortField === 'start_date' &&
filters.sortOrder === 'desc') {
setActiveFilter('multi-mois');
return;
}
}
} catch (e) {
// ignore
@ -140,6 +150,7 @@ export default function StaffContractsPageClient({ initialData, activeOrgId }: {
paieATraiterMoisDernier: gridRef.current?.getCountPaieATraiterMoisDernier() ?? null,
paieATraiterToutes: gridRef.current?.getCountPaieATraiterToutes() ?? null,
nonSignesDateProche: gridRef.current?.getCountNonSignesDateProche() ?? null,
multiMois: gridRef.current?.getCountMultiMois() ?? null,
});
};
@ -178,6 +189,11 @@ export default function StaffContractsPageClient({ initialData, activeOrgId }: {
setActiveFilter('contrats-en-cours');
};
const handleMultiMois = () => {
gridRef.current?.quickFilterMultiMois();
setActiveFilter('multi-mois');
};
return (
<div className="space-y-3">
{/* Card de raccourcis pleine largeur */}
@ -268,6 +284,29 @@ export default function StaffContractsPageClient({ initialData, activeOrgId }: {
<Calendar size={16} />
En cours
</button>
{/* Bouton Multi-mois */}
<button
onClick={handleMultiMois}
className={`inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg font-medium transition-all relative ${
activeFilter === 'multi-mois'
? 'bg-gradient-to-r from-purple-500 to-purple-600 text-white shadow-md'
: 'bg-white text-purple-700 border border-purple-300 hover:border-purple-500 hover:shadow-sm'
}`}
title="Afficher uniquement les contrats CDDU multi-mois (date de début et date de fin sur des mois calendaires différents)"
>
<Calendar size={16} />
Multi-mois
{counts.multiMois !== null && (
<span className={`ml-1 inline-flex items-center justify-center w-5 h-5 rounded-full text-xs font-bold ${
activeFilter === 'multi-mois'
? 'bg-purple-300 text-purple-900'
: 'bg-purple-200 text-purple-800'
}`}>
{counts.multiMois}
</span>
)}
</button>
</div>
</div>