const { useState, useMemo, useEffect } = React; const IconSup = ({ name, className = "w-4 h-4" }) => { const icons = { 'users': , 'clock': , 'map-pin': , 'calendar': , 'shield': , 'log-out': , 'check': , 'x': , 'refresh': }; return icons[name] || null; }; window.SupDashboard = function({ profile, globalUsers = [], logs = [], vacations = [], absences = [], settings = {}, actions = [], onLogout, setView, refreshData }) { const [activeTab, setActiveTab] = useState('attendance'); // 'attendance' | 'gps' | 'schedules' | 'vacations' const [searchTerm, setSearchTerm] = useState(''); const [selectedUserPhoto, setSelectedUserPhoto] = useState(null); const [isPunching, setIsPunching] = useState(false); const todayStr = window.getNICDate(); // Estado del marcaje personal del Supervisor hoy const todaySupervisorLogs = useMemo(() => { const profId = Number(profile?.id || 0); return (logs || []).filter(l => Number(l.userId || l.user_id) === profId && (l.date === todayStr || (l.timestamp && String(l.timestamp).startsWith(todayStr)))); }, [logs, profile?.id, todayStr]); const isSupervisorCheckedIn = useMemo(() => { if (!todaySupervisorLogs.length) return false; const sorted = [...todaySupervisorLogs].sort((a, b) => Number(a.timestamp || 0) - Number(b.timestamp || 0)); const lastLog = sorted[sorted.length - 1]; return lastLog && (lastLog.type === 'entrada' || (lastLog.type && lastLog.type.endsWith('_in'))); }, [todaySupervisorLogs]); const handleQuickPunch = async (punchType) => { setIsPunching(true); let coords = { lat: 0, lng: 0 }; try { if (navigator.geolocation) { const pos = await new Promise((resolve) => { navigator.geolocation.getCurrentPosition(resolve, () => resolve(null), { timeout: 4000 }); }); if (pos && pos.coords) coords = { lat: pos.coords.latitude, lng: pos.coords.longitude }; } } catch (eGeo) {} const supHasClockedOut = (todaySupervisorLogs || []).some(l => l.type === 'salida'); let quickJustification = null; if (punchType === 'entrada' && profile?.scheduleStart && !supHasClockedOut) { const now = new Date(); const currentMins = now.getHours() * 60 + now.getMinutes(); const [schedH, schedM] = profile.scheduleStart.split(':').map(Number); const schedMins = schedH * 60 + schedM; const graceMins = Number(settings?.gracePeriodMinutes || settings?.grace_period_minutes || 5); const diffMins = currentMins - schedMins; if (diffMins > graceMins) { quickJustification = `ALERTA: Llegada Tardía de ${diffMins} min (Horario oficial: ${profile.scheduleStart}, Tolerancia: ${graceMins} min)`; } } try { const res = await window.apiCall('save_log', { userId: profile?.id, type: punchType, timestamp: Date.now(), date: window.getNICDate ? window.getNICDate() : new Date().toISOString().split('T')[0], location: coords, client: 'Marcaje Rápido Supervisor', justification: quickJustification }); if (res && res.success) { if (punchType === 'salida' && profile) { profile.overrideCheckOut = 0; } if (typeof window.showToast === 'function') window.showToast(`¡${punchType.toUpperCase()} registrada!`); if (refreshData) await refreshData(); } else { if (typeof window.showToast === 'function') window.showToast("Error al registrar marcaje."); } } catch (err) { if (typeof window.showToast === 'function') window.showToast("Error de conexión al marcar."); } finally { setIsPunching(false); } }; // Personal a cargo estrictamente filtrado por supervisor_id const myTeam = useMemo(() => { const profId = Number(profile?.id || 0); return (globalUsers || []).filter(u => { const supId = Number(u.supervisorId || u.supervisor_id || 0); return supId === profId; }); }, [globalUsers, profile?.id]); const myTeamIds = useMemo(() => new Set(myTeam.map(u => Number(u.id))), [myTeam]); const filteredTeam = useMemo(() => { if (!searchTerm.trim()) return myTeam; const term = searchTerm.toLowerCase(); return myTeam.filter(u => (u.name || '').toLowerCase().includes(term) || (u.positionTitle || u.role || '').toLowerCase().includes(term) || (u.idCard || u.id_card || '').toLowerCase().includes(term) ); }, [myTeam, searchTerm]); const myTeamLogs = useMemo(() => { return (logs || []).filter(l => myTeamIds.has(Number(l.userId || l.user_id))); }, [logs, myTeamIds]); const todayTeamLogs = useMemo(() => { return myTeamLogs.filter(l => l.date === todayStr); }, [myTeamLogs, todayStr]); const myTeamVacations = useMemo(() => { return (vacations || []).filter(v => myTeamIds.has(Number(v.userId || v.user_id))); }, [vacations, myTeamIds]); const metrics = useMemo(() => { const total = myTeam.length; const activeToday = myTeam.filter(u => { const userLogs = todayTeamLogs.filter(l => Number(l.userId || l.user_id) === Number(u.id)); if (userLogs.length === 0) return false; const lastType = userLogs[0].type; return lastType !== 'salida'; }).length; const gpsActive = myTeam.filter(u => u.gpsTrackingEnabled).length; const pendingVacations = myTeamVacations.filter(v => v.status === 'pending').length; return { total, activeToday, gpsActive, pendingVacations }; }, [myTeam, todayTeamLogs, myTeamVacations]); const getEmployeeTodayStatus = (u) => { const uLogs = todayTeamLogs.filter(l => Number(l.userId || l.user_id) === Number(u.id)).sort((a,b) => b.timestamp - a.timestamp); if (uLogs.length === 0) { return { status: 'sin_marca', label: 'Sin Marcaje Hoy', color: 'slate' }; } const lastLog = uLogs[0]; if (lastLog.type === 'salida') { return { status: 'salida', label: 'Jornada Finalizada', color: 'slate', lastTime: lastLog.timestamp }; } if (lastLog.type.endsWith('_in') && lastLog.type !== 'entrada') { const actName = lastLog.type.replace('_in', '').toUpperCase(); return { status: 'actividad', label: `En ${actName}`, color: 'amber', lastTime: lastLog.timestamp }; } return { status: 'activo', label: 'En Jornada Laboral', color: 'emerald', lastTime: lastLog.timestamp }; }; return (
{/* CABECERA SUPERVISOR */}
{profile?.name ? profile.name[0].toUpperCase() : 'S'}

{profile?.name || 'Supervisor'}

Portal de Supervisión

Supervisión de Personal a Cargo • {settings?.companyName || 'Empresa'}

{!isSupervisorCheckedIn ? ( ) : ( )} {typeof setView === 'function' && ( )}
{/* CONTENIDO PRINCIPAL */}
{/* METRICAS KPI DE EQUIPO */}
Personal a Cargo

{metrics.total}

Colaboradores bajo tu jefatura

Activos Hoy

{metrics.activeToday}

En jornada en este momento

Rastreo GPS Activo

{metrics.gpsActive}

Personal de campo monitoreado

Solicitudes Pendientes

{metrics.pendingVacations}

Vacaciones requeridas por revisar

{/* BARRA DE NAVEGACIÓN Y BÚSQUEDA */}
setSearchTerm(e.target.value)} placeholder="Buscar en personal a cargo..." className="w-full sm:w-64 p-2.5 bg-slate-50 border border-slate-200 rounded-xl text-xs font-medium outline-none focus:border-slate-400" />
{/* TAB 1: ASISTENCIA Y MARCAJES EN VIVO DE MI EQUIPO */} {activeTab === 'attendance' && (
{filteredTeam.length === 0 ? (
No tienes colaboradores asignados bajo tu supervisión directa actualmente.
) : (
{filteredTeam.map(u => { const stInfo = getEmployeeTodayStatus(u); const uLogs = todayTeamLogs.filter(l => Number(l.userId || l.user_id) === Number(u.id)).sort((a,b) => b.timestamp - a.timestamp); const lastLog = uLogs[0]; return (
{u.avatarUrl ? ( {u.name} ) : (
{u.name ? u.name[0].toUpperCase() : 'U'}
)}

{u.name}

{u.positionTitle || u.role}

{stInfo.label}
{/* ULTIMA MARCA HOY */}
Última Actividad Registrada {lastLog ? (
{lastLog.type.replace('_in','').replace('_out','')} {new Date(lastLog.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
) : ( Sin registros el día de hoy )}
{/* BOTÓN VER FOTO DE MARCAJE */} {lastLog && lastLog.photo && ( )}
); })}
)}
)} {/* TAB 2: RASTREO GPS Y RUTAS DE CAMPO */} {activeTab === 'gps' && (
{filteredTeam.map(u => { const uLogsWithGeo = myTeamLogs.filter(l => Number(l.userId || l.user_id) === Number(u.id) && l.location && (l.location.lat || l.location.lng)).sort((a,b) => b.timestamp - a.timestamp); const lastGeoLog = uLogsWithGeo[0]; return (

{u.name}

{u.positionTitle || u.role}

{u.gpsTrackingEnabled ? 'GPS Activo' : 'Sin GPS'}
{lastGeoLog && lastGeoLog.location ? (
Último reporte: {new Date(lastGeoLog.timestamp).toLocaleString()}
Ver Ubicación en Google Maps
) : (

Sin datos de geolocalización recientes.

)}
); })}
)} {/* TAB 3: MATRIZ DE TURNOS */} {activeTab === 'schedules' && (
{filteredTeam.map(u => ( ))}
Colaborador Horario Habitual Días de Trabajo
{u.name} {u.scheduleStart || '08:00'} - {u.scheduleEnd || '17:00'} {(u.scheduleDays || ['lun','mar','mie','jue','vie']).join(', ')}
)} {/* TAB 4: VACACIONES Y PERMISOS DE SU EQUIPO */} {activeTab === 'vacations' && (
{myTeamVacations.length === 0 ? (
No hay solicitudes de vacaciones registradas por tu personal a cargo.
) : ( myTeamVacations.map(v => { const userObj = myTeam.find(u => Number(u.id) === Number(v.userId || v.user_id)); return (

{userObj?.name || 'Colaborador'}

Período solicitado: {v.startDate} {v.endDate && v.endDate !== v.startDate ? `al ${v.endDate}` : ''}

{v.status === 'pending' ? 'Pendiente Revisión' : v.status}
); }) )}
)}
{/* MODAL FOTO DE MARCAJE */} {selectedUserPhoto && (
setSelectedUserPhoto(null)}>
e.stopPropagation()}>

{selectedUserPhoto.user?.name}

Marcaje
Marcaje de {selectedUserPhoto.log?.type?.toUpperCase()} • {new Date(selectedUserPhoto.log?.timestamp).toLocaleString()}
)}
); };