648 lines
25 KiB
Plaintext
648 lines
25 KiB
Plaintext
@using MieSystem.Models.ViewModels
|
|
@{
|
|
ViewData["Title"] = "Expedientes";
|
|
ViewData["ActionButtons"] = @"<button class='btn btn-primary' data-bs-toggle='modal' data-bs-target='#createModal'>
|
|
<i class='bi bi-plus-circle me-1'></i> Nuevo Expediente
|
|
</button>";
|
|
}
|
|
|
|
<div class="row mb-4">
|
|
<!-- Tarjeta de total de niños registrados -->
|
|
<div class="col-md-4">
|
|
<div class="card border-primary">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="card-title text-primary">Total de Niños</h5>
|
|
<h2 class="text-primary" id="totalNinos">0</h2>
|
|
<p class="card-text text-muted">Registrados en el sistema</p>
|
|
</div>
|
|
<div class="bg-primary text-white rounded-circle p-3">
|
|
<i class="bi bi-people-fill" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tarjeta de cumpleaños del mes -->
|
|
<div class="col-md-4">
|
|
<div class="card border-success">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="card-title text-success">Cumpleaños del Mes</h5>
|
|
<h2 class="text-success" id="cumpleanerosMes">0</h2>
|
|
<p class="card-text text-muted">Cumplen años este mes</p>
|
|
</div>
|
|
<div class="bg-success text-white rounded-circle p-3">
|
|
<i class="bi bi-balloon-fill" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tarjeta de mayores de 14 años -->
|
|
<div class="col-md-4">
|
|
<div class="card border-warning">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="card-title text-warning">Mayores de 14 años</h5>
|
|
<h2 class="text-warning" id="mayores14">0</h2>
|
|
<p class="card-text text-muted">Niños con 14+ años</p>
|
|
</div>
|
|
<div class="bg-warning text-white rounded-circle p-3">
|
|
<i class="bi bi-person-badge" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabla de expedientes -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Lista de Expedientes</h5>
|
|
<div class="input-group" style="max-width: 300px;">
|
|
<input type="text" class="form-control" placeholder="Buscar niño..." id="searchInput">
|
|
<button class="btn btn-outline-secondary" type="button" id="searchButton">
|
|
<i class="bi bi-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="expedientesTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Foto</th>
|
|
<th>Nombre Completo</th>
|
|
<th>Edad</th>
|
|
<th>Sexo</th>
|
|
<th>Fecha Nacimiento</th>
|
|
<th>Responsable</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="expedientesTableBody">
|
|
<!-- Los datos se cargarán dinámicamente -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Paginación -->
|
|
<nav aria-label="Page navigation" class="mt-3">
|
|
<ul class="pagination justify-content-center" id="pagination">
|
|
<!-- La paginación se generará dinámicamente -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal para crear nuevo expediente -->
|
|
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true" >
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createModalLabel">Nuevo Expediente</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
@await Html.PartialAsync("_CreateOrEdit", new ExpedienteViewModel())
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button type="button" class="btn btn-primary" id="saveExpediente">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal para editar expediente -->
|
|
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="editModalLabel">Editar Expediente</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="editModalBody">
|
|
<!-- El contenido se cargará dinámicamente -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button type="button" class="btn btn-primary" id="updateExpediente">Actualizar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
// Variables globales
|
|
let currentPage = 1;
|
|
const itemsPerPage = 10;
|
|
let allExpedientes = [];
|
|
|
|
$(document).ready(function() {
|
|
// Cargar datos iniciales
|
|
loadDashboardStats();
|
|
loadExpedientes();
|
|
|
|
// Configurar búsqueda
|
|
$('#searchButton').click(searchExpedientes);
|
|
$('#searchInput').on('keyup', function(e) {
|
|
if (e.key === 'Enter') {
|
|
searchExpedientes();
|
|
}
|
|
});
|
|
|
|
// Configurar guardar expediente
|
|
$('#saveExpediente').click(saveExpediente);
|
|
$('#updateExpediente').click(updateExpediente);
|
|
|
|
// Configurar subida de imagen independiente
|
|
$(document).on('change', '#Foto, #FotoEdit', function() {
|
|
const input = this;
|
|
const isEdit = $(input).attr('id') === 'FotoEdit';
|
|
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
|
|
|
|
if (input.files && input.files[0]) {
|
|
// Mostrar vista previa inmediata
|
|
previewImage(input, previewId);
|
|
|
|
// Subir imagen al servidor
|
|
uploadImage(input.files[0], isEdit);
|
|
}
|
|
});
|
|
|
|
// Configurar botones de eliminar imagen
|
|
$(document).on('click', '#deleteFoto, #deleteFotoEdit', function() {
|
|
const isEdit = $(this).attr('id') === 'deleteFotoEdit';
|
|
deleteImage(isEdit);
|
|
});
|
|
});
|
|
|
|
// Cargar estadísticas del dashboard
|
|
function loadDashboardStats() {
|
|
$.ajax({
|
|
url: '/Expedientes/GetDashboardStats',
|
|
type: 'GET',
|
|
success: function(data) {
|
|
$('#totalNinos').text(data.totalNinos);
|
|
$('#cumpleanerosMes').text(data.cumpleanerosMes);
|
|
$('#mayores14').text(data.mayores14);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Cargar expedientes
|
|
function loadExpedientes(page = 1) {
|
|
currentPage = page;
|
|
$.ajax({
|
|
url: `/Expedientes/GetExpedientes?page=${page}&pageSize=${itemsPerPage}`,
|
|
type: 'GET',
|
|
success: function(data) {
|
|
allExpedientes = data.items;
|
|
renderExpedientesTable(data.items);
|
|
renderPagination(data.totalItems, data.totalPages, page);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Renderizar tabla de expedientes
|
|
function renderExpedientesTable(expedientes) {
|
|
const tbody = $('#expedientesTableBody');
|
|
tbody.empty();
|
|
|
|
if (expedientes.length === 0) {
|
|
tbody.append(`
|
|
<tr>
|
|
<td colspan="7" class="text-center py-4">
|
|
<i class="bi bi-folder-x" style="font-size: 3rem; color: #6c757d;"></i>
|
|
<p class="mt-2">No se encontraron expedientes</p>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
return;
|
|
}
|
|
|
|
expedientes.forEach(expediente => {
|
|
const edad = calculateAge(expediente.fechaNacimiento);
|
|
const row = `
|
|
<tr>
|
|
<td>
|
|
<div class="avatar avatar-sm">
|
|
<img src="${expediente.fotoUrl || '/images/default-avatar.png'}"
|
|
alt="${expediente.nombre}"
|
|
class="rounded-circle"
|
|
style="width: 40px; height: 40px; object-fit: cover;">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<strong>${expediente.nombre} ${expediente.apellidos}</strong>
|
|
</td>
|
|
<td>${edad} años</td>
|
|
<td>
|
|
<span class="badge ${expediente.sexo === 'M' ? 'bg-info' : 'bg-pink'}">
|
|
${expediente.sexo === 'M' ? 'Masculino' : 'Femenino'}
|
|
</span>
|
|
</td>
|
|
<td>${formatDate(expediente.fechaNacimiento)}</td>
|
|
<td>${expediente.nombreResponsable}</td>
|
|
<td>
|
|
<div class="btn-group" role="group">
|
|
<button class="btn btn-sm btn-outline-primary" onclick="viewExpediente(${expediente.id})" title="Ver">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-warning" onclick="editExpediente(${expediente.id})" title="Editar">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button style="visibility:hidden" class="btn btn-sm btn-outline-danger" onclick="deleteExpediente(${expediente.id})" title="Eliminar">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
tbody.append(row);
|
|
});
|
|
}
|
|
|
|
// Renderizar paginación
|
|
function renderPagination(totalItems, totalPages, currentPage) {
|
|
const pagination = $('#pagination');
|
|
pagination.empty();
|
|
|
|
// Botón anterior
|
|
pagination.append(`
|
|
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="loadExpedientes(${currentPage - 1})" aria-label="Previous">
|
|
<span aria-hidden="true">«</span>
|
|
</a>
|
|
</li>
|
|
`);
|
|
|
|
// Números de página
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
pagination.append(`
|
|
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
|
<a class="page-link" href="#" onclick="loadExpedientes(${i})">${i}</a>
|
|
</li>
|
|
`);
|
|
}
|
|
|
|
// Botón siguiente
|
|
pagination.append(`
|
|
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="loadExpedientes(${currentPage + 1})" aria-label="Next">
|
|
<span aria-hidden="true">»</span>
|
|
</a>
|
|
</li>
|
|
`);
|
|
}
|
|
|
|
// Buscar expedientes
|
|
function searchExpedientes() {
|
|
const searchTerm = $('#searchInput').val().toLowerCase();
|
|
|
|
if (searchTerm.trim() === '') {
|
|
loadExpedientes(1);
|
|
return;
|
|
}
|
|
|
|
const filtered = allExpedientes.filter(exp =>
|
|
exp.nombre.toLowerCase().includes(searchTerm) ||
|
|
exp.apellidos.toLowerCase().includes(searchTerm) ||
|
|
exp.nombreResponsable.toLowerCase().includes(searchTerm)
|
|
);
|
|
|
|
renderExpedientesTable(filtered);
|
|
$('#pagination').empty(); // Limpiar paginación en búsqueda
|
|
}
|
|
|
|
// Guardar nuevo expediente
|
|
function saveExpediente() {
|
|
const formData = new FormData($('#createForm')[0]);
|
|
|
|
$.ajax({
|
|
url: '/Expedientes/Create',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
// Mostrar siempre el mensaje que viene en response.message
|
|
const message = response.message ||
|
|
(response.success ? 'Operación completada' : 'Error en la operación');
|
|
|
|
if (response.success) {
|
|
$('#createModal').modal('hide');
|
|
loadDashboardStats();
|
|
loadExpedientes(currentPage);
|
|
showAlert('success', message);
|
|
} else {
|
|
showAlert('error', message);
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
// Extraer el message del JSON si existe
|
|
let message = 'Error en la solicitud';
|
|
try {
|
|
const jsonResponse = JSON.parse(xhr.responseText);
|
|
if (jsonResponse && jsonResponse.message) {
|
|
message = jsonResponse.message;
|
|
}
|
|
} catch (e) {
|
|
// Si no es JSON válido, usar el texto de respuesta
|
|
if (xhr.responseText) {
|
|
message = xhr.responseText;
|
|
}
|
|
}
|
|
showAlert('error', message);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Editar expediente
|
|
function editExpediente(id) {
|
|
$.ajax({
|
|
url: `/Expedientes/Edit/${id}`,
|
|
type: 'GET',
|
|
success: function(data) {
|
|
$('#editModalBody').html(data);
|
|
$('#editModal').modal('show');
|
|
|
|
// Mostrar/ocultar botón de eliminar imagen según si hay imagen personalizada
|
|
const fotoUrl = $('#editForm input[name="FotoUrl"]').val();
|
|
const deleteBtn = $('#deleteFotoEdit');
|
|
if (fotoUrl && fotoUrl !== '/images/default-avatar.png') {
|
|
deleteBtn.show();
|
|
} else {
|
|
deleteBtn.hide();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Actualizar expediente
|
|
function updateExpediente() {
|
|
const formData = new FormData($('#editForm')[0]);
|
|
const id = $('#editForm').data('id');
|
|
|
|
$.ajax({
|
|
url: `/Expedientes/Edit/${id}`,
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#editModal').modal('hide');
|
|
loadDashboardStats();
|
|
loadExpedientes(currentPage);
|
|
showAlert('success', 'Expediente actualizado exitosamente');
|
|
} else {
|
|
showAlert('error', response.message || 'Error al actualizar expediente');
|
|
}
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Error al procesar la solicitud');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Ver expediente (detalle)
|
|
function viewExpediente(id) {
|
|
window.location.href = `/Expedientes/Details/${id}`;
|
|
}
|
|
|
|
// Eliminar expediente
|
|
function deleteExpediente(id) {
|
|
if (confirm('¿Está seguro de eliminar este expediente?')) {
|
|
$.ajax({
|
|
url: `/Expedientes/Delete/${id}`,
|
|
type: 'DELETE',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
loadDashboardStats();
|
|
loadExpedientes(currentPage);
|
|
showAlert('success', 'Expediente eliminado exitosamente');
|
|
} else {
|
|
showAlert('error', response.message || 'Error al eliminar expediente');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// ========== FUNCIONES PARA MANEJO DE IMÁGENES ==========
|
|
|
|
// Mostrar vista previa de imagen
|
|
function previewImage(input, previewId) {
|
|
const preview = document.getElementById(previewId);
|
|
const file = input.files[0];
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.src = e.target.result;
|
|
preview.style.display = 'block';
|
|
}
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}
|
|
|
|
// Subir imagen al servidor
|
|
function uploadImage(file, isEdit = false) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
// Mostrar indicador de carga
|
|
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
|
|
const preview = document.getElementById(previewId);
|
|
const originalOpacity = preview.style.opacity;
|
|
preview.style.opacity = '0.5';
|
|
|
|
$.ajax({
|
|
url: '/Expedientes/UploadImage',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
preview.style.opacity = originalOpacity;
|
|
|
|
if (response.success) {
|
|
// Actualizar el campo oculto con la URL de la imagen
|
|
const formId = isEdit ? 'editForm' : 'createForm';
|
|
const hiddenInput = $(`#${formId} input[name="FotoUrl"]`);
|
|
|
|
if (hiddenInput.length === 0) {
|
|
// Crear campo oculto si no existe
|
|
$(`#${formId}`).append(`<input type="hidden" name="FotoUrl" value="${response.imageUrl}" />`);
|
|
} else {
|
|
// Actualizar valor existente
|
|
hiddenInput.val(response.imageUrl);
|
|
}
|
|
|
|
// Mostrar botón de eliminar
|
|
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
|
|
$(`#${deleteBtnId}`).show();
|
|
|
|
showAlert('success', 'Imagen subida exitosamente');
|
|
} else {
|
|
showAlert('error', response.message || 'Error al subir imagen');
|
|
// Restaurar imagen predeterminada
|
|
preview.src = '/images/default-avatar.png';
|
|
// Ocultar botón de eliminar
|
|
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
|
|
$(`#${deleteBtnId}`).hide();
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
preview.style.opacity = originalOpacity;
|
|
let message = 'Error en la solicitud';
|
|
try {
|
|
const jsonResponse = JSON.parse(xhr.responseText);
|
|
if (jsonResponse && jsonResponse.message) {
|
|
message = jsonResponse.message;
|
|
}
|
|
} catch (e) {
|
|
if (xhr.responseText) {
|
|
message = xhr.responseText;
|
|
}
|
|
}
|
|
showAlert('error', message);
|
|
// Restaurar imagen predeterminada
|
|
preview.src = '/images/default-avatar.png';
|
|
// Ocultar botón de eliminar
|
|
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
|
|
$(`#${deleteBtnId}`).hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Eliminar imagen
|
|
function deleteImage(isEdit = false) {
|
|
const formId = isEdit ? 'editForm' : 'createForm';
|
|
const hiddenInput = $(`#${formId} input[name="FotoUrl"]`);
|
|
const imageUrl = hiddenInput.val();
|
|
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
|
|
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
|
|
|
|
if (imageUrl && imageUrl !== '/images/default-avatar.png') {
|
|
if (confirm('¿Está seguro de eliminar esta imagen?')) {
|
|
$.ajax({
|
|
url: '/Expedientes/DeleteImage',
|
|
type: 'DELETE',
|
|
data: { imageUrl: imageUrl },
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Restaurar imagen predeterminada
|
|
$(`#${previewId}`).attr('src', '/images/default-avatar.png');
|
|
hiddenInput.val('/images/default-avatar.png');
|
|
$(`#${deleteBtnId}`).hide();
|
|
showAlert('success', 'Imagen eliminada exitosamente');
|
|
} else {
|
|
showAlert('error', response.message || 'Error al eliminar imagen');
|
|
}
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Error al procesar la solicitud');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== FUNCIONES UTILITARIAS ==========
|
|
|
|
// Calcular edad
|
|
function calculateAge(birthDate) {
|
|
const today = new Date();
|
|
const birth = new Date(birthDate);
|
|
let age = today.getFullYear() - birth.getFullYear();
|
|
const monthDiff = today.getMonth() - birth.getMonth();
|
|
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
|
age--;
|
|
}
|
|
|
|
return age;
|
|
}
|
|
|
|
// Formatear fecha
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('es-ES', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
}
|
|
|
|
// Mostrar alerta
|
|
function showAlert(type, message) {
|
|
// Crear elemento de alerta
|
|
const alertDiv = document.createElement('div');
|
|
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
|
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
|
|
alertDiv.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
`;
|
|
|
|
// Agregar al body
|
|
document.body.appendChild(alertDiv);
|
|
|
|
// Auto-eliminar después de 5 segundos
|
|
setTimeout(() => {
|
|
if (alertDiv.parentNode) {
|
|
alertDiv.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.bg-pink {
|
|
background-color: #e83e8c !important;
|
|
}
|
|
|
|
.avatar {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.table th {
|
|
font-weight: 600;
|
|
color: #495057;
|
|
border-bottom: 2px solid #dee2e6;
|
|
}
|
|
|
|
.table td {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.75rem;
|
|
padding: 0.35em 0.65em;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.modal-lg {
|
|
max-width: 800px;
|
|
}
|
|
</style>
|
|
} |