Se elimino rol de usuario y consolido admin
This commit is contained in:
parent
6b1d644adc
commit
7f4aa87aa4
|
@ -9,31 +9,14 @@ if (!is_logged_in()) {
|
|||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$user = $_SESSION['user'];
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
if ($user['rol'] === 'admin') {
|
||||
$stmt = $pdo->query("SELECT * FROM cursos");
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.*, uc.estado, uc.fecha_inicio, uc.fecha_fin, uc.profesor
|
||||
FROM usuario_cursos uc
|
||||
JOIN cursos c ON uc.curso_id = c.id
|
||||
WHERE uc.usuario_id = ?
|
||||
");
|
||||
$stmt->execute([$user['id']]);
|
||||
}
|
||||
$stmt = $pdo->query("SELECT * FROM cursos");
|
||||
echo json_encode($stmt->fetchAll());
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
if ($user['rol'] !== 'admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Acceso no autorizado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
|
@ -52,5 +35,4 @@ switch ($method) {
|
|||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Método no permitido']);
|
||||
}
|
||||
?>
|
||||
}
|
|
@ -1,4 +1,21 @@
|
|||
/* Reset y estilos base */
|
||||
/* --- Nueva paleta de colores --- */
|
||||
:root {
|
||||
--primary-dark: #002b5c;
|
||||
--primary-main: #003f7d;
|
||||
--primary-light: #0066cc;
|
||||
--primary-lighter: #e1f0ff;
|
||||
--accent-main: #4facfe;
|
||||
--accent-light: #00f2fe;
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #4a5568;
|
||||
--background: #f8fafc;
|
||||
--surface: #ffffff;
|
||||
--border: #e2e8f0;
|
||||
--error: #e53e3e;
|
||||
--success: #38a169;
|
||||
}
|
||||
|
||||
/* --- Reset y tipografía --- */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
|
@ -7,30 +24,34 @@
|
|||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--background);
|
||||
line-height: 1.6;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Estilos para el contenedor del login */
|
||||
/* --- Login - Versión elegante --- */
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: linear-gradient(135deg, #003f7d 0%, #0066cc 100%);
|
||||
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-main) 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
background: var(--surface);
|
||||
border-radius: 12px;
|
||||
padding: 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
max-width: 420px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.login-card::before {
|
||||
|
@ -39,35 +60,37 @@ body {
|
|||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, var(--accent-main) 0%, var(--accent-light) 100%);
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #2563eb;
|
||||
margin-right: 10px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: var(--primary-light);
|
||||
margin: 0 auto 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-card h1 {
|
||||
color: #1e293b;
|
||||
color: var(--primary-dark);
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
color: #64748b;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 0.95rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
|
@ -77,76 +100,58 @@ body {
|
|||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #94a3b8;
|
||||
color: var(--text-secondary);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.login-card input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 0.75rem 0.75rem 40px;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 0.85rem 0.85rem 0.85rem 48px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #f8fafc;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background-color: var(--surface);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.login-card input:focus {
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
background-color: white;
|
||||
border-color: var(--primary-light);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 0.85rem;
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 1rem;
|
||||
background: linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%);
|
||||
gap: 10px;
|
||||
margin-top: 1.5rem;
|
||||
background: linear-gradient(90deg, var(--primary-main) 0%, var(--primary-light) 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 6px rgba(0, 63, 125, 0.1);
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
background: linear-gradient(90deg, #1d4ed8 0%, #1e40af 100%);
|
||||
transform: translateY(-1px);
|
||||
background: linear-gradient(90deg, var(--primary-dark) 0%, var(--primary-main) 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 63, 125, 0.15);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: #64748b;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
/* Estilos para la aplicación principal */
|
||||
/* --- Dashboard - Versión elegante --- */
|
||||
#app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -158,67 +163,39 @@ header {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
background-color: #003f7d;
|
||||
padding: 1rem 2.5rem;
|
||||
background-color: var(--primary-dark);
|
||||
color: white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#user-info {
|
||||
order: -1;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Estilos para la tabla en el dashboard */
|
||||
.stats {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stats p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Paginación */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
#page-info {
|
||||
font-size: 0.9rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#current-user {
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
|
@ -228,11 +205,12 @@ header h1 {
|
|||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #f8fafc;
|
||||
padding: 1.5rem 1rem;
|
||||
border-right: 1px solid #e2e8f0;
|
||||
width: 280px;
|
||||
background-color: var(--surface);
|
||||
padding: 1.5rem 0;
|
||||
border-right: 1px solid var(--border);
|
||||
overflow-y: auto;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
|
@ -242,207 +220,179 @@ header h1 {
|
|||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 6px;
|
||||
padding: 0.85rem 1.5rem;
|
||||
margin: 0.25rem 1rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
gap: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.sidebar-menu li:hover {
|
||||
background-color: #e0e7ff;
|
||||
background-color: var(--primary-lighter);
|
||||
color: var(--primary-main);
|
||||
}
|
||||
|
||||
.sidebar-menu li.active {
|
||||
background-color: #2563eb;
|
||||
background-color: var(--primary-main);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar-menu li svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
padding: 2.5rem;
|
||||
overflow-y: auto;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content-section.active {
|
||||
display: block;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-left: 4px solid #2563eb;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background: var(--surface);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
border-left: none;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-top: 0;
|
||||
color: #003f7d;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
padding: 0.6rem 1.25rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--primary-dark);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
font-family: 'Inter', sans-serif;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
|
||||
}
|
||||
|
||||
.oculto {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Estilos para el contenido específico */
|
||||
.course-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.course-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.25rem;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.course-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.diploma-preview {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.diploma-preview img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Estilos para el buscador y tabla */
|
||||
.search-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* --- Tablas mejoradas --- */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin-top: 20px;
|
||||
margin-top: 1.5rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.courses-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.courses-table th, .courses-table td {
|
||||
padding: 12px 15px;
|
||||
padding: 1rem 1.25rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.courses-table th {
|
||||
background-color: #f8fafc;
|
||||
background-color: var(--primary-lighter);
|
||||
font-weight: 600;
|
||||
color: #003f7d;
|
||||
color: var(--primary-dark);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.courses-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.courses-table tr:hover {
|
||||
background-color: #f5f7fa;
|
||||
background-color: var(--primary-lighter);
|
||||
}
|
||||
|
||||
/* --- Botones mejorados --- */
|
||||
.btn {
|
||||
background-color: var(--primary-main);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-family: 'Inter', sans-serif;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.95rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--primary-dark);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
.download-btn:hover {
|
||||
background-color: #2f855a;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
/* Mantén todos tus estilos anteriores y añade estos */
|
||||
|
||||
/* Estilos para el dashboard */
|
||||
.admin .sidebar-menu li[data-section="courses"],
|
||||
.admin .sidebar-menu li[data-section="students"],
|
||||
.user .sidebar-menu li[data-section="my-courses"],
|
||||
.user .sidebar-menu li[data-section="diplomas"] {
|
||||
display: none;
|
||||
/* --- Formularios mejorados --- */
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.admin .sidebar-menu li[data-section="courses"],
|
||||
.admin .sidebar-menu li[data-section="students"] {
|
||||
display: flex;
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-light);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
|
||||
}
|
||||
|
||||
.user .sidebar-menu li[data-section="my-courses"],
|
||||
.user .sidebar-menu li[data-section="diplomas"] {
|
||||
display: flex;
|
||||
label {
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* --- Efectos de transición --- */
|
||||
.content-section {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* --- Responsive --- */
|
||||
@media (max-width: 1024px) {
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mejoras de responsive */
|
||||
@media (max-width: 768px) {
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
|
@ -451,118 +401,98 @@ select:focus {
|
|||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 90%;
|
||||
padding: 1.5rem;
|
||||
.sidebar-menu {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
position: static;
|
||||
transform: none;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#user-info {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
/* Estilos adicionales */
|
||||
.loader {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
font-style: italic;
|
||||
color: #64748b;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-card {
|
||||
padding: 1.75rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.course-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
transition: transform 0.2s ease;
|
||||
.error-card {
|
||||
border-left: 4px solid var(--error);
|
||||
}
|
||||
|
||||
.course-card:hover {
|
||||
transform: translateY(-2px);
|
||||
.error-card h2 {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.course-card h3 {
|
||||
margin-top: 0;
|
||||
color: #003f7d;
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.diploma-preview {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
.loading-spinner {
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top: 2px solid white;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
animation: spin 1s linear infinite;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.diploma-preview h3 {
|
||||
color: #003f7d;
|
||||
.loading-spinner.small {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* Añadir al final del archivo */
|
||||
|
||||
/* Estilos para el header */
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
.badge.pildora {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
position: static;
|
||||
transform: none;
|
||||
margin: 0;
|
||||
.badge.inyeccion {
|
||||
background-color: #D1FAE5;
|
||||
color: #065F46;
|
||||
}
|
||||
|
||||
#user-info {
|
||||
order: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Estilos para el enlace de cerrar sesión */
|
||||
.logout-link {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 0.75rem 1rem;
|
||||
display: block;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.logout-link:hover {
|
||||
color: #e53e3e;
|
||||
background-color: #fee2e2;
|
||||
}
|
||||
|
||||
/* Estilos para la columna de acciones */
|
||||
.courses-table td:last-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
background-color: #38a169;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background-color: #2f855a;
|
||||
}
|
||||
|
||||
.download-btn:disabled {
|
||||
background-color: #a0aec0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.badge.tratamiento {
|
||||
background-color: #E0E7FF;
|
||||
color: #3730A3;
|
||||
}
|
|
@ -1,16 +1,31 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Manejo del formulario de login
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', handleLogin);
|
||||
}
|
||||
|
||||
// Configuración inicial del dashboard
|
||||
if (document.body.classList.contains('admin') || document.body.classList.contains('user')) {
|
||||
initializeDashboard();
|
||||
} else {
|
||||
initDashboard(); // Nombre de función consistente
|
||||
}
|
||||
});
|
||||
|
||||
function initDashboard() {
|
||||
// Configuración inicial
|
||||
setupSidebarNavigation();
|
||||
loadInitialData();
|
||||
|
||||
// Mostrar sección activa
|
||||
const activeSection = document.querySelector('.sidebar-menu li.active');
|
||||
if (activeSection) {
|
||||
const sectionId = activeSection.dataset.section;
|
||||
showSection(sectionId, true);
|
||||
}
|
||||
|
||||
// Configurar tooltips para botones
|
||||
initTooltips();
|
||||
|
||||
// Configurar eventos globales
|
||||
document.addEventListener('click', handleGlobalEvents);
|
||||
}
|
||||
|
||||
function handleLogin(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
|
@ -30,160 +45,59 @@ function handleLogin(e) {
|
|||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
|
||||
function initializeDashboard() {
|
||||
// Configurar eventos del menú lateral
|
||||
setupSidebarNavigation();
|
||||
|
||||
// Cargar datos iniciales
|
||||
loadInitialData();
|
||||
|
||||
// Mostrar la sección activa inicial
|
||||
const activeSection = document.querySelector('.sidebar-menu li.active');
|
||||
if (activeSection) {
|
||||
const sectionId = activeSection.getAttribute('data-section');
|
||||
showSection(sectionId, true); // true indica que es la carga inicial
|
||||
}
|
||||
}
|
||||
|
||||
function setupSidebarNavigation() {
|
||||
document.querySelectorAll('.sidebar-menu li').forEach(item => {
|
||||
const menuItems = document.querySelectorAll('.sidebar-menu li:not(.logout-link)');
|
||||
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
const section = this.getAttribute('data-section');
|
||||
// Agregar efecto visual al hacer clic
|
||||
this.classList.add('click-feedback');
|
||||
setTimeout(() => this.classList.remove('click-feedback'), 200);
|
||||
|
||||
const section = this.dataset.section;
|
||||
showSection(section);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadInitialData() {
|
||||
fetch('api/cursos.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (document.body.classList.contains('admin')) {
|
||||
updateAdminStats(data);
|
||||
} else {
|
||||
updateUserStats(data);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
async function loadInitialData() {
|
||||
try {
|
||||
// Mostrar skeleton loading
|
||||
document.querySelectorAll('.stats p span').forEach(span => {
|
||||
span.innerHTML = '<span class="skeleton-loader"></span>';
|
||||
});
|
||||
|
||||
const [courses, users] = await Promise.all([
|
||||
fetchData('api/cursos.php'),
|
||||
fetchData('api/usuarios.php')
|
||||
]);
|
||||
|
||||
updateStats(courses, users);
|
||||
} catch (error) {
|
||||
console.error('Error loading initial data:', error);
|
||||
showToast('Error al cargar datos iniciales', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdminStats(courses) {
|
||||
document.getElementById('active-courses-count').textContent = courses.length;
|
||||
// Aquí puedes agregar más llamadas para estudiantes y diplomas
|
||||
// fetch('api/estudiantes.php')...
|
||||
// fetch('api/diplomas.php')...
|
||||
}
|
||||
|
||||
function updateUserStats(courses) {
|
||||
document.getElementById('user-courses-count').textContent = courses.length;
|
||||
function updateStats(courses = [], users = []) {
|
||||
// Efecto de conteo animado
|
||||
animateValue('active-courses-count', 0, courses.length, 1000);
|
||||
animateValue('students-count', 0, users.length, 1000);
|
||||
|
||||
const approvedCourses = courses.filter(c => c.estado === 'Aprobado');
|
||||
document.getElementById('user-diplomas-count').textContent = approvedCourses.length;
|
||||
window.userCourses = courses;
|
||||
|
||||
// Mostrar los primeros 5 cursos en el dashboard
|
||||
renderDashboardCourses(courses.slice(0, 5));
|
||||
setupDashboardPagination(courses);
|
||||
setupDashboardSearch();
|
||||
}
|
||||
function renderDashboardCourses(courses) {
|
||||
const tbody = document.getElementById('dashboard-courses-body');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = courses.map(course => `
|
||||
<tr>
|
||||
<td>${course.nombre}</td>
|
||||
<td>${course.fecha_inicio || '-'}</td>
|
||||
<td>${course.fecha_fin || '-'}</td>
|
||||
<td>
|
||||
${course.estado === 'Aprobado' ?
|
||||
`<button class="btn download-btn" data-course-id="${course.id}">Descargar</button>` :
|
||||
'No disponible'}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Configurar eventos de descarga
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const courseId = this.getAttribute('data-course-id');
|
||||
window.open(`certificado.php?course_id=${courseId}`, '_blank');
|
||||
});
|
||||
});
|
||||
animateValue('diplomas-count', 0, approvedCourses.length, 1000);
|
||||
}
|
||||
|
||||
function setupDashboardPagination(allCourses) {
|
||||
let currentPage = 1;
|
||||
const perPage = 5;
|
||||
const totalPages = Math.ceil(allCourses.length / perPage);
|
||||
|
||||
function updatePagination() {
|
||||
const start = (currentPage - 1) * perPage;
|
||||
const end = start + perPage;
|
||||
renderDashboardCourses(allCourses.slice(start, end));
|
||||
|
||||
document.getElementById('page-info').textContent = `Página ${currentPage} de ${totalPages}`;
|
||||
document.getElementById('prev-page').disabled = currentPage === 1;
|
||||
document.getElementById('next-page').disabled = currentPage === totalPages || totalPages === 0;
|
||||
}
|
||||
|
||||
document.getElementById('prev-page')?.addEventListener('click', () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
updatePagination();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('next-page')?.addEventListener('click', () => {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
updatePagination();
|
||||
}
|
||||
});
|
||||
|
||||
updatePagination();
|
||||
}
|
||||
|
||||
function setupDashboardSearch() {
|
||||
const searchBtn = document.getElementById('dashboard-search-btn');
|
||||
const searchInput = document.getElementById('dashboard-course-search');
|
||||
|
||||
if (searchBtn && searchInput) {
|
||||
searchBtn.addEventListener('click', () => {
|
||||
const term = searchInput.value.toLowerCase();
|
||||
const filtered = window.userCourses.filter(course =>
|
||||
course.nombre.toLowerCase().includes(term) ||
|
||||
course.tipo.toLowerCase().includes(term) ||
|
||||
course.estado.toLowerCase().includes(term)
|
||||
);
|
||||
|
||||
setupDashboardPagination(filtered);
|
||||
});
|
||||
|
||||
// Permitir búsqueda al presionar Enter
|
||||
searchInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
searchBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function showSection(sectionId, isInitialLoad = false) {
|
||||
// Actualizar menú activo
|
||||
async function showSection(sectionId, isInitialLoad = false) {
|
||||
updateActiveMenu(sectionId);
|
||||
|
||||
// Mostrar la sección correspondiente
|
||||
const sectionElement = getSectionElement(sectionId);
|
||||
|
||||
if (sectionElement) {
|
||||
toggleSections(sectionElement);
|
||||
|
||||
// Solo cargar contenido dinámico si no es la carga inicial del dashboard
|
||||
if (!(isInitialLoad && sectionId === 'dashboard')) {
|
||||
if (sectionId === 'dashboard') {
|
||||
// Recargar los datos del dashboard
|
||||
loadInitialData();
|
||||
} else {
|
||||
loadDynamicContent(sectionId, sectionElement);
|
||||
}
|
||||
await loadDynamicContent(sectionId, sectionElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,13 +106,13 @@ function updateActiveMenu(sectionId) {
|
|||
document.querySelectorAll('.sidebar-menu li').forEach(li => {
|
||||
li.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`.sidebar-menu li[data-section="${sectionId}"]`).classList.add('active');
|
||||
const activeItem = document.querySelector(`.sidebar-menu li[data-section="${sectionId}"]`);
|
||||
if (activeItem) {
|
||||
activeItem.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function getSectionElement(sectionId) {
|
||||
if (sectionId === 'dashboard') {
|
||||
return document.getElementById('dashboard-content');
|
||||
}
|
||||
return document.getElementById(`${sectionId}-content`);
|
||||
}
|
||||
|
||||
|
@ -209,217 +123,405 @@ function toggleSections(activeSection) {
|
|||
activeSection.classList.add('active');
|
||||
}
|
||||
|
||||
function loadDynamicContent(sectionId, container) {
|
||||
// Mostrar loader mientras se carga
|
||||
container.innerHTML = '<div class="loader">Cargando...</div>';
|
||||
|
||||
switch(sectionId) {
|
||||
case 'courses':
|
||||
loadAdminCourses(container);
|
||||
break;
|
||||
case 'students':
|
||||
loadAdminStudents(container);
|
||||
break;
|
||||
case 'my-courses':
|
||||
loadUserCourses(container);
|
||||
break;
|
||||
case 'diplomas':
|
||||
loadUserDiplomas(container);
|
||||
break;
|
||||
case 'profile':
|
||||
loadProfileSection(container);
|
||||
break;
|
||||
default:
|
||||
container.innerHTML = '<div class="card"><h2>Sección no implementada</h2></div>';
|
||||
async function loadDynamicContent(sectionId, container) {
|
||||
// Mostrar skeleton loading
|
||||
container.innerHTML = `
|
||||
<div class="skeleton-card">
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
switch(sectionId) {
|
||||
case 'courses':
|
||||
await loadCoursesSection(container);
|
||||
break;
|
||||
case 'students':
|
||||
await loadStudentsSection(container);
|
||||
break;
|
||||
case 'diplomas':
|
||||
await loadDiplomasSection(container);
|
||||
break;
|
||||
default:
|
||||
container.innerHTML = '<div class="card"><h2>Sección no implementada</h2></div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading ${sectionId} section:`, error);
|
||||
container.innerHTML = `
|
||||
<div class="card error-card">
|
||||
<h2>Error al cargar contenido</h2>
|
||||
<p>No se pudo cargar la información solicitada.</p>
|
||||
<button class="btn retry-btn" onclick="showSection('${sectionId}')">
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para cargar contenido específico
|
||||
function loadAdminCourses(container) {
|
||||
fetch('api/cursos.php')
|
||||
.then(response => response.json())
|
||||
.then(courses => {
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Gestión de Cursos</h2>
|
||||
<form id="courseForm">
|
||||
async function loadCoursesSection(container) {
|
||||
try {
|
||||
const courses = await fetchData('api/cursos.php');
|
||||
|
||||
// Verificar si courses es un array
|
||||
if (!Array.isArray(courses)) {
|
||||
throw new Error('Formato de datos inválido');
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Gestión de Cursos</h2>
|
||||
<form id="courseForm" class="elegant-form">
|
||||
<div class="form-group">
|
||||
<label>Nombre del Curso</label>
|
||||
<input type="text" name="nombre" placeholder="Ej. Seguridad Informática" required>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tipo de Curso</label>
|
||||
<select id="courseType" name="tipo" required>
|
||||
<option value="">Seleccionar</option>
|
||||
<option value="">Seleccionar tipo</option>
|
||||
<option value="pildora">Píldora</option>
|
||||
<option value="inyeccion">Inyección</option>
|
||||
<option value="tratamiento">Tratamiento</option>
|
||||
</select>
|
||||
|
||||
<div id="competencesField" class="oculto">
|
||||
<label>Competencias Asociadas</label>
|
||||
<input type="text" name="competencias" placeholder="Ej. Análisis de datos, Comunicación efectiva">
|
||||
</div>
|
||||
|
||||
<button class="btn" type="submit">Guardar Curso</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="competencesField" class="form-group hidden">
|
||||
<label>Competencias Asociadas</label>
|
||||
<input type="text" name="competencias" placeholder="Ej. Análisis de datos, Comunicación efectiva">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">
|
||||
<svg class="btn-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z"/>
|
||||
</svg>
|
||||
Guardar Curso
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Lista de Cursos</h2>
|
||||
<div class="table-actions">
|
||||
<div class="search-container">
|
||||
<input type="text" id="courseSearch" placeholder="Buscar cursos..." class="search-input">
|
||||
<button class="btn search-btn">
|
||||
<svg class="btn-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="elegant-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Tipo</th>
|
||||
<th>Competencias</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="courses-list-body">
|
||||
${generateCoursesTableRows(courses)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
setupCourseForm();
|
||||
} catch (error) {
|
||||
console.error('Error loading courses:', error);
|
||||
container.innerHTML = `
|
||||
<div class="card error-card">
|
||||
<h2>Error al cargar los cursos</h2>
|
||||
<p>${error.message || 'No se pudieron cargar los cursos'}</p>
|
||||
<button class="btn retry-btn" onclick="loadCoursesSection(this.closest('.content-section'))">
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
function formatCourseType(type) {
|
||||
const types = {
|
||||
'pildora': 'Píldora',
|
||||
'inyeccion': 'Inyección',
|
||||
'tratamiento': 'Tratamiento'
|
||||
};
|
||||
return types[type] || type;
|
||||
}
|
||||
|
||||
function loadStudentsSection(container) {
|
||||
fetch('api/usuarios.php')
|
||||
.then(response => response.json())
|
||||
.then(users => {
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Lista de Cursos</h2>
|
||||
<div class="course-list">
|
||||
${generateCoursesList(courses)}
|
||||
<h2>Gestión de Estudiantes</h2>
|
||||
<div class="search-container">
|
||||
<input type="text" id="studentSearch" placeholder="Buscar estudiantes..." class="search-input">
|
||||
<button class="btn" id="searchStudentButton">Buscar</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="courses-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Usuario</th>
|
||||
<th>Email</th>
|
||||
<th>Cursos Inscritos</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="students-list-body">
|
||||
${generateStudentsTableRows(users)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
setupCourseForm();
|
||||
setupStudentSearch();
|
||||
})
|
||||
.catch(error => {
|
||||
container.innerHTML = '<div class="card"><h2>Error al cargar los cursos</h2></div>';
|
||||
container.innerHTML = '<div class="card"><h2>Error al cargar los estudiantes</h2></div>';
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function loadUserCourses(container) {
|
||||
const courses = window.userCourses || [];
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Mis Cursos</h2>
|
||||
<div class="search-container">
|
||||
<input type="text" id="courseSearch" placeholder="Buscar cursos..." class="search-input">
|
||||
<button class="btn" id="searchButton">Buscar</button>
|
||||
</div>
|
||||
<div class="course-list">
|
||||
${courses.length > 0 ?
|
||||
courses.map(course => generateCourseCard(course)).join('') :
|
||||
'<p>No tienes cursos registrados.</p>'}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
setupCourseSearch();
|
||||
function loadDiplomasSection(container) {
|
||||
fetch('api/cursos.php')
|
||||
.then(response => response.json())
|
||||
.then(courses => {
|
||||
const approvedCourses = courses.filter(c => c.estado === 'Aprobado');
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Diplomas Emitidos</h2>
|
||||
<div class="search-container">
|
||||
<input type="text" id="diplomaSearch" placeholder="Buscar diplomas..." class="search-input">
|
||||
<button class="btn" id="searchDiplomaButton">Buscar</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="courses-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Curso</th>
|
||||
<th>Estudiante</th>
|
||||
<th>Fecha de Aprobación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="diplomas-list-body">
|
||||
${generateDiplomasTableRows(approvedCourses)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
setupDiplomaSearch();
|
||||
})
|
||||
.catch(error => {
|
||||
container.innerHTML = '<div class="card"><h2>Error al cargar los diplomas</h2></div>';
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function loadUserDiplomas(container) {
|
||||
const courses = (window.userCourses || []).filter(c => c.estado === 'Aprobado');
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<h2>Mis Diplomas</h2>
|
||||
${courses.length > 0 ?
|
||||
courses.map(course => generateDiplomaCard(course)).join('') :
|
||||
'<p>No tienes diplomas disponibles todavía.</p>'}
|
||||
</div>`;
|
||||
|
||||
setupDiplomaDownloads();
|
||||
}
|
||||
|
||||
// Funciones auxiliares
|
||||
function generateCoursesList(courses) {
|
||||
// Funciones auxiliares para generar contenido
|
||||
function generateCoursesTableRows(courses = []) {
|
||||
return courses.map(course => `
|
||||
<div class="course-card">
|
||||
<h3>${course.nombre}</h3>
|
||||
<p>Tipo: ${course.tipo}</p>
|
||||
<p>ID: ${course.id}</p>
|
||||
</div>
|
||||
<tr>
|
||||
<td>${course.nombre}</td>
|
||||
<td><span class="badge ${course.tipo}">${formatCourseType(course.tipo)}</span></td>
|
||||
<td>${course.competencias || '-'}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn-icon-btn edit-btn" data-id="${course.id}" title="Editar">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon-btn delete-btn" data-id="${course.id}" title="Eliminar">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function generateCourseCard(course) {
|
||||
return `
|
||||
<div class="course-card">
|
||||
<h3>${course.nombre}</h3>
|
||||
<p>Tipo: ${course.tipo}</p>
|
||||
<p>Estado: ${course.estado}</p>
|
||||
${course.competencias ? `<p>Competencias: ${course.competencias}</p>` : ''}
|
||||
<p>Profesor: ${course.profesor || 'No asignado'}</p>
|
||||
<p>Fecha: ${course.fecha_inicio} a ${course.fecha_fin}</p>
|
||||
</div>`;
|
||||
/**
|
||||
* Muestra un toast notification
|
||||
*/
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast-notification ${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('fade-out');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function generateDiplomaCard(course) {
|
||||
return `
|
||||
<div class="diploma-preview">
|
||||
<h3>Diploma de ${course.nombre}</h3>
|
||||
<p>Fecha: ${course.fecha_fin || 'Completado'}</p>
|
||||
<p>Profesor: ${course.profesor || 'No especificado'}</p>
|
||||
<button class="btn download-btn"
|
||||
data-course-id="${course.id}"
|
||||
data-course-name="${course.nombre}">
|
||||
Descargar Diploma
|
||||
</button>
|
||||
</div>`;
|
||||
function generateStudentsTableRows(users) {
|
||||
return users.map(user => `
|
||||
<tr>
|
||||
<td>${user.nombre}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>${user.cursos ? user.cursos.length : 0}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function generateDiplomasTableRows(courses) {
|
||||
return courses.map(course => `
|
||||
<tr>
|
||||
<td>${course.nombre}</td>
|
||||
<td>${course.estudiante || 'N/A'}</td>
|
||||
<td>${course.fecha_fin || 'N/A'}</td>
|
||||
<td>
|
||||
<button class="btn download-btn" data-course-id="${course.id}">
|
||||
Descargar Diploma
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
function animateValue(id, start, end, duration) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) return;
|
||||
|
||||
const range = end - start;
|
||||
const startTime = Date.now();
|
||||
const endTime = startTime + duration;
|
||||
|
||||
const update = () => {
|
||||
const now = Date.now();
|
||||
const progress = Math.min((now - startTime) / duration, 1);
|
||||
const value = Math.floor(start + progress * range);
|
||||
element.textContent = value.toLocaleString();
|
||||
|
||||
if (now < endTime) {
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
};
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
// Configuración de eventos dinámicos
|
||||
function setupCourseForm() {
|
||||
const courseTypeSelect = document.getElementById('courseType');
|
||||
if (courseTypeSelect) {
|
||||
courseTypeSelect.addEventListener('change', function() {
|
||||
const competencesField = document.getElementById('competencesField');
|
||||
if (competencesField) {
|
||||
competencesField.classList.toggle('oculto', this.value !== 'tratamiento');
|
||||
}
|
||||
competencesField.classList.toggle('hidden', this.value !== 'tratamiento');
|
||||
});
|
||||
}
|
||||
|
||||
const courseForm = document.getElementById('courseForm');
|
||||
if (courseForm) {
|
||||
courseForm.addEventListener('submit', function(e) {
|
||||
courseForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const jsonData = {};
|
||||
formData.forEach((value, key) => jsonData[key] = value);
|
||||
|
||||
fetch('api/cursos.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(jsonData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
submitBtn.innerHTML = '<span class="loading-spinner"></span>';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData(this);
|
||||
const jsonData = Object.fromEntries(formData.entries());
|
||||
|
||||
const response = await fetch('api/cursos.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(jsonData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Curso creado exitosamente');
|
||||
showSection('courses');
|
||||
showToast('Curso creado exitosamente', 'success');
|
||||
await loadCoursesSection(document.getElementById('courses-content'));
|
||||
} else {
|
||||
alert('Error al crear el curso');
|
||||
showToast('Error al crear el curso', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupCourseSearch() {
|
||||
const searchButton = document.getElementById('searchButton');
|
||||
if (searchButton) {
|
||||
searchButton.addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('courseSearch').value.toLowerCase();
|
||||
const courses = window.userCourses || [];
|
||||
const filteredCourses = courses.filter(course =>
|
||||
course.nombre.toLowerCase().includes(searchTerm) ||
|
||||
(course.profesor && course.profesor.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
|
||||
const courseList = document.querySelector('.course-list');
|
||||
if (courseList) {
|
||||
courseList.innerHTML = filteredCourses.length > 0 ?
|
||||
filteredCourses.map(course => generateCourseCard(course)).join('') :
|
||||
'<p>No se encontraron cursos que coincidan con la búsqueda.</p>';
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showToast('Error en la conexión', 'error');
|
||||
} finally {
|
||||
submitBtn.innerHTML = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupDiplomaDownloads() {
|
||||
function setupStudentSearch() {
|
||||
const searchButton = document.getElementById('searchStudentButton');
|
||||
if (searchButton) {
|
||||
searchButton.addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('studentSearch').value.toLowerCase();
|
||||
const tableBody = document.getElementById('students-list-body');
|
||||
|
||||
fetch('api/usuarios.php')
|
||||
.then(response => response.json())
|
||||
.then(users => {
|
||||
const filteredUsers = users.filter(user =>
|
||||
user.nombre.toLowerCase().includes(searchTerm) ||
|
||||
user.username.toLowerCase().includes(searchTerm) ||
|
||||
user.email.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
tableBody.innerHTML = generateStudentsTableRows(filteredUsers);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupDiplomaSearch() {
|
||||
const searchButton = document.getElementById('searchDiplomaButton');
|
||||
if (searchButton) {
|
||||
searchButton.addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('diplomaSearch').value.toLowerCase();
|
||||
const tableBody = document.getElementById('diplomas-list-body');
|
||||
|
||||
fetch('api/cursos.php')
|
||||
.then(response => response.json())
|
||||
.then(courses => {
|
||||
const approvedCourses = courses.filter(c => c.estado === 'Aprobado');
|
||||
const filteredCourses = approvedCourses.filter(course =>
|
||||
course.nombre.toLowerCase().includes(searchTerm) ||
|
||||
(course.estudiante && course.estudiante.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
|
||||
tableBody.innerHTML = generateDiplomasTableRows(filteredCourses);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Configurar eventos de descarga de diplomas
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
|
||||
const courseId = this.getAttribute('data-course-id');
|
||||
|
||||
// Mostrar mensaje de carga
|
||||
const originalText = this.innerHTML;
|
||||
this.innerHTML = '<span class="loading-text">Generando diploma...</span>';
|
||||
this.disabled = true;
|
||||
|
||||
// Abrir en nueva pestaña en lugar de usar fetch
|
||||
window.open(`certificado.php`, '_blank');
|
||||
// Abrir en nueva pestaña
|
||||
window.open(`certificado.php?course_id=${courseId}`, '_blank');
|
||||
|
||||
// Restaurar botón después de un breve retraso
|
||||
setTimeout(() => {
|
||||
|
@ -430,11 +532,56 @@ function setupDiplomaDownloads() {
|
|||
});
|
||||
}
|
||||
|
||||
// Funciones de secciones no implementadas (para completar)
|
||||
function loadAdminStudents(container) {
|
||||
container.innerHTML = '<div class="card"><h2>Gestión de Estudiantes</h2><p>Sección en desarrollo</p></div>';
|
||||
function handleGlobalEvents(e) {
|
||||
// Delegación de eventos para mejor performance
|
||||
if (e.target.closest('.edit-btn')) {
|
||||
const courseId = e.target.closest('.edit-btn').dataset.id;
|
||||
handleEditCourse(courseId);
|
||||
}
|
||||
|
||||
if (e.target.closest('.delete-btn')) {
|
||||
const courseId = e.target.closest('.delete-btn').dataset.id;
|
||||
handleDeleteCourse(courseId);
|
||||
}
|
||||
|
||||
if (e.target.closest('.download-btn')) {
|
||||
const courseId = e.target.closest('.download-btn').dataset.id;
|
||||
handleDownloadDiploma(courseId);
|
||||
}
|
||||
}
|
||||
|
||||
function loadProfileSection(container) {
|
||||
container.innerHTML = '<div class="card"><h2>Perfil de Usuario</h2><p>Sección en desarrollo</p></div>';
|
||||
function handleDownloadDiploma(courseId) {
|
||||
const btn = document.querySelector(`.download-btn[data-course-id="${courseId}"]`);
|
||||
const originalText = btn.innerHTML;
|
||||
|
||||
btn.innerHTML = '<span class="loading-spinner small"></span>';
|
||||
btn.disabled = true;
|
||||
|
||||
// Simular generación de diploma
|
||||
setTimeout(() => {
|
||||
window.open(`certificado.php?course_id=${courseId}`, '_blank');
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
showToast('Diploma generado con éxito', 'success');
|
||||
}, 1500);
|
||||
}
|
||||
async function fetchData(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Verificar si la respuesta es válida
|
||||
if (data === null || data === undefined) {
|
||||
throw new Error('Respuesta vacía del servidor');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data from ${url}:`, error);
|
||||
throw error; // Re-lanzamos el error para manejarlo en el nivel superior
|
||||
}
|
||||
}
|
107
dashboard.php
107
dashboard.php
|
@ -13,7 +13,7 @@ $user = $_SESSION['user'];
|
|||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="<?= $user['rol'] ?>">
|
||||
<body>
|
||||
<div id="app-content">
|
||||
<header>
|
||||
<h1>DiploMaster</h1>
|
||||
|
@ -25,96 +25,37 @@ $user = $_SESSION['user'];
|
|||
<div class="main-container">
|
||||
<div class="sidebar" id="sidebar">
|
||||
<ul class="sidebar-menu">
|
||||
<?php if ($user['rol'] === 'admin'): ?>
|
||||
<li class="active" data-section="dashboard"><span>🏠 Inicio</span></li>
|
||||
<li data-section="courses"><span>📚 Gestión de Cursos</span></li>
|
||||
<li data-section="students"><span>👨🎓 Gestión de Estudiantes</span></li>
|
||||
<?php else: ?>
|
||||
<li class="active" data-section="dashboard"><span>🏠 Inicio</span></li>
|
||||
<li data-section="my-courses"><span>📚 Mis Cursos</span></li>
|
||||
<li data-section="diplomas"><span>🎓 Mis Diplomas</span></li>
|
||||
<?php endif; ?>
|
||||
<li data-section="profile"><span>👤 Perfil</span></li>
|
||||
<li class="active" data-section="dashboard"><span>Inicio</span></li>
|
||||
<li data-section="courses"><span>Gestión de Cursos</span></li>
|
||||
<li data-section="students"><span>Gestión de Estudiantes</span></li>
|
||||
<li data-section="diplomas"><span>Diplomas</span></li>
|
||||
<li><a href="api/logout.php" class="logout-link">Cerrar sesión</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="content" id="main-content">
|
||||
<?php if ($user['rol'] === 'admin'): ?>
|
||||
<!-- Contenido para Administrador -->
|
||||
<div id="dashboard-content" class="content-section active">
|
||||
<div class="card">
|
||||
<h2>Panel de Administración</h2>
|
||||
<p>Bienvenido al sistema de gestión de DiploMaster</p>
|
||||
<div class="stats">
|
||||
<p><strong>Estadísticas:</strong></p>
|
||||
<p>• <span id="active-courses-count">0</span> cursos activos</p>
|
||||
<p>• <span id="students-count">0</span> estudiantes registrados</p>
|
||||
<p>• <span id="diplomas-count">0</span> diplomas emitidos</p>
|
||||
</div>
|
||||
<div id="dashboard-content" class="content-section active">
|
||||
<div class="card">
|
||||
<h2>Panel de Administración</h2>
|
||||
<p>Bienvenido al sistema de gestión de DiploMaster</p>
|
||||
<div class="stats">
|
||||
<p><strong>Estadísticas:</strong></p>
|
||||
<p>• <span id="active-courses-count">0</span> cursos activos</p>
|
||||
<p>• <span id="students-count">0</span> estudiantes registrados</p>
|
||||
<p>• <span id="diplomas-count">0</span> diplomas emitidos</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="courses-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
|
||||
<div id="students-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- Contenido para Usuario Normal -->
|
||||
<div id="dashboard-content" class="content-section active">
|
||||
<div class="card">
|
||||
<h2>Bienvenido <?= htmlspecialchars($user['nombre']) ?></h2>
|
||||
<p>Este es tu panel de control en DiploMaster</p>
|
||||
<div class="stats">
|
||||
<p><strong>Resumen:</strong></p>
|
||||
<p>• <span id="user-courses-count">0</span> cursos registrados</p>
|
||||
<p>• <span id="user-diplomas-count">0</span> diplomas disponibles</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Mis Cursos Recientes</h2>
|
||||
<div class="search-container">
|
||||
<input type="text" id="dashboard-course-search" placeholder="Buscar mis cursos..." class="search-input">
|
||||
<button class="btn" id="dashboard-search-btn">Buscar</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="courses-table" id="dashboard-courses-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Fecha Inicio</th>
|
||||
<th>Fecha Fin</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dashboard-courses-body">
|
||||
<!-- Se llenará dinámicamente -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination" id="dashboard-pagination">
|
||||
<button class="btn pagination-btn" id="prev-page">Anterior</button>
|
||||
<span id="page-info">Página 1</span>
|
||||
<button class="btn pagination-btn" id="next-page">Siguiente</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="my-courses-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
|
||||
<div id="diplomas-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Sección común para todos los usuarios -->
|
||||
<div id="profile-content" class="content-section">
|
||||
<div id="courses-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
|
||||
<div id="students-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
|
||||
<div id="diplomas-content" class="content-section">
|
||||
<!-- Se cargará dinámicamente -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue