Revisé botones en cursos y alumnos
This commit is contained in:
parent
64b4a2b075
commit
20c618ab73
|
@ -1013,3 +1013,38 @@ textarea[name="descripcion"] {
|
|||
resize: none;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.tooltip-csv {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 140%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #2563eb;
|
||||
color: #fff;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
font-size: 0.75rem;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tooltip-csv::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
border-width: 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #2563eb transparent;
|
||||
}
|
||||
|
||||
svg:hover + .tooltip-csv,
|
||||
.tooltip-csv:hover {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
"nombre,email,telefono,tipoCurso,nombreCurso"
|
||||
"Rafael,rafael1@mail.com,10987654321,pildora,Curso Avanzado"
|
|
|
@ -373,12 +373,15 @@ function loadProfessorCourses(container) {
|
|||
<option value="tratamiento">Tratamiento</option>
|
||||
</select>
|
||||
|
||||
<div id="competencesField" name="competencias" class="oculto">
|
||||
<div id="competencesField" class="oculto">
|
||||
<label>Competencias Asociadas *</label>
|
||||
<input type="text" name="competencias" placeholder="Ej. Análisis de datos, Comunicación efectiva">
|
||||
<small style="display:block; margin-bottom: 0.5rem; color: #64748b;">
|
||||
Escribe <strong>una competencia por línea</strong>. Estas se mostrarán individualmente en el diploma.
|
||||
</small>
|
||||
<textarea name="competencias" rows="4" style="width: 100%; padding: 0.75rem; border: 1px solid #e2e8f0; border-radius: 6px; font-family: 'Inter', sans-serif;"></textarea>
|
||||
</div>
|
||||
|
||||
<label>Estado</label>
|
||||
<label style="margin-top: 1rem; display: block;">Estado</label>
|
||||
<select name="estado">
|
||||
<option value="activo">Activo</option>
|
||||
<option value="archivado">Archivado</option>
|
||||
|
@ -569,8 +572,15 @@ function setupCourseForm() {
|
|||
const courseTypeSelect = document.getElementById('courseType');
|
||||
const competencesField = document.getElementById('competencesField');
|
||||
const competenciasInput = competencesField
|
||||
? competencesField.querySelector('input[name="competencias"]')
|
||||
: null;
|
||||
? competencesField.querySelector('textarea[name="competencias"]')
|
||||
: null;
|
||||
if (competenciasInput) {
|
||||
competenciasInput.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (courseTypeSelect && competenciasInput) {
|
||||
courseTypeSelect.addEventListener('change', function () {
|
||||
|
@ -718,7 +728,7 @@ window.editCourse = function (id) {
|
|||
|
||||
// 🟦 Mostrar/ocultar campo de competencias según tipo
|
||||
const competencesField = document.getElementById("competencesField");
|
||||
const competenciasInput = competencesField.querySelector('input[name="competencias"]');
|
||||
const competenciasInput = competencesField.querySelector('textarea[name="competencias"]');
|
||||
if (course.tipo === "tratamiento") {
|
||||
competencesField.classList.remove("oculto");
|
||||
competenciasInput.value = course.competencias || "";
|
||||
|
@ -818,38 +828,73 @@ function loadStudentsManagement(container) {
|
|||
throw new Error(data.error || "Error al obtener alumnos");
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="students-management">
|
||||
${renderStudentForm()}
|
||||
${renderStudentsTable(data.data || [])}
|
||||
</div>
|
||||
`;
|
||||
<div class="students-management">
|
||||
${renderStudentForm()}
|
||||
${renderStudentsTable(data.data || [])}
|
||||
</div>
|
||||
`;
|
||||
|
||||
setupStudentForm();
|
||||
|
||||
// Cargar cursos por alumno
|
||||
(data.data || []).forEach((alumno) => loadStudentCourses(alumno.id));
|
||||
|
||||
// Activar filtro por estado de curso
|
||||
const filtro = document.getElementById("studentCourseFilter");
|
||||
if (filtro) {
|
||||
filtro.addEventListener("change", () => {
|
||||
document.querySelectorAll(".students-table tbody tr").forEach((fila) => {
|
||||
const estadoSeleccionado = filtro.value;
|
||||
const badges = fila.querySelectorAll(".course-badge");
|
||||
const visible = Array.from(badges).some((badge) => {
|
||||
const estado = (badge.dataset.estado || "").toLowerCase();
|
||||
return estadoSeleccionado === "todos" || estado === estadoSeleccionado;
|
||||
});
|
||||
fila.style.display = visible ? "" : "none";
|
||||
});
|
||||
});
|
||||
|
||||
// Aplicar inmediatamente al cargar
|
||||
filtro.dispatchEvent(new Event("change"));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
container.innerHTML = `
|
||||
<div class="card error-card">
|
||||
<h2>Error al cargar alumnos</h2>
|
||||
<p>${error.message}</p>
|
||||
<button class="btn" onclick="loadStudentsManagement(this.closest('.content-section'))">
|
||||
Reintentar
|
||||
</button>
|
||||
</div>`;
|
||||
<div class="card error-card">
|
||||
<h2>Error al cargar alumnos</h2>
|
||||
<p>${error.message}</p>
|
||||
<button class="btn" onclick="loadStudentsManagement(this.closest('.content-section'))">
|
||||
Reintentar
|
||||
</button>
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function renderStudentForm() {
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h2>Gestión de Alumnos</h2>
|
||||
<label class="btn btn-primary" style="margin: 0;">
|
||||
Importar CSV
|
||||
<input type="file" accept=".csv" onchange="handleCSVUpload(event)" style="display: none;">
|
||||
</label>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; position: relative;">
|
||||
<label class="btn btn-primary" style="margin: 0;">
|
||||
Importar CSV
|
||||
<input type="file" accept=".csv" onchange="handleCSVUpload(event)" style="display: none;">
|
||||
</label>
|
||||
<div style="position: relative;">
|
||||
<svg onclick="downloadCSVTemplate()"
|
||||
xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
style="cursor: pointer;">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M12 16v-1"></path>
|
||||
<path d="M12 12a2 2 0 1 0-2-2"></path>
|
||||
</svg>
|
||||
<div class="tooltip-csv">
|
||||
Descargar formato CSV
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="studentForm">
|
||||
<div class="form-grid">
|
||||
|
@ -1140,11 +1185,13 @@ function filterStudents(searchTerm) {
|
|||
const nombre = row.cells[0].textContent.toLowerCase();
|
||||
const email = row.cells[1].textContent.toLowerCase();
|
||||
const telefono = row.cells[2].textContent.toLowerCase();
|
||||
const cursos = row.cells[3].textContent.toLowerCase(); // ✅ aquí
|
||||
|
||||
if (
|
||||
nombre.includes(searchTerm) ||
|
||||
email.includes(searchTerm) ||
|
||||
telefono.includes(searchTerm)
|
||||
telefono.includes(searchTerm) ||
|
||||
cursos.includes(searchTerm) // ✅ permite buscar tipo o nombre de curso
|
||||
) {
|
||||
row.style.display = "";
|
||||
} else {
|
||||
|
@ -1174,15 +1221,13 @@ function renderStudentsTable(alumnos) {
|
|||
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Lista de Alumnos</h2>
|
||||
<div class="header-actions">
|
||||
<div class="search-box">
|
||||
<input type="text" id="studentSearch" placeholder="Buscar alumno...">
|
||||
</div>
|
||||
|
||||
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<h2 style="margin: 0;">Lista de Alumnos</h2>
|
||||
<div class="search-box">
|
||||
<input type="text" id="studentSearch" placeholder="Buscar alumno...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="students-table">
|
||||
<thead>
|
||||
|
@ -1198,31 +1243,23 @@ function renderStudentsTable(alumnos) {
|
|||
${alumnos
|
||||
.map(
|
||||
(alumno) => `
|
||||
<tr>
|
||||
<td>${alumno.nombre}</td>
|
||||
<td>${alumno.email}</td>
|
||||
<td>${alumno.telefono || "-"}</td>
|
||||
<td>
|
||||
<div class="course-badges" id="courses-${alumno.id}">
|
||||
${renderCourseBadges(alumno.id)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-sm btn-edit" onclick="editStudent(${
|
||||
alumno.id
|
||||
})">
|
||||
Editar
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteStudent(${
|
||||
alumno.id
|
||||
})">
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
<tr>
|
||||
<td>${alumno.nombre}</td>
|
||||
<td>${alumno.email}</td>
|
||||
<td>${alumno.telefono || "-"}</td>
|
||||
<td>
|
||||
<div class="course-badges" id="courses-${alumno.id}">
|
||||
${renderCourseBadges(alumno.id)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-sm btn-edit" onclick="editStudent(${alumno.id})">Editar</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteStudent(${alumno.id})">Eliminar</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
|
@ -1237,27 +1274,41 @@ function renderCourseBadges(alumnoId) {
|
|||
|
||||
async function loadStudentCourses(alumnoId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`api/alumnos-cursos.php?alumno_id=${alumnoId}`
|
||||
);
|
||||
const response = await fetch(`api/alumnos-cursos.php?alumno_id=${alumnoId}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const container = document.getElementById(`courses-${alumnoId}`);
|
||||
if (container) {
|
||||
container.innerHTML =
|
||||
data.data
|
||||
.map(
|
||||
(curso) => `
|
||||
<span class="badge course-badge">
|
||||
${curso.nombre}
|
||||
<button class="badge-remove" onclick="unassignStudent(${alumnoId}, ${curso.id})">
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
`
|
||||
)
|
||||
.join("") || '<span class="text-muted">Sin cursos</span>';
|
||||
data.data.length > 0
|
||||
? data.data
|
||||
.map(
|
||||
(curso) => `
|
||||
<span class="badge course-badge" data-estado="${curso.estado}">
|
||||
<span style="display: inline-block; font-weight: 500; color: #0f62fe;">
|
||||
${formatCourseType(curso.tipo)} /
|
||||
</span>
|
||||
${curso.nombre}
|
||||
</span>`
|
||||
)
|
||||
.join("")
|
||||
: '<span class="text-muted">Sin cursos</span>';
|
||||
}
|
||||
|
||||
// ✅ Aplicar filtro visual si está activo
|
||||
const filtro = document.getElementById("studentCourseFilter");
|
||||
if (filtro) {
|
||||
const estadoSeleccionado = filtro.value;
|
||||
|
||||
document.querySelectorAll(".students-table tbody tr").forEach((fila) => {
|
||||
const badges = fila.querySelectorAll(".course-badge");
|
||||
const visible = Array.from(badges).some((badge) => {
|
||||
const estado = (badge.dataset.estado || "").toLowerCase();
|
||||
return estadoSeleccionado === "todos" || estado === estadoSeleccionado;
|
||||
});
|
||||
fila.style.display = visible ? "" : "none";
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1623,5 +1674,23 @@ window.handleCSVUpload = function (event) {
|
|||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
|
||||
function formatCourseType(type) {
|
||||
const types = {
|
||||
pildora: "Píldora",
|
||||
inyeccion: "Inyección",
|
||||
tratamiento: "Tratamiento"
|
||||
};
|
||||
return types[type] || type;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function downloadCSVTemplate() {
|
||||
const link = document.createElement("a");
|
||||
link.href = "assets/formato_alumnos.csv";
|
||||
link.download = "formato_alumnos.csv";
|
||||
document.body.appendChild(link); // requerido en algunos navegadores
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue