Files
MIESYSTEM/MieSystem/Views/Asistencia/Index.cshtml
2025-12-25 13:54:49 -06:00

496 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@model MieSystem.Models.ViewModels.AsistenciaViewModel
@{
ViewData["Title"] = "Control de Asistencia";
var diasSeleccionadosList = new List<string>();
if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados))
{
diasSeleccionadosList = Model.DiasSemanaSeleccionados
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(d => d.Trim())
.ToList();
}
var diasAMostrar = Model.DiasDelMes
.Where(d => diasSeleccionadosList.Count == 0 ||
diasSeleccionadosList.Contains(((int)d.DayOfWeek).ToString()))
.ToList();
}
<div class="container-fluid">
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-filter"></i> Filtros</h5>
</div>
<div class="card-body">
<form method="get" id="filtroForm" class="row g-3">
<div class="col-md-3">
<label class="form-label">Año</label>
<select name="año" class="form-select" id="selectAnio">
@foreach (var año in ViewBag.Años)
{
<option value="@año.Value" selected="@año.Selected">
@año.Text
</option>
}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Mes</label>
<select name="mes" class="form-select" id="selectMes">
@foreach (var mes in ViewBag.Meses)
{
<option value="@mes.Value" selected="@(mes.Value == Model.Mes.ToString())">
@mes.Text
</option>
}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Días de la semana</label>
<div class="dias-semana-checkboxes">
@foreach (var dia in ViewBag.DiasSemana)
{
var isChecked = diasSeleccionadosList.Contains(dia.Value);
<div class="form-check form-check-inline">
<input class="form-check-input dia-checkbox"
type="checkbox"
value="@dia.Value"
id="dia@(dia.Value)"
@(isChecked ? "checked" : "")>
<label class="form-check-label" for="dia@(dia.Value)">
@dia.Text
</label>
</div>
}
<input type="hidden" name="diasSemana" id="diasSemanaInput"
value="@Model.DiasSemanaSeleccionados">
</div>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-search"></i> Filtrar
</button>
</div>
</form>
</div>
</div>
<div class="row mb-4" id="estadisticasContainer">
</div>
<div class="card">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-calendar-check"></i>
Asistencia - @Model.NombreMes @Model.Año
<span class="badge bg-light text-dark ms-2">@Model.Expedientes.Count niños</span>
</h5>
<div>
<button class="btn btn-light btn-sm" id="btnGuardarTodo">
<i class="fas fa-save"></i> Guardar todo
</button>
<button class="btn btn-light btn-sm" id="btnExportar">
<i class="fas fa-file-excel"></i> Exportar
</button>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
<table class="table table-bordered table-striped mb-0" id="tablaAsistencia">
<thead class="table-dark sticky-top" style="top: 0;">
<tr>
<th style="min-width: 200px; position: sticky; left: 0; background: #343a40; z-index: 10;">
Niño
</th>
@foreach (var dia in diasAMostrar)
{
var esFinSemana = dia.DayOfWeek == DayOfWeek.Saturday || dia.DayOfWeek == DayOfWeek.Sunday;
<th class="text-center @(esFinSemana ? "" : "")"
style="min-width: 60px;">
<div class="small">@dia.ToString("ddd")</div>
<div class="fw-bold">@dia.Day</div>
</th>
}
</tr>
</thead>
<tbody>
@foreach (var expediente in Model.Expedientes)
{
<tr data-expediente-id="@expediente.Id">
<td style="position: sticky; left: 0; background: white; z-index: 5;">
<div class="fw-bold">@expediente.NombreCompleto</div>
<div class="small text-muted">Edad: @expediente.Edad años</div>
</td>
@foreach (var dia in diasAMostrar)
{
var key = $"{expediente.Id}_{dia:yyyy-MM-dd}";
var estadoActual = Model.Asistencias.ContainsKey(key)
? Model.Asistencias[key]
: "";
<td class="text-center p-0 celda-asistencia"
data-expediente="@expediente.Id"
data-fecha="@dia.ToString("yyyy-MM-dd")">
<select class="form-select form-select-sm estado-select border-0 rounded-0 h-100 w-100"
data-initial="@estadoActual">
<option value=""></option>
@if (estadoActual == "P")
{
<option value="P" selected class="bg-success text-white">P</option>
}
else
{
<option value="P" class="bg-success text-white">P</option>
}
@if (estadoActual == "T")
{
<option value="T" selected class="bg-warning">T</option>
}
else
{
<option value="T" class="bg-warning">T</option>
}
@if (estadoActual == "F")
{
<option value="F" selected class="bg-danger text-white">F</option>
}
else
{
<option value="F" class="bg-danger text-white">F</option>
}
</select>
</td>
}
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<div class="row">
<div class="col-md-6">
<div class="leyenda">
<span class="badge bg-success me-2">P = Presente</span>
<span class="badge bg-warning me-2">T = Tarde</span>
<span class="badge bg-danger me-2">F = Falto</span>
<span class="text-muted ms-3">(Vacío = No registrado)</span>
</div>
</div>
<div class="col-md-6 text-end">
<small class="text-muted">
Total: @Model.Expedientes.Count niños × @diasAMostrar.Count días =
@(Model.Expedientes.Count * diasAMostrar.Count) registros posibles
</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalCarga" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-body text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p class="mt-2">Guardando cambios...</p>
</div>
</div>
</div>
</div>
@section Styles {
<style>
.table th, .table td {
vertical-align: middle;
}
.estado-select {
cursor: pointer;
transition: all 0.2s;
}
.estado-select:hover {
transform: scale(1.05);
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.estado-select option[value="P"] {
background-color: #198754 !important;
color: white !important;
}
.estado-select option[value="T"] {
background-color: #ffc107 !important;
color: black !important;
}
.estado-select option[value="F"] {
background-color: #dc3545 !important;
color: white !important;
}
.celda-asistencia.changed {
outline: 2px solid #0d6efd;
outline-offset: -2px;
}
.sticky-left {
position: sticky;
left: 0;
background: white;
z-index: 5;
}
.dias-semana-checkboxes {
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
border: 1px solid #dee2e6;
}
.bg-lunes { background-color: #e3f2fd !important; }
.bg-martes { background-color: #f3e5f5 !important; }
.bg-miercoles { background-color: #e8f5e8 !important; }
.bg-jueves { background-color: #fff3e0 !important; }
.bg-viernes { background-color: #fce4ec !important; }
.bg-sabado { background-color: #f1f8e9 !important; }
.bg-domingo { background-color: #fff8e1 !important; }
</style>
}
@section Scripts {
<script>
$(document).ready(function() {
$('.dia-checkbox').change(function() {
var diasSeleccionados = $('.dia-checkbox:checked').map(function() {
return $(this).val();
}).get().join(',');
$('#diasSemanaInput').val(diasSeleccionados);
});
$('.estado-select').change(function() {
var initial = $(this).data('initial');
var current = $(this).val();
if (initial !== current) {
$(this).closest('.celda-asistencia').addClass('changed');
} else {
$(this).closest('.celda-asistencia').removeClass('changed');
}
});
$('.estado-select').change(function() {
var celda = $(this).closest('.celda-asistencia');
var expedienteId = celda.data('expediente');
var fecha = celda.data('fecha');
var estado = $(this).val();
guardarAsistencia(expedienteId, fecha, estado, celda);
});
$('#btnGuardarTodo').click(function() {
var cambios = [];
var celdasCambiadas = $('.celda-asistencia.changed');
if (celdasCambiadas.length === 0) {
toastr.info('No hay cambios para guardar');
return;
}
if (!confirm(`¿Guardar ${celdasCambiadas.length} cambios?`)) {
return;
}
$('#modalCarga').modal('show');
celdasCambiadas.each(function() {
var celda = $(this);
var select = celda.find('.estado-select');
var expedienteId = celda.data('expediente');
var fecha = celda.data('fecha');
var estado = select.val();
cambios.push({
expedienteId: expedienteId,
fecha: fecha,
estado: estado
});
});
$.ajax({
url: '@Url.Action("GuardarAsistenciasMasivas", "Asistencia")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(cambios),
success: function(response) {
$('#modalCarga').modal('hide');
if (response.success) {
$('.celda-asistencia').removeClass('changed');
$('.estado-select').each(function() {
$(this).data('initial', $(this).val());
});
toastr.success(response.message);
cargarEstadisticas();
} else {
toastr.error(response.message);
}
},
error: function() {
$('#modalCarga').modal('hide');
toastr.error('Error al guardar los cambios');
}
});
});
$('#btnExportar').click(function() {
var año = $('#selectAnio').val();
var mes = $('#selectMes').val();
var diasSemana = $('#diasSemanaInput').val();
var url = '@Url.Action("ExportarExcel", "Asistencia")' +
'?año=' + año +
'&mes=' + mes +
'&diasSemana=' + diasSemana;
window.open(url, '_blank');
});
$('#selectAnio, #selectMes').change(function() {
$('#filtroForm').submit();
});
aplicarColoresDias();
});
function guardarAsistencia(expedienteId, fecha, estado, celda) {
$.ajax({
url: '@Url.Action("GuardarAsistencia", "Asistencia")',
type: 'POST',
data: {
expedienteId: expedienteId,
fecha: fecha,
estado: estado
},
success: function(response) {
if (response.success) {
celda.removeClass('changed');
celda.find('.estado-select').data('initial', estado);
toastr.success('Guardado');
cargarEstadisticas();
} else {
toastr.error(response.message);
}
},
error: function() {
toastr.error('Error al guardar');
}
});
}
function cargarEstadisticas() {
var año = $('#selectAnio').val();
var mes = $('#selectMes').val();
$.ajax({
url: '@Url.Action("ObtenerEstadisticas", "Asistencia")',
type: 'GET',
data: { año: año, mes: mes },
success: function(data) {
if (data.error) {
console.error(data.error);
return;
}
var total = data.total || 0;
var presentes = data.presentes || 0;
var tardes = data.tardes || 0;
var faltas = data.faltas || 0;
var porcentajePresentes = total > 0 ? ((presentes / total) * 100).toFixed(1) : 0;
var porcentajeTardes = total > 0 ? ((tardes / total) * 100).toFixed(1) : 0;
var porcentajeFaltas = total > 0 ? ((faltas / total) * 100).toFixed(1) : 0;
$('#estadisticasContainer').html(`
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h6 class="card-title">Presentes</h6>
<h2>${presentes}</h2>
<small>${porcentajePresentes}%</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning">
<div class="card-body text-center">
<h6 class="card-title">Tardes</h6>
<h2>${tardes}</h2>
<small>${porcentajeTardes}%</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-danger text-white">
<div class="card-body text-center">
<h6 class="card-title">Faltas</h6>
<h2>${faltas}</h2>
<small>${porcentajeFaltas}%</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<h6 class="card-title">Total Registros</h6>
<h2>${total}</h2>
<small>Asistencia: ${porcentajePresentes}%</small>
</div>
</div>
</div>
`);
},
error: function() {
console.error('Error cargando estadísticas');
}
});
}
function aplicarColoresDias() {
$('th.text-center').each(function() {
var textoDia = $(this).find('.small').text().trim();
var claseColor = '';
switch(textoDia) {
case 'Lun': claseColor = 'bg-lunes'; break;
case 'Mar': claseColor = 'bg-martes'; break;
case 'Mié': claseColor = 'bg-miercoles'; break;
case 'Jue': claseColor = 'bg-jueves'; break
case 'Vie': claseColor = 'bg-viernes'; break;
case 'Sáb': claseColor = 'bg-sabado'; break;
case 'Dom': claseColor = 'bg-domingo'; break;
}
if (claseColor) {
$(this).addClass(claseColor);
}
});
}
</script>
}