diff --git a/components/staff/ContractsGrid.tsx b/components/staff/ContractsGrid.tsx index b8942e8..e95208e 100644 --- a/components/staff/ContractsGrid.tsx +++ b/components/staff/ContractsGrid.tsx @@ -28,6 +28,41 @@ function formatDate(dateString: string | null | undefined): string { } } +// Utility function to format notification date as DD/MM HH:MM +function formatNotificationDate(dateString: string | null): string { + if (!dateString) return "—"; + try { + const date = new Date(dateString); + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${day}/${month} ${hours}:${minutes}`; + } catch { + return "—"; + } +} + +// Utility function to get notification color based on contract start date +function getNotificationColor(startDate: string | null | undefined): string { + if (!startDate) return 'text-slate-400'; + + const start = new Date(startDate); + const today = new Date(); + today.setHours(0, 0, 0, 0); + start.setHours(0, 0, 0, 0); + + const diffTime = start.getTime() - today.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + // Rouge si contrat démarre aujourd'hui ou dans le passé + if (diffDays <= 0) return 'text-red-600'; + // Orange si moins de 48 heures (moins de 2 jours) + if (diffDays < 2) return 'text-orange-600'; + // Vert si plus de 48 heures + return 'text-green-600'; +} + // Utility function to format employee name as "NOM Prénom" // Priorité : utiliser salaries.salarie si disponible, sinon formater employee_name function formatEmployeeName(contract: { employee_name?: string | null; salaries?: { salarie?: string | null; nom?: string | null; prenom?: string | null; } | null }): string { @@ -98,6 +133,11 @@ type Contract = { } | null; }; +type NotificationInfo = { + employerLastSent: string | null; + employeeLastSent: string | null; +}; + type ContractProgress = { id: string; contractNumber?: string; @@ -137,6 +177,9 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract // Selection state const [selectedContractIds, setSelectedContractIds] = useState>(new Set()); + // Notification tracking state + const [notificationMap, setNotificationMap] = useState>(new Map()); + // Key for localStorage const FILTERS_STORAGE_KEY = 'staff-contracts-filters'; @@ -1040,6 +1083,44 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract setIsESignCancelled(false); }; + // Fonction pour récupérer les dernières notifications de signature pour un ensemble de contrats + const fetchLastNotifications = async (contractIds: string[]) => { + if (contractIds.length === 0) return; + + try { + const { data, error } = await supabase + .from('email_logs') + .select('contract_id, created_at, email_type') + .in('contract_id', contractIds) + .in('email_type', ['signature-request-employer', 'signature-request-employee', 'signature-request-salarie']) + .order('created_at', { ascending: false }); + + if (error) { + console.error('Erreur lors de la récupération des notifications:', error); + return; + } + + // Grouper par contract_id et trouver les dernières notifications + const map = new Map(); + data?.forEach(log => { + if (!map.has(log.contract_id)) { + map.set(log.contract_id, { employerLastSent: null, employeeLastSent: null }); + } + const info = map.get(log.contract_id)!; + if (log.email_type === 'signature-request-employer' && !info.employerLastSent) { + info.employerLastSent = log.created_at; + } + if (['signature-request-employee', 'signature-request-salarie'].includes(log.email_type) && !info.employeeLastSent) { + info.employeeLastSent = log.created_at; + } + }); + + setNotificationMap(map); + } catch (error) { + console.error('Exception lors de la récupération des notifications:', error); + } + }; + // Debounce searches when filters change useEffect(() => { // if no filters applied, prefer initial data @@ -1054,6 +1135,14 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract // eslint-disable-next-line react-hooks/exhaustive-deps }, [q, structureFilter, typeFilter, etatContratFilters, etatPaieFilter, dpaeFilter, signatureFilter, startFrom, startTo, sortField, sortOrder, limit]); + // Récupérer les notifications quand les données changent + useEffect(() => { + const contractIds = rows.map(r => r.id); + if (contractIds.length > 0) { + fetchLastNotifications(contractIds); + } + }, [rows]); + // Calculate counts for quick filters useEffect(() => { const calculateCounts = async () => { @@ -1671,7 +1760,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract { setSortField('end_date'); setSortOrder((o) => o === 'asc' ? 'desc' : 'asc'); }}> Date fin {sortField === 'end_date' ? (sortOrder === 'asc' ? '▲' : '▼') : ''} - Actions + Notif. @@ -1775,17 +1864,31 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract {formatDate(r.start_date)} {formatDate(r.end_date)} -
- -
+ {(() => { + const notifInfo = notificationMap.get(r.id); + const color = getNotificationColor(r.start_date); + + if (!notifInfo || (!notifInfo.employerLastSent && !notifInfo.employeeLastSent)) { + return ; + } + + return ( +
+ {notifInfo.employerLastSent && ( +
+ E: + {formatNotificationDate(notifInfo.employerLastSent)} +
+ )} + {notifInfo.employeeLastSent && ( +
+ S: + {formatNotificationDate(notifInfo.employeeLastSent)} +
+ )} +
+ ); + })()} ))}