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 */}
{/* CONTENIDO PRINCIPAL */}
{/* METRICAS KPI DE EQUIPO */}
{metrics.total}
Colaboradores bajo tu jefatura
{metrics.activeToday}
En jornada en este momento
{metrics.gpsActive}
Personal de campo monitoreado
{metrics.pendingVacations}
Vacaciones requeridas por revisar
{/* BARRA DE NAVEGACIÓN Y BÚSQUEDA */}
{/* 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[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 ? (
) : (
Sin datos de geolocalización recientes.
)}
);
})}
)}
{/* TAB 3: MATRIZ DE TURNOS */}
{activeTab === 'schedules' && (
| Colaborador |
Horario Habitual |
Días de Trabajo |
{filteredTeam.map(u => (
| {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 de {selectedUserPhoto.log?.type?.toUpperCase()} • {new Date(selectedUserPhoto.log?.timestamp).toLocaleString()}
)}
);
};