Stable 2-ago-2023

This commit is contained in:
2023-08-02 09:12:46 -06:00
parent 6a7c6b7ed9
commit f0cc3c585d
60 changed files with 6497 additions and 908 deletions

View File

@@ -0,0 +1,57 @@
<?
#input $_GET['id_espacio_sgu']
#output rutas: [ ...ruta, salones: [{...salon}] ]
header('Content-Type: application/json charset=utf-8');
$information = [
'GET' => [
#'periodo_id',
],
];
$ruta = "../";
require_once "../class/c_login.php";
// check method
try {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
array_walk($information['GET'], function ($value) {
if (!array_key_exists($value, $_GET)) {
http_response_code(400);
echo json_encode(['error' => "$value is required"]);
exit;
}
});
$data = $db->query("SELECT *, horario_view.facultad_id FROM registro
JOIN horario_view USING (horario_id)
LEFT JOIN estado_supervisor USING (estado_supervisor_id)
LEFT JOIN profesor USING (profesor_id)
LEFT JOIN usuario ON usuario.usuario_id = registro.supervisor_id
ORDER BY registro_fecha_ideal DESC, horario_hora ASC, registro_fecha_supervisor ASC");
$last_query = [
'query' => $db->getLastQuery(),
];
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
'query' => $db->getLastQuery(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

View File

@@ -0,0 +1,42 @@
<?
#input $_GET['id_espacio_sgu']
define("INFORMATION", [
'GET' => [
],
]);
#output rutas: [ ...ruta, salones: [{...salon}] ]
header('Content-Type: application/json charset=utf-8');
#return html
$ruta = "../";
require_once "../class/c_login.php";
// check method
try {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// check parameters
$raw = file_get_contents('php://input');
$post_get = json_decode($raw, true);
$data = $db->get('estado_supervisor');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
'query' => $db->getLastQuery(),
'post_data' => $post_get,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

View File

@@ -1,11 +1,51 @@
<?php <?
$information = [
'GET' => [],
];
header('Content-Type: application/json charset=utf-8');
$ruta = "../"; $ruta = "../";
require_once "../class/c_login.php"; require_once "../class/c_login.php";
// check if the session is started // check if the session is started
if (!isset($_SESSION['user'])) if (!isset($_SESSION['user'])) {
die(json_encode(['error' => 'No se ha iniciado sesión'])); http_response_code(500);
echo json_encode([
'error' => 'No se ha iniciado sesión'
]);
exit;
}
$user = unserialize($_SESSION['user']); $user = unserialize($_SESSION['user']);
$ruta = "../"; try {
require '../include/bd_pdo.php'; if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// check parameters
array_walk($information['GET'], function ($value) {
if (!array_key_exists($value, $_GET)) {
http_response_code(400);
echo json_encode(['error' => "$value is required"]);
exit;
}
});
// step 1: get subrutas
$data = $db->get('facultad');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
'query' => $db->getLastQuery(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

View File

@@ -0,0 +1,26 @@
<?php
$ruta = "../";
require_once "../class/c_login.php";
if (!isset($_SESSION['user']))
die(json_encode(['error' => 'No se ha iniciado sesión']));
$user = unserialize($_SESSION['user']);
$ruta = "../";
require_once "../include/bd_pdo.php";
// if method is get
header("Content-Type: application/json");
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$user->print_to_log("Acceso a reposiciones");
if (empty($_GET['horario_id']))
die(json_encode(['error' => 'No se ha enviado el id del horario']));
// fecha greater than today
$reposiciones = $db->query("SELECT fecha, EXTRACT(DOW FROM fecha) as day, EXTRACT(MONTH FROM fecha) as month, EXTRACT(YEAR FROM fecha) as year, EXTRACT(DAY FROM fecha) as dia_mes FROM fechas_clase(:horario_id) WHERE fecha > CURRENT_DATE", [
'horario_id' => $_GET['horario_id']
]);
echo json_encode([
'status' => 'success',
'data' => $reposiciones
]);
}

View File

@@ -10,13 +10,12 @@ $user = unserialize($_SESSION['user']);
$ruta = "../"; $ruta = "../";
require_once("../include/bd_pdo.php"); require_once("../include/bd_pdo.php");
extract($_POST); extract($_POST);
$params = ['per' => $periodo, 'fac' => $facultad, 'car' => $carrera]; $params = ['per' => $_POST['periodo'], 'fac' => $_POST['facultad'], 'car' => $_POST['carrera']];
$user->print_to_log("Acceso a grupos", old: $params); $user->print_to_log("Acceso a grupos", old: $params);
$grupos = queryAll("SELECT DISTINCT LENGTH(GRUPO), GRUPO FROM fs_horario_basic WHERE PERIODO_ID = COALESCE(:per, PERIODO_ID) AND FACULTAD_ID = COALESCE(:fac, FACULTAD_ID) AND CARRERA_ID = COALESCE(:car, CARRERA_ID) ORDER BY LENGTH(GRUPO), GRUPO", $params); $grupos = queryAll("SELECT DISTINCT LENGTH(GRUPO), GRUPO FROM fs_horario_basic WHERE PERIODO_ID = COALESCE(:per, PERIODO_ID) AND FACULTAD_ID = COALESCE(:fac, FACULTAD_ID) AND CARRERA_ID = COALESCE(:car, CARRERA_ID) ORDER BY LENGTH(GRUPO), GRUPO", $params);
$grupos = array_map(function ($grupo) {
return $grupo['grupo']; $grupos = array_map(fn ($grupo) => $grupo['grupo'], $grupos);
}, $grupos);
echo json_encode([ echo json_encode([
'status' => 'success', 'status' => 'success',

View File

@@ -0,0 +1,28 @@
<?php
header('Content-Type: application/json');
$ruta = "../";
require_once("../include/bd_pdo.php");
$grupo = isset($_GET['grupo']) ? $_GET['grupo'] : 1;
$grupo_horarios = $db->querySingle(
"WITH bloques AS (
SELECT id, hora_inicio, hora_fin
FROM public.bloque_horario
WHERE grupo = ?
ORDER BY hora_inicio ASC
)
SELECT json_agg(json_build_object(
'id', id,
'hora_inicio', hora_inicio,
'hora_fin', hora_fin,
'selected', current_time between hora_inicio and hora_fin
)) AS bloque_horario
FROM bloques
",
[$grupo]
)['bloque_horario'];
echo $grupo_horarios;

View File

@@ -1,4 +1,38 @@
<?php <?php
header('Content-Type: application/json');
$ruta = "../";
require_once("../include/bd_pdo.php");
$dias = array("domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado");
try {
if(empty($_POST['profesor_id']))
throw new Exception("No se ha especificado un profesor");
// RECORD LAST QUERY
$horarios = $db->query("SELECT * FROM fs_horario(_periodo_id => ?, _last => true, _profesor_id => ?) ORDER BY MATERIA", [
$_POST['periodo_id'],
$_POST['profesor_id'],
]);
$horarios = array_map(function ($horario) use ($dias, $db) {
$horario['profesores'] = array_map(
fn ($profesor) =>
$db->where("id", $profesor)->getOne("fs_profesor"),
explode(",", substr($horario['profesores'], 1, -1))
);
$horario['dia'] = $dias[$horario['dia']];
return $horario;
}, $horarios);
die(json_encode([ die(json_encode([
'message' => 'ok', "status" => "success",
"data" => $horarios,
// "data" => [],
])); ]));
} catch (Exception $e) {
die(json_encode([
"status" => "error",
"message" => $e->getMessage(),
"query" => $db->getLastQuery(),
]));
}

View File

@@ -29,12 +29,15 @@
$user = Login::validUser($usr, $pass); $user = Login::validUser($usr, $pass);
if ($user === false) { if (is_array($user)) {
$_SESSION['error'] = true; $_SESSION['error'] = true;
header("Location: ../"); // build query params
$params = http_build_query($user);
header("Location: ../index.php?$params");
} else { } else {
$_SESSION['user'] = serialize($user); $_SESSION['user'] = serialize($user);
header("Location: ../main.php");
header("Location: " . (isset($_SESSION['ruta']) ? $_SESSION['ruta'] : "../main.php"));
} }
exit; exit;

View File

@@ -0,0 +1,54 @@
<?php
$ruta = "../";
require_once "../class/c_login.php";
if (!isset($_SESSION['user']))
die(json_encode(['error' => 'No se ha iniciado sesión']));
$user = unserialize($_SESSION['user']);
$ruta = "../";
require_once "../include/bd_pdo.php";
// if method is get
header("Content-Type: application/json");
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$user->print_to_log("Acceso a reposiciones");
$reposiciones = $db
->where('periodo_id', $_GET['periodo_id'] ?? null)
->where('profesor_id', $_GET['profesor_id'] ?? [])
->get("reposicion");
echo json_encode([
'status' => 'success',
'reposiciones' => $reposiciones
]);
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user->print_to_log("Creación de reposición", new: $params);
try {
$requiredParams = ['horario_id', 'fecha', 'hora', 'duracion_id', 'descripcion', 'profesor_id', 'salon', 'unidad', 'periodo_id', 'fecha_clase'];
// Filter params based on requiredParams
$params = array_filter($_POST, function ($key) use ($requiredParams) {
return in_array($key, $requiredParams);
}, ARRAY_FILTER_USE_KEY);
// Check if all required params are present
if (count($params) !== count($requiredParams)) {
throw new Exception('Falta uno o más parámetros requeridos');
}
$db->insert("reposicion", $params);
// Return success response
echo json_encode([
"status" => "success",
"message" => "Reposición creada correctamente",
]);
} catch (Exception $e) {
// Return error response
echo json_encode([
"status" => "error",
"message" => "No se pudo crear la reposición",
"error" => $e->getMessage(),
]);
}
}

View File

@@ -29,7 +29,7 @@ $user = [
]; ];
$user = new Login($user, $facultad, $rol, $admin, $periodo); $user = new Login($user, $facultad, $rol, $admin, $periodo);
if (isset($_SESSION))
session_start(); session_start();
$_SESSION['user'] = serialize($user); $_SESSION['user'] = serialize($user);

View File

@@ -0,0 +1,60 @@
<?
#input $_GET['id_espacio_sgu']
define("INFORMATION", [
'POST' => [
'profesor_id',
'horario_id',
'estado',
'comentario',
'supervisor_id',
],
]);
#output rutas: [ ...ruta, salones: [{...salon}] ]
header('Content-Type: application/json charset=utf-8');
#return html
$ruta = "../";
require_once "../class/c_login.php";
// check method
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// check parameters
$raw = file_get_contents('php://input');
$post_data = json_decode($raw, true);
// if it's a list
// step 1: get subrutas
if (empty($post_data)) {
http_response_code(400);
echo json_encode(['error' => 'No hay clases pendientes']);
exit;
}
$data = $db->query(
'INSERT INTO registro (profesor_id, horario_id, registro_fecha_supervisor, estado_supervisor_id, registro_fecha_ideal, supervisor_id, comentario)
VALUES' .
implode(',', array_map(fn($x) => "({$x['profesor_id']} , {$x['horario_id']}, NOW()," . (is_null($x['estado']) ? 'null' : $x['estado']) . ", NOW(), {$x['supervisor_id']}," . (empty($x['comentario']) ? 'null' : "'{$x['comentario']}'") . ')', $post_data))
. ' ON CONFLICT (profesor_id, horario_id, registro_fecha_ideal) DO UPDATE SET estado_supervisor_id = EXCLUDED.estado_supervisor_id, registro_fecha_supervisor = NOW(), comentario = EXCLUDED.comentario
RETURNING *'
);
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
'query' => $db->getLastQuery(),
'post_data' => $post_data,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

28
action/rutas.php Normal file
View File

@@ -0,0 +1,28 @@
<?
header('Content-Type: application/json charset=utf-8');
$ruta = "../";
require_once "../class/c_login.php";
$universidad_la_salle = $db
->where('salon', 'UNIVERSIDAD LA SALLE', 'ILIKE')
->getOne('salon_view');
$rutas =
array_map(
function (&$ruta) use ($db) {
$ruta['subrutas'] =
$db
->where('id_espacio_padre', $ruta['id_espacio_sgu'])
->orderBy('salon')
->get('salon_view');
return $ruta;
},
$db
->where('id_espacio_padre', $universidad_la_salle['id_espacio_sgu'])
->orderBy('salon')
->get('salon_view')
);
// echo json_encode($universidad_la_salle, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); EXIT;
echo json_encode($rutas, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

View File

@@ -0,0 +1,73 @@
<?
#input $_GET['id_espacio_sgu']
$information = [
'GET' => [
'id_espacio_sgu',
'bloque_horario_id',
],
];
#output rutas: [ ...ruta, salones: [{...salon}] ]
header('Content-Type: application/json charset=utf-8');
$ruta = "../";
require_once "../class/c_login.php";
// check method
try {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// check parameters
array_walk($information['GET'], function ($value) {
if (!array_key_exists($value, $_GET)) {
http_response_code(400);
echo json_encode(['error' => "$value is required"]);
exit;
}
});
// step 1: get subrutas
$data = $db
->where('tiene_salones')
->where("{$_GET['id_espacio_sgu']} = ANY(id_espacio_sgu_array)")
->get('salon_view');
// step 3: get horarios
$data = array_map(
fn($ruta) => array_merge(
[
'horarios' => $db
->join('periodo', 'periodo.periodo_id = horario_view.periodo_id')
->join('bloque_horario', '(bloque_horario.hora_inicio, bloque_horario.hora_fin) OVERLAPS (horario_view.horario_hora, horario_view.horario_hora + horario_view.duracion)')
->join('salon_view', 'salon_view.salon_id = horario_view.salon_id')
->join('horario_profesor', 'horario_profesor.horario_id = horario_view.horario_id')
->join('profesor', 'profesor.profesor_id = horario_profesor.profesor_id')
->join('registro', '(registro.profesor_id, registro.horario_id, registro.registro_fecha_ideal) = (profesor.profesor_id, horario_view.horario_id, CURRENT_DATE)', 'LEFT')
->where('CURRENT_DATE BETWEEN periodo.periodo_fecha_inicio AND periodo.periodo_fecha_fin')
->where('horario_dia = EXTRACT(DOW FROM CURRENT_DATE)')
->where('bloque_horario.id', $_GET['bloque_horario_id'])
->where('id_espacio_padre', $ruta['id_espacio_sgu'])
->get('horario_view', null, '*, horario_view.horario_id, profesor.profesor_id'),
],
$ruta
),
$data
);
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
'query' => $db->getLastQuery(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

View File

@@ -0,0 +1,24 @@
{
"type": "array",
"items": {
"type": "object",
"properties": {
"profesor_id": {
"type": "integer"
},
"horario_id": {
"type": "integer"
},
"estado": {
"type": ["integer", "null"]
},
"comentario": {
"type": "string"
},
"supervisor_id": {
"type": "integer"
}
},
"required": ["profesor_id", "horario_id", "comentario", "supervisor_id"]
}
}

377
auditoría.php Normal file
View File

@@ -0,0 +1,377 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Supervisor</title>
<?php
include 'import/html_css_files.php';
?>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<?
$redirect = $_SERVER['PHP_SELF'];
include "import/html_header.php";
global $user;
html_header(
"Registro de asistencia - Vicerrectoría Académica",
"Sistema de gestión de checador",
);
#include "import/periodo.php";
?>
<main class="container-fluid px-4 mt-4" id="app" v-cloak @vue:mounted="mounted">
<form action="">
<div class="form-box">
<div class="form-group row">
<label for="periodo" class="col-4 col-form-label">Facultad</label>
<div class="col-6">
<div id="dlPeriodo" class="datalist datalist-select mb-1 w-100">
<div class="datalist-input">Selecciona una facultad</div>
<span class="ing-buscar icono"></span>
<ul style="display:none">
<li class="datalist-option" data-id="0" @click="store.filters.facultad_id = null;">
Todas las facultades
</li>
<li class="datalist-option" v-for="facultad in store.facultades.data"
:key="facultad.facultad_id" :data-id="facultad.facultad_id"
@click="store.filters.facultad_id = facultad.facultad_id">
(<small> {{facultad.clave_dependencia}} </small>) {{ facultad.facultad_nombre }}
</li>
</ul>
<input type="hidden" id="facultad_id" name="id">
</div>
</div>
</div>
<div class="form-group row align-items-center">
<label for="switchFecha" class="col-4 col-form-label">
Fecha
<!-- switch -->
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="switchFecha"
v-model="store.filters.switchFecha" @input="store.filters.switchFechas">
<label class="custom-control-label" for="switchFecha"></label>
</div>
</label>
<div class="col-3" v-if="store.filters.switchFecha">
<div class="form-row">
<input id="fecha_inicio" name="fecha_inicio" class="form-control date-picker"
placeholder="Seleccione una fecha de inicio" readonly
v-model="store.filters.fecha_inicio">
</div>
</div>
<div class="col-3" v-if="store.filters.switchFecha">
<div class="form-row">
<input id="fecha_fin" name="fecha_fin" class="form-control date-picker"
placeholder="Seleccione una fecha final" readonly v-model="store.filters.fecha_fin">
</div>
</div>
<div class="col-6" v-if="!store.filters.switchFecha">
<div class="form-row">
<input id="fecha" name="fecha" class="form-control date-picker"
placeholder="Seleccione una fecha" readonly v-model="store.filters.fecha">
</div>
</div>
</div>
<div class="form-group row">
<label for="profesor" class="col-4 col-form-label">Profesor</label>
<div class="col-6">
<div class="form-row justify-content-around align-items-center">
<input id="profesor" name="profesor" class="form-control col-11 mr-1 px-2"
placeholder="Seleccione una profesor" list="dlProfesor"
v-model="store.filters.profesor">
<button type="button" class="btn btn-info btn-sm form-control col ml-auto"
@click="store.filters.profesor = null">
<i class="ing-borrar"></i>
</button>
</div>
<datalist id="dlProfesor">
<option v-for="profesor in profesores" :key="profesor.profesor_id"
:value="`(${profesor.profesor_clave}) ${profesor.profesor_nombre}`">
</datalist>
</div>
</div>
<div class="form-group row">
<label for="periodo" class="col-4 col-form-label">Asistencia</label>
<div class="col-6">
<div class="form-row justify-content-around align-items-center">
<div id="dlPeriodo" class="datalist datalist-select mb-1 w-100">
<div class="datalist-input" id="estados">Selecciona un estado de asistencia</div>
<span class="ing-buscar icono"></span>
<ul style="display:none">
<li class="datalist-option" data-id="0" @click="store.filters.estados = [];">
Todos los registros
</li>
<li class="datalist-option" v-for="estado in store.estados.data"
:key="estado.estado_supervisor_id" :data-id="estado.estado_supervisor_id"
@click="store.filters.estados = store.toggle(store.filters.estados, estado.estado_supervisor_id); ; setTimeout(store.estados.printEstados, 0);">
<span class="badge"
:class="`badge-${store.filters.estados.includes(estado.estado_supervisor_id) ? 'dark' : estado.estado_color}`"><i
:class="estado.estado_icon"></i> {{estado.nombre}}</span>
</li>
</ul>
<input type="hidden" id="estado_id" name="estado_id">
</div>
</div>
</div>
</div>
</div>
</form>
<div class="mt-3 d-flex justify-content-center flex-wrap">
<!-- refresh -->
<div class="table-responsive">
<table class="table table-hover table-striped table-bordered table-sm">
<thead class="thead-dark">
<tr>
<th scope="col" class="text-center align-middle px-2">
<button @click="registros.invertir" class="btn btn-info mr-3"
v-if="registros.relevant.length > 1">
<i class="ing-cambiar ing-rotate-90"></i>
</button>
Fecha
</th>
<th scope="col" class="text-center align-middle px-2">Salón</th>
<th scope="col" class="text-center align-middle px-2">Profesor</th>
<th scope="col" class="text-center align-middle px-2">Horario</th>
<th scope="col" class="text-center align-middle px-2">Registro</th>
<th scope="col" class="text-center align-middle px-2">Supervisor</th>
</tr>
</thead>
<tbody>
<tr v-if="registros.relevant.length == 0">
<td colspan="7" class="text-center">No hay clases en este horario</td>
</tr>
<tr v-for="registro in registros.relevant" :key="registro.registro_id">
<td class="text-center align-middle px-2">{{ registro.registro_fecha_ideal }}
</td>
<td class="text-center align-middle px-2">{{ registro.salon }}</td>
<td class="text-center align-middle px-2">
<div class="col-12">
<strong>{{ registro.profesor_clave }}</strong>
{{ registro.profesor_nombre }}
</div>
<div class="col-12">
<button type="button" class="btn btn-outline-dark btn-sm"
@click="store.current.clase_vista = registro" data-toggle="modal"
data-target="#ver-detalle">
Ver detalle <i class="ing-ojo"></i>
</button>
</div>
</td>
<td class="text-center align-middle px-2">{{ registro.horario_hora.slice(0,5) }} - {{
registro.horario_fin.slice(0,5) }}</td>
<!-- -->
<td class="text-center align-middle px-2">
<div v-if="registro.registro_fecha">
<div class="col-12">
Registro <small>{{ registro.registro_fecha.slice(11,16) }}</small>
</div>
</div>
<div v-else>
<strong>
<div class="col-12">
<span class="badge badge-danger"><i class="ing-cancelar"></i></span>
</div>
<div class="col-12 mt-2">
Sin registro
</div>
</strong>
</div>
</td>
<!-- Sí checó supervisor -->
<td class="text-center align-middle px-2">
<div v-if="registro.registro_fecha_supervisor">
<div class="row">
<div class="col-12">
<strong>{{ registro.usuario_nombre }}</strong>
</div>
<div class="col-12">
Hora
<small>{{ registro.registro_fecha_supervisor.slice(11,19) }}</small>
</div>
<div class="col-12 mt-2">
<span class="badge" :class="`badge-${registro.estado_color}`">
<i :class="`${registro.estado_icon}`"></i>
<strong>{{ registro.nombre }}</strong>
</span>
</div>
</div>
<!-- comentario -->
<hr v-if="registro.comentario">
<div class="col-12 " @click="registros.mostrarComentario(registro.registro_id)"
v-if="registro.comentario" style="cursor: pointer;">
<strong class="badge badge-primary">Observaciones:</strong>
<small class="text-truncate">{{registro.comentario.slice(0,
25)}}{{registro.comentario.length > 10 ? '...' : ''}}</small>
</div>
</div>
<!-- No checó -->
<div v-else>
<div class="col-12">
<span class="badge badge-danger"><i class="ing-cancelar"></i></span>
</div>
<div class="col-12 mt-2">
<strong>Sin registro</strong>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal" tabindex="-1" id="ver-comentario">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Comentario</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<div class="input-group">
<textarea class="form-control" aria-label="Comentarios de la clase" rows="5"
v-model="store.current.comentario" disabled></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">
Aceptar
</button>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="ver-detalle">
<div class="modal-dialog modal-dialog-centered modal-xl" v-if="clase_vista">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" :data-id="clase_vista.horario_id">Detalle de la clase</h2>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<section class="col-12 col-md-6">
<h4 class="h4">Profesor</h4>
<div class="row">
<div class="col-12">
<strong>Nombre:</strong>
{{ clase_vista.profesor_nombre }}
</div>
<div class="col-12">
<strong>Correo:</strong>
<a :href="`mailto:${clase_vista.profesor_correo}`"><strong>{{
clase_vista.profesor_correo }}</strong></a>
</div>
<div class="col-12">
<strong>Clave:</strong>
{{ clase_vista.profesor_clave }}
</div>
<div class="col-12">
<strong>Facultad:</strong>
{{ clase_vista.facultad }}
</div>
</div>
</section>
<section class="col-12 col-md-6">
<h4 class="h4">Clase</h4>
<div class="row">
<div class="col-12">
<strong>Materia:</strong>
{{ clase_vista.materia }}
</div>
<div class="col-12">
<strong>Carrera:</strong>
{{ clase_vista.carrera }}
</div>
<div class="col-12">
<strong>Nivel:</strong>
{{ clase_vista.nivel}}
</div>
<div class="col-12">
<strong>Grupo:</strong>
{{ clase_vista.horario_grupo }}
</div>
<div class="col-12">
<strong>Horario:</strong>
<!-- hora hh:mm:ss to hh:mm -->
{{ clase_vista.horario_hora?.slice(0, 5) }} - {{
clase_vista.horario_fin?.slice(0, 5) }}
</div>
<div class="col-12">
<strong>Salón:</strong>
{{ clase_vista.salon }}
</div>
</div>
</section>
</div>
<div class="row">
<section class="col-12">
<h4 class="h4 mt-4">Registro</h4>
<div class="row">
<div class="col-12 text-center" v-if="!clase_vista.registro_fecha">
<strong><span class="badge badge-danger"><i class="ing-cancelar"></i></span>
El profesor aún no ha registrado su asistencia</strong>
</div>
<div class="col-6 text-center" v-else>
El profesor registró su asistencia a las
<code>{{clase_vista.registro_fecha.slice(11, 16)}}</code>
<hr>
<p v-if="!clase_vista.registro_retardo" class="text-center">
<span class="badge badge-success"><i class="ing-aceptar"></i></span>
A tiempo
</p>
<p v-else class="text-center">
<span class="badge badge-warning"><i class="ing-retardo"></i></span>
Con retardo
</p>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="modal-footer">
<!-- botón aceptar -->
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">
<i class="ing-aceptar"></i>
Aceptar
</button>
</div>
</div>
</div>
</div>
</main>
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
<script src="js/datalist.js"></script>
<script src="js/datepicker-es.js"></script>
<script src="js/auditoría.js" type="module"></script>
</body>
</html>

View File

@@ -1,5 +1,4 @@
<?php <?php
require_once 'class/c_login.php';
if (isset($_GET["error"]) && is_numeric($_GET["error"])) { if (isset($_GET["error"]) && is_numeric($_GET["error"])) {
switch ($_GET["error"]) { switch ($_GET["error"]) {
case 0: case 0:

View File

@@ -1,4 +1,11 @@
<?php <?php
date_default_timezone_set('America/Mexico_City');
$currentTime = time();
$endOfDay = strtotime('tomorrow') - 1;
$remainingTime = $endOfDay - $currentTime;
session_set_cookie_params($remainingTime, '/', $_SERVER['HTTP_HOST'], false, true);
require_once($ruta ?? '') . "include/bd_pdo.php"; require_once($ruta ?? '') . "include/bd_pdo.php";
require_once($ruta ?? '') . "class/c_logasistencia.php"; require_once($ruta ?? '') . "class/c_logasistencia.php";
require_once($ruta ?? '') . "include/nusoap/nusoap.php"; require_once($ruta ?? '') . "include/nusoap/nusoap.php";
@@ -7,14 +14,16 @@ session_start();
class Login class Login
{ {
public string $acceso; public string $acceso;
public function __construct(public array $user, public array $facultad, public array $rol, public bool $admin, public ?int $periodo) public function __construct(public array $user, public array $facultad, public array $rol, public bool $admin, public ?int $periodo, public bool $supervisor, public bool $jefe_carrera, public bool $profesor)
{ {
} }
public function print_to_log(string $desc, array $old = null, array $new = null): void public function print_to_log(string $desc, array $old = null, array $new = null): void
{ {
$log = new classes\LogAsistencias($_ENV["RUTA_RAIZ"]); $log = new classes\LogAsistencias($_ENV["RUTA_RAIZ"]);
if ($old) $desc .= " |#| OLD:" . json_encode($old); if ($old)
if ($new) $desc .= " |#| NEW:" . json_encode($new); $desc .= " |#| OLD:" . json_encode($old);
if ($new)
$desc .= " |#| NEW:" . json_encode($new);
$log->appendLog($this->user["id"], $this->user["nombre"], $desc); $log->appendLog($this->user["id"], $this->user["nombre"], $desc);
} }
public function access(string $pagina = null): void public function access(string $pagina = null): void
@@ -25,59 +34,93 @@ class Login
} }
# print_r( $access ); # print_r( $access );
$this->acceso = query('SELECT tipo FROM PERMISO_VIEW WHERE ID = :usr AND PAGINA_RUTA ILIKE :ruta', array( $this->acceso = query(
'SELECT tipo FROM PERMISO_VIEW WHERE ID = :usr AND PAGINA_RUTA ILIKE :ruta',
array(
':usr' => $this->user["id"], ':usr' => $this->user["id"],
':ruta' => $pagina ?? substr(basename($_SERVER['PHP_SELF']), 0, -4) ':ruta' => $pagina ?? substr(basename($_SERVER['PHP_SELF']), 0, -4)
))["tipo"] ?? 'n'; )
)["tipo"] ?? 'n';
} }
public function __toString(): string public function __toString(): string
{ {
return "Usuario: {$this->user["nombre"]} ({$this->user["id"]})"; return "Usuario: {$this->user["nombre"]} ({$this->user["id"]}), Es admin: {$this->admin}, supervisor: {$this->supervisor}, jefe carrera: {$this->jefe_carrera}, profesor: {$this->profesor}";
} }
private static function validaUsuario($user, $pass): bool private static function validaUsuario($user, $pass): bool
{ {
file_put_contents('php://stderr', $user); file_put_contents('php://stderr', $user);
if (in_array($user, ['ad012821']) and $pass == "admin") return true; if (in_array($user, ['ad017045']) and $pass == "admin")
if (in_array($user, ['ad017045']) and $pass == "admin") return true ; return true;
$client = new nusoap_client('http://200.13.89.2/validacion.php?wsdl', 'wsdl'); $client = new nusoap_client('http://200.13.89.2/validacion.php?wsdl', 'wsdl');
$error = $client->getError(); $client->getError() and die('Error al crear el cliente: ' . $client->getError());
if ($error) return false;
$pass = utf8_decode($pass); $pass = utf8_decode($pass);
$result = $client->call("valida_user", array($user, $pass)); $result = $client->call("valida_user", array($user, $pass));
$client->fault and die('Error al llamar al servicio: ' . $client->getError());
if ($client->fault) return false;
return $result; return $result;
} }
public static function validUser(string $user, string $pass): Login | false public static function validUser(string $user, string $pass): Login|array
{ {
$fs_validaclaveulsa = query( if (!Login::validaUsuario($user, $pass)) {
'SELECT * FROM FS_VALIDACLAVEULSA(:usr)', [':usr' => $user] return [
); 'error' => true,
'msg' => 'Error al autenticar usuario'
];
}
global $db;
if (empty($fs_validaclaveulsa["id"])) return false; if ($db->has("FS_VALIDACLAVEULSA('$user')")) {
#die (Login::validaUsuario($user, $pass)); #die (Login::validaUsuario($user, $pass));
if (!Login::validaUsuario($user, $pass)) return false; $fs_validaclaveulsa = $db->querySingle(
'SELECT * FROM FS_VALIDACLAVEULSA(?)',
[$user]
);
$user = array( $user = array(
'id' => $fs_validaclaveulsa["id"], 'id' => $fs_validaclaveulsa["id"],
'nombre' => $fs_validaclaveulsa["nombre"], 'nombre' => $fs_validaclaveulsa["nombre"],
); );
$facultades = query("SELECT FACULTAD_ID id, FACULTAD f FROM FS_PERIODO WHERE ID = :id", [':id' => $fs_validaclaveulsa["periodo_id"]]);
$facultad = array( $facultad = array(
'facultad_id' => $fs_validaclaveulsa["facultad_id"] ?? $facultades["id"], 'facultad_id' => $fs_validaclaveulsa["facultad_id"],
'facultad' => $fs_validaclaveulsa["facultad"] ?? $facultades["f"], 'facultad' => $fs_validaclaveulsa["facultad"],
); );
$rol = array( $rol = array(
'id' => $fs_validaclaveulsa["rol_id"], 'id' => $fs_validaclaveulsa["rol_id"],
'rol' => $fs_validaclaveulsa["rol"] 'rol' => $fs_validaclaveulsa["rol"]
); );
$supervisor = $db
->join('rol', 'rol.rol_id = usuario.rol_id')
->where('usuario_id', $user["id"])
->where('rol.rol_titulo', 'Supervisor')
->has('usuario');
$jefe_carrera = $db->where('usuario_id', $user["id"])->has('usuario_carrera');
$admin = $fs_validaclaveulsa["is_admin"]; $admin = $fs_validaclaveulsa["is_admin"];
$periodo = $fs_validaclaveulsa["periodo_id"]; $periodo = $fs_validaclaveulsa["periodo_id"];
return new Login($user, $facultad, $rol, $admin, $periodo);
return new Login($user, $facultad, $rol, $admin, $periodo, $supervisor, $jefe_carrera, false);
} else if ($db->where('profesor_clave', preg_replace('/^do0*/', '', $user))->has("profesor")) {
$profesor = $db->where('profesor_clave', preg_replace('/^do0*/', '', $user))->getOne("profesor");
$user = array(
'id' => $profesor["profesor_clave"],
'nombre' => $profesor["profesor_nombre"],
);
$facultad = $rol = array(
'facultad_id' => null,
'facultad' => 'Docente',
);
$supervisor = false;
$jefe_carrera = false;
$admin = false;
$periodo = null;
// CREATE A COOKIE FOR THE REST OF THE day for example: 23:00 then duration will be 1 hour
setcookie("profesor", $user["id"], strtotime('today midnight') + 86400, "/");
return new Login($user, $facultad, $rol, $admin, $periodo, $supervisor, $jefe_carrera, true);
} else
return [
'error' => true,
'msg' => 'Usuario no encontrado'
];
} }
public static function log_out(): void public static function log_out(): void
{ {

View File

@@ -2,6 +2,7 @@
"require": { "require": {
"vlucas/phpdotenv": "^5.5", "vlucas/phpdotenv": "^5.5",
"phpoffice/phpspreadsheet": "^1.25", "phpoffice/phpspreadsheet": "^1.25",
"seinopsys/postgresql-database-class": "^3.1" "seinopsys/postgresql-database-class": "^3.1",
"justinrainbow/json-schema": "^5.2"
} }
} }

72
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5e701c768afe8ce8feabe1b539fa7234", "content-hash": "2b67052b0f31b7059a262343c2640316",
"packages": [ "packages": [
{ {
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
@@ -129,6 +129,76 @@
], ],
"time": "2022-07-30T15:56:11+00:00" "time": "2022-07-30T15:56:11+00:00"
}, },
{
"name": "justinrainbow/json-schema",
"version": "5.2.12",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
"reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"bin": [
"bin/validate-json"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema",
"keywords": [
"json",
"schema"
],
"support": {
"issues": "https://github.com/justinrainbow/json-schema/issues",
"source": "https://github.com/justinrainbow/json-schema/tree/5.2.12"
},
"time": "2022-04-13T08:02:27+00:00"
},
{ {
"name": "maennchen/zipstream-php", "name": "maennchen/zipstream-php",
"version": "2.2.1", "version": "2.2.1",

View File

@@ -17,7 +17,9 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<html lang="en"> <html lang="en">
<head> <head>
<title>Consultar horario | <?= $user->facultad['facultad'] ?? 'General' ?></title> <title>Consultar horario |
<?= $user->facultad['facultad'] ?? 'General' ?>
</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="content-type" content="text/plain; charset=UTF-8" /> <meta http-equiv="content-type" content="text/plain; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@@ -91,7 +93,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-group mt-4 row justify-content-center"> <div class="form-group mt-4 row justify-content-center">
<?php if ($write) { ?> <?php if ($write) { ?>
<button type="button" id="nuevo" class="btn btn-outline-primary ml-4 d-none" title="Nuevo horario" data-toggle="modal" data-target="#modal-editar"> <button type="button" id="nuevo" class="btn btn-outline-primary ml-4 d-none"
title="Nuevo horario" data-toggle="modal" data-target="#modal-editar">
<span class="ing-mas ing-fw"></span> Nuevo <span class="ing-mas ing-fw"></span> Nuevo
</button> </button>
<?php } ?> <?php } ?>
@@ -127,7 +130,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
</table> </table>
</div> </div>
<div class="modal fade" id="modal-editar" tabindex="-1" aria-labelledby="modal-editar" aria-hidden="true" data-backdrop="static" data-keyboard="false"> <div class="modal fade" id="modal-editar" tabindex="-1" aria-labelledby="modal-editar" aria-hidden="true"
data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered modal-lg"> <div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -158,7 +162,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-grupo row mb-3"> <div class="form-grupo row mb-3">
<div class="col-4"></div> <div class="col-4"></div>
<div class="col-6"> <div class="col-6">
<input type="text" id="grupo" name="grupo" value="" class="form-control" placeholder="Grupo" required="required" hidden> <input type="text" id="grupo" name="grupo" value="" class="form-control"
placeholder="Grupo" required="required" hidden>
<div class="invalid-feedback"> <div class="invalid-feedback">
Por favor, ingrese un grupo. Por favor, ingrese un grupo.
</div> </div>
@@ -167,7 +172,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-group row"> <div class="form-group row">
<label for="materia" class="col-4 col-form-label">Materia</label> <label for="materia" class="col-4 col-form-label">Materia</label>
<div class="col-6"> <div class="col-6">
<input list="lista_materias" name="dlMateria" id="dlMateria" class="form-control text-center" placeholder="Materia" required="required"> <input list="lista_materias" name="dlMateria" id="dlMateria"
class="form-control text-center" placeholder="Materia" required="required">
<datalist id="lista_materias"></datalist> <datalist id="lista_materias"></datalist>
<input type="hidden" id="materia" name="materia" value=""> <input type="hidden" id="materia" name="materia" value="">
<div class="invalid-feedback"> <div class="invalid-feedback">
@@ -196,7 +202,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<span class="ing-buscar icono"></span> <span class="ing-buscar icono"></span>
<ul style="display:none"> <ul style="display:none">
<?php foreach (range(0, 45, 15) as $minuto) { ?> <?php foreach (range(0, 45, 15) as $minuto) { ?>
<li data-id='<?= $minuto ?>'><?= str_pad($minuto, 2, "0", STR_PAD_LEFT) ?></li> <li data-id='<?= $minuto ?>'><?= str_pad($minuto, 2, "0", STR_PAD_LEFT) ?>
</li>
<?php } ?> <?php } ?>
</ul> </ul>
<input type="hidden" id="selector_minutos" name="minutos" value=""> <input type="hidden" id="selector_minutos" name="minutos" value="">
@@ -239,7 +246,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
$id = $duración['duracion_id']; $id = $duración['duracion_id'];
$bloques = $duración['duracion_bloques']; $bloques = $duración['duracion_bloques'];
?> ?>
<li data-id="<?= $id; ?>" data-bloques="<?= $bloques; ?>"><?= $nombre; ?></li> <li data-id="<?= $id; ?>" data-bloques="<?= $bloques; ?>"><?= $nombre; ?>
</li>
<?php <?php
} }
?> ?>
@@ -256,7 +264,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-group row"> <div class="form-group row">
<label for="editor_profesor" class="col-4 col-form-label">Profesor</label> <label for="editor_profesor" class="col-4 col-form-label">Profesor</label>
<div class="col-6"> <div class="col-6">
<input list="lista_profesores" name="dlProfesor" id="dlProfesor" class="form-control" placeholder="Profesor" required="required"> <input list="lista_profesores" name="dlProfesor" id="dlProfesor"
class="form-control" placeholder="Profesor" required="required">
<div class="valid-feedback"> <div class="valid-feedback">
Profesor encontrado Profesor encontrado
</div> </div>
@@ -265,11 +274,10 @@ $write = $user->admin || in_array($user->acceso, ['w']);
</div> </div>
<datalist id="lista_profesores"> <datalist id="lista_profesores">
<?php <?php
$profesores = $db->where('facultad_id', $user->facultad['facultad_id'])->get("fs_profesor"); $profesores = $db->get("profesor");
foreach ($profesores as $profesor) { foreach ($profesores as $profesor) {
extract($profesor);
?> ?>
<option data-grado="<?= $grado ?>" data-clave="<?= $clave ?>" data-profesor="<?= $profesor ?>" data-id="<?= $id; ?>" value="<?= "$clave | $grado $profesor" ?>"></option> <option data-clave="<?= $profesor['profesor_clave'] ?>" data-profesor="<?= $profesor['profesor_nombre'] ?>" data-id="<?= $id; ?>" value="<?= "{$profesor['profesor_clave']} | {$profesor['profesor_grado']} {$profesor['profesor_nombre']}" ?>"></option>
<?php <?php
} }
?> ?>
@@ -283,7 +291,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-group row"> <div class="form-group row">
<label for="editor_salón" class="col-4 col-form-label">Salón</label> <label for="editor_salón" class="col-4 col-form-label">Salón</label>
<div class="col-6"> <div class="col-6">
<input type="text" class="form-control" id="editor_salón" name="salón" placeholder="Salón" maxlength="100" required="required"> <input type="text" class="form-control" id="editor_salón" name="salón"
placeholder="Salón" maxlength="100" required="required">
<div class="invalid-feedback"> <div class="invalid-feedback">
El salón no puede estar vacío. El salón no puede estar vacío.
</div> </div>
@@ -292,7 +301,8 @@ $write = $user->admin || in_array($user->acceso, ['w']);
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button data-id="" type="button" class="btn btn-primary" id="btn-guardar"><i class="ing-guardar ing"></i> Guardar</button> <button data-id="" type="button" class="btn btn-primary" id="btn-guardar"><i
class="ing-guardar ing"></i> Guardar</button>
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">Cancelar</button> <button type="button" class="btn btn-outline-primary" data-dismiss="modal">Cancelar</button>
</div> </div>
</div> </div>
@@ -980,7 +990,6 @@ require_once("import/html_footer.php");
} }
} }
function guardarHorario() { function guardarHorario() {
let goBack = false; let goBack = false;
const data = { const data = {
@@ -1111,7 +1120,6 @@ require_once("import/html_footer.php");
// buscarGrupo(); // buscarGrupo();
} }
function moveHorario(id, día, hora) { function moveHorario(id, día, hora) {
const formData = new FormData(); const formData = new FormData();
@@ -1136,7 +1144,6 @@ require_once("import/html_footer.php");
}); });
} }
function extractFromModal() { function extractFromModal() {
// remove all is-valid and is-invalid // remove all is-valid and is-invalid
document.querySelectorAll(".is-valid, .is-invalid").forEach(el => document.querySelectorAll(".is-valid, .is-invalid").forEach(el =>
@@ -1213,7 +1220,6 @@ require_once("import/html_footer.php");
return formData; return formData;
} }
function resetFormModal() { function resetFormModal() {
const modalNuevo = document.querySelector("#modal-editar"); const modalNuevo = document.querySelector("#modal-editar");
modalNuevo.querySelectorAll("input").forEach(input => input.value = ""); modalNuevo.querySelectorAll("input").forEach(input => input.value = "");
@@ -1231,7 +1237,6 @@ require_once("import/html_footer.php");
// remove bg-info // remove bg-info
modalNuevo.querySelectorAll(".bg-info").forEach(el => el.classList.remove("bg-info")); modalNuevo.querySelectorAll(".bg-info").forEach(el => el.classList.remove("bg-info"));
} }
function insertHorario(horario) { function insertHorario(horario) {
const fetchOptions = { const fetchOptions = {
method: "POST", method: "POST",
@@ -1256,7 +1261,6 @@ require_once("import/html_footer.php");
triggerMessage(err, "Error"); triggerMessage(err, "Error");
}); });
} }
// initial state // initial state
{ {
// fill the table with empty cells // fill the table with empty cells

View File

@@ -1054,3 +1054,21 @@ footer .tab-pane p {
align-items: center; align-items: center;
}*/ }*/
} }
.movie {
transition: all 0.1s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.movie:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
font-size: 1.1em;
}
.movie:active {
transform: scale(1.1);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
font-size: 1.2em;
font-weight: bold;
}

View File

@@ -5,3 +5,95 @@
.azul { .azul {
color: #00a6CE; color: #00a6CE;
} }
/* for sm */
.custom-switch.custom-switch-sm .custom-control-label {
padding-left: 1rem;
padding-bottom: 1rem;
}
.custom-switch.custom-switch-sm .custom-control-label::before {
height: 1rem;
width: calc(1rem + 0.75rem);
border-radius: 2rem;
}
.custom-switch.custom-switch-sm .custom-control-label::after {
width: calc(1rem - 4px);
height: calc(1rem - 4px);
border-radius: calc(1rem - (1rem / 2));
}
.custom-switch.custom-switch-sm .custom-control-input:checked ~ .custom-control-label::after {
transform: translateX(calc(1rem - 0.25rem));
}
/* for md */
.custom-switch.custom-switch-md .custom-control-label {
padding-left: 2rem;
padding-bottom: 1.5rem;
}
.custom-switch.custom-switch-md .custom-control-label::before {
height: 1.5rem;
width: calc(2rem + 0.75rem);
border-radius: 3rem;
}
.custom-switch.custom-switch-md .custom-control-label::after {
width: calc(1.5rem - 4px);
height: calc(1.5rem - 4px);
border-radius: calc(2rem - (1.5rem / 2));
}
.custom-switch.custom-switch-md .custom-control-input:checked ~ .custom-control-label::after {
transform: translateX(calc(1.5rem - 0.25rem));
}
/* for lg */
.custom-switch.custom-switch-lg .custom-control-label {
padding-left: 3rem;
padding-bottom: 2rem;
}
.custom-switch.custom-switch-lg .custom-control-label::before {
height: 2rem;
width: calc(3rem + 0.75rem);
border-radius: 4rem;
}
.custom-switch.custom-switch-lg .custom-control-label::after {
width: calc(2rem - 4px);
height: calc(2rem - 4px);
border-radius: calc(3rem - (2rem / 2));
}
.custom-switch.custom-switch-lg .custom-control-input:checked ~ .custom-control-label::after {
transform: translateX(calc(2rem - 0.25rem));
}
/* for xl */
.custom-switch.custom-switch-xl .custom-control-label {
padding-left: 4rem;
padding-bottom: 2.5rem;
}
.custom-switch.custom-switch-xl .custom-control-label::before {
height: 2.5rem;
width: calc(4rem + 0.75rem);
border-radius: 5rem;
}
.custom-switch.custom-switch-xl .custom-control-label::after {
width: calc(2.5rem - 4px);
height: calc(2.5rem - 4px);
border-radius: calc(4rem - (2.5rem / 2));
}
.custom-switch.custom-switch-xl .custom-control-input:checked ~ .custom-control-label::after {
transform: translateX(calc(2.5rem - 0.25rem));
}

235
demo.html Normal file
View File

@@ -0,0 +1,235 @@
<link rel="stylesheet" href="css/indivisa.css">
<p class="container">
<i class="ing-fb1"></i> ing-fb1
</p>
<p class="container">
<i class="ing-fb2"></i> ing-fb2
</p>
<p class="container">
<i class="ing-tw1"></i> ing-tw1
</p>
<p class="container">
<i class="ing-tw2"></i> ing-tw2
</p>
<p class="container">
<i class="ing-in1"></i> ing-in1
</p>
<p class="container">
<i class="ing-in2"></i> ing-in2
</p>
<p class="container">
<i class="ing-instra1"></i> ing-instra1
</p>
<p class="container">
<i class="ing-instra2"></i> ing-instra2
</p>
<p class="container">
<i class="ing-youtube"></i> ing-youtube
</p>
<p class="container">
<i class="ing-telefono"></i> ing-telefono
</p>
<p class="container">
<i class="ing-mail"></i> ing-mail
</p>
<p class="container">
<i class="ing-link"></i> ing-link
</p>
<p class="container">
<i class="ing-ubicacion"></i> ing-ubicacion
</p>
<p class="container">
<i class="ing-puntos"></i> ing-puntos
</p>
<p class="container">
<i class="ing-usuario"></i> ing-usuario
</p>
<p class="container">
<i class="ing-pass"></i> ing-pass
</p>
<p class="container">
<i class="ing-menu"></i> ing-menu
</p>
<p class="container">
<i class="ing-salir"></i> ing-salir
</p>
<p class="container">
<i class="ing-flecha"></i> ing-flecha
</p>
<p class="container">
<i class="ing-cambiar"></i> ing-cambiar
</p>
<p class="container">
<i class="ing-caret"></i> ing-caret
</p>
<p class="container">
<i class="ing-aceptar"></i> ing-aceptar
</p>
<p class="container">
<i class="ing-cancelar"></i> ing-cancelar
</p>
<p class="container">
<i class="ing-mas"></i> ing-mas
</p>
<p class="container">
<i class="ing-menos"></i> ing-menos
</p>
<p class="container">
<i class="ing-editar"></i> ing-editar
</p>
<p class="container">
<i class="ing-buscar"></i> ing-buscar
</p>
<p class="container">
<i class="ing-ojo"></i> ing-ojo
</p>
<p class="container">
<i class="ing-borrar"></i> ing-borrar
</p>
<p class="container">
<i class="ing-basura"></i> ing-basura
</p>
<p class="container">
<i class="ing-camara"></i> ing-camara
</p>
<p class="container">
<i class="ing-importante"></i> ing-importante
</p>
<p class="container">
<i class="ing-bullet"></i> ing-bullet
</p>
<p class="container">
<i class="ing-home"></i> ing-home
</p>
<p class="container">
<i class="ing-formacion"></i> ing-formacion
</p>
<p class="container">
<i class="ing-empleo"></i> ing-empleo
</p>
<p class="container">
<i class="ing-insignia1"></i> ing-insignia1
</p>
<p class="container">
<i class="ing-insignia2"></i> ing-insignia2
</p>
<p class="container">
<i class="ing-insignia3"></i> ing-insignia3
</p>
<p class="container">
<i class="ing-insignia4"></i> ing-insignia4
</p>
<p class="container">
<i class="ing-eventos"></i> ing-eventos
</p>
<p class="container">
<i class="ing-reporte"></i> ing-reporte
</p>
<p class="container">
<i class="ing-catalogo"></i> ing-catalogo
</p>
<p class="container">
<i class="ing-evalua-cartel"></i> ing-evalua-cartel
</p>
<p class="container">
<i class="ing-revision-cartel"></i> ing-revision-cartel
</p>
<p class="container">
<i class="ing-reporte-resultados"></i> ing-reporte-resultados
</p>
<p class="container">
<i class="ing-mi-cartel"></i> ing-mi-cartel
</p>
<p class="container">
<i class="ing-galeria1"></i> ing-galeria1
</p>
<p class="container">
<i class="ing-galeria2"></i> ing-galeria2
</p>
<p class="container">
<i class="ing-iniciar-sesion"></i> ing-iniciar-sesion
</p>
<p class="container">
<i class="ing-finalistas"></i> ing-finalistas
</p>
<p class="container">
<i class="ing-comite"></i> ing-comite
</p>
<p class="container">
<i class="ing-administrador"></i> ing-administrador
</p>
<p class="container">
<i class="ing-estrella1"></i> ing-estrella1
</p>
<p class="container">
<i class="ing-estrella2"></i> ing-estrella2
</p>
<p class="container">
<i class="ing-carga-archivo"></i> ing-carga-archivo
</p>
<p class="container">
<i class="ing-carga-multiple"></i> ing-carga-multiple
</p>
<p class="container">
<i class="ing-descarga"></i> ing-descarga
</p>
<p class="container">
<i class="ing-autorizar"></i> ing-autorizar
</p>
<p class="container">
<i class="ing-negar"></i> ing-negar
</p>
<p class="container">
<i class="ing-no-cargado"></i> ing-no-cargado
</p>
<p class="container">
<i class="ing-alumnos"></i> ing-alumnos
</p>
<p class="container">
<i class="ing-cardex"></i> ing-cardex
</p>
<p class="container">
<i class="ing-configuracion"></i> ing-configuracion
</p>
<p class="container">
<i class="ing-listado-menus"></i> ing-listado-menus
</p>
<p class="container">
<i class="ing-mi-cuenta"></i> ing-mi-cuenta
</p>
<p class="container">
<i class="ing-ver"></i> ing-ver
</p>
<p class="container">
<i class="ing-grafica"></i> ing-grafica
</p>
<p class="container">
<i class="ing-clic"></i> ing-clic
</p>
<p class="container">
<i class="ing-guardar"></i> ing-guardar
</p>
<p class="container">
<i class="ing-regresar"></i> ing-regresar
</p>
<p class="container">
<i class="ing-cuadrado"></i> ing-cuadrado
</p>
<p class="container">
<i class="ing-imprimir"></i> ing-imprimir
</p>
<p class="container">
<i class="ing-importante2"></i> ing-importante2
</p>
<p class="container">
<i class="ing-copiar"></i> ing-copiar
</p>
<p class="container">
<i class="ing-reloj"></i> ing-reloj
</p>
<p class="container">
<i class="ing-retardo"></i> ing-retardo
</p>
<p class="container">
<i class="ing-justificar"></i> ing-justificar
</p>

View File

@@ -28,7 +28,11 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<script src="js/bootstrap/bootstrap.min.js" defer></script> <script src="js/bootstrap/bootstrap.min.js" defer></script>
<script src="js/messages.js" defer></script> <script src="js/messages.js" defer></script>
<script src="js/horarios_profesor.js" defer></script> <script>
const write = <?= $write ? 'true' : 'false' ?>;
</script>
<script src="js/moment.js" defer></script>
<script src="js/horario_profesor.js" defer></script>
</head> </head>
<!-- --> <!-- -->
@@ -47,29 +51,40 @@ $write = $user->admin || in_array($user->acceso, ['w']);
<div class="form-group"> <div class="form-group">
<div class="form-box"> <div class="form-box">
<input type="hidden" name="periodo" value="<?= $user->periodo ?>" /> <input type="hidden" name="periodo" value="<?= $user->periodo ?>" />
<div class="form-box">
<div class="form-group row"> <div class="form-group row">
<label for="clave" class="col-4 col-form-label">Carrera</label> <label for="clave_profesor" class="col-4 col-form-label">Profesor</label>
<div class="col-6"> <div class="col-6">
<input type="text" class="form-control" id="clave" name="clave" placeholder="Clave del profesor (opcional)" value="<?= $clave ?? '' ?>" pattern="(do)?[0-9]{3,6}" title="La clave debe tener 8 caracteres, los primeros 2 deben ser letras y los últimos 6 números" minlength="3" maxlength="8"> <input list="lista_profesores" name="clave_profesor" id="clave_profesor" class="form-control" placeholder="Profesor" required="required">
<div class="valid-feedback">
Profesor encontrado
</div> </div>
<div class="invalid-feedback">
Profesor no encontrado
</div> </div>
<div class="form-group row"> <datalist id="lista_profesores">
<label for="profesor" class="col-4 col-form-label">Nombre</label> <?php
<div class="col-6 "> $profesores = $db->where('facultad_id', $user->facultad['facultad_id'])->get("fs_profesor");
<input type="text" class="form-control" id="profesor" name="nombre" placeholder="Nombre del profesor (opcional)"> foreach ($profesores as $profesor) {
extract($profesor);
?>
<option data-grado="<?= $grado ?>" data-clave="<?= $clave ?>" data-profesor="<?= $profesor ?>" data-id="<?= $id; ?>" value="<?= "$clave | $grado $profesor" ?>"></option>
<?php
}
?>
</datalist>
<ul class="list-group" id="profesores"></ul>
<input type="hidden" id="periodo_id" name="periodo_id" value="<?= $user->periodo ?>">
<input type="hidden" id="profesor_id" name="profesor_id" value="">
</div> </div>
</div> </div>
</div>
<!-- ICO-BUSCAR FILTRAR & ICO-BORRAR LIMPIAR --> <!-- ICO-BUSCAR FILTRAR & ICO-BORRAR LIMPIAR -->
<div class="form-group row justify-content-center"> <div class="form-group row justify-content-center">
<button class="btn btn-outline-primary mr-2"> <button class="btn btn-outline-primary mr-2">
<span class="ing-buscar icono"></span> <span class="ing-buscar icono"></span>
Buscar horario Buscar horario
</button> </button>
<button type="button" class="btn btn-outline-danger" onclick=""> <button type="button" class="btn btn-outline-danger" onclick="location.reload()">
<span class="ing-borrar icono"></span> <span class="ing-borrar icono"></span>
Limpiar Limpiar
</button> </button>
@@ -99,7 +114,7 @@ $write = $user->admin || in_array($user->acceso, ['w']);
</div> </div>
<!-- Table responsive --> <!-- Table responsive -->
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-sm table-responsive-sm" id="table-horario"> <table class="table table-bordered table-sm table-responsive-md" id="table-horario">
<thead class="thead-dark"> <thead class="thead-dark">
<tr id="headers"> <tr id="headers">
<th scope="col" class="text-center">Hora</th> <th scope="col" class="text-center">Hora</th>

View File

@@ -3,3 +3,4 @@
<link rel="stylesheet" href="css/indivisa.css" type="text/css"> <link rel="stylesheet" href="css/indivisa.css" type="text/css">
<link rel="stylesheet" href="css/sgi.css?rand=<?php echo rand(); ?>" type="text/css"> <link rel="stylesheet" href="css/sgi.css?rand=<?php echo rand(); ?>" type="text/css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/jquery-ui.css">

View File

@@ -6,8 +6,12 @@ require_once 'class/c_login.php';
$ruta = "../"; $ruta = "../";
require_once 'include/bd_pdo.php'; require_once 'include/bd_pdo.php';
if (!isset($_SESSION['user'])) if (!isset($_SESSION['user'])) {
die(header('Location: index.php')); if (isset($redirect))
$_SESSION['ruta'] = $redirect;
header('Location: index.php');
}
$user = unserialize($_SESSION['user']); $user = unserialize($_SESSION['user']);
@@ -24,7 +28,8 @@ function html_header($title, $header = null)
?> ?>
<aside id="sidebar" class="bg-light defaultShadow d-flex flex-column p-4"> <aside id="sidebar" class="bg-light defaultShadow d-flex flex-column p-4">
<div class="d-flex align-items-center mb-5"> <div class="d-flex align-items-center mb-5">
<div class="logotipo"><a href="https://lasalle.mx/" target="_blank"><img src="imagenes/logo_lasalle.png"></a></div> <div class="logotipo"><a href="https://lasalle.mx/" target="_blank"><img src="imagenes/logo_lasalle.png"></a>
</div>
<div class="flex-grow-1 d-flex justify-content-end"> <div class="flex-grow-1 d-flex justify-content-end">
<nav class="navbar navbar-expand d-none d-flex"> <nav class="navbar navbar-expand d-none d-flex">
<ul class="navbar-nav"> <ul class="navbar-nav">
@@ -33,10 +38,12 @@ function html_header($title, $header = null)
</nav> </nav>
<div class="d-flex mainMenu justify-content-center align-items-center"> <div class="d-flex mainMenu justify-content-center align-items-center">
<div class="max-h iconSesion"> <div class="max-h iconSesion">
<a href="salir.php" class="iconOff max-h pl-3 d-flex justify-content-start align-items-center"><i class="ing-salir"></i></a> <a href="salir.php" class="iconOff max-h pl-3 d-flex justify-content-start align-items-center"><i
class="ing-salir"></i></a>
</div> </div>
<div class="max-h"> <div class="max-h">
<div class="bg-primary rounded-circle pointer max-h max-w d-flex justify-content-center align-items-center" id="dismiss"> <div class="bg-primary rounded-circle pointer max-h max-w d-flex justify-content-center align-items-center"
id="dismiss">
<span class="text-white iconMenuSidebar ing-cancelar"></span> <span class="text-white iconMenuSidebar ing-cancelar"></span>
</div> </div>
</div> </div>
@@ -62,9 +69,11 @@ function html_header($title, $header = null)
foreach ($grupos as $grupo) { foreach ($grupos as $grupo) {
?> ?>
<p class="mb-0 mt-3"> <p class="mb-0 mt-3">
<a class="d-block side-menu collapsed" data-toggle="collapse" href="#menu_<?= $cont ?>" role="button" aria-expanded="flase"> <a class="d-block side-menu collapsed" data-toggle="collapse" href="#menu_<?= $cont ?>" role="button"
aria-expanded="false">
<i class="ing-caret ing-fw mr-2"></i> <i class="ing-caret ing-fw mr-2"></i>
<span class="<?= $grupo['grupo_icon'] ?>"></span> <?= ucfirst($grupo['grupo_nombre']) ?> <span class="<?= $grupo['grupo_icon'] ?>"></span>
<?= ucfirst($grupo['grupo_nombre']) ?>
</a> </a>
</p> </p>
<div id="menu_<?= $cont ?>" class="collapse" data-parent="#accordionMenu" style> <div id="menu_<?= $cont ?>" class="collapse" data-parent="#accordionMenu" style>
@@ -96,37 +105,54 @@ function html_header($title, $header = null)
<div class="overlay"></div> <div class="overlay"></div>
<header class="sticky-top bg-white"> <header class="sticky-top bg-white">
<div class="container marco menu d-flex align-items-center"> <div class="container marco menu d-flex align-items-center">
<div class="logotipo"><a href="https://lasalle.mx/" target="_blank"><img src="imagenes/logo_lasalle.png"></a></div> <div class="logotipo">
<a href="https://lasalle.mx/" target="_blank">
<img src="imagenes/logo_lasalle.png">
</a>
</div>
<div class="flex-grow-1 d-flex justify-content-end"> <div class="flex-grow-1 d-flex justify-content-end">
<nav class="navbar navbar-expand d-none d-flex"> <nav class="navbar navbar-expand-lg d-flex">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav">
<!-- Add your navigation items here -->
</ul> </ul>
</div>
</nav> </nav>
<div class="d-flex mainMenu justify-content-center align-items-center"> <div class="d-flex mainMenu justify-content-center align-items-center">
<div class="max-h iconSesion"> <div class="max-h iconSesion">
<a href="salir.php" class="iconOff max-h pl-3 d-flex justify-content-start align-items-center"><i class="ing-salir"></i></a> <a href="salir.php" class="iconOff max-h pl-3 d-flex justify-content-start align-items-center">
<i class="ing-salir"></i>
</a>
</div> </div>
<div class="max-h"> <div class="max-h">
<span id="sidebarCollapse" style="font-size: 44px;" class="ing-menu bg-white rounded-circle pointer max-w d-flex justify-content-center align-items-center"></span> <span id="sidebarCollapse" style="font-size: 44px;"
class="ing-menu bg-white rounded-circle pointer max-w d-flex justify-content-center align-items-center"></span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</header> </header>
<div class="row bg-info mx-0 barra-gris d-flex flex-column"> <div class="row bg-info mx-0 barra-gris d-flex flex-column">
<?php <?php
if ($header != null) { if ($header != null) {
?> ?>
<div class="marco"> <div class="marco">
<div class="col-sm-12"> <div class="col-sm-12">
<h2 class="text-muted"><?= $header; ?> <h2 class="text-muted">
<?= $header; ?>
</div> </div>
</div> </div>
<?php } ?> <?php } ?>
<div class="marco"> <div class="marco">
<div class="col-sm-12 py-3"> <div class="col-sm-12 py-3">
<h2 class="text-uppercase"><?= $title; ?></h2> <h2 class="text-uppercase">
<?= $title; ?>
</h2>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,10 +5,14 @@
<div class="col-12"> <div class="col-12">
<?php <?php
$target = $target ?? strtok($_SERVER["REQUEST_URI"], '?'); $target = $target ?? strtok($_SERVER["REQUEST_URI"], '?');
$periodos = queryAll("SELECT * FROM FS_PERIODO WHERE FACULTAD_ID = COALESCE(:fac, FACULTAD_ID) ORDER BY INICIO DESC", [":fac" => $user->admin ? null : $user->facultad['facultad_id']]); $niveles = array_map(
fn($nivel) => array_merge(
$nivel,
['periodos' => $db->where('nivel_id', $nivel['nivel_id'])->get('periodo_view')]
), $db->get("nivel")
);
// collect facultad_id's with facultad from $periodos // collect facultad_id's with facultad from $periodos
if ($user->admin)
$facultades = array_unique(array_column($periodos, 'facultad', 'facultad_id'));
?> ?>
<input type="hidden" name="target" value="<?= $target ?>"> <input type="hidden" name="target" value="<?= $target ?>">
<div class="form-box"> <div class="form-box">
@@ -19,27 +23,26 @@
<div class="datalist-input">Selecciona un periodo</div> <div class="datalist-input">Selecciona un periodo</div>
<span class="ing-buscar icono"></span> <span class="ing-buscar icono"></span>
<ul style="display:none"> <ul style="display:none">
<?php if (!$user->admin) foreach ($periodos as $periodo) { ?>
<li data-id="<?= $periodo['id'] ?>">
<?= "{$periodo['nivel']} - {$periodo['periodo']} ({$periodo['estado']})" ?>
</li>
<?php }
else {
foreach ($facultades as $facultad_id => $facultad) {
?>
<li class="facultad not-selectable" data-id="<?= $facultad_id ?>">
<?= $facultad ?>
</li>
<?php <?php
foreach (array_filter($periodos, fn ($p) => $p['facultad_id'] == $facultad_id) as $periodo) { foreach ($niveles as $nivel) {
?> ?>
<li data-id="<?= $periodo['id'] ?>"> <li data-id="<?= $nivel['nivel_id'] ?>" class="not-selectable disable">
<?= "{$periodo['nivel']} - {$periodo['periodo']} ({$periodo['estado']})" ?> <?= $nivel['nivel_nombre'] ?>
</li> </li>
<?php } <?
} $periodos_rs = $db->query(
} ?> 'SELECT * FROM fs_periodo(NULL, :nivel, 4)',
[':nivel' => $nivel['nivel_id']]
);
foreach ($periodos_rs as $per) {
?>
<li data-id="<?= $per['periodo_id'] ?>" <?php if ($user->periodo == $per["periodo_id"]) {
echo 'class="selected"';
} ?>>
<?= $per['periodo_nombre'] ?>
</li>
<?php } ?>
<?php } ?>
</ul> </ul>
<input type="hidden" id="periodo" name="id" value=""> <input type="hidden" id="periodo" name="id" value="">
</div> </div>

View File

@@ -3,6 +3,7 @@ require_once 'class/c_login.php';
$error = isset($_SESSION["error"]); $error = isset($_SESSION["error"]);
unset($_SESSION["error"]); unset($_SESSION["error"]);
if ($error) $errorDesc = "El usuario y/o contraseña son incorrectos."; if ($error) $errorDesc = "El usuario y/o contraseña son incorrectos.";
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es" prefix="og: http://ogp.me/ns#"> <html lang="es" prefix="og: http://ogp.me/ns#">

160
js/auditoría.js Normal file
View File

@@ -0,0 +1,160 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module';
const store = reactive({
loading: false,
current: {
comentario: '',
clase_vista: null,
empty: '',
},
facultades: {
data: [],
async fetch() {
this.data = [];
const res = await fetch('action/action_facultad.php');
this.data = await res.json();
},
},
filters: {
facultad_id: null,
fecha: null,
fecha_inicio: null,
fecha_fin: null,
profesor: null,
estados: [],
switchFecha: false,
switchFechas() {
$(function () {
store.filters.fecha_inicio = store.filters.fecha_fin = store.filters.fecha = null;
$("#fecha, #fecha_inicio, #fecha_fin").datepicker({
minDate: -3,
maxDate: new Date(),
dateFormat: "yy-mm-dd",
showAnim: "slide",
});
const fecha = $("#fecha"), inicio = $("#fecha_inicio"), fin = $("#fecha_fin");
inicio.on("change", function () {
store.filters.fecha_inicio = inicio.val();
fin.datepicker("option", "minDate", inicio.val());
});
fin.on("change", function () {
store.filters.fecha_fin = fin.val();
inicio.datepicker("option", "maxDate", fin.val());
});
fecha.on("change", function () {
store.filters.fecha = fecha.val();
});
});
}
},
estados: {
data: [],
async fetch() {
this.data = [];
const res = await fetch('action/action_estado_supervisor.php');
this.data = await res.json();
},
getEstado(id) {
return this.data.find((estado) => estado.estado_supervisor_id === id);
},
printEstados() {
if (store.filters.estados.length > 0)
document.querySelector('#estados').innerHTML = store.filters.estados.map((estado) => `<span class="mx-2 badge badge-${store.estados.getEstado(estado).estado_color}">
<i class="${store.estados.getEstado(estado).estado_icon}"></i> ${store.estados.getEstado(estado).nombre}
</span>`).join('');
else
document.querySelector('#estados').innerHTML = `Todos los registros`;
}
},
toggle(arr, element) {
const newArray = arr.includes(element) ? arr.filter((item) => item !== element) : [...arr, element];
// if all are selected, then unselect all
if (newArray.length === this.estados.data.length)
return [];
return newArray;
},
});
createApp({
store,
get clase_vista() {
return store.current.clase_vista;
},
registros: {
data: [],
async fetch() {
this.loading = true;
this.data = [];
const res = await fetch('action/action_auditoria.php');
this.data = await res.json();
this.loading = false;
},
invertir() {
this.data = this.data.reverse();
},
mostrarComentario(registro_id) {
const registro = this.data.find((registro) => registro.registro_id === registro_id);
store.current.comentario = registro.comentario;
$('#ver-comentario').modal('show');
},
get relevant() {
/*
facultad_id: null,
fecha: null,
fecha_inicio: null,
fecha_fin: null,
profesor: null,
asistencia: null,
estado_id: null,
if one of the filters is null, then it is not relevant
*/
const filters = Object.keys(store.filters).filter((filtro) => store.filters[filtro] || store.filters[filtro]?.length > 0);
return this.data.filter((registro) => {
return filters.every((filtro) => {
switch (filtro) {
case 'fecha':
return registro.registro_fecha_ideal === store.filters[filtro];
case 'fecha_inicio':
return registro.registro_fecha_ideal >= store.filters[filtro];
case 'fecha_fin':
return registro.registro_fecha_ideal <= store.filters[filtro];
case 'profesor':
const textoFiltro = store.filters[filtro].toLowerCase();
if (/^\([^)]+\)\s[\s\S]+$/.test(textoFiltro)) {
const clave = registro.profesor_clave.toLowerCase();
const filtroClave = textoFiltro.match(/\((.*?)\)/)?.[1];
console.log(clave, filtroClave);
return clave.includes(filtroClave);
}
else {
const nombre = registro.profesor_nombre.toLowerCase();
return nombre.includes(textoFiltro);
}
case 'facultad_id':
return registro.facultad_id === store.filters[filtro];
case 'estados':
if (store.filters[filtro].length === 0)
return true;
return store.filters[filtro].includes(registro.estado_supervisor_id);
default:
return true;
}
});
});
},
},
get profesores() {
return this.registros.data.map((registro) => ({
profesor_id: registro.profesor_id,
profesor_nombre: registro.profesor_nombre,
profesor_correo: registro.profesor_correo,
profesor_clave: registro.profesor_clave,
profesor_grado: registro.profesor_grado,
})).sort((a, b) => a.profesor_nombre.localeCompare(b.profesor_nombre));
},
async mounted() {
await this.registros.fetch();
await store.facultades.fetch();
await store.estados.fetch();
store.filters.switchFechas();
}
}).mount('#app');

120
js/client.js Normal file
View File

@@ -0,0 +1,120 @@
// @ts-ignore Import module
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module';
const webServices = {
getPeriodosV1: async () => {
try {
const response = await fetch('periodos.v1.php');
return await response.json();
}
catch (error) {
console.log(error);
return [];
}
},
getPeriodosV2: async () => {
try {
const response = await fetch('periodos.v2.php');
return await response.json();
}
catch (error) {
console.log(error);
return [];
}
}
};
const store = reactive({
periodosV1: [],
periodosV2: [],
errors: [],
fechas(idPeriodo) {
const periodo = this.periodosV2.find((periodo) => periodo.IdPeriodo === idPeriodo);
return {
inicio: periodo ? periodo.FechaInicio : '',
fin: periodo ? periodo.FechaFin : ''
};
},
periodov1(idPeriodo) {
return this.periodosV1.find((periodo) => periodo.IdPeriodo === idPeriodo);
},
periodov2(idPeriodo) {
return this.periodosV2.filter((periodo) => periodo.IdPeriodo === idPeriodo);
},
async addPeriodo(periodo) {
try {
const result = await fetch('backend/periodos.php', {
method: 'POST',
body: JSON.stringify({
...periodo,
...this.fechas(periodo.IdPeriodo)
}),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
this.periodosV1 = this.periodosV1.map((periodoV1) => {
if (periodoV1.IdPeriodo === periodo.IdPeriodo) {
periodoV1.in_db = true;
}
return periodoV1;
});
return result;
}
else {
this.errors.push(result.message);
}
}
catch (error) {
this.errors.push(error);
}
},
async addCarreras(idPeriodo) {
try {
const periodoV1 = this.periodov1(idPeriodo);
const periodoV2 = this.periodov2(idPeriodo);
const data = periodoV2.map(({ ClaveCarrera, NombreCarrera }) => ({
ClaveCarrera: ClaveCarrera,
NombreCarrera: NombreCarrera,
IdNivel: periodoV1.IdNivel,
}));
const result = await fetch('backend/carreras.php', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
await webServices.getPeriodosV1().then((periodosV1) => {
this.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
this.periodosV2 = periodosV2;
});
}
}
catch (error) {
this.errors.push(error);
}
}
});
createApp({
store,
info(IdPeriodo) {
const periodo = store.periodosV2.find((periodo) => periodo.IdPeriodo === IdPeriodo &&
periodo.FechaInicio != '' && periodo.FechaFin != '');
return periodo;
},
complete(IdPeriodo) {
const info = this.info(IdPeriodo);
return info !== undefined;
},
mounted: async () => {
await webServices.getPeriodosV1().then((periodosV1) => {
store.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
store.periodosV2 = periodosV2;
});
}
}).mount();

View File

@@ -70,7 +70,7 @@ $(function () {
$.each($(".datalist").find('ul li:not(.not-selectable)'), function () { $.each($(".datalist").find('ul li:not(.not-selectable)'), function () {
if ($(this).hasClass("selected")) { if ($(this).hasClass("selected")) {
var elementRoot = $(this).parents('.datalist'); var elementRoot = $(this).parents('.datalist');
elementRoot.find('.datalist-input').text($(this).html().replace(/[\t\n]+/g, ' ').trim()); elementRoot.find('.datalist-input').html($(this).html().replace(/[\t\n]+/g, ' ').trim());
var cid = $(this).data('id'); var cid = $(this).data('id');
elementRoot.find("input[type=hidden]").val(cid); elementRoot.find("input[type=hidden]").val(cid);
} }
@@ -82,7 +82,7 @@ $(function () {
$(document).on('click', '.datalist-select > ul li:not(.not-selectable)', function () { $(document).on('click', '.datalist-select > ul li:not(.not-selectable)', function () {
var elementRoot = $(this).parents('.datalist'); var elementRoot = $(this).parents('.datalist');
elementRoot.find('.datalist-input').text($(this).html().replace(/[\t\n]+/g, ' ').trim()); elementRoot.find('.datalist-input').html($(this).html().replace(/[\t\n]+/g, ' ').trim());
// $(this).parent('ul').siblings('input[type="text"]').blur(); // $(this).parent('ul').siblings('input[type="text"]').blur();
ocultaList({ "data": { "padre": elementRoot } }); ocultaList({ "data": { "padre": elementRoot } });
var cid = $(this).data('id'); var cid = $(this).data('id');

0
js/declaration.js Normal file
View File

414
js/horario_profesor.js Normal file
View File

@@ -0,0 +1,414 @@
const compareHours = (hora1, hora2) => {
const [h1, m1] = hora1.split(":").map(Number);
const [h2, m2] = hora2.split(":").map(Number);
if (h1 !== h2) {
return h1 > h2 ? 1 : -1;
}
if (m1 !== m2) {
return m1 > m2 ? 1 : -1;
}
return 0;
};
let horarios = [];
const table = document.querySelector("table");
if (!(table instanceof HTMLTableElement)) {
triggerMessage("No se ha encontrado la tabla", "Error", "error");
throw new Error("No se ha encontrado la tabla");
}
[...Array(16).keys()].map(x => x + 7).forEach(hora => {
// add 7 rows for each hour
[0, 15, 30, 45].map((minute) => `${minute}`.padStart(2, '0')).forEach((minute) => {
const tr = document.createElement("tr");
tr.id = `hora-${hora}:${minute}`;
tr.classList.add(hora > 13 ? "tarde" : "mañana");
if (minute == "00") {
const th = document.createElement("th");
th.classList.add("text-center");
th.scope = "row";
th.rowSpan = 4;
th.innerText = `${hora}:00`;
th.style.verticalAlign = "middle";
tr.appendChild(th);
}
["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"].forEach(día => {
const td = document.createElement("td");
td.id = `hora-${hora}:${minute}-${día}`;
tr.appendChild(td);
});
const tbody = document.querySelector("tbody#horario");
if (!(tbody instanceof HTMLTableSectionElement)) {
throw new Error("No se ha encontrado el tbody");
}
tbody.appendChild(tr);
});
});
const empty_table = table.cloneNode(true);
document.querySelectorAll('.hidden').forEach((element) => {
element.style.display = "none";
});
// hide the table
table.style.display = "none";
function moveHorario(id, día, hora) {
const formData = new FormData();
formData.append("id", id);
formData.append("hora", hora);
formData.append("día", día);
fetch("action/action_horario_update.php", {
method: "POST",
body: formData
}).then(res => res.json()).then(response => {
if (response.status == "success") {
triggerMessage("Horario movido", "Éxito", "success");
}
else {
triggerMessage(response.message, "Error");
}
}).then(() => {
renderHorario();
}).catch(err => {
triggerMessage(err, "Error");
});
}
function renderHorario() {
if (horarios.length == 0) {
triggerMessage("Este profesor hay horarios para mostrar", "Error", "info");
table.style.display = "none";
document.querySelectorAll('.hidden').forEach((element) => element.style.display = "none");
return;
}
// show the table
table.style.display = "table";
document.querySelectorAll('.hidden').forEach((element) => element.style.display = "block");
// clear the table
table.innerHTML = empty_table.outerHTML;
function conflicts(horario1, horario2) {
const { hora: hora_inicio1, hora_final: hora_final1, dia: dia1 } = horario1;
const { hora: hora_inicio2, hora_final: hora_final2, dia: dia2 } = horario2;
if (dia1 !== dia2) {
return false;
}
const compareInicios = compareHours(hora_inicio1, hora_inicio2);
const compareFinales = compareHours(hora_final1, hora_final2);
if (compareInicios >= 0 && compareInicios <= compareFinales ||
compareFinales >= 0 && compareFinales <= -compareInicios) {
return true;
}
return false;
}
// remove the next 5 cells
function removeNextCells(horas, minutos, dia, cells = 5) {
for (let i = 1; i <= cells; i++) {
const minute = minutos + i * 15;
const nextMinute = (minute % 60).toString().padStart(2, "0");
const nextHour = horas + Math.floor(minute / 60);
const cellId = `hora-${nextHour}:${nextMinute}-${dia}`;
const cellElement = document.getElementById(cellId);
if (cellElement) {
cellElement.remove();
}
else {
console.log(`No se ha encontrado la celda ${cellId}`);
break;
}
}
}
function newBlock(horario, edit = false) {
function move(horario, cells = 5) {
const [horas, minutos] = horario.hora.split(":").map(Number);
const cell = document.getElementById(`hora-${horas}:${minutos.toString().padStart(2, "0")}-${horario.dia}`);
const { top, left } = cell.getBoundingClientRect();
const block = document.getElementById(`block-${horario.id}`);
block.style.top = `${top}px`;
block.style.left = `${left}px`;
removeNextCells(horas, minutos, horario.dia, cells);
}
const [horas, minutos] = horario.hora.split(":").map(x => parseInt(x));
const hora = `${horas}:${minutos.toString().padStart(2, "0")}`;
horario.hora = hora;
const cell = document.getElementById(`hora-${horario.hora}-${horario.dia}`);
if (!cell)
return;
cell.dataset.ids = `${horario.id}`;
const float_menu = edit ?
`<div class="menu-flotante p-2" style="opacity: .7;">
<a class="mx-2" href="#" data-toggle="modal" data-target="#modal-editar">
<i class="ing-editar ing"></i>
</a>
<a class="mx-2" href="#" data-toggle="modal" data-target="#modal-borrar">
<i class="ing-basura ing"></i>
</a>
</div>`
: '';
cell.innerHTML =
`<div style="overflow-y: auto; overflow-x: hidden; height: 100%;" id="block-${horario.id}" class="position-absolute w-100 h-100">
<small class="text-gray">${horario.hora}</small>
<b class="title">${horario.materia}</b> <br>
<br><span>Salón: </span>${horario.salon} <br>
<small class="my-2">
${horario.profesores.map((profesor) => ` <span class="ing ing-formacion mx-1"></span>${profesor.grado ?? ''} ${profesor.profesor}`).join("<br>")}
</small>
</div>
${float_menu}`;
cell.classList.add("bloque-clase", "position-relative");
cell.rowSpan = horario.bloques;
// draggable
cell.draggable = write;
if (horario.bloques > 0) {
removeNextCells(horas, minutos, horario.dia, horario.bloques - 1);
}
}
function newConflictBlock(horarios, edit = false) {
const first_horario = horarios[0];
const [horas, minutos] = first_horario.hora.split(":").map(x => parseInt(x));
const hora = `${horas}:${minutos.toString().padStart(2, "0")}`;
const ids = horarios.map(horario => horario.id);
const cell = document.getElementById(`hora-${hora}-${first_horario.dia}`);
if (cell == null) {
console.error(`Error: No se encontró la celda: hora-${hora}-${first_horario.dia}`);
return;
}
cell.dataset.ids = ids.join(",");
// replace the content of the cell
cell.innerHTML = `
<small class='text-danger'>
${hora}
</small>
<div class="d-flex justify-content-center align-items-center mt-4">
<div class="d-flex flex-column justify-content-center align-items-center">
<span class="ing ing-importante text-danger" style="font-size: 2rem;"></span>
<b class='text-danger'>
Empalme de ${ids.length} horarios
</b>
<hr>
<i class="text-danger">Ver horarios &#8230;</i>
</div>
</div>
`;
// Add classes and attributes
cell.classList.add("conflict", "bloque-clase");
cell.setAttribute("role", "button");
// Add event listener for the cell
cell.addEventListener("click", () => {
$("#modal-choose").modal("show");
const ids = cell.getAttribute("data-ids").split(",").map(x => parseInt(x));
const tbody = document.querySelector("#modal-choose tbody");
tbody.innerHTML = "";
horarios.filter(horario => ids.includes(horario.id)).sort((a, b) => compareHours(a.hora, b.hora)).forEach(horario => {
tbody.innerHTML += `
<tr data-ids="${horario.id}">
<td><small>${horario.hora.slice(0, -3)}-${horario.hora_final.slice(0, -3)}</small></td>
<td>${horario.materia}</td>
<td>
${horario.profesores.map(({ grado, profesor }) => `${grado ?? ''} ${profesor}`).join(", ")}
</td>
<td>${horario.salon}</td>
${edit ? `
<td class="text-center">
<button class="btn btn-sm btn-primary dismiss-editar" data-toggle="modal" data-target="#modal-editar">
<i class="ing-editar ing"></i>
</button>
</td>
<td class="text-center">
<button class="btn btn-sm btn-danger dismiss-editar" data-toggle="modal" data-target="#modal-borrar">
<i class="ing-basura ing"></i>
</button>
</td>
` : ""}
</tr>`;
});
document.querySelectorAll(".dismiss-editar").forEach(btn => {
btn.addEventListener("click", () => $("#modal-choose").modal("hide"));
});
});
function getDuration(hora_i, hora_f) {
const [horas_i, minutos_i] = hora_i.split(":").map(x => parseInt(x));
const [horas_f, minutos_f] = hora_f.split(":").map(x => parseInt(x));
const date_i = new Date(0, 0, 0, horas_i, minutos_i);
const date_f = new Date(0, 0, 0, horas_f, minutos_f);
const diffInMilliseconds = date_f.getTime() - date_i.getTime();
const diffInMinutes = diffInMilliseconds / (1000 * 60);
const diffIn15MinuteIntervals = diffInMinutes / 15;
return Math.floor(diffIn15MinuteIntervals);
}
const maxHoraFinal = horarios.reduce((max, horario) => {
const [horas, minutos] = horario.hora_final.split(":").map(x => parseInt(x));
const date = new Date(0, 0, 0, horas, minutos);
return date > max ? date : max;
}, new Date(0, 0, 0, 0, 0));
const horaFinalMax = new Date(0, 0, 0, maxHoraFinal.getHours(), maxHoraFinal.getMinutes());
const blocks = getDuration(first_horario.hora, `${horaFinalMax.getHours()}:${horaFinalMax.getMinutes()}`);
cell.setAttribute("rowSpan", blocks.toString());
removeNextCells(horas, minutos, first_horario.dia, blocks - 1);
}
const conflictBlocks = horarios.filter((horario, index, arrayHorario) => arrayHorario.filter((_, i) => i != index).some(horario2 => conflicts(horario, horario2)))
.sort((a, b) => compareHours(a.hora, b.hora));
const classes = horarios.filter(horario => !conflictBlocks.includes(horario));
const conflictBlocksPacked = []; // array of sets
conflictBlocks.forEach(horario => {
const setIndex = conflictBlocksPacked.findIndex(set => set.some(horario2 => conflicts(horario, horario2)));
if (setIndex === -1) {
conflictBlocksPacked.push([horario]);
}
else {
conflictBlocksPacked[setIndex].push(horario);
}
});
classes.forEach(horario => newBlock(horario, write));
conflictBlocksPacked.forEach(horarios => newConflictBlock(horarios, write));
// remove the elements that are not in the limits
let max_hour = Math.max(...horarios.map(horario => {
const lastMoment = moment(horario.hora, "HH:mm").add(horario.bloques * 15, "minutes");
const lastHour = moment(`${lastMoment.hours()}:00`, "HH:mm");
const hourInt = parseInt(lastMoment.format("HH"));
return lastMoment.isSame(lastHour) ? hourInt - 1 : hourInt;
}));
let min_hour = Math.min(...horarios.map(horario => parseInt(horario.hora.split(":")[0])));
document.querySelectorAll("tbody#horario tr").forEach(hora => {
const hora_id = parseInt(hora.id.split("-")[1].split(":")[0]);
(hora_id < min_hour || hora_id > max_hour) ? hora.remove() : null;
});
// if there is no sábado, remove the column
if (!horarios.some(horario => horario.dia == "sábado")) {
document.querySelectorAll("tbody#horario td").forEach(td => {
if (td.id.split("-")[2] == "sábado") {
td.remove();
}
});
// remove the header (the last)
document.querySelector("#headers").lastElementChild.remove();
}
// adjust width
const ths = document.querySelectorAll("tr#headers th");
ths.forEach((th, key) => th.style.width = (key == 0) ? "5%" : `${95 / (ths.length - 1)}%`);
// search item animation
const menúFlontantes = document.querySelectorAll(".menu-flotante");
menúFlontantes.forEach((element) => {
element.classList.add("d-none");
element.parentElement.addEventListener("mouseover", () => element.classList.remove("d-none"));
element.parentElement.addEventListener("mouseout", (e) => element.classList.add("d-none"));
});
// droppables
// forall the .bloque-elements add the event listeners for drag and drop
document.querySelectorAll(".bloque-clase").forEach(element => {
function dragStart() {
this.classList.add("dragging");
}
function dragEnd() {
this.classList.remove("dragging");
}
element.addEventListener("dragstart", dragStart);
element.addEventListener("dragend", dragEnd);
});
// forall the cells that are not .bloque-clase add the event listeners for drag and drop
document.querySelectorAll("td:not(.bloque-clase)").forEach(element => {
function dragOver(e) {
e.preventDefault();
this.classList.add("dragging-over");
}
function dragLeave() {
this.classList.remove("dragging-over");
}
function drop() {
this.classList.remove("dragging-over");
const dragging = document.querySelector(".dragging");
const id = dragging.getAttribute("data-ids");
const hora = this.id.split("-")[1];
const días = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"];
let día = this.id.split("-")[2];
día = días.indexOf(día) + 1;
// rowspan
const bloques = parseInt(dragging.getAttribute("rowspan"));
const horaMoment = moment(hora, "HH:mm");
const horaFin = horaMoment.add(bloques * 15, "minutes");
const limit = moment('22:00', 'HH:mm');
if (horaFin.isAfter(limit)) {
triggerMessage("No se puede mover el bloque a esa hora", "Error");
// scroll to the top
window.scrollTo(0, 0);
return;
}
// get the horario
// remove the horario
const bloque = document.querySelector(`.bloque-clase[data-ids="${id}"]`);
// remove all children
while (bloque.firstChild) {
bloque.removeChild(bloque.firstChild);
}
// prepend a loading child
const loading = `<div class="spinner-border" role="status" style="width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>`;
bloque.insertAdjacentHTML("afterbegin", loading);
// add style vertical-align: middle
bloque.style.verticalAlign = "middle";
bloque.classList.add("text-center");
// remove draggable
bloque.removeAttribute("draggable");
moveHorario(id, día, hora);
}
element.addEventListener("dragover", dragOver);
element.addEventListener("dragleave", dragLeave);
element.addEventListener("drop", drop);
});
}
const form = document.getElementById('form');
if (!(form instanceof HTMLFormElement)) {
triggerMessage('No se ha encontrado el formulario', 'Error', 'danger');
throw new Error("No se ha encontrado el formulario");
}
form.querySelector('#clave_profesor').addEventListener('input', function (e) {
const input = form.querySelector('#clave_profesor');
const option = form.querySelector(`option[value="${input.value}"]`);
if (input.value == "") {
input.classList.remove("is-invalid", "is-valid");
return;
}
if (!option) {
input.classList.remove("is-valid");
input.classList.add("is-invalid");
}
else {
const profesor_id = form.querySelector('#profesor_id');
profesor_id.value = option.dataset.id;
input.classList.remove("is-invalid");
input.classList.add("is-valid");
}
});
form.addEventListener('submit', async function (e) {
e.preventDefault();
const input = form.querySelector('#clave_profesor');
if (input.classList.contains("is-invalid")) {
triggerMessage('El profesor no se encuentra registrado', 'Error', 'danger');
return;
}
const formData = new FormData(form);
try {
const buttons = document.querySelectorAll("button");
buttons.forEach(button => {
button.disabled = true;
button.classList.add("disabled");
});
const response = await fetch('action/action_horario_profesor.php', {
method: 'POST',
body: formData,
});
const data = await response.json();
buttons.forEach(button => {
button.disabled = false;
button.classList.remove("disabled");
});
if (data.status == 'success') {
horarios = data.data;
renderHorario();
}
else {
triggerMessage(data.message, 'Error en la consulta', 'warning');
}
}
catch (error) {
triggerMessage('Fallo al consutar los datos ', 'Error', 'danger');
console.log(error);
}
});
const input = form.querySelector('#clave_profesor');
const option = form.querySelector(`option[value="${input.value}"]`);

View File

@@ -1,20 +0,0 @@
document.getElementById('form').addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(this);
try {
const response = await fetch('action/action_horario_profesor.php', {
method: 'POST',
body: formData,
});
const data = await response.json();
if (data.status == 'ok') {
}
else {
triggerMessage(data.message, 'Error en la consulta', 'warning');
}
} catch (error) {
triggerMessage('Fallo al consutar los datos ', 'Error', 'danger');
}
});

11
js/jquery-ui.touch-punch.min.js vendored Normal file
View File

@@ -0,0 +1,11 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
!function (a) { function f(a, b) { if (!(a.originalEvent.touches.length > 1)) { a.preventDefault(); var c = a.originalEvent.changedTouches[0], d = document.createEvent("MouseEvents"); d.initMouseEvent(b, !0, !0, window, 1, c.screenX, c.screenY, c.clientX, c.clientY, !1, !1, !1, !1, 0, null), a.target.dispatchEvent(d) } } if (a.support.touch = "ontouchend" in document, a.support.touch) { var e, b = a.ui.mouse.prototype, c = b._mouseInit, d = b._mouseDestroy; b._touchStart = function (a) { var b = this; !e && b._mouseCapture(a.originalEvent.changedTouches[0]) && (e = !0, b._touchMoved = !1, f(a, "mouseover"), f(a, "mousemove"), f(a, "mousedown")) }, b._touchMove = function (a) { e && (this._touchMoved = !0, f(a, "mousemove")) }, b._touchEnd = function (a) { e && (f(a, "mouseup"), f(a, "mouseout"), this._touchMoved || f(a, "click"), e = !1) }, b._mouseInit = function () { var b = this; b.element.bind({ touchstart: a.proxy(b, "_touchStart"), touchmove: a.proxy(b, "_touchMove"), touchend: a.proxy(b, "_touchEnd") }), c.call(b) }, b._mouseDestroy = function () { var b = this; b.element.unbind({ touchstart: a.proxy(b, "_touchStart"), touchmove: a.proxy(b, "_touchMove"), touchend: a.proxy(b, "_touchEnd") }), d.call(b) } } }(jQuery);

178
js/reposiciones.js Normal file
View File

@@ -0,0 +1,178 @@
// Get references to the HTML elements
const form = document.getElementById('form');
const steps = Array.from(form.querySelectorAll('.step'));
const nextButton = document.getElementById('next-button');
const prevButton = document.getElementById('prev-button');
let currentStep = 0;
// #clave_profesor on change => show step 2
const clave_profesor = document.getElementById('clave_profesor');
const horario_reponer = document.getElementById('horario_reponer');
const fechas_clase = document.getElementById('fechas_clase');
const fecha_reponer = $('#fecha_reponer');
const hora_reponer = $('#hora_reponer');
const minutos_reponer = $('#minutos_reponer');
clave_profesor.addEventListener('change', async () => {
const step2 = document.getElementById('step-2');
clave_profesor.disabled = true;
// get option which value is the same as clave_profesor.value
const option = document.querySelector(`option[value="${clave_profesor.value}"]`);
// make a form data with #form
const profesor_id = document.getElementById('profesor_id');
profesor_id.value = option.dataset.id;
const formData = new FormData(form);
const response = await fetch(`./action/action_horario_profesor.php`, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener los horarios del profesor.";
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
const horarios = data.data;
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione un horario';
initial.selected = true;
initial.disabled = true;
horario_reponer.innerHTML = '';
horario_reponer.appendChild(initial);
horarios.forEach((horario) => {
const dias = ['Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado', 'Domingo'];
const option = document.createElement('option');
option.value = `${horario.id}`;
// materia máx 25 caracteres, if materia.length > 25 then slice(0, 20)
const max = 25;
option.textContent = `${horario.materia.slice(0, max) + (horario.materia.length > max ? '...' : '')} - Grupo: ${horario.grupo} - ${horario.hora.slice(0, 5)}-${horario.hora_final.slice(0, 5)} - Salon: ${horario.salon} - ${horario.dia}`;
option.dataset.materia = `${horario.materia}`;
option.dataset.grupo = `${horario.grupo}`;
option.dataset.hora = `${horario.hora.slice(0, 5)}`; // slice(0, 5) => HH:MM
option.dataset.hora_final = `${horario.hora_final.slice(0, 5)}`;
option.dataset.salon = `${horario.salon}`;
option.dataset.dia = `${horario.dia}`;
option.dataset.id = `${horario.id}`;
horario_reponer.appendChild(option);
});
currentStep = 1;
step2.style.display = 'block';
prevButton.disabled = false;
});
// disable clave_profesor
// from second step to first step
prevButton.addEventListener('click', () => {
const inputs = [clave_profesor, horario_reponer, fechas_clase, fecha_reponer, hora_reponer];
switch (currentStep) {
case 1:
case 2:
case 3:
const step = document.getElementById(`step-${currentStep + 1}`);
step.style.display = 'none';
inputs[currentStep - 1].disabled = false;
inputs[currentStep - 1].value = '';
if (--currentStep === 0) {
prevButton.disabled = true;
}
break;
case 4:
const step5 = document.getElementById('step-5');
step5.style.display = 'none';
fecha_reponer.prop('disabled', false);
fecha_reponer.val('');
hora_reponer.parent().removeClass('disabled');
hora_reponer.siblings('.datalist-input').text('hh');
hora_reponer.val('');
minutos_reponer.parent().removeClass('disabled');
minutos_reponer.siblings('.datalist-input').text('mm');
minutos_reponer.val('');
currentStep--;
break;
}
nextButton.disabled = true;
});
// #horario_reponer on change => show step 3
horario_reponer.addEventListener('change', async () => {
const selected = horario_reponer.querySelector(`option[value="${horario_reponer.value}"]`);
horario_reponer.title = `Materia: ${selected.dataset.materia} - Grupo: ${selected.dataset.grupo} - Horario: ${selected.dataset.hora}-${selected.dataset.hora_final} - Salon: ${selected.dataset.salon} - Día: ${selected.dataset.dia}`;
const step3 = document.getElementById('step-3');
horario_reponer.disabled = true;
// make a form data with #form
const response = await fetch(`./action/action_fechas_clase.php?horario_id=${horario_reponer.value}`, {
method: 'GET',
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener las fechas de clase.";
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
const meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
const fechas = data.data;
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione la fecha de la falta';
initial.selected = true;
initial.disabled = true;
fechas_clase.innerHTML = '';
fechas_clase.appendChild(initial);
fechas_clase.title = 'Seleccione la fecha de la falta';
fechas.forEach((fecha) => {
const option = document.createElement('option');
option.value = `${fecha}`;
option.textContent = `${fecha.dia_mes} de ${meses[fecha.month - 1]} de ${fecha.year}`;
fechas_clase.appendChild(option);
});
step3.style.display = 'block';
currentStep = 2;
});
// #fechas_clase on change => show step 4
fechas_clase.addEventListener('change', () => {
const step4 = document.getElementById('step-4');
step4.style.display = 'block';
fechas_clase.disabled = true;
currentStep = 3;
});
// when both #fecha_reponer and #hora_reponer are selected => show step 5
const lastStep = () => {
// timeout to wait for the value to be set
setTimeout(() => {
if (fecha_reponer.val() !== '' && hora_reponer.val() !== '' && minutos_reponer.val() !== '') {
const step5 = document.getElementById('step-5');
step5.style.display = 'block';
// disable both
fecha_reponer.prop('disabled', true);
hora_reponer.parent().addClass('disabled');
minutos_reponer.parent().addClass('disabled');
const nextButton = document.getElementById('next-button');
// remove property disabled
nextButton.removeAttribute('disabled');
currentStep = 4;
}
}, 100);
};
fecha_reponer.on('change', lastStep);
// on click on the sibling ul>li of #hora_reponer and #minutos_reponer
hora_reponer.siblings('ul').children('li').on('click', lastStep);
minutos_reponer.siblings('ul').children('li').on('click', lastStep);
// Initialize the form
hideSteps();
showCurrentStep();
function hideSteps() {
steps.forEach((step) => {
step.style.display = 'none';
});
}
function showCurrentStep() {
steps[currentStep].style.display = 'block';
prevButton.disabled = currentStep === 0;
}
function handleSubmit(event) {
event.preventDefault();
// Handle form submission
// You can access the form data using the FormData API or serialize it manually
}
export {};

View File

@@ -11,7 +11,12 @@ $user = unserialize($_SESSION['user']);
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editar Horarios | <?php echo $user->facultad['facultad'] ?? "Administrador"; ?></title> <title>Editar Horarios |
<?= $user->facultad['facultad'] ?? "Administrador"; ?>
</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<?php <?php
include 'import/html_css_files.php'; include 'import/html_css_files.php';
?> ?>
@@ -28,10 +33,65 @@ $user = unserialize($_SESSION['user']);
<main class="content marco"> <main class="content marco">
<section id="message"></section> <section id="message"></section>
<h3 class="text-center mt-3"> <h3 class="text-center mt-3">
<b><?= $user->user['nombre']; ?></b> | <?= $user->facultad['facultad'] ?? "General"; ?> | <?= $user->rol['rol']; ?> <b>
<?= $user->user['nombre']; ?>
</b>
<i>
<?= $user->facultad['facultad'] ?? "General"; ?>
</i>
<small>
<?= $user->rol['rol']; ?>
</small>
</h3> </h3>
<hr> <hr>
<div class="d-flex justify-content-center align-items-center flex-wrap">
<?php
$has_token = $db->querySingle("SELECT FALSE") or die($db->getLastError());
if (array_pop($has_token)) {
?>
<div class="movie card col-10 col-md-3 border-primary border-3 m-2">
<div class="card-body text-center bg-light">
<a href="http://200.13.89.27/checador_otros/main"
class="card-link text-decoration-none text-primary d-flex flex-column align-items-center">
<i class="fa fa-clock" aria-hidden="true"></i>
<h5 class="card-title mt-2">Checador</h5>
</a>
</div>
</div>
<?php
} else {
?>
<div class="card col-10 col-md-3 border-primary border-3 m-2 disabled bg-dark">
<div class="card-body text-center disabled">
<a href="#"
class="card-link text-decoration-none text-primary d-flex flex-column align-items-center disabled text-danger">
<i class="ing-cancelar" aria-hidden="true"></i>
<h5 class="card-title mt-2">Checador</h5>
</a>
</div>
</div>
<?php
}
?>
<div class="movie card col-10 col-md-3 border-primary border-3 m-2">
<div class="card-body text-center bg-light">
<a href="#"
class="card-link text-decoration-none text-primary d-flex flex-column align-items-center">
<i class="fa fa-calendar" aria-hidden="true"></i>
<h5 class="card-title mt-2">Mis horarios</h5>
</a>
</div>
</div>
<div class="movie card col-10 col-md-3 border-primary border-3 m-2">
<div class="card-body text-center bg-light">
<a href="#"
class="card-link text-decoration-none text-primary d-flex flex-column align-items-center">
<i class="fa fa-table" aria-hidden="true"></i>
<h5 class="card-title mt-2">Mis asistencias</h5>
</a>
</div>
</div>
</div>
</main> </main>
<?php <?php
include "import/html_footer.php"; include "import/html_footer.php";

222
package-lock.json generated
View File

@@ -1,19 +1,26 @@
{ {
"name": "admin_checador", "name": "admin_checador (pruebas)",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"@popperjs/core": "^2.11.7",
"axios": "^1.4.0",
"es6-promise": "^4.2.8",
"moment": "^2.29.4",
"petite-vue": "^0.4.1"
},
"devDependencies": { "devDependencies": {
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/jquery": "^3.5.14" "@types/jquery": "^3.5.14",
"@types/node": "^20.2.1"
} }
}, },
"node_modules/@popperjs/core": { "node_modules/@popperjs/core": {
"version": "2.11.6", "version": "2.11.7",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==",
"dev": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
@@ -37,19 +44,132 @@
"@types/sizzle": "*" "@types/sizzle": "*"
} }
}, },
"node_modules/@types/node": {
"version": "20.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz",
"integrity": "sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==",
"dev": true
},
"node_modules/@types/sizzle": { "node_modules/@types/sizzle": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true "dev": true
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/petite-vue": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/petite-vue/-/petite-vue-0.4.1.tgz",
"integrity": "sha512-/gtYKQe9r1OV4IEwn2RsPXAHgFTe1nVq4QhldAP6/l8DSe9I754K6Oe1+Ff6dbnT5P8X2XP7PTUZkGRz5uFnFQ=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
} }
}, },
"dependencies": { "dependencies": {
"@popperjs/core": { "@popperjs/core": {
"version": "2.11.6", "version": "2.11.7",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw=="
"dev": true
}, },
"@types/bootstrap": { "@types/bootstrap": {
"version": "5.2.6", "version": "5.2.6",
@@ -69,11 +189,93 @@
"@types/sizzle": "*" "@types/sizzle": "*"
} }
}, },
"@types/node": {
"version": "20.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz",
"integrity": "sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==",
"dev": true
},
"@types/sizzle": { "@types/sizzle": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true "dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"petite-vue": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/petite-vue/-/petite-vue-0.4.1.tgz",
"integrity": "sha512-/gtYKQe9r1OV4IEwn2RsPXAHgFTe1nVq4QhldAP6/l8DSe9I754K6Oe1+Ff6dbnT5P8X2XP7PTUZkGRz5uFnFQ=="
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
} }
} }
} }

View File

@@ -1,6 +1,14 @@
{ {
"devDependencies": { "devDependencies": {
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/jquery": "^3.5.14" "@types/jquery": "^3.5.14",
"@types/node": "^20.2.1"
},
"dependencies": {
"@popperjs/core": "^2.11.7",
"axios": "^1.4.0",
"es6-promise": "^4.2.8",
"moment": "^2.29.4",
"petite-vue": "^0.4.1"
} }
} }

284
reposiciones.php Normal file
View File

@@ -0,0 +1,284 @@
<?php
require_once 'class/c_login.php';
if (!isset($_SESSION['user']))
die(header('Location: index.php'));
$user = unserialize($_SESSION['user']);
$user->access();
if (!$user->admin && in_array($user->acceso, ['n']))
die(header('Location: main.php?error=1'));
$user->print_to_log('Consultar horario');
$write = $user->admin || in_array($user->acceso, ['w']);
$en_fecha = $db->querySingle("SELECT ESTA_EN_PERIODO(NOW()::DATE, :periodo_id)", [':periodo_id' => $user->periodo])['esta_en_periodo'];
$periodo_fin = $db->querySingle("SELECT periodo_fecha_fin FROM periodo WHERE periodo_id = :periodo_id", [':periodo_id' => $user->periodo])['periodo_fecha_fin'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Consultar horario | <?= $user->facultad['facultad'] ?? 'General' ?></title>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/plain; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<?php include_once "import/html_css_files.php"; ?>
<link rel="stylesheet" href="css/jquery-ui.css">
<link rel="stylesheet" href="css/richtext.css" type="text/css">
<link rel="stylesheet" href="css/clockpicker.css">
<link rel="stylesheet" href="css/calendar.css">
<link rel="stylesheet" href="css/fa_all.css" type="text/css">
<script src="js/scrollables.js" defer></script>
<script>
const write = <?= $write ? 'true' : 'false' ?>;
</script>
<script src="js/moment.js" defer></script>
</head>
<!-- -->
<body style="display: block;">
<?php
include('include/constantes.php');
include("import/html_header.php");
html_header("Consultar horario", "Sistema de gestión de checador");
?>
<?= "<!-- $user -->" ?>
<main class="container content marco content-margin" id="local-app">
<section id="message"></section>
<?php require('import/periodo.php') ?>
<!-- Botón para abrir el modal -->
<span class="d-inline-block" tabindex="0" data-toggle="tooltip" <?php if (!$en_fecha) : ?> title="No se puede crear una reposición fuera de la fecha de reposición" <?php endif; ?>>
<button type="button" class="btn btn-primary" data-toggle="modal" <?php if ($en_fecha) : ?>data-target="#crearReposición" <?php else : ?>disabled style="pointer-events: none;" <?php endif; ?>>
Crear Reposición
</button>
</span>
<!-- Modal del formulario -->
<div class="modal fade" id="crearReposición" tabindex="-1" role="dialog" aria-labelledby="crearReposiciónLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="crearReposiciónLabel">Crear Reposición</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="form" class="form-horizontal">
<div class="form-group step" id="step-1">
<div class="form-box">
<div class="form-group row">
<label for="clave_profesor" class="col-4 col-form-label">Profesor</label>
<div class="col-8">
<input list="lista_profesores" name="clave_profesor" id="clave_profesor" class="form-control" placeholder="Profesor" required="required">
<div class="valid-feedback">
Profesor encontrado
</div>
<div class="invalid-feedback">
Profesor no encontrado
</div>
<datalist id="lista_profesores">
<?php
$profesores = $db->query('SELECT * FROM fs_profesor A WHERE facultad_id = :facultad_id AND EXISTS (
SELECT * FROM horario join HORARIO_PROFESOR ON horario.HORARIO_ID = HORARIO_PROFESOR.horario_id WHERE HORARIO_PROFESOR.profesor_id = A.id AND HORARIO.periodo_id = :periodo_id
) ORDER BY grado, nombre', [':facultad_id' => $user->facultad['facultad_id'], ':periodo_id' => $user->periodo]);
foreach ($profesores as $profesor) {
extract($profesor);
?>
<option data-grado="<?= $grado ?>" data-clave="<?= $clave ?>" data-profesor="<?= $profesor ?>" data-id="<?= $id; ?>" value="<?= "$clave | $grado $profesor" ?>"></option>
<?php
}
?>
</datalist>
<ul class="list-group" id="profesores"></ul>
<input type="hidden" id="periodo_id" name="periodo_id" value="<?= $user->periodo ?>">
<input type="hidden" id="profesor_id" name="profesor_id" value="">
</div>
</div>
</div>
</div>
<div class="form-group step" id="step-2">
<div class="form-box">
<div class="form-group row">
<label for="horario_reponer" class="col-4 col-form-label">Horario a reponer</label>
<div class="col-8">
<select name="horario_reponer" id="horario_reponer" class="form-control" required="required">
</select>
</div>
</div>
</div>
<input type="hidden" name="horario_id" id="horario_id">
</div>
<div class="form-group step" id="step-3">
<div class="form-box">
<div class="form-group row">
<label for="fechas_clase" class="col-4 col-form-label">Fecha de clase</label>
<div class="col-8">
<select name="fechas_clase" id="fechas_clase" class="form-control" required="required">
</select>
</div>
</div>
</div>
</div>
<div class="form-group step" id="step-4">
<div class="form-box">
<div class="form-group row">
<label for="fecha_reponer" class="col-4 col-form-label">Fecha de reposición</label>
<div class="col-6">
<input type="text" placeholder="dd/mm/aaaa" name="fecha_reponer" id="fecha_reponer" class="form-control date-picker" required="required">
</div>
</div>
<div class="form-group row">
<label for="hora" class="col-4 col-form-label">Hora</label>
<div class="col-3">
<div id="hora" class="datalist datalist-select mb-1">
<div class="datalist-input text-center">hh</div>
<span class="ing-buscar icono"></span>
<ul style="display:none">
<?php foreach (range(7, 21) as $hora) { ?>
<li data-id='<?= $hora ?>'><?= str_pad($hora, 2, "0", STR_PAD_LEFT) ?></li>
<?php } ?>
</ul>
<input type="hidden" id="hora_reponer" name="horas" value="">
</div>
</div>
<div class="col-3">
<div id="minutos" class="datalist datalist-select mb-1">
<div class="datalist-input text-center">mm</div>
<span class="ing-buscar icono"></span>
<ul style="display:none">
<?php foreach (range(0, 45, 15) as $minuto) { ?>
<li data-id='<?= $minuto ?>'><?= str_pad($minuto, 2, "0", STR_PAD_LEFT) ?></li>
<?php } ?>
</ul>
<input type="hidden" id="minutos_reponer" name="minutos" value="">
</div>
</div>
</div>
</div>
</div>
<div class="form-group step" id="step-5">
<div class="form-box">
<div class="form-group row">
<label for="descripcion_reposicion" class="col-4 col-form-label">Comentarios</label>
<div class="col-6">
<textarea name="descripcion_reposicion" id="descripcion_reposicion" rows="4" required="required" placeholder="Se requiere proyector, etc." maxlength="255" class="form-control"></textarea>
</div>
</div>
<div class="form-group row align-items-center">
<label class="col-4 col-form-label" for="sala">¿En sala de cómputo?</label>
<div class="col-6">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sala">
<label class="custom-control-label" for="sala"></label>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer justify-content-center">
<button class="btn btn-secondary" type="button" id="prev-button">Anterior</button>
<button class="btn btn-secondary" type="button" id="next-button" disabled data-toggle="modal" data-target="#confirmationModal">Proponer reposición</button>
</div>
<!-- Modal confirmación -->
<div class="modal fade" id="confirmationModal" tabindex="-1" role="dialog" aria-labelledby="confirmationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmationModalLabel">Confirmación</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>¿Estás seguro de que deseas proponer la reposición?</p>
<small>Recuerda que la aprobará tu jefe de carrera.</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info" onclick="$('#confirmationModal').modal('hide');">Cancelar</button>
<button type="button" class="btn btn-primary" data-dismiss="modal">Aceptar</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap/popper.min.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
<script src="js/richtext.js"></script>
<script src="js/clockpicker.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/datepicker-es.js"></script>
<script>
$(document).ready(function() {
$('.richtext').richText({
fontList: ['indivisa-text', 'Arial'],
imageUpload: true,
placeholder: 'Escribe aquí la información de la reposición: necesito un proyector, etc.',
});
});
$(".date-picker").datepicker($.datepicker.regional.es);
$(".date-picker").datepicker({
dateFormat: "dd/mm/yyyy",
changeMonth: true,
beforeShowDay: function(date) {
// Disable Sundays (0 represents Sunday)
return [date.getDay() != 0, ''];
// Disable 2020-05-01
}
});
// the minimum is today + 3 laboral days
function getNextWorkingDay(date) {
const day = date.getDay(); // Get the day of the week (0-6, where 0 is Sunday)
// Check if it's Saturday (6), if so, add 2 days
if (day === 6) {
date.setDate(date.getDate() + 2);
}
// Add 1 day to skip to the next day
date.setDate(date.getDate() + 1);
// Check if it's a Sunday (0), if so, add 1 day
if (date.getDay() === 0) {
date.setDate(date.getDate() + 1);
}
// Add laboral days
let laboralDaysCount = 1; // Start with 1 to account for the current day
while (laboralDaysCount < 3) {
date.setDate(date.getDate() + 1); // Add a day
if (date.getDay() !== 6) { // Skip Saturdays
laboralDaysCount++;
}
}
return date;
}
$(".date-picker").datepicker("option", "minDate", getNextWorkingDay(new Date()));
// the maximum is periodo_fin
$(".date-picker").datepicker("option", "maxDate", new Date("<?= $periodo_fin ?>T03:24:00"));
$(function() {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
<script src="js/messages.js"></script>
<script type="module" src="js/reposiciones.js"></script>
</html>

136
route.php
View File

@@ -1,136 +0,0 @@
<?php
//@NabiKAZ
//https://gist.github.com/NabiKAZ/91f716faa89aab747317fe09db694be8
//For show advanced list of files and directories with sort, date, size, icon type,...,
//Save bellow content code as route.php file, and then run this command:
// php -S 0.0.0.0:8080 -t . route.php
//And then open http://localhost:8080/ in the browser.
//////////////////////////////////////////////////////////////////
// This block MUST be at the very top of the page!
@ob_start('ob_gzhandler');
if(isset($_GET['icon']))
{
$e=$_GET['icon'];
$I['file']='R0lGODlhEAAPAOYAAIyMlu7u9PHx9vDw9fT0+PPz97u7vvf3+vb2+d/f4vn5+/39/vv7/Pr6+/b29+3t7pCRnI6PmZOVn5ibpZWYopqeqJ2hq6KnsaClr9fZ3ff4+t/g4qSqtKmwuqeuuM3P0vHz9tze4be6vuzv8+vu8urt8eXo7Kuzva61vquyu9/k6uXp7uTo7cvU3dHZ4dDY4Nfe5d3j6dzi6Nvh5+Po7eLn7OHm69HV2ejs8Ofr7+7x9OHk597h5PT2+PP19/Hz9evt7+Lk5t3f4fr7/Pn6+/b3+PX299Tc49rh5+ru8fDz9ff5+vb4+fP19vz9/f////7+/vv7+/Pz8+/v7+zs7Orq6ubm5uHh4d7e3sDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAFkALAAAAAAQAA8AAAevgFmCJ4SFhDuCiYIdUI1QDQ9NKCGKgh4LjQsOG0smKRmVHAxPUAtGQktLPxxTihcKjU5FQR8iBhdXihgHC05DTEA8NwkYWIoWCENEGj1KJCsIFsaJFQRMPT5KIzk2BBXTghMFIEo6JDk1MQUT4FkUAiVJOCs2MTACFO0SAyw0NiozYLgYIKEdhAAyZiCBceRFiwAQ2kUIQLFixQjtAGjcyBFAuyhSqFgZSXJklSyBAAA7';
$I['dir']='R0lGODlhEAAOAOYAAP79uv//4/j43f//5f7+5v7+6f//7f//7//8qf/9vf/9wf/+xP/9yf793P/+4f32hP/6iv/6pP/9zP/4kf/4nP/4n/373//1jf/2nP32tvnodfzuff/xhv/3uP/revftrvz32v/ocf/odP/rhPnz0/z44+DAPOHAPe/gnPXrvv3zyM6hAMicAMeaANStJdu4PNy7RODDWN7EaOLIauLIa//10ffuzcudAMiaAMSWAMOVAMGTAL2PALqMAMilLtayPNe2TNW0TNKxTNm7V9e5V82vU9zEd+7jvurfvPXryfbu072NALiKALaIALOEALKDALGCAK+AAMinRMalRMOiRNCuTM6sTMuqTMmnTLyfUdm8ZtW9et3Kl+XUoa19AKp6AK6EG7GHIr2YNr+cQ7iXQ8akTMSjTb6jXs2waMOqbNjEkNDDpM2xctfQwPz8/Pv7+/r6+vX19fHx8ezs7Ovr6+jo6Ofn5+bm5uHh4eDg4N/f397e3t3d3cDAwAAAAAAAACH5BAEAAH0ALAAAAAAQAA4AAAe4gEkzMkNERl19iYqKMSkGjwZbRVJTVGhqijACEJwQEpCPWXKJOBYPp6ioFjh2bn06IBuys7MgOnpwfTwkGiY/QEFCVVZXWGVmYl9MNic0BwHQ0QMOBUhgT0cuJQoJ3d4LAARja15aKAwI6QgRFRQYGVxke1AvDRMXHB4iIfwjSmFt6iz50KGGwYMGVbBJgyeODxYrbrTIsYNHjyZOopzhM8fVHT17QooUmYdOrj5v4KhcyRKOqz6BAAA7';
$I['doc']='R0lGODlhEAAQAPcAAAEyeCg+bQgviwU2ggg8iAZCmwlLsiFMmjpamDJbtipitzhhrjppuE1qp0BmtERquVVtpF11q2d+s0JuxEl0zFJ3ylV7zl99w1h+0XeKnG6Ov3KQv3KRv3aTvXqVu3uVvH6XulWAyFmBxliCxV2ExF6ExGCBzWGIw2KJw2WKwmeLwWmMwWyOwGeK1XeR1XyX2P8A/4KavIWdvoOc2oCe5oigwIuiwoyiwouk3ZGnxpesyZCu1p2xzYml6ZOr5qO20am71K260K+836q+8a/A2LPD2rfI9MnS4tbc6tLi+tTj+tbk+tfl+9zi9Nnm+9vn+9zo+97p++Dq/OHs/OPt/OXu/Obv/Ojw/erx/evy/e3z/e/0/fDy+vD2/vL3/vT4/vX5/vf6/vn7/////wCpEQAAABLs7NS5srGlQNcVPRQCgBQCQBLtDNdNrxQCgGQCeNdN4xQCgBLtFAAAAJEFyCNr8BLt4JEFURQHqJEFbRLuOAAAABLtPAAAAJEFyFiHuBLuCJEFURQHSBLtWAAAAJEFyFiHuBLuJJEFURQHSJEFbRLuaAAABAAAAOaERAAAAgAABAAAMAAAACNr+NSLsf3QAAAAMAAABBQAABLrmJD7bAAAIAAAAFiHwBLuOAAAAAAAIADwqgAAIAAAAAAAAJDnvJDVhhLuCJD7bJD7cZDVhpDnvBQAABLt5JDnyBLujJDuGJD7eAH//wAABBLtaAAAABLujJDuGJEFcP///5EFbZEJvBQAAAAAAFiHwBLuSJEJkliHwAAAABLunN3tDt3tIGKmyAABxGKm1AAAAAAAAAAAAAAAAAAAABLuaBLu7BS3YBS3YBLuoOb8I8OlLsYaoBLu2MLCzQAABMLC4xS04BS3YAAAAxSwbsXS4BSwABLu1BLupP///xLvQMNclMEgcP///8LC40SV1RS3YGMboGMboEUEtRQAABS04IoASAAAAAAAAOqG1OqG1OqG1OqG1AAC8BLvJN1sdBLvLIoASIoASObgowAACeaCsAAABCH5BAEAADAALAAAAAAQABAAAAjhAGEILALkBw8dOWzIAAFCoEMYRMSEAfPFS5ctIMY0hOHDRw8aL1pgqDBBgZaMGjmOWclypYEsKDX2GDLDBBITTSDgMICFoU8aTWZcaPKgSYMMBq5YqUJlCggXY1w8EHIAB4IjBZY2lQKixRgJDyIMSBBgTIEqO3ZIieLBwhgICIwMGBBkDIGtUaB0oDBGwAIuAxysHDAlLZQnGxi0bAlg7WEnLBQYmFygMoEBAKKkdcJEhcMbWqc4fsJ5CQqHNZimXZ12iZISDmXgfczEdRIRDmN8+NCBg4YVKU6QGBEiREAAADs=';
$I['xls']='R0lGODlhEAAQAPcAADVJGjRNGTVNGDRSFzRTFzRYFTReEzReFDVeGjNpDzNtDjRtDjRjETRkEjZjGztoHjpvHjNwDTlwHjp3GTx3Hz57HjJoKDtxIjp9Jz99IWB+XEKAJUaELEeFLUKJNUeIO02MNk+OOUiSP1OTQFKXSFiYR1qaSVieUl6YVV+hU1uiWHCbbWGiVGGgWGWnW2eqX2SoYGmtYmqsZW2wZ3SlcG6Ov3KQv3KRv3aTvXqVu3uVvH6XulWAyFmBxliCxV2ExF6ExGGIw2KJw2WKwmeLwWmMwWyOwP8A/4KavIWdvoephZC9i5ywm6Gzn4igwIuiwoyiwpGnxpesyZCu1p2xzaO20am71JPCjpPEjZbEkZrGlq/A2LPD2sDRwMzay8/dz9bi1t/p39Li+tTj+tbk+tfl+9nm+9vn+9zo+97p++Xs5eDq/OHs/OPt/OXu/Obv/Ojw/erx/evy/e3z/e/0/fD2/vL3/vT4/vX5/vf6/vn7/////xLtPAAAAJEFyCLVGBLuCJEFURQHSBLtWAAAAJEFyCLVGBLuJJEFURQHSJEFbRLuaAAABAAAAOaERAAAAgAABAAAMAAAAFeQiNSLsf3QAAAAMAAABBQAABLrmJD7bAAAIAAAACLVIBLuOAAAAAAAIADwqgAAIAAAAAAAAJDnvJDVhhLuCJD7bJD7cZDVhpDnvBQAABLt5JDnyBLujJDuGJD7eAH//wAABBLtaAAAABLujJDuGJEFcP///5EFbZEJvBQAAAAAACLVIBLuSJEJkiLVIAAAABLunN3tDt3tIGKmyAABwGKm1AAAAAAAAAAAAAAAAAAAABLuaBLu7BSuABSuABLuoOb8I8OlLsYaoBLu2MLCzQAABMLC4xSsQBSuAAAAAxSg2MXS4BSgABLu1BLupP///xLvQMNclMEgcP///8LC40SV1RSuAGMboGMboEUEtRQAABSsQKR+UAAAAAAAAOqG1OqG1OqG1OqG1AACBBLvJN1sdBLvLKR+UKR+UObgowAACeaCsAAABCH5BAEAAEcALAAAAAAQABAAAAjhAI8I5GKlChUpUZ4k2bFDoMMjW/TkwXPHTh06O/Y0PDIjhosUJkaA6LChwpyMGjnuWclyZQQ5KDXOuAKDxB4vKFAwURCHoc8XarDI8ECjxQcwCeC8cdOGzQ4We75oUYHBQpc9DJY2XbOjxJ4lJ7KouLAizAE3U6asSZMjxBIKEBwsEfGgSYGtadDg4NBSiQQEGgawSYvmjI0MLVsKWFvYjJEJERYkaGCgAIEAANKkNVOGiEMoWtkwPsOZjBCHTpimXZ2WzBggDpPgbVzGtZgeDpHo0IHjRo0iQ4L88MGDR0AAADs=';
$I['jpg']=$I['gif']=$I['png']='R0lGODlhEAAQAPcAAPuBhP0RI9fU1r24vL25vn+CmKSxzLfF4cPL28bO3srO1oOk4WF4opyuzpWlwpurx6i30qm30ae1zqa0zb3M57G/2Kq3z6m2zcTR6aSvw9Dd9crW7NPe8tXg88PM3OLs/tXe7+Lr/Njh8eHp98/W4wBe9Yibup+vyZemv6e2z6e2zsXW8rLC26e1zK+91Kezx8jU6Nvo/eDr/dzm9uLs/OTt/MvT4ODn897l8dvi7uvy/gxn7Keyw7K9zsTQ4uPu/t7p+dzm9c/Y5t7n9dri7svb8djh7ejw++vw9+fs87jI2+Hu/lem/vH3/lSp/uTx/vP5/snj9+33/qGoqPz+/v3+/lXSYAC1AH3GdS6qHnDIW1OmL+Hp173JqoGaKby9srurRv3slf7dbf7cc/7Xb/7QZ/3SdP7FVd6wUP7LaP6/SP68SeS7cv6vMP62QNycN+KuWOCuW/vt1fueGf6qL/6vN/6xOf65V+C/jP2XEv6eGvmeH/6gI/6iJOCSKM+WR9ScUPjEff6ZG8qELbB/ROG0f/2EAPeAAf6IAuR5BP6JCP6QENKTTdmseuC7kbBhEKlfFa5iGMR0I8d+M45aJr6BRM2VW9ikcNuugNG8pvLk1v37+f17AO1zAKlUBrNiFqBdHplZHr97PcB/Q690Pc6KTcWHT7F9T9CYY9Opg9KujN25l6NJAJJFBK5sMpNnQcqcdKaGbL+fhPfw6qpKALeDWtijebGReOC4mdm2nKuQe+XDqfjy7tm0nedXBa+ZjPjz8OfOwauSh/BvO/55QcY0AN7a2d4dA+kwFdsTBv7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAMsALAAAAAAQABAAAAj/AJcJXIbDSI6DREDAQKBs4MAgVZRJVNaEwwYbDgUaozJRGZQON46QcLgplaNLmFZpoiLDh5AKHgTuKmVJlzBVqEbBSlIEQgMTy2wxkhOFiZMFXLoQOiWCwgEHuCQ1KvRlRwkGWqxgERVrBYYHkwAF68VmSgE8Wa5sCRQqgYYTg8LEgWOGTJk0YLz8uQOJQAwDptCMEXNmjZs6dIgBGOZpwJIJs3JVeqPGDp0+c5AFSNZKwQ8JAnnV8tOGj6A8vo4VozTjg4qBwFzt0bNIESJDrH49oZHCoaxHiQ5x6kTr1QggIXoPFJCJVKRPoG4hkVJDRwSHQ5T04JHhBQsXF1pYA0AREAA7';
$I['txt']='R0lGODlhEAAQAPcAAB6Kcm6Ov3KQv3KRv3aTvXqVu3uVvH6XulWAyFmBxliCxV2ExF6ExGGIw2KJw2WKwmeLwWmMwWyOwP8A/4KavIWdvoigwIuiwoyiwo+lxJGnxpKoxpWryJesyZmtypCu1p2xzaO20am71K/A2LPD2tLi+tTj+tbk+tfl+9nm+9vn+9zo+97p++Dq/OHs/OPt/OXu/Obv/Ojw/erx/evy/e3z/e/0/fD2/vL3/vT4/vX5/vf6/vn7/////xLuYAAAQAAAAAAAABLuqBLuaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQBLurJDuGAAAAAAAAgAAAQACEBLuYAACEJEZcAa6SAa6KAAAAAAAAAHskAABHhLsBJDuGBLswNS5TACpEQCpERLs1NS47q0nqACpEQAAABLs7NS5sq0nqNcVPRQCgBQCQBLtDNdNrxQCgCoFBtdN4xQCgBLtFAAAAJEFyCLqgBLt4JEFURQHqJEFbRLuOAAAABLtPAAAAJEFyAcKIBLuCJEFURQHSBLtWAAAAJEFyAcKIBLuJJEFURQHSJEFbRLuaAAABAAAAOaERAAAAgAABAAAMAAAACLqiNSLsf3wAAAAMAAABBQAABLrmJD7bAAAIAAAAAcKKBLuOAAAAAAAIADwqgAAIAAAAAAAAJDnvJDVhhLuCJD7bJD7cZDVhpDnvBQAABLt5JDnyBLujJDuGJD7eAH//wAABBLtaAAAABLujJDuGJEFcP///5EFbZEJvBQAAAAAAAcKKBLuSJEJkgcKKAAAABLunN3tDt3tIGKmyAABsGKm1AAAAAAAAAAAAAAAAAAAABLuaBLu7BR5EBR5EBLuoOb8I8OlLsYaoBLu2MLCzQAABMLC4xR2sBR5EAAAAxRwicXS4BRwABLu1BLupP///xLvQMNclMEgcP///8LC40SV1RR5EGMboGMboEUEtRQAABR2sIPdOAAAAAAAAOqG1OqG1OqG1OqG1AABsBLvJN1sdBLvLIPdOIPdOObgowAACeaCsAAABCH5BAEAABMALAAAAAAQABAAAAipACcIJCEiBIgOGi5UOHBAoMMJI3js0JEDxw0bB3o0nACgo8ePHXto5CiyBwCTKEuKPBCypcmTABgybEkTJowXLljChPnSJM4WOkGCbNGCRQGHHmrQmCEjxk0XRVcQcMhh6YerWD+sUCHA4QamTn+y2JpCgsMMTbNiTYECgkMMYaGOVcH2hAOHFm6qvXrCBAOHFcSSRdG3RAKHFAwYIDAgQIQHDRYoQIAgIAA7';
$I['avi']=$I['mpg']=$I['mpeg']=$I['mp3']='R0lGODlhEAAQAPcAAEhHSHd2d//+/+/q9+7r9KalqPLx9NrZ3HZ1e4mJjx0dHoeHi+Dg4/n5++np6+jo6tLS1NjY2ZqamxYelholkUBHhIWGjHB2lREwshozpCdDujZPuoOEiIWNqBI7tJWWmdTV2B1NwihTuC9iyDZqzlF60X+Vw/z9/xtbyxZczvb5/h1m0EqA0EZ/z1aP3srd9h502hh24VSc6SuD3oqLjNfY2SGN70ik6cHe9ODn6urv8MTGxnG+AYTVDXiuKaPOX6DEZ+nu4XO3AWWgAU56AYypWoWWaNPcw22pAWacAVyKCTpTCm2bFF+BHHehLEVnAWGNBk9zBXWDV1BhKsbKvZm0VajRMHKKKJG5AUlaBnp9b4ulGoqMgYiIhoKCgLOzseDg325qVf/++dqvAdmvAei7As2oBtCoB8qlB+jAF+jAG6eLE5yDGezHK+fFN4RyIN7AOOTEOuPDO4BuIu3MQ+XIR+rLSuXHSOXHSu3VaO3Wc+7YeIuFa/ry0vz343lxVn16cnh2cMHAvpOOiPjx6evq6eHc2v9zTfV6WfnRxuHLxfvx7v9KG/lHG/hIG/lOIvxNI/dOJP5ZLv5aMexYMvVeOPRjPv9xTP92U/BzU/99Xet2WPKijvWvnu+unvGxofnMwNvLx/r19P////v7+/Ly8uzs7Ojo6Obm5uTk5OLi4uDg4N/f39jY2NbW1tLS0s/Pz87Ozs3NzczMzMfHx7Ozs62traenp56enpycnJqamnl5eWtra2RkZE5OTiEhIRwcHBsbGxMTEwgICAQEBP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAMUALAAAAAAQABAAAAj/AIsJXMRpE6UtPopQMSCwYTFQlRpBeoSFxxAlRg44TBRpEiZNiKz0EJKEiBQIAkVZkoTpU6gINY78QALlCRdVxTw5utSJEK8dDggMAMIkypQCpDIxOqSIEABeu2gJCOKkyRIvq5QeMuTU169hvR5UuZJFC6wWM27kcBqMGDBYDPLAMcNnFosYNnAQUiDsFqwTfuy4OQNolokUMGToGCSowahRe+6oWWMhFggRKFa4eKFCTB89deSM+SOBVbEOHkKMIFGCDh08ccq8+WBLYKELGTBo2NCmTRoyczjkStUQTIIKFCagQcMmDA1crRwWK/WlC4JAARboqnVKekNTrmTJBnqFapTDgAA7';
$I['pdf']='R0lGODlhEAAQAPcAAFoAAGMAAHMYGG6Ov3KQv3KRv3aTvXqVu3uVvH6XulWAyFmBxliCxV2ExF6ExGGIw2KJw2WKwmeLwWmMwWyOwIwACJQAAJwhIa0ACLUAAL05OZxCQr1KSr1SWsYAAM4ICM4QENYYGM4pMd45OecIEPcQEPcYGO85OfcpKf8xOc5KSt5KStZja+dKSu9CSu9KSudaWu9SUu9SWudaY+dzc+97e/9zc/8A/4KavIWdvoigwIuiwoyiwo+lxJGnxpKoxpWryJesyZmtypywzJ2xzaO20am71Ky+1q/A2LPD2t6EhN61veeMjO+cnO+trdLi+tTj+tbk+tfl+9nm+9vn+9zo+97p++/W1ufv9+Dq/OHs/OPt/OXu/Obv/Ojw/erx/evy/e3z/e/0/fD2/vL3/vT4/vX5/vf6/vn7/////xQCgBQCQBLtDNdNrxQCgBEGqNdN4xQCgBLtFAAAAJEFyCJ8mBLt4JEFURQHqJEFbRLuOAAAABLtPAAAAJEFyFWi2BLuCJEFURQHSBLtWAAAAJEFyFWi2BLuJJEFURQHSJEFbRLuaAAABAAAAOaERAAAAgAABAAAMAAAACJ8oNSLsf3QAAAAMAAABBQAABLrmJD7bAAAIAAAAFWi4BLuOAAAAAAAIADwqgAAIAAAAAAAAJDnvJDVhhLuCJD7bJD7cZDVhpDnvBQAABLt5JDnyBLujJDuGJD7eAH//wAABBLtaAAAABLujJDuGJEFcP///5EFbZEJvBQAAAAAAFWi4BLuSJEJklWi4AAAABLunN3tDt3tIGKmyAACvGKm1AAAAAAAAAAAAAAAAAAAABLuaBLu7BSjUBSjUBLuoOb8I8OlLsYaoBLu2MLCzQAABMLC4xSo8BSjUAAAAxSgLcXS4BSgABLu1BLupP///xLvQMNclMEgcP///8LC40SV1RSjUGMboGMboEUEtRQAABSo8KR+UAAAAAAAAOqG1OqG1OqG1OqG1AACXBLvJN1sdBLvLKR+UKR+UObgowAACeaCsAAABCH5BAEAADcALAAAAAAQABAAAAjcAG8ITGKkCJEgPnbkSJBAoMMbSNCcMVOGzBgxCdI0fHhkYsWLYTJqvNGBgwYVM2DIgOFCBBiRGm28SIHChIkSJkhg+MKw54kQIEB4+OABQ4YKXrpw2aIlgYUAAARsmLrhwgalTLNsvDFETBgwX1Zg1ZLFygGHQr5+uTKiSVYrVQw4BALWSw0sTJyUrUKFgMMfX7xcaeHEyYoWMZRMoeCwR1IYS8jCXcJCigSHPLrMYCKZ7xQpUSA41EGDRmcqn6NAceAwx1vPoKE8WeAQBwIEBgoMmBDhQQMGChQEBAA7';
$I['rar']=$I['zip']='R0lGODlhDwAQAOYAAMjY9gRLsBJPqRZQpydpx7TP9iFbrSNfsChltixquzt6xEmH0VqU2G2h4HSk4HGc05jC9ZCz3jmF1zqA0EOK106P2JC235S335a535i531am9FOa5FOa4l2l61uZ13Cv7ne09IKx3ziZ81yr9mGz/2i3/2Go62as7XK7/26x7ni+/3W38Ha38HKx5nu88XWx5IjD84HA8oTD84vK+ZvP9YnJ963b+bzl+8fs/fT///79mf//r///uf//xPr2k//9pP/4hv/6kPr1kPr1kf/7mvfvgvPkbdm/Ktm/LPn25dm8L/vwvfvzzt61AfbNLNm3KfzUMdm3Mdu5M9q6Nd6+O/3ZR/vdZPzkhfvnl/roodq0Kv7TOP3VOf3aUfzZWPzebfvedPziffrjj/rprfrrs/juytKgB9SjCNWlC8+hDtWmEM+hENKmFNWmFtSnG9WqH9SqH9WqIdWrItiuKOjRh7J9DrR8D7mFKaptDbJ7I6ttDax0HfEZAf///wAAAAAAACH5BAEAAH0ALAAAAAAPABAAAAe8gH2CgzIxLispJyaCdGmOdCY5ODc2NDAsC31pfJxpBJ+gEyMeAWtaT0dISlFSU1R2fSEvAXI6PTw7P0RBQEZ1fRYyAW8+SWVjYmBeW02/GDMBcENMZFhhVlVQzX3P0dPV19nbF9BxQktZV19dXE7bGTUBbEVzbm1qaGdm2xEtAXd59ODZAwrUgw8dAgwStKgECRQqVHRQsHCQiQQIDhwwoLCiRxOLPA7i4KABgwoUJAgQuQFAAQggNIgYEAgAOw==';
header('Cache-control: max-age=2592000');
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T',time()+2592000));
header('Content-type: image/gif');
print base64_decode(isset($I[$e])?$I[$e]:$I['file']);
exit;
}
// End block
// Start configs
$sitename='My files';
$date='Y-m-d H:i:s'; // date format
$ignore=array('.','..','.htaccess','index.php','icon.php','Thumbs.db','web.config'); // ignore these files
// End configs
$root=dirname(__FILE__);
$dir=isset($_GET['dir'])?$_GET['dir']:'';if(strstr($dir,'..'))$dir='';
$path="$root/$dir/";
$dirs=$files=array();
if(!is_dir($path)||false==($h=opendir($path)))exit('Directory does not exist.');
while(false!==($f=readdir($h)))
{
if(in_array($f,$ignore))continue;
if(is_dir($path.$f))$dirs[]=array('name'=>$f,'date'=>filemtime($path.$f),'url'=>'index.php?dir='.rawurlencode(trim("$dir/$f",'/')));
else$files[]=array('name'=>$f,'size'=>filesize($path.$f),'date'=>filemtime($path.$f),'url'=>trim("$dir/".rawurlencode($f),'/'));
}
closedir($h);
$current_dir_name = basename($dir);
$up_dir=dirname($dir);
$up_url=($up_dir!=''&&$up_dir!='.')?'index.php?dir='.rawurlencode($up_dir):'index.php';
// END PHP ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=iso-8859-1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><?=$current_dir_name==''?'Directory list':$current_dir_name?></title>
<style type="text/css">
body { font-family: tahoma, verdana, arial; font-size: 0.7em; color: black; padding-top: 8px; cursor: default; background-color: #fff; }
#idx { border: 3px solid #fff; width: 500px; }
#idx td.center { text-align: center; }
#idx td { border-bottom: 1px solid #f0f0f0; }
#idx img { margin-bottom: -2px; }
#idx table { color: #606060; width: 100%; margin-top:3px; }
#idx span.link { color: #0066DF; cursor: pointer; }
#idx .rounded { padding: 10px 7px 10px 10px; -moz-border-radius:6px; }
#idx .gray { background-color:#fafafa;border-bottom: 1px solid #e5e5e5; }
#idx p { padding: 0px; margin: 0px;line-height:1.4em;}
#idx p.left { float:left;width:60%;padding:3px;color:#606060;}
#idx p.right {float:right;width:35%;text-align:right;color:#707070;padding:3px;}
#idx strong { font-family: "Trebuchet MS", tahoma, arial; font-size: 1.2em; font-weight: bold; color: #202020; padding-bottom: 3px; margin: 0px; }
#idx a:link { color: #0066CC; }
#idx a:visited { color: #003366; }
#idx a:hover { text-decoration: none; }
#idx a:active { color: #9DCC00; }
</style>
<script type="text/javascript">
<!--
var _c1='#fefefe'; var _c2='#fafafa'; var _ppg=100; var _cpg=1; var _files=[]; var _dirs=[]; var _tpg=null; var _tsize=0; var _sort='date'; var _sdir={'type':0,'name':0,'size':0,'date':1}; var idx=null; var tbl=null;
function _obj(s){return document.getElementById(s);}
function _ge(n){n=n.substr(n.lastIndexOf('.')+1);return n.toLowerCase();}
function _nf(n,p){if(p>=0){var t=Math.pow(10,p);return Math.round(n*t)/t;}}
function _s(v,u){if(!u)u='B';if(v>1024&&u=='B')return _s(v/1024,'KB');if(v>1024&&u=='KB')return _s(v/1024,'MB');if(v>1024&&u=='MB')return _s(v/1024,'GB');return _nf(v,1)+'&nbsp;'+u;}
function _f(name,size,date,url,rdate){_files[_files.length]={'dir':0,'name':name,'size':size,'date':date,'type':_ge(name),'url':url,'rdate':rdate,'icon':'index.php?icon='+_ge(name)};_tsize+=size;}
function _d(name,date,url){_dirs[_dirs.length]={'dir':1,'name':name,'date':date,'url':url,'icon':'index.php?icon=dir'};}
function _np(){_cpg++;_tbl();}
function _pp(){_cpg--;_tbl();}
function _sa(l,r){return(l['size']==r['size'])?0:(l['size']>r['size']?1:-1);}
function _sb(l,r){return(l['type']==r['type'])?0:(l['type']>r['type']?1:-1);}
function _sc(l,r){return(l['rdate']==r['rdate'])?0:(l['rdate']>r['rdate']?1:-1);}
function _sd(l,r){var a=l['name'].toLowerCase();var b=r['name'].toLowerCase();return(a==b)?0:(a>b?1:-1);}
function _srt(c){switch(c){case'type':_sort='type';_files.sort(_sb);if(_sdir['type'])_files.reverse();break;case'name':_sort='name';_files.sort(_sd);if(_sdir['name'])_files.reverse();break;case'size':_sort='size';_files.sort(_sa);if(_sdir['size'])_files.reverse();break;case'date':_sort='date';_files.sort(_sc);if(_sdir['date'])_files.reverse();break;}_sdir[c]=!_sdir[c];_obj('sort_type').style.fontStyle=(c=='type'?'italic':'normal');_obj('sort_name').style.fontStyle=(c=='name'?'italic':'normal');_obj('sort_size').style.fontStyle=(c=='size'?'italic':'normal');_obj('sort_date').style.fontStyle=(c=='date'?'italic':'normal');_tbl();return false;}
function _head()
{
if(!idx)return;
_tpg=Math.ceil((_files.length+_dirs.length)/_ppg);
idx.innerHTML='<div class="rounded gray" style="padding:5px 10px 5px 7px;color:#202020">' +
'<p class="left">' +
'<strong><?=$current_dir_name==''?$sitename:$current_dir_name?></strong><?=$dir!=''?'&nbsp; (<a href="'.$up_url.'">Back</a>)':''?><br />' + (_files.length+_dirs.length) + ' objects in this folder, ' + _s(_tsize) + ' total.' +
'</p>' +
'<p class="right">' +
'Sort: <span class="link" onmousedown="return _srt(\'name\');" id="sort_name">Name</span>, <span class="link" onmousedown="return _srt(\'type\');" id="sort_type">Type</span>, <span class="link" onmousedown="return _srt(\'size\');" id="sort_size">Size</span>, <span class="link" onmousedown="return _srt(\'date\');" id="sort_date">Date</span>' +
'</p>' +
'<div style="clear:both;"></div>' +
'</div><div id="idx_tbl"></div>';
tbl=_obj('idx_tbl');
}
function _tbl()
{
var _cnt=_dirs.concat(_files);if(!tbl)return;if(_cpg>_tpg){_cpg=_tpg;return;}else if(_cpg<1){_cpg=1;return;}var a=(_cpg-1)*_ppg;var b=_cpg*_ppg;var j=0;var html='';
if(_tpg>1)html+='<p style="padding:5px 5px 0px 7px;color:#202020;text-align:right;"><span class="link" onmousedown="_pp();return false;">Previous</span> ('+_cpg+'/'+_tpg+') <span class="link" onmousedown="_np();return false;">Next</span></p>';
html+='<table cellspacing="0" cellpadding="5" border="0">';
for(var i=a;i<b&&i<(_files.length+_dirs.length);++i)
{
var f=_cnt[i];var rc=j++&1?_c1:_c2;
html+='<tr style="background-color:'+rc+'"><td><img src="'+f['icon']+'" alt="" /> &nbsp;<a href="'+f['url']+'">'+f['name']+'</a></td><td class="center" style="width:50px;">'+(f['dir']?'':_s(f['size']))+'</td><td class="center" style="width:110px;">'+f['date']+'</td></tr>';
}
tbl.innerHTML=html+'</table>';
}
<?php foreach($dirs as $d)print sprintf("_d('%s','%s','%s');\n",addslashes($d['name']),date($date,$d['date']),addslashes($d['url'])); ?>
<?php foreach($files as $f)print sprintf("_f('%s',%d,'%s','%s',%d);\n",addslashes($f['name']),$f['size'],date($date,$f['date']),addslashes($f['url']),$f['date']);?>
window.onload=function()
{
idx=_obj('idx'); _head(); _srt('name');
};
-->
</script>
</head>
<body>
<div id="idx"><!-- do not remove --></div>
</body>
</html>

803
sample/rutas.json Normal file
View File

@@ -0,0 +1,803 @@
[
{
"id": 0,
"ruta": "Edificio 1 / Piso 0",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 4,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "10:00",
"estado": 1,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 1,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Base de datos",
"hora_inicio": "10:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 0,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "11:00",
"estado": 2,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
},
{
"id": 10,
"salon": "A-11",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "11:00",
"estado": 2,
"comentario": ""
},
{
"id": 11,
"salon": "A-12",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 12,
"salon": "A-13",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 4,
"comentario": ""
},
{
"id": 13,
"salon": "A-14",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "11:00",
"estado": 1,
"comentario": ""
},
{
"id": 14,
"salon": "A-15",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 15,
"salon": "A-16",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
},
{
"id": 16,
"salon": "A-17",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 17,
"salon": "A-18",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 18,
"salon": "A-19",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 1,
"comentario": ""
}
]
},
{
"id": 1,
"ruta": "Edificio 1 / Piso 1",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 2,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 4,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 1,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 0,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Base de datos",
"hora_inicio": "8:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "10:00",
"estado": 4,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "9:00",
"estado": 4,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
}
]
},
{
"id": 2,
"ruta": "Edificio 1 / Piso 2",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Sistemas",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 3,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Sistemas",
"hora_inicio": "8:00",
"hora_fin": "10:00",
"estado": 0,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 4,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "11:00",
"estado": 0,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 2,
"comentario": ""
}
]
},
{
"id": 3,
"ruta": "Edificio 1 / Piso 3",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Base de datos",
"hora_inicio": "8:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 1,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Sistemas",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 2,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 2,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 2,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Sistemas",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 3,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 2,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Base de datos",
"hora_inicio": "8:00",
"hora_fin": "10:00",
"estado": 1,
"comentario": ""
},
{
"id": 10,
"salon": "A-11",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 11,
"salon": "A-12",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 12,
"salon": "A-13",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 0,
"comentario": ""
}
]
},
{
"id": 4,
"ruta": "Edificio 1 / Piso 4",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 1,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 4,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 1,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 0,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Base de datos",
"hora_inicio": "10:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 0,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 2,
"comentario": ""
},
{
"id": 10,
"salon": "A-11",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 2,
"comentario": ""
},
{
"id": 11,
"salon": "A-12",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 4,
"comentario": ""
},
{
"id": 12,
"salon": "A-13",
"materia": "Sistemas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 13,
"salon": "A-14",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 3,
"comentario": ""
},
{
"id": 14,
"salon": "A-15",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "11:00",
"estado": 3,
"comentario": ""
},
{
"id": 15,
"salon": "A-16",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "9:00",
"estado": 1,
"comentario": ""
},
{
"id": 16,
"salon": "A-17",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "11:00",
"estado": 0,
"comentario": ""
},
{
"id": 17,
"salon": "A-18",
"materia": "Base de datos",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 3,
"comentario": ""
}
]
},
{
"id": 5,
"ruta": "Edificio 1 / Piso 5",
"horarios": [
{
"id": 0,
"salon": "A-1",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 1,
"salon": "A-2",
"materia": "Base de datos",
"hora_inicio": "10:00",
"hora_fin": "10:00",
"estado": 2,
"comentario": ""
},
{
"id": 2,
"salon": "A-3",
"materia": "Matemáticas",
"hora_inicio": "9:00",
"hora_fin": "11:00",
"estado": 0,
"comentario": ""
},
{
"id": 3,
"salon": "A-4",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "10:00",
"estado": 0,
"comentario": ""
},
{
"id": 4,
"salon": "A-5",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
},
{
"id": 5,
"salon": "A-6",
"materia": "Sistemas",
"hora_inicio": "10:00",
"hora_fin": "9:00",
"estado": 2,
"comentario": ""
},
{
"id": 6,
"salon": "A-7",
"materia": "Base de datos",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 7,
"salon": "A-8",
"materia": "Matemáticas",
"hora_inicio": "8:00",
"hora_fin": "11:00",
"estado": 1,
"comentario": ""
},
{
"id": 8,
"salon": "A-9",
"materia": "Base de datos",
"hora_inicio": "10:00",
"hora_fin": "8:00",
"estado": 3,
"comentario": ""
},
{
"id": 9,
"salon": "A-10",
"materia": "Matemáticas",
"hora_inicio": "10:00",
"hora_fin": "10:00",
"estado": 0,
"comentario": ""
},
{
"id": 10,
"salon": "A-11",
"materia": "Sistemas",
"hora_inicio": "9:00",
"hora_fin": "10:00",
"estado": 3,
"comentario": ""
},
{
"id": 11,
"salon": "A-12",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "8:00",
"estado": 1,
"comentario": ""
},
{
"id": 12,
"salon": "A-13",
"materia": "Base de datos",
"hora_inicio": "8:00",
"hora_fin": "10:00",
"estado": 4,
"comentario": ""
},
{
"id": 13,
"salon": "A-14",
"materia": "Base de datos",
"hora_inicio": "8:00",
"hora_fin": "8:00",
"estado": 0,
"comentario": ""
},
{
"id": 14,
"salon": "A-15",
"materia": "Matemáticas",
"hora_inicio": "11:00",
"hora_fin": "9:00",
"estado": 0,
"comentario": ""
}
]
}
]

224
selector_rutas.php Normal file
View File

@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Supervisor - Seleccionar rutas</title>
<?php
include 'import/html_css_files.php';
?>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<?php
include "import/html_header.php";
html_header(
"Selecciona la ruta a supervisar",
"Sistema de gestión de checador",
);
?>
<main class="container-fluid px-4" id="app" v-cloak @vue:mounted="mounted">
<!-- filtros -->
<div class="card mt-4">
<div class="card-header bg-dark d-flex justify-content-between align-items-center flex-wrap text-white">
<h2 class="col-md-10 col-12 text-white font-weight-bold text-uppercase text-center">
Facultad de ingeniería: {{ store.rutas.data[store.rutas.selected]?.ruta ?? '' }}
</h2>
<button type="button" class="btn btn-success btn-sm" data-toggle="modal"
data-target="#editar-ubicaciones">
<i class="ing-editar"></i>
</button>
</div>
<div class="card-body bg-info">
<div class="container-fluid">
<div class="row flex-nowrap overflow-auto">
<!-- size big -->
<div class="col-9 col-sm-7 col-md-4 col-lg-3 col-xl-2 my-2"
v-for="(ruta, index) in store.rutas.data" :key="ruta.id">
<span class="shadow badge badge-pill py-2 px-4" @click="store.selectRuta(ruta.id)"
:class="{ 'badge-primary': store.rutas.selected == ruta.id, 'badge-light text-primary': store.rutas.selected != ruta.id, 'badge-dark text-muted disabled' : ruta.horarios.every(({estado}) => estado != 0) && store.rutas.selected != ruta.id }">
{{ ruta.ruta }}
<span class="badge mx-3" v-if="ruta.horarios.some(({estado}) => estado == 0)"
:class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
{{ruta.horarios.filter(({estado}) => estado != 0).length}} / {{
ruta.horarios.length}}
</span>
<span v-else class="badge mx-3"
:class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
<i class="ing-aceptar"></i>
</span>
<span class="sr-only">Faltan {{ruta.horarios.filter(({estado}) => estado == 0).length}}
horarios por registrar</span>
</span>
</div>
</div>
</div>
</div>
<div class="card-footer bg-dark d-flex justify-content-between align-items-center flex-wrap text-white">
<button class="btn btn-info" :disabled="store.bloquesHorario.selected == 0"
@click="store.selectBloque(store.bloquesHorario.selected - 1)">
<i class="ing-caret ing-rotate-90"></i>
<span class="d-none d-md-inline-block">
Bloque horario anterior
</span>
</button>
<h3 class="text-white font-weight-bold text-uppercase text-center">
{{ store.hora_inicio.slice(0, 5) }} - {{ store.hora_fin.slice(0, 5) }}
</h3>
<button class="btn btn-info" @click="store.selectBloque(store.bloquesHorario.selected + 1)"
:disabled="store.bloquesHorario.selected == store.bloquesHorario.data.length - 1">
<span class="d-none d-md-inline-block">
Bloque horario siguiente
</span>
<i class="ing-caret ing-rotate-270"></i>
</button>
</div>
</div>
<div class="mt-3 d-flex justify-content-center flex-wrap">
<!-- refresh -->
<div class="table-responsive">
<table class="table table-hover table-striped table-bordered table-sm">
<thead class="thead-dark">
<tr>
<th scope="col" class="text-center align-middle text-nowrap px-2">
<button @click="store.invertir" class="btn btn-info mr-3" v-if="clases.length > 0">
<i class="ing-cambiar ing-rotate-90"></i>
</button>
Salón
</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Profesor</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Horario</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Acciones</th>
</tr>
</thead>
<tbody>
<tr v-if="clases.length == 0">
<td colspan="6" class="text-center">No hay clases en este horario</td>
</tr>
<tr v-for="clase in clases" :key="clase.id">
<td class="text-center align-middle">{{ clase.salon }}</td>
<td class="text-center align-middle">{{ clase.profesor }} {{ clase.materia }}
<td class="text-center align-middle">
{{ clase.hora_inicio }} - {{ clase.hora_fin }}
</td>
<td class="text-center align-middle text-nowrap">
<button class="btn btn-outline text-center mx-2" v-for="estado in estados"
:key="estado.id" @click="store.cambiarEstado(clase.id, estado.id)"
:class="[{'active': estado.id === clase.estado}, `btn-outline-${estado.color}`]">
<i :class="estado.icon"></i>
</button>
<button class="btn btn-outline-primary text-center mx-2" data-toggle="modal"
data-target="#editar-comentario" :class="{ 'active': clase.comentario != '' }"
@click="store.selectEditor(clase.id)">
<i class="ing-editar"></i>
<span class="badge badge-pill badge-primary"
v-if="clase.comentario != ''">...</span>
<span class="sr-only">Editar comentario</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- MODAL -->
<div class="modal" tabindex="-1" id="editar-ubicaciones">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Editar rutas</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<h2>Reordena las rutas</h2>
<ul id="sortable" class="list-group">
<li class="list-group-item" v-for="ruta in store.rutas.data" :key="ruta.id"
:id="'ruta-' + ruta.id"
:class="[ruta.horarios.every(horario => horario.estado != 0) ? ['disabled', 'bg-light', 'undraggable'] : '']">
{{ruta.ruta}}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block mb-4" @click="store.guardarCambios">
<i class="ing-guardar"></i>
Guardar cambios
</button>
<div class="modal" tabindex="-1" id="editar-comentario">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Añadir comentario</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<h2 class="text-center">Comentarios de la clase</h2>
<br>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text bg-primary text-white">Comentario
<button class="btn btn-light ml-2 text-primary"
@click="store.limpiarComentario">
<i class="ing-borrar"></i>
</button>
</span>
</div>
<textarea class="form-control" aria-label="Comentarios de la clase"
v-model="store.editor.texto"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" data-dismiss="modal">
<i class="ing-cancelar"></i>
Cancelar
</button>
<button type="button" class="btn btn-primary" data-dismiss="modal"
@click="store.guardarComentario">
Guardar comentario
</button>
</div>
</div>
</div>
</div>
</main>
<?php
include "import/html_footer.php";
?>
<!-- filtro modal -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/jquery-ui.touch-punch.min.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
<?php include_once 'js/messages.php'; ?>
<script src="https://unpkg.com/petite-vue"></script>
<script>
</script>
</body>
</html>

27
service/auto.php Normal file
View File

@@ -0,0 +1,27 @@
<?
$ruta = "../";
require_once "$ruta/include/bd_pdo.php";
header('Content-Type: application/json');
// json data from service\periodos.v1.php (input)
$urls = array(
'periodos.v1',
'periodos.v2',
'horarios',
);
$urls = array_map(fn($item) => "../$item.php", $urls);
ob_start();
include_once 'periodos.v1.php';
$periodos_v1 = ob_get_contents();
ob_end_clean();
ob_start();
include_once 'periodos.v2.php';
$periodos_v2 = ob_get_contents();
ob_end_clean();
// echo $periodos_v1;
echo $periodos_v2;

View File

@@ -0,0 +1,76 @@
<?
$ruta = "../../";
require_once "$ruta/include/bd_pdo.php";
header('Content-Type: application/json');
global $db;
// json data from service\periodos.v1.php (input)
$data = json_decode(file_get_contents('php://input'), true);
// check if the input is empty
if (is_response_empty($data)) {
echo json_encode([
'status' => 'error',
'message' => 'No se recibieron datos',
'data' => $data
]);
exit;
}
// check if data is array
if (!is_array($data)) {
echo json_encode([
'status' => 'error',
'message' => 'La información recibida no es válida',
'data' => $data
]);
exit;
}
/**
* [{
* carrera_nombre
* carrera_clave
* id_nivel
* },]
*/
// check for this schema
array_walk($data, function ($item) {
if (!isset($item['ClaveCarrera']) || !isset($item['NombreCarrera']) || !isset($item['IdNivel'])) {
echo json_encode([
'status' => 'error',
'message' => 'Los datos recibidos no son validos',
'data' => $item
]);
exit;
}
});
array_walk($data, function ($item) use ($db) {
if ($db->where('carrera_nombre', "%{$item['NombreCarrera']}", 'ILIKE')->has('carrera'))
$db
->where('carrera_nombre', "%{$item['NombreCarrera']}", 'ILIKE')
->update('carrera', [
'carrera_nombre' => $item['NombreCarrera'],
'id_referencia' => $item['ClaveCarrera'],
]);
else {
try {
$db->insert('carrera', [
'carrera_nombre' => $item['NombreCarrera'],
'id_referencia' => $item['ClaveCarrera'],
'nivel_id' => $item['IdNivel'],
]);
} catch (PDOException $th) {
echo json_encode([
'success' => false,
'message' => $th->getMessage(),
'last_query' => $db->getLastQuery(),
]);
exit;
}
}
});

View File

@@ -0,0 +1,60 @@
<?
$ruta = "../../";
require_once "$ruta/include/bd_pdo.php";
header('Content-Type: application/json');
// json data from service\periodos.v1.php (input)
$data = json_decode(file_get_contents('php://input'), true);
// check if the input is empty
if (is_response_empty($data)) {
echo json_encode([
'status' => 'error',
'message' => 'No se recibieron datos',
]);
exit;
}
/*
{
"IdNivel": 1,
"IdPeriodo": 635,
"NombreNivel": "LICENCIATURA",
"NombrePeriodo": "241",
"in_db": false
inicio,
fin
}
*/
// insert into database
setlocale(LC_TIME, 'es_MX.UTF-8');
$formatter = new IntlDateFormatter('es_MX', IntlDateFormatter::FULL, IntlDateFormatter::FULL, 'America/Mexico_City', IntlDateFormatter::GREGORIAN, 'MMMM');
$inicio = strtotime($data['inicio']);
$fin = strtotime($data['fin']);
try {
$result = $db->insert('periodo', [
'id_reference' => $data['IdPeriodo'],
'periodo_nombre' => "{$data['NombreNivel']}: {$formatter->format($inicio)} - {$formatter->format($fin)} " . date('Y', $inicio),
'nivel_id' => $data['IdNivel'],
'periodo_fecha_inicio' => $data['inicio'],
'periodo_fecha_fin' => $data['fin'],
'estado_id' => 4,
'periodo_clave' => $data['NombrePeriodo']
], ['id_reference']);
} catch (PDOException $th) {
echo json_encode([
'success' => false,
'message' => $th->getMessage()
]);
exit;
}
echo json_encode($result ? [
'success' => true,
'message' => 'Periodo agregado correctamente'
] : [
'success' => false,
'message' => 'Error al agregar el periodo'
]);

155
service/client.html Normal file
View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cliente REST</title>
<script type="module" src="../js/client.js" defer></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<link rel="stylesheet" href="../css/indivisa.css">
</head>
<body>
<header class="container-fluid bg-dark text-white text-center">
Página de Cliente REST
</header>
<main class="container" @vue:mounted="mounted">
<div v-for="error in store.errors" class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>Error!</strong> {{error}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="row">
<div class="col-12">
<h1>Periodos activos</h1>
</div>
</div>
<ul class="list-group">
<li href="#" class="list-group-item" v-for="(periodo, index) in store.periodosV1" :key="periodo.IdPeriodo">
<!-- v1
IdNivel: number;
IdPeriodo: number;
NombreNivel: string;
NombrePeriodo: string;
-->
<div class="row">
<span class="badge bg-secondary">{{index}}</span>
<h2 class="text-center">
Información
</h2>
<div class="col-md-3">
<strong>ID Nivel:</strong> {{periodo.IdNivel}}
</div>
<div class="col-md-3">
<strong>ID Periodo:</strong> {{periodo.IdPeriodo}}
</div>
<div class="col-md-3">
<strong>Nombre Nivel:</strong> {{periodo.NombreNivel}}
</div>
<div class="col-md-3">
<strong>Nombre Periodo:</strong> {{periodo.NombrePeriodo}}
</div>
</div>
<!--
FechaFin: string;
FechaInicio: string;
IdPeriodo: number;
-- info(IdPeriodo) --
-->
<div class="row mt-2" v-if="complete(periodo.IdPeriodo)">
<h2 class="text-center">
Fechas
</h2>
<div class="col-md-2">
<strong>Fecha Inicio:</strong> {{info(periodo.IdPeriodo).FechaInicio}}
</div>
<div class="col-md-2">
<strong>Fecha Fin:</strong> {{info(periodo.IdPeriodo).FechaFin}}
</div>
</div>
<div v-if="!periodo.in_db" class="row mt-2">
<!--
PeriodoV2
ClaveCarrera: string;
NombreCarrera: string;
PeriodoV1
IdNivel: number;
-->
<div class="col-md-12">
<button class="btn btn-primary float-end" @click="store.addPeriodo(periodo)"
:disabled="!complete(periodo.IdPeriodo)">
Agregar
<i class="ing ing-mas"></i>
</button>
</div>
</div>
<div v-else class="row mt-2">
<div class="col-md-12">
<button class="btn btn-success float-end disabled">
Agregado
<i class="ing-aceptar"></i>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<button class="btn btn-secondary float-end" @click="store.addCarreras(periodo.IdPeriodo)">
Sincronizar Carreras
<i class="ing ing-link"></i>
</button>
</div>
</div>
<div class="accordion mt-2">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-toggle="collapse"
:data-bs-target="`#collapse-${periodo.IdPeriodo}`">
Horarios del periodo
</button>
</h2>
<div :id="`collapse-${periodo.IdPeriodo}`" class="accordion-collapse collapse">
<div class="accordion-body">
<ul class="list-group">
<li class="list-group-item"
v-for="periodo in store.periodosV2.filter(periodov2 => periodov2.IdPeriodo === periodo.IdPeriodo)">
<div class="row">
<div class="col-md-3">
<strong>Clave Carrera:</strong> {{periodo.ClaveCarrera}}
</div>
<div class="col-md-6">
<strong>Nombre Carrera:</strong> {{periodo.NombreCarrera}}
</div>
<div class="col-md-3">
<span class="badge float-end mx-1"
:class="periodo.linked ?'bg-success':'bg-secondary'">
<i class="ing-link"></i>
</span>
<span class="badge float-end mx-1"
:class="periodo.in_db ?'bg-success':'bg-secondary'">
<i class="ing-aceptar"></i>
</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</li>
</ul>
</main>
<footer>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"></script>
</body>
</html>

81
service/horarios.php Normal file
View File

@@ -0,0 +1,81 @@
<?
/*
• idPeriodo: identificador del periodo a consultar (obligatorio, número entero)
• claveFacultad: clave de la facultad a consultar (opcional, cadena)
• claveCarrera: clave de la carrera a consultar (opcional, cadena)
• claveProfesor: clave del empleado a consultar (opcional, cadena)
• fecha: fecha de la clase (opcional, cadena en formato yyyy-MM-dd)
*/
$required_params = [
'idPeriodo'
];
$optional_params = [
'claveFacultad',
'claveCarrera',
'claveProfesor',
'fecha'
];
// Check if all required params are present in $_GET
$params = array_map('strtolower', $_GET); // Convert keys to lowercase for case-insensitive comparison
// Check for missing required parameters
$missing_params = array_diff($required_params, array_keys($params));
if (!empty($missing_params)) {
$missing_params_str = implode(', ', $missing_params);
die("Missing required parameter(s): $missing_params_str");
}
// Filter and retain only the required and optional parameters
$params = array_filter($params, function ($key) use ($required_params, $optional_params) {
return in_array($key, $required_params) || in_array($key, $optional_params);
}, ARRAY_FILTER_USE_KEY);
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/seleccionar",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => [
"token: e12e2dde0e95a32e274328fd274e07d53f127630c211d838efffacd3cafc4f14edf3f3de6a649eb23f98edf6a1863a008f60e78a316d4dec996b79aeea161a0c",
"username: SGU_APSA_AUD_ASIST",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err)
die("cURL Error #:$err");
$selectedData = json_decode($response, true);
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
// check for {collect: []} in raw input
if (isset($input['collect']) && is_array($input['collect'])) {
$collect = $input['collect'];
$selectedData = array_map(function ($item) use ($collect) {
return array_intersect_key($item, array_flip($collect));
}, $selectedData);
// unique and distinct
$selectedData = array_unique($selectedData, SORT_REGULAR);
}
else {
// return invalid request error
die($rawInput);
}
// Output the selected data directly
header('Content-Type: application/json');
echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

35
service/periodos.v1.php Normal file
View File

@@ -0,0 +1,35 @@
<?
$ruta = "../";
require_once '../include/bd_pdo.php';
$curl = curl_init();
global $db;
curl_setopt_array($curl, [
CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/periodos/v1/seleccionar",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => "",
CURLOPT_HTTPHEADER => [
"token: f65f921264e4ab135472adb5a946212dd4b995d929294afec31eef192b8de8d6a076648906f70012c9803e5918d0fc99499d7d1fb7c998cc06c7a10eef61f66a",
"username: SGU_APSA_AUD_ASIST"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err)
die("cURL Error #:$err");
$data = json_decode($response, true);
$in_db = array_map(function ($item) use ($db) {
$item['in_db'] = $db->where('id_reference', $item['IdPeriodo'])->has('periodo');
return $item;
}, $data);
$selectedData = array_unique($in_db, SORT_REGULAR);
// Output the selected data directly
header('Content-Type: application/json');
echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

37
service/periodos.v2.php Normal file
View File

@@ -0,0 +1,37 @@
<?
$ruta = "../";
require_once '../include/bd_pdo.php';
global $db;
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://portal.ulsa.edu.mx/servicios/AuditoriaAsistencialRest/AuditoriaAsistencialService.svc/auditoriaAsistencial/catalogos/periodos/v2/seleccionar",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => "",
CURLOPT_HTTPHEADER => [
"token: f65f921264e4ab135472adb5a946212dd4b995d929294afec31eef192b8de8d6a076648906f70012c9803e5918d0fc99499d7d1fb7c998cc06c7a10eef61f66a",
"username: SGU_APSA_AUD_ASIST"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err)
die("cURL Error #:$err");
$json = json_decode($response, true);
$selectedData = array_map(function ($item) use ($db) {
$item['in_db'] = $db->where('carrera_nombre', $item['NombreCarrera'], 'ILIKE')->has('carrera');
$item['linked'] = $db->where('id_referencia', $item['ClaveCarrera'], 'ILIKE')->has('carrera');
return $item;
}, $json);
// Output the selected data directly
header('Content-Type: application/json');
echo json_encode($selectedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

734
supervisor.php Normal file
View File

@@ -0,0 +1,734 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Supervisor</title>
<?php
include 'import/html_css_files.php';
?>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<?
$redirect = $_SERVER['PHP_SELF'];
include "import/html_header.php";
// 200.0.0.1/checador_otros/admin_checdor/[this_page].php => ruta = [this_page].php
global $user;
html_header(
"Registro de asistencia - Vicerrectoría Académica",
"Sistema de gestión de checador",
);
?>
<main class="container-fluid px-4" id="app" v-cloak @vue:mounted="mounted">
<!-- error messages -->
<div class="container mb-4 mt-2">
<div class="row">
<div class="col-12">
<div class="alert alert-dismissible fade show" role="alert" v-for="message in messages.data"
:class="`alert-${message.color}`" :key="message.hora">
<!-- messages: {error, hora} -->
<div :key="message" class="d-flex justify-content-between">
<span>
<code>[{{message.hora}}]</code>
<strong>{{message.prefix}}</strong>
</span>
{{ message.message }}
<button type="button" class="close"
@click="messages.data.splice(messages.data.indexOf(message), 1)" data-dismiss="alert">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- filtros -->
<div v-if="store.rutas.data.length > 0">
<div class="card mt-4">
<div class="card-header bg-dark d-flex justify-content-between align-items-center flex-wrap text-white">
<h2 class="col-md-10 col-12 text-white font-weight-bold text-uppercase text-center">
{{ JSON.parse(store.rutas.data.find(ruta => ruta.salon_id === store.rutas.selected)?.salon_array
?? null)?.splice(1)?.join('/') ?? 'No datos' }}
</h2>
<div>
<button type="button" class="btn btn-info btn-sm"
@click="store.rutas.data = []; header = 'Seleccione una ruta'">
<i class="ing-flecha ing-rotate-180"></i>
</button>
<button type="button" class="btn btn-success btn-sm" data-toggle="modal"
data-target="#editar-ubicaciones">
<i class="ing-editar"></i>
</button>
</div>
</div>
<div class="card-body bg-info">
<div class="container-fluid">
<div class="row flex-nowrap mw-100 overflow-auto">
<!-- size big -->
<div class="mx-2 my-2 col-auto" v-for="ruta in store.rutas.data" :key="ruta.salon_id">
<span class="shadow badge badge-pill py-2 px-4" @click="store.selectRuta(ruta.salon_id)"
:class="{ 'badge-primary': store.rutas.selected == ruta.salon_id, 'badge-light text-primary': store.rutas.selected != ruta.salon_id, 'badge-dark text-muted disabled' : ruta.horarios.every(({estado_supervisor_id}) => estado_supervisor_id) && store.rutas.selected != ruta.salon_id }">
{{ JSON.parse(ruta.salon_array).splice(1).join('/') }}
<span class="badge mx-3"
v-if="ruta.horarios.some(({estado_supervisor_id}) => !estado_supervisor_id)"
:class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
{{ ruta.horarios.filter(({estado_supervisor_id}) => estado_supervisor_id).length
}} / {{
ruta.horarios.length }}
</span>
<span v-else class="badge mx-3"
:class="{ 'badge-success': ruta.horarios.length > 0, 'badge-danger': ruta.horarios.length == 0 }">
<i class="ing-aceptar"></i>
</span>
<span class="sr-only">
Faltan {{ ruta.horarios.filter(({estado_supervisor_id}) =>
estado_supervisor_id).length }} horarios
por registrar
</span>
<span class="badge mx-1 badge-warning" @click="location.hash = '#sin-internet'"
v-if="ruta.horarios.some(({pendiente}) => pendiente)">
<i class="ing-importante2"></i>
</span>
</span>
</div>
</div>
</div>
</div>
<div class="card-footer bg-dark d-flex justify-content-between align-items-center flex-wrap text-white">
<button class="btn btn-info" :disabled="store.bloquesHorario.selected == 0"
@click="store.selectBloque(store.bloquesHorario.selected - 1); rutas(current_espacio)">
<i class="ing-caret ing-rotate-90"></i>
<span class="d-none d-md-inline-block">
Bloque horario anterior
</span>
</button>
<h3 class="text-white font-weight-bold text-uppercase text-center">
{{ store.hora_inicio.slice(0, 5) }} - {{ store.hora_fin.slice(0, 5) }}
</h3>
<button class="btn btn-info"
@click="store.selectBloque(store.bloquesHorario.selected + 1); rutas(current_espacio)"
:disabled="store.bloquesHorario.selected == store.bloquesHorario.data.length - 1">
<span class="d-none d-md-inline-block">
Bloque horario siguiente
</span>
<i class="ing-caret ing-rotate-270"></i>
</button>
</div>
</div>
<section id="#warnings" class="mt-4" v-if="clases.some(clase => clase.pendiente)">
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading"><i class="ing-importante2"></i> Sin conexión a internet</h4>
<p>
Hay datos en esta ruta que no pudieron guardarse, por favor, revise su conexión a internet y dé
click en
<button class="btn btn-outline-dark btn-sm mb-4" @click="guardarCambios"><i
class="ing-guardar"></i> Guardar cambios</button>
</p>
<hr>
<p class="mb-0">
Los datos se mantendrán mientras tenga la página abierta, pero si la cierra o la refresca, se
perderán.
</p>
</div>
</section>
<div class="mt-3 d-flex justify-content-center">
<!-- refresh -->
<div class="table-responsive">
<table class="table table-hover table-striped table-bordered table-sm">
<thead class="thead-dark">
<tr>
<th scope="col" class="text-center align-middle text-nowrap px-2">
<button @click="invertir" class="btn btn-info mr-3" v-if="clases.length > 0">
<i class="ing-cambiar ing-rotate-90"></i>
</button>
Salón
</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Profesor</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Horario</th>
<th scope="col" class="text-center align-middle text-nowrap px-2">Acciones</th>
</tr>
</thead>
<tbody>
<tr v-if="clases.length == 0">
<td colspan="6" class="text-center">No hay clases en este horario</td>
</tr>
<tr v-for="clase in clases" :key="clase.horario_id">
<td class="text-center align-middle">{{ clase.salon }}</td>
<td class="text-center align-middle">
<div class="col-12">
{{ clase.profesor_nombre }}
</div>
<div class="col-12">
<button type="button" class="btn btn-outline-dark btn-sm"
@click="store.profesor_selected = clase.horario_id" data-toggle="modal"
data-target="#ver-detalle">
Ver detalle <i class="ing-ojo"></i>
</button>
</div>
</td>
<td class="text-center align-middle">
{{ clase.hora_inicio.slice(0, 5) }} - {{ clase.hora_fin.slice(0, 5) }}
</td>
<td class="text-center align-middle text-nowrap">
<!-- data-toggle="button" -->
<button class="btn text-center mx-2" v-for="estado in estados" :key="estado.id"
@click="store.cambiarEstado(clase.horario_id, estado.id === clase.estado_supervisor_id ? null : estado.id)"
:class="[{'active': estado.id === clase.estado_supervisor_id}, `btn-outline-${estado.color}`]"
:aria-pressed="estado.id === clase.estado_supervisor_id">
<i :class="estado.icon"></i>
</button>
<button class="btn btn-outline-primary text-center mx-2" data-toggle="modal"
data-target="#editar-comentario" :class="{ 'active': clase.comentario }"
@click="store.selectEditor(clase.horario_id)">
<i class="ing-editar"></i>
<span class="badge badge-pill badge-primary" v-if="clase.comentario">...</span>
<span class="sr-only">Editar comentario</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block mb-4" @click="guardarCambios">
<i class="ing-guardar"></i>
Guardar cambios
</button>
</div>
<div v-else-if="store.bloquesHorario.selected === -1">
<div class="list-group my-4 container">
<div class="card text-center">
<div class="card-header bg-dark text-white">
<h2 class="text-center">
{{header}}
</h2>
</div>
<div class="card-body" v-if="!loading">
<a :href="`#horario-${horario.id}`" class="list-group-item list-group-item-action"
v-for="horario in store.bloquesHorario.data" :key="horario.id"
@click="store.bloquesHorario.selected = store.bloquesHorario.data.indexOf(horario)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{ horario.hora_inicio.slice(0, 5) }} - {{horario.hora_fin.slice(0, 5)
}}</h5>
</div>
</a>
</div>
<div class="card-body" v-else>
<div class="d-flex justify-content-center">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Cargando...</span>
</div>
</div>
</div>
<div class="card-footer text-muted bg-dark text-white">
Lista de bloques horario
</div>
</div>
</div>
</div>
<div v-else>
<div class="list-group my-4 container">
<div class="card text-center">
<div class="card-header bg-dark text-white">
<h2 class="text-center">
{{header}}
</h2>
</div>
<div class="card-body" v-if="!loading">
<a :href="`#ruta-${ruta.id_espacio_sgu}`" class="list-group-item list-group-item-action"
v-for="ruta in catálogo_rutas.data" :key="ruta.salon_id" @click="rutas(ruta.id_espacio_sgu)"
disabled>
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{ ruta.salon }}</h5>
<small v-if="ruta.subrutas.length > 0">{{ ruta.subrutas.length }} espacios</small>
<small v-else class="text-danger">Sin espacios</small>
</div>
</a>
</div>
<div class="card-body" v-else>
<div class="d-flex justify-content-center">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Cargando...</span>
</div>
</div>
</div>
<div class="card-footer text-muted bg-dark text-white">
Rutas de la Universidad La Salle
</div>
</div>
</div>
</div>
<!-- MODAL -->
<div class="modal" tabindex="-1" id="editar-ubicaciones">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Editar rutas</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<h2>Reordena las rutas</h2>
<ul id="sortable" class="list-group">
<li class="list-group-item" v-for="ruta in store.rutas.data" :key="ruta.salon_id"
:id="'ruta-' + ruta.salon_id"
:class="[ruta.horarios.every(horario => horario.estado_supervisor_id) ? ['disabled', 'bg-light', 'undraggable'] : '']">
{{ JSON.parse(ruta.salon_array).join('/') }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="editar-comentario">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Añadir comentario</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<h2 class="text-center">Comentarios de la clase</h2>
<br>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text bg-primary text-white">Comentario
<button class="btn btn-light ml-2 text-primary"
@click="store.limpiarComentario">
<i class="ing-borrar"></i>
</button>
</span>
</div>
<textarea class="form-control" aria-label="Comentarios de la clase"
v-model="store.editor.texto"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" data-dismiss="modal">
<i class="ing-cancelar"></i>
Cancelar
</button>
<button type="button" class="btn btn-primary" data-dismiss="modal"
@click="store.guardarComentario">
Guardar comentario
</button>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="ver-detalle">
<div class="modal-dialog modal-dialog-centered modal-xl" v-if="clase_vista">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" :data-id="clase_vista.horario_id">Detalle de la clase</h2>Detalle de la clase</h2>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container" v-if="store.profesor_selected">
<div class="row">
<section class="col-12 col-md-6">
<h4 class="h4">Profesor</h4>
<div class="row">
<div class="col-12">
<strong>Nombre:</strong>
{{ clase_vista.profesor_nombre }}
</div>
<div class="col-12">
<strong>Correo:</strong>
<a :href="`mailto:${clase_vista.profesor_correo}`"><strong>{{
clase_vista.profesor_correo }}</strong></a>
</div>
<div class="col-12">
<strong>Clave:</strong>
{{ clase_vista.profesor_clave }}
</div>
<div class="col-12">
<strong>Facultad:</strong>
{{ clase_vista.facultad }}
</div>
</div>
</section>
<section class="col-12 col-md-6">
<h4 class="h4">Clase</h4>
<div class="row">
<div class="col-12">
<strong>Materia:</strong>
{{ clase_vista.materia }}
</div>
<div class="col-12">
<strong>Carrera:</strong>
{{ clase_vista.carrera }}
</div>
<div class="col-12">
<strong>Grupo:</strong>
{{ clase_vista.horario_grupo }}
</div>
<div class="col-12">
<strong>Horario:</strong>
<!-- hora hh:mm:ss to hh:mm -->
{{ clase_vista.hora_inicio?.slice(0, 5) }} - {{
clase_vista.hora_fin?.slice(0, 5) }}
</div>
<div class="col-12">
<strong>Salón:</strong>
{{ clase_vista.salon }}
</div>
</div>
</section>
</div>
<div class="row">
<section class="col-12">
<h4 class="h4 mt-4">Registro</h4>
<div class="row">
<div class="col-12 text-center" v-if="!clase_vista.registro_fecha">
<strong><span class="badge badge-danger"><i class="ing-cancelar"></i></span>
El profesor aún no ha registrado su asistencia</strong>
</div>
<div class="col-6 text-center" v-else>
El profesor registró su asistencia a las
<code>{{clase_vista.registro_fecha.slice(11, 16)}}</code>
<hr>
<p v-if="!clase_vista.registro_retardo" class="text-center">
<span class="badge badge-success"><i class="ing-aceptar"></i></span>
A tiempo
</p>
<p v-else class="text-center">
<span class="badge badge-warning"><i class="ing-retardo"></i></span>
Con retardo
</p>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="modal-footer">
<!-- botón aceptar -->
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">
<i class="ing-aceptar"></i>
Aceptar
</button>
</div>
</div>
</div>
</div>
</div>
</main>
<?php
include "import/html_footer.php";
?>
<!-- filtro modal -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/jquery-ui.touch-punch.min.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
<?php include_once 'js/messages.php'; ?>
<script src="https://unpkg.com/petite-vue"></script>
<script>
const estados = [
{
color: "success",
icon: "ing-autorizar",
id: 1,
},
{
color: "danger",
icon: "ing-negar",
id: 2,
},
{
color: "warning",
icon: "ing-retardo",
id: 3,
},
{
color: "info",
icon: "ing-justificar",
id: 4,
},
];
const messages = PetiteVue.reactive({
data: [],
push_message(message, silent = false) {
if (silent) {
console.log(message);
return
}
// go to the top
window.scrollTo({
top: 0,
behavior: 'smooth'
});
this.data.push(message);
setTimeout(() => {
this.data.pop();
}, 5000);
},
});
const store = PetiteVue.reactive({
messages,
bloquesHorario: {
data: [],
selected: 0
},
rutas: {
data: [],
selected: 0
},
editor: {
id: 0,
texto: "",
},
get hora_inicio() {
return this.bloquesHorario.data[this.bloquesHorario.selected]?.hora_inicio ?? "";
},
get hora_fin() {
return this.bloquesHorario.data[this.bloquesHorario.selected]?.hora_fin ?? "";
},
selectRuta(index) {
this.rutas.selected = index;
},
order() {
const finals = this.rutas.data.filter(ruta => ruta.horarios.length > 0 && ruta.horarios.every(horario => horario.estado_supervisor_id));
const lasts = this.rutas.data.filter(ruta => ruta.horarios.length == 0);
const notLasts = this.rutas.data.filter(ruta => ruta.horarios.some(horario => !horario.estado_supervisor_id));
// console.log("finals", finals, "lasts", lasts, "notLasts", notLasts)
this.rutas.data = [...notLasts, ...finals, ...lasts];
},
// clases
selectBloque(bloqueIndex) {
this.bloquesHorario.selected = bloqueIndex;
},
// estado
async cambiarEstado(horario_id, estadoId) {
const ruta = store.rutas.data.find(ruta => ruta.salon_id == this.rutas.selected);
const clase = ruta.horarios.find(clase => clase.horario_id == horario_id);
clase.estado_supervisor_id = estadoId;
try {
if (!navigator.onLine) {
clase.pendiente = true;
throw ("No hay conexión a internet");
}
const cambio = await fetch("action/registro_supervisor.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify([{
horario_id: horario_id,
estado: estadoId,
profesor_id: clase.profesor_id,
comentario: clase.comentario,
supervisor_id: <?= $user->user['id'] ?>,
}])
}).then(res => res.json());
if (cambio.error) throw cambio.error;
clase.pendiente = false;
} catch (error) {
messages.push_message({
message: error,
hora: new Date().toLocaleTimeString('es-MX', { timeZone: 'America/Mexico_City' }),
color: "danger",
prefix: "Error",
}, true);
}
// scroll to the top only if this ruta has no clases with estado 0
if (ruta.horarios.every(clase => clase.estado_supervisor_id != null))
window.scrollTo({
top: 0,
behavior: 'smooth'
});
this.order();
},
// editor
selectEditor(horario_id) {
this.editor.id = horario_id;
this.editor.texto = store.rutas.data.find(ruta => ruta.salon_id == this.rutas.selected).horarios.find(clase => clase.horario_id == horario_id).comentario;
},
guardarComentario() {
const ruta = store.rutas.data.find(ruta => ruta.salon_id == this.rutas.selected);
const clase = ruta.horarios.find(clase => clase.horario_id == this.editor.id);
clase.comentario = this.editor.texto;
store.cambiarEstado(clase.horario_id, clase.estado_supervisor_id);
},
limpiarComentario() {
this.editor.texto = "";
},
profesor_selected: null,
});
$(document).ready(function () {
$("#sortable").sortable({
update: function (event, ui) {
// get the new order
var newOrder = $(this).children().map(function () {
// id = ruta-{id}
return parseInt(this.id.split('-')[1]);
}).get();
// store the new order
store.rutas.data = newOrder.map(function (id) {
return store.rutas.data.find(function (ruta) {
return ruta.salon_id === id;
});
});
},
items: "li:not(.undraggable)"
}).disableSelection();
$('#sortable>li:not(.undraggable)').draggable({
axis: 'y',
containment: 'parent',
})
});
PetiteVue.createApp({
store,
messages,
header: "Cargando auditoría",
loading: true,
catálogo_rutas: {
data: [],
selected: 0
},
get clases() {
const clases = store.rutas.data.find(ruta => ruta.salon_id == store.rutas.selected)?.horarios ?? [];
// console.log("All clases", JSON.parse(JSON.stringify(clases)), "Selected: ", store.rutas.selected);
return clases;
},
async guardarCambios() {
try {
if (!navigator.onLine)
throw "No hay conexión a internet";
console.log(store.rutas.data.map(ruta => ruta.horarios).flat(1).filter(clase => clase.pendiente));
const cambio = await fetch("action/registro_supervisor.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(store.rutas.data.map(ruta => ruta.horarios).flat(1).filter(clase => clase.pendiente).map(clase => ({
horario_id: clase.horario_id,
estado: clase.estado_supervisor_id,
profesor_id: clase.profesor_id,
comentario: clase.comentario,
supervisor_id: <?= $user->user['id'] ?>,
}))),
}).then(res => res.json());
if (cambio.error) throw cambio.error;
} catch (error) {
messages.push_message({
message: error,
hora: new Date().toLocaleTimeString('es-MX', { timeZone: 'America/Mexico_City' }),
color: "danger",
prefix: "Error",
});
return
}
store.rutas.data.map(ruta => ruta.horarios).flat(1).filter(clase => clase.pendiente).forEach(clase => clase.pendiente = false);
messages.push_message({
message: "Cambios guardados",
hora: new Date().toLocaleTimeString('es-MX', { timeZone: 'America/Mexico_City' }),
color: "success",
prefix: "Éxito",
});
},
invertir() {
this.clases.reverse();
},
async mounted() {
store.bloquesHorario.data = await fetch('action/action_grupo_horario.php').then(res => res.json());
store.bloquesHorario.selected = store.bloquesHorario.data.findIndex(bloque => bloque.selected);
// console.log(store.bloquesHorario.selected);
if (store.bloquesHorario.selected == -1) {
this.header = "Seleccione un horario";
}
else {
this.header = "Seleccione una ruta";
}
this.catálogo_rutas.data = await fetch('action/rutas.php').then(res => res.json());
this.loading = false;
},
current_espacio: null,
async rutas(id_espacio_sgu) {
store.rutas.data = [];
store.rutas.selected = 0;
this.loading = true;
this.current_espacio = id_espacio_sgu;
this.loading = true;
this.header = `Cargando rutas para ${this.catálogo_rutas.data.find(ruta => ruta.id_espacio_sgu == id_espacio_sgu).salon}`;
this.catálogo_rutas.selected = id_espacio_sgu;
const url = 'action/rutas_salón_horario.php'
const searchParams = new URLSearchParams({
id_espacio_sgu: id_espacio_sgu,
bloque_horario_id: store.bloquesHorario.data[store.bloquesHorario.selected].id
});
const rutas = await fetch(`${url}?${searchParams}`).then(res => res.json());
store.rutas.data = rutas.filter(ruta => ruta.horarios.length > 0);
if (store.rutas.data.length == 0) {
this.header = `No hay clases en este horario`;
this.loading = false;
return
}
store.rutas.selected = store.rutas.data[0].salon_id;
store.order();
// inject horarios
this.loading = false;
},
get clase_vista() {
return this.clases.find(clase => clase.horario_id == store.profesor_selected) ?? false;
},
}).mount('#app')
</script>
</body>
</html>

237
ts/auditoría.ts Normal file
View File

@@ -0,0 +1,237 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
import { text } from 'stream/consumers';
type Registro = {
carrera: string;
carrera_id: number;
comentario: null;
dia: string;
duracion: string;
duracion_id: number;
estado_supervisor_id: number;
facultad: string;
facultad_id: number;
horario_dia: number;
horario_fin: string;
horario_grupo: string;
horario_hora: string;
horario_id: number;
materia: string;
materia_id: number;
nombre: string;
periodo: string;
periodo_id: number;
profesor_clave: string;
profesor_correo: string;
profesor_grado: null;
profesor_id: number;
profesor_nombre: string;
registro_fecha: null;
registro_fecha_ideal: Date;
registro_fecha_supervisor: Date;
registro_id: number;
registro_justificada: null;
registro_retardo: null;
salon: string;
salon_id: number;
supervisor_id: number;
}
type Estado = {
color: string;
icon: string;
estado_supervisor_id: number;
}
type Facultad = {
clave_dependencia: string;
facultad_id: number;
facultad_nombre: string;
}
type Filter = {
type: string;
value: string;
icon: string;
field: string;
label: string;
}
const store = reactive({
loading: false,
current: {
comentario: '',
clase_vista: null,
empty: '',
},
facultades: {
data: [] as Facultad[],
async fetch() {
this.data = [] as Facultad[]
const res = await fetch('action/action_facultad.php')
this.data = await res.json()
},
},
filters: {
facultad_id: null,
fecha: null,
fecha_inicio: null,
fecha_fin: null,
profesor: null,
estados: [],
switchFecha: false,
switchFechas() {
$(function () {
store.filters.fecha_inicio = store.filters.fecha_fin = store.filters.fecha = null
$("#fecha, #fecha_inicio, #fecha_fin").datepicker({
minDate: -3,
maxDate: new Date(),
dateFormat: "yy-mm-dd",
showAnim: "slide",
});
const fecha = $("#fecha"), inicio = $("#fecha_inicio"), fin = $("#fecha_fin")
inicio.on("change", function () {
store.filters.fecha_inicio = inicio.val()
fin.datepicker("option", "minDate", inicio.val());
});
fin.on("change", function () {
store.filters.fecha_fin = fin.val()
inicio.datepicker("option", "maxDate", fin.val());
});
fecha.on("change", function () {
store.filters.fecha = fecha.val()
});
});
}
},
estados: {
data: [] as Estado[],
async fetch() {
this.data = [] as Estado[]
const res = await fetch('action/action_estado_supervisor.php')
this.data = await res.json()
},
getEstado(id: number): Estado {
return this.data.find((estado: Estado) => estado.estado_supervisor_id === id)
},
printEstados() {
if (store.filters.estados.length > 0)
document.querySelector('#estados')!.innerHTML = store.filters.estados.map((estado: number) =>
`<span class="mx-2 badge badge-${store.estados.getEstado(estado).estado_color}">
<i class="${store.estados.getEstado(estado).estado_icon}"></i> ${store.estados.getEstado(estado).nombre}
</span>`
).join('')
else
document.querySelector('#estados')!.innerHTML = `Todos los registros`
}
},
toggle(arr: any, element: any) {
const newArray = arr.includes(element) ? arr.filter((item: any) => item !== element) : [...arr, element]
// if all are selected, then unselect all
if (newArray.length === this.estados.data.length) return []
return newArray
},
})
declare var $: any
type Profesor = {
profesor_id: number;
profesor_nombre: string;
profesor_correo: string;
profesor_clave: string;
profesor_grado: string;
}
createApp({
store,
get clase_vista() {
return store.current.clase_vista
},
registros: {
data: [] as Registro[],
async fetch() {
this.loading = true
this.data = [] as Registro[]
const res = await fetch('action/action_auditoria.php')
this.data = await res.json()
this.loading = false
},
invertir() {
this.data = this.data.reverse()
},
mostrarComentario(registro_id: number) {
const registro = this.data.find((registro: Registro) => registro.registro_id === registro_id)
store.current.comentario = registro.comentario
$('#ver-comentario').modal('show')
},
get relevant() {
/*
facultad_id: null,
fecha: null,
fecha_inicio: null,
fecha_fin: null,
profesor: null,
asistencia: null,
estado_id: null,
if one of the filters is null, then it is not relevant
*/
const filters = Object.keys(store.filters).filter((filtro) => store.filters[filtro] || store.filters[filtro]?.length > 0)
return this.data.filter((registro: Registro) => {
return filters.every((filtro) => {
switch (filtro) {
case 'fecha':
return registro.registro_fecha_ideal === store.filters[filtro];
case 'fecha_inicio':
return registro.registro_fecha_ideal >= store.filters[filtro];
case 'fecha_fin':
return registro.registro_fecha_ideal <= store.filters[filtro];
case 'profesor':
const textoFiltro = store.filters[filtro].toLowerCase();
if (/^\([^)]+\)\s[\s\S]+$/.test(textoFiltro)) {
const clave = registro.profesor_clave.toLowerCase();
const filtroClave = textoFiltro.match(/\((.*?)\)/)?.[1];
console.log(clave, filtroClave);
return clave.includes(filtroClave);
} else {
const nombre = registro.profesor_nombre.toLowerCase();
return nombre.includes(textoFiltro);
}
case 'facultad_id':
return registro.facultad_id === store.filters[filtro];
case 'estados':
if (store.filters[filtro].length === 0) return true;
return store.filters[filtro].includes(registro.estado_supervisor_id);
default:
return true;
}
})
})
},
},
get profesores() {
return this.registros.data.map((registro: Registro) => (
{
profesor_id: registro.profesor_id,
profesor_nombre: registro.profesor_nombre,
profesor_correo: registro.profesor_correo,
profesor_clave: registro.profesor_clave,
profesor_grado: registro.profesor_grado,
}
)).sort((a: Profesor, b: Profesor) =>
a.profesor_nombre.localeCompare(b.profesor_nombre)
)
},
async mounted() {
await this.registros.fetch()
await store.facultades.fetch()
await store.estados.fetch()
store.filters.switchFechas()
}
}).mount('#app')

149
ts/client.ts Normal file
View File

@@ -0,0 +1,149 @@
// @ts-ignore Import module
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
export interface PeridoV1 {
IdNivel: number;
IdPeriodo: number;
NombreNivel: string;
NombrePeriodo: string;
in_db: boolean;
linked: boolean;
}
export interface PeridoV2 {
ClaveCarrera: string;
FechaFin: string;
FechaInicio: string;
IdPeriodo: number;
NombreCarrera: string;
}
const webServices = {
getPeriodosV1: async (): Promise<PeridoV1[]> => {
try {
const response = await fetch('periodos.v1.php');
return await response.json();
} catch (error) {
console.log(error);
return [];
}
},
getPeriodosV2: async (): Promise<PeridoV2[]> => {
try {
const response = await fetch('periodos.v2.php');
return await response.json();
} catch (error) {
console.log(error);
return [];
}
}
}
const store = reactive({
periodosV1: [] as PeridoV1[],
periodosV2: [] as PeridoV2[],
errors: [] as string[],
fechas(idPeriodo: number): { inicio: string, fin: string } {
const periodo = this.periodosV2.find((periodo: PeridoV2) => periodo.IdPeriodo === idPeriodo);
return {
inicio: periodo ? periodo.FechaInicio : '',
fin: periodo ? periodo.FechaFin : ''
}
},
periodov1(idPeriodo: number): PeridoV1 | undefined {
return this.periodosV1.find((periodo: PeridoV1) => periodo.IdPeriodo === idPeriodo);
},
periodov2(idPeriodo: number): PeridoV2[] {
return this.periodosV2.filter((periodo: PeridoV2) => periodo.IdPeriodo === idPeriodo);
},
async addPeriodo(periodo: PeridoV1 | PeridoV2) {
try {
const result = await fetch('backend/periodos.php', {
method: 'POST',
body: JSON.stringify({
...periodo,
...this.fechas(periodo.IdPeriodo)
}),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
this.periodosV1 = this.periodosV1.map((periodoV1: PeridoV1) => {
if (periodoV1.IdPeriodo === periodo.IdPeriodo) {
periodoV1.in_db = true;
}
return periodoV1;
});
return result;
}
else {
this.errors.push(result.message);
}
} catch (error) {
this.errors.push(error);
}
},
async addCarreras(idPeriodo: number) {
try {
const periodoV1 = this.periodov1(idPeriodo) as PeridoV1;
const periodoV2 = this.periodov2(idPeriodo) as PeridoV2[];
const data = periodoV2.map(({ ClaveCarrera, NombreCarrera }: PeridoV2) =>
({
ClaveCarrera: ClaveCarrera,
NombreCarrera: NombreCarrera,
IdNivel: periodoV1.IdNivel,
})
);
const result = await fetch('backend/carreras.php', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
await webServices.getPeriodosV1().then((periodosV1) => {
this.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
this.periodosV2 = periodosV2;
});
}
} catch (error) {
this.errors.push(error);
}
}
})
createApp({
store,
info(IdPeriodo: number): PeridoV2 {
const periodo = store.periodosV2.find((periodo: PeridoV2) => periodo.IdPeriodo === IdPeriodo &&
periodo.FechaInicio != '' && periodo.FechaFin != '');
return periodo
},
complete(IdPeriodo: number): boolean {
const info = this.info(IdPeriodo);
return info !== undefined;
},
mounted: async () => {
await webServices.getPeriodosV1().then((periodosV1) => {
store.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
store.periodosV2 = periodosV2;
});
}
}).mount()

View File

@@ -1,347 +0,0 @@
// initial state
const días = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"];
const horas_estándar = /* range from 7 to 22 */ Array.from(Array(22 - 7 + 1).keys()).map(x => x + 7);
// fill the table with empty cells
for (let i = 0; i < horas_estándar.length - 1; i++) {
const hora = horas_estándar[i];
const tr = document.createElement("tr");
tr.id = `hora-${hora}-00`;
tr.classList.add(hora > 13 ? "tarde" : "mañana");
const th = document.createElement("th");
th.classList.add("text-center");
th.scope = "row";
th.rowSpan = 4;
th.innerText = `${hora}:00`;
th.style.verticalAlign = "middle";
tr.appendChild(th);
for (let j = 0; j < días.length; j++) {
const día = días[j];
const td = document.createElement("td");
td.id = `hora-${hora}-00-${día}`;
tr.appendChild(td);
}
document.querySelector("tbody#horario")?.appendChild(tr);
// add 7 rows for each hour
const hours = [15, 30, 45];
for (let j = 1; j < 4; j++) {
const tr = document.createElement("tr");
tr.id = `hora-${hora}-${hours[j - 1]}`;
tr.classList.add(hora > 13 ? "tarde" : "mañana");
for (let k = 0; k < días.length; k++) {
const día = días[k];
const td = document.createElement("td");
td.id = `hora-${hora}-${hours[j - 1]}-${día}`;
// td.innerText = `hora-${hora}-${hours[j - 1]}-${día}`;
tr.appendChild(td);
}
document.querySelector("tbody#horario")?.appendChild(tr);
}
}
// add an inital height to the table cells
const tds = document.querySelectorAll<HTMLTableRowElement>("tbody#horario td");
tds.forEach(td => td.style.height = "2rem");
var table = document.querySelector("table") as HTMLTableElement;
var empty_table = table?.innerHTML || "";
// hide the table
table.style.display = "none";
document.getElementById('dlProfesor')?.addEventListener('input', function () {
var input = document.getElementById('dlProfesor') as HTMLInputElement;
var value = input.value;
var option = document.querySelector(`option[value="${value}"]`);
if (option) {
var id: string = option.getAttribute('data-id')!;
const input_profesor: HTMLInputElement = document.getElementById('editor_profesor') as HTMLInputElement;
input_profesor.value = id;
// remove is invalid class
input.classList.remove("is-invalid");
// add is valid class
input.classList.add("is-valid");
} else {
const input_profesor: HTMLInputElement = document.getElementById('editor_profesor') as HTMLInputElement;
input_profesor.value = "";
// remove is valid class
input.classList.remove("is-valid");
// add is invalid class
input.classList.add("is-invalid");
}
});
/**
* Functions and Methods
**/
const buscarGrupo = async () => {
// Add loading animation in the button
const btn = document.querySelector("#btn-buscar") as HTMLButtonElement;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Cargando...';
btn.disabled = true;
const carrera = document.querySelector<HTMLInputElement>("#filter_carrera")?.value as string;
const grupo = document.querySelector<HTMLInputElement>("#filter_grupo")?.value as string;
console.log(`Carrera: ${carrera}, Grupo: ${grupo}`);
if (carrera == "" || grupo == "") {
triggerMessage("El nombre del grupo y la carrera son requeridos", "Faltan campos");
// Remove loading animation in the button
btn.innerHTML = '<i class="ing-buscar ing"></i> Buscar';
btn.disabled = false;
return;
}
const formData = new FormData();
formData.append("carrera", carrera);
formData.append("grupo", grupo);
formData.append("periodo", document.querySelector<HTMLInputElement>("#periodo")?.value!);
const thisScript = document.currentScript as HTMLScriptElement;
const facultad = thisScript.getAttribute("data-facultad") as string;
formData.append('facultad', facultad);
try {
const response = await fetch("action/action_horario.php", {
method: "POST",
body: formData
}).then(res => res.json());
if (response.status == "success") {
let limits = {
min: 22,
max: 7
};
let sábado = false;
const horario = response.horario;
// show the table
table.style.display = "table";
// clear the table
table.innerHTML = empty_table;
// fill the table
for (let i = 0; i < horario.length; i++) {
const dia = horario[i].dia;
if (dia == "sábado") sábado = true;
const {
hora,
minutos
} = {
hora: parseInt(horario[i].hora.split(":")[0]),
minutos: horario[i].hora.split(":")[1]
}
// update the limits
if (hora < limits.min) {
limits.min = hora;
}
if (hora > limits.max) {
limits.max = hora;
}
const materia = horario[i].materia;
const profesor = horario[i].profesor;
const salon = horario[i].salon;
const id = horario[i].horario_id;
const prof_id = horario[i].profesor_id;
const cell = document.querySelector(`#hora-${hora}-${minutos}-${dia}`) as HTMLTableCellElement;
cell.innerHTML =
`
<div>
<small class="text-gray">${hora}:${minutos}</small>
<b class="title">${materia}</b> <br>
<br><span>Salón: </span>${salon} <br>
<span class="ing ing-formacion mx-1"></span>${profesor}
</div>
`;
const html = `<div class="menu-flotante p-2">
<a
class="mx-2"
href="#"
data-toggle="modal"
data-target="#modal-editar"
data-dia="${dia}"
data-hora="${hora}:${minutos}"
data-materia="${materia}"
data-profesor="${prof_id}"
data-salon="${salon}"
data-id="${id}"
>
<i class="ing-editar ing"></i>
</a>
<a
class="mx-2"
href="#"
data-toggle="modal"
data-target="#modal-borrar"
data-hoario_id="${id}"
>
<i class="ing-basura ing"></i>
</a>
</div>`;
const td = cell.closest("td") as HTMLTableCellElement;
td.innerHTML += html;
td.classList.add("position-relative");
// this cell spans 4 rows
cell.rowSpan = 6;
cell.classList.add("bloque-clase", "overflow");
for (let j = 1; j < 6; j++) {
const minute = (parseInt(minutos) + j * 15)
const next_minute = (minute % 60).toString().padStart(2, "0");
const next_hour = (hora + Math.floor(minute / 60))
const next_cell = document.querySelector(`#hora-${next_hour}-${next_minute}-${dia}`) as HTMLTableCellElement;
next_cell.remove();
}
}
// remove the elements that are not in the limits
const horas = document.querySelectorAll("tbody#horario tr") as NodeListOf<HTMLTableRowElement>;
for (let i = 0; i < horas.length; i++) {
const hora = horas[i];
const hora_id = parseInt(hora.id.split("-")[1]);
if (hora_id < limits.min || hora_id > limits.max) {
hora.remove();
}
}
// if there is no sábado, remove the column
if (!sábado) {
document.querySelectorAll("tbody#horario td").forEach(td => {
if (td.id.split("-")[3] == "sábado") {
td.remove();
}
});
// remove the header (the last)
const headers = document.querySelector("#headers") as HTMLTableRowElement;
headers.lastElementChild?.remove();
}
// adjust width
const ths = document.querySelectorAll("tr#headers th") as NodeListOf<HTMLTableHeaderCellElement>;
const width = 95 / (ths.length - 1);
ths.forEach((th, key) =>
th.style.width = (key == 0) ? "5%" : `${width}%`
);
// search item animation
const menúFlontantes = document.querySelectorAll(".menu-flotante");
menúFlontantes.forEach((element) => {
element.classList.add("d-none");
element.parentElement?.addEventListener("mouseover", () => {
element.classList.remove("d-none");
});
element.parentElement?.addEventListener("mouseout", () => {
element.classList.add("d-none");
});
});
} else {
triggerMessage(response.message, "Error");
// Remove loading animation in the button
btn.innerHTML = '<i class="ing-buscar ing"></i> Buscar';
btn.disabled = false;
}
} catch (error) {
triggerMessage("Error al cargar el horario", "Error");
triggerMessage(error, "Error");
}
// Remove loading animation in the button
btn.innerHTML = '<i class="ing-buscar ing"></i> Buscar';
btn.disabled = false;
}
async function guardar(id: string) {
interface Data {
hora: HTMLInputElement;
dia: HTMLInputElement;
profesor: HTMLInputElement;
salon: HTMLInputElement;
}
const btn: HTMLButtonElement = document.querySelector("#btn-guardar") as HTMLButtonElement;
const clone: HTMLButtonElement = btn.cloneNode(true) as HTMLButtonElement;
btn.innerHTML = '<i class="ing-cargando ing"></i> Guardando...';
btn.disabled = true;
const data: Data = {
hora: document.querySelector("#editor_hora") as HTMLInputElement,
dia: document.querySelector("#editor_dia") as HTMLInputElement,
salon: document.querySelector("#editor_salón") as HTMLInputElement,
profesor: document.querySelector("#editor_profesor") as HTMLInputElement,
};
const hora = data.hora.value; // h:mm
const { compareHours } = await import('./date_functions');
const hora_antes = compareHours(hora, "07:15") < 0;
const hora_después = compareHours(hora, "21:30") > 0;
if (hora_antes || hora_después) {
alert(`La hora ${hora} no es válida`);
triggerMessage("Selecciona una hora", "Error");
btn.innerHTML = clone.innerHTML;
btn.disabled = false;
data.hora.focus();
data.hora.classList.add("is-invalid");
return;
}
const dia = data.dia.value;
const salon = data.salon.value;
const profesor = data.profesor.value;
const formData = new FormData();
formData.append("id", id);
formData.append("hora", hora);
formData.append("dia", dia);
formData.append("salon", salon);
formData.append("profesor", profesor);
const response = await fetch("action/action_horario_update.php", {
method: "POST",
body: formData
}).then(res => res.json());
if (response.status == "success") {
triggerMessage(response.message, "Éxito", "success");
btn.innerHTML = '<i class="ing-aceptar ing"></i> Guardado';
btn.classList.add("btn-success");
btn.classList.remove("btn-primary");
// return to the initial state
setTimeout(() => {
buscarGrupo();
btn.replaceWith(clone);
$("#modal-editar").modal("hide");
}, 1000);
} else {
triggerMessage(response.message, "Error");
btn.replaceWith(clone);
$("#modal-editar").modal("hide");
}
}
function triggerMessage(message: string, header: string, colour: string = "danger") {
throw new Error('Function not implemented.');
}

View File

@@ -1,107 +0,0 @@
// get this script tag
const script = document.currentScript as HTMLScriptElement;
// get data-facultad attribute from script tag
const dataFacultad = script.getAttribute("data-facultad") as string;
const table = document.querySelector("table") as HTMLTableElement;
// hide the table
table.style.display = "none";
disableDatalist("#filter_grupo");
const buscarGrupo = async () => {
// Add loading animation in the button
const btn = document.querySelector("#btn-buscar") as HTMLButtonElement;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Cargando...';
btn.disabled = true;
const carrera = document.querySelector("#filter_carrera") as HTMLInputElement;
const grupo = document.querySelector("#filter_grupo") as HTMLInputElement;
const periodo = document.querySelector("#periodo") as HTMLInputElement;
console.log(`Carrera: ${carrera}, Grupo: ${grupo}`);
if (carrera.value == "" || grupo.value == "") {
triggerMessage("El nombre del grupo y la carrera son requeridos", "Faltan campos");
// Remove loading animation in the button
btn.innerHTML = '<i class="ing-buscar ing"></i> Buscar';
btn.disabled = false;
return;
}
const formData = new FormData();
formData.append("carrera", carrera.value);
formData.append("grupo", grupo.value);
formData.append("periodo", periodo.value);
formData.append('facultad', dataFacultad);
try {
const response = await fetch("api/horario.php", {
method: "POST",
body: formData
}).then(res => res.json());
} catch (error) {
triggerMessage("Error al cargar el horario", "Error");
}
// Remove loading animation in the button
btn.innerHTML = '<i class="ing-buscar ing"></i> Buscar';
btn.disabled = false;
}
// on click the li element, inside datalist #dlcarera
const dlcarreras = document.querySelectorAll("#dlcarrera li");
dlcarreras.forEach(li => {
li.addEventListener("click", async () => {
// get the data-id from the li element
const carrera= li.getAttribute("data-id") as string;
const facultad = dataFacultad;
const periodo = document.querySelector("#periodo") as HTMLSelectElement;
const formData = new FormData();
formData.append("carrera", carrera);
formData.append("facultad", facultad);
formData.append("periodo", periodo.value);
try {
const {
status,
grupos
} = await fetch("action/action_grupo.php", {
method: "POST",
body: formData
}).then(res => res.json());
if (status != "success") {
throw new Error("Error al cargar los grupos");
}
const dlgrupo = document.querySelector("#dlgrupo ul") as HTMLUListElement;
const prompt = document.querySelector("#dlgrupo .datalist-input") as HTMLInputElement;
dlgrupo.innerHTML = "";
grupos.forEach(grupo => {
const li = document.createElement("li");
// data-id is the id of the group
li.setAttribute("data-id", grupo);
li.textContent = grupo;
dlgrupo.appendChild(li);
});
// write Seleccionar grupo
prompt.textContent = "Seleccionar grupo";
disableDatalist("#filter_grupo", false);
} catch (error) {
triggerMessage("Error al cargar los grupos", "Error");
console.log(error);
}
});
});

View File

@@ -1,10 +0,0 @@
// function that receives two hours in hh:mm format and compares as a spaceship operator
export function compareHours(h1: string, h2: string): number {
const [h1h, h1m] = h1.split(":").map(Number);
const [h2h, h2m] = h2.split(":").map(Number);
if (h1h > h2h) return 1;
if (h1h < h2h) return -1;
if (h1m > h2m) return 1;
if (h1m < h2m) return -1;
return 0;
}

1
ts/declaration.ts Normal file
View File

@@ -0,0 +1 @@
declare module 'https://*'

532
ts/horario_profesor.ts Normal file
View File

@@ -0,0 +1,532 @@
declare function triggerMessage(message: string, title: string, color?: string): void;
declare const write: boolean;
declare const moment: any;
/**
* Funciones auxiliares
*/
type Profesor = {
id: number,
grado: string,
profesor: string,
clave: string,
}
type Horario = {
id: number,
carrera_id: number,
materia: string,
salon: string,
profesores: Profesor[],
hora: string,
hora_final: string,
dia: string,
duracion: number,
bloques: number,
grupo: string,
materia_id: number,
}
const compareHours = (hora1: string, hora2: string): number => {
const [h1, m1] = hora1.split(":").map(Number);
const [h2, m2] = hora2.split(":").map(Number);
if (h1 !== h2) {
return h1 > h2 ? 1 : -1;
}
if (m1 !== m2) {
return m1 > m2 ? 1 : -1;
}
return 0;
};
let horarios = [] as Horario[];
const table = document.querySelector("table") as HTMLTableElement;
if (!(table instanceof HTMLTableElement)) {
triggerMessage("No se ha encontrado la tabla", "Error", "error");
throw new Error("No se ha encontrado la tabla");
}
[...Array(16).keys()].map(x => x + 7).forEach(hora => {
// add 7 rows for each hour
[0, 15, 30, 45].map((minute: number) => `${minute}`.padStart(2, '0')).forEach((minute: string) => {
const tr = document.createElement("tr") as HTMLTableRowElement;
tr.id = `hora-${hora}:${minute}`;
tr.classList.add(hora > 13 ? "tarde" : "mañana");
if (minute == "00") {
const th = document.createElement("th") as HTMLTableCellElement;
th.classList.add("text-center");
th.scope = "row";
th.rowSpan = 4;
th.innerText = `${hora}:00`;
th.style.verticalAlign = "middle";
tr.appendChild(th);
}
["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"].forEach(día => {
const td = document.createElement("td") as HTMLTableCellElement;
td.id = `hora-${hora}:${minute}-${día}`;
tr.appendChild(td);
});
const tbody = document.querySelector("tbody#horario") as HTMLTableSectionElement;
if (!(tbody instanceof HTMLTableSectionElement)) {
throw new Error("No se ha encontrado el tbody");
}
tbody.appendChild(tr);
});
});
const empty_table = table.cloneNode(true) as HTMLTableElement;
document.querySelectorAll('.hidden').forEach((element: HTMLElement) => {
element.style.display = "none";
});
// hide the table
table.style.display = "none";
function moveHorario(id: string, día: string, hora: string) {
const formData = new FormData();
formData.append("id", id);
formData.append("hora", hora);
formData.append("día", día);
fetch("action/action_horario_update.php", {
method: "POST",
body: formData
}).then(res => res.json()).then(response => {
if (response.status == "success") {
triggerMessage("Horario movido", "Éxito", "success");
} else {
triggerMessage(response.message, "Error");
}
}).then(() => {
renderHorario();
}).catch(err => {
triggerMessage(err, "Error");
});
}
function renderHorario() {
if (horarios.length == 0) {
triggerMessage("Este profesor hay horarios para mostrar", "Error", "info");
table.style.display = "none";
document.querySelectorAll('.hidden').forEach((element: HTMLElement) => element.style.display = "none");
return;
}
// show the table
table.style.display = "table";
document.querySelectorAll('.hidden').forEach((element: HTMLElement) => element.style.display = "block");
// clear the table
table.innerHTML = empty_table.outerHTML;
function conflicts(horario1: Horario, horario2: Horario): boolean {
const { hora: hora_inicio1, hora_final: hora_final1, dia: dia1 } = horario1;
const { hora: hora_inicio2, hora_final: hora_final2, dia: dia2 } = horario2;
if (dia1 !== dia2) {
return false;
}
const compareInicios = compareHours(hora_inicio1, hora_inicio2);
const compareFinales = compareHours(hora_final1, hora_final2);
if (
compareInicios >= 0 && compareInicios <= compareFinales ||
compareFinales >= 0 && compareFinales <= -compareInicios
) {
return true;
}
return false;
}
// remove the next 5 cells
function removeNextCells(horas: number, minutos: number, dia: string, cells: number = 5) {
for (let i = 1; i <= cells; i++) {
const minute = minutos + i * 15;
const nextMinute = (minute % 60).toString().padStart(2, "0");
const nextHour = horas + Math.floor(minute / 60);
const cellId = `hora-${nextHour}:${nextMinute}-${dia}`;
const cellElement = document.getElementById(cellId);
if (cellElement) {
cellElement.remove();
}
else {
console.log(`No se ha encontrado la celda ${cellId}`);
break;
}
}
}
function newBlock(horario: Horario, edit = false) {
function move(horario: Horario, cells: number = 5) {
const [horas, minutos] = horario.hora.split(":").map(Number);
const cell = document.getElementById(`hora-${horas}:${minutos.toString().padStart(2, "0")}-${horario.dia}`);
const { top, left } = cell.getBoundingClientRect();
const block = document.getElementById(`block-${horario.id}`);
block.style.top = `${top}px`;
block.style.left = `${left}px`;
removeNextCells(horas, minutos, horario.dia, cells);
}
const [horas, minutos] = horario.hora.split(":").map(x => parseInt(x));
const hora = `${horas}:${minutos.toString().padStart(2, "0")}`;
horario.hora = hora;
const cell = document.getElementById(`hora-${horario.hora}-${horario.dia}`) as HTMLTableCellElement;
if (!cell) return;
cell.dataset.ids = `${horario.id}`;
const float_menu = edit ?
`<div class="menu-flotante p-2" style="opacity: .7;">
<a class="mx-2" href="#" data-toggle="modal" data-target="#modal-editar">
<i class="ing-editar ing"></i>
</a>
<a class="mx-2" href="#" data-toggle="modal" data-target="#modal-borrar">
<i class="ing-basura ing"></i>
</a>
</div>`
: '';
cell.innerHTML =
`<div style="overflow-y: auto; overflow-x: hidden; height: 100%;" id="block-${horario.id}" class="position-absolute w-100 h-100">
<small class="text-gray">${horario.hora}</small>
<b class="title">${horario.materia}</b> <br>
<br><span>Salón: </span>${horario.salon} <br>
<small class="my-2">
${horario.profesores.map((profesor: Profesor) => ` <span class="ing ing-formacion mx-1"></span>${profesor.grado ?? ''} ${profesor.profesor}`).join("<br>")}
</small>
</div>
${float_menu}`;
cell.classList.add("bloque-clase", "position-relative");
cell.rowSpan = horario.bloques;
// draggable
cell.draggable = write;
if (horario.bloques > 0) {
removeNextCells(horas, minutos, horario.dia, horario.bloques - 1);
}
}
function newConflictBlock(horarios: Horario[], edit = false) {
const first_horario = horarios[0];
const [horas, minutos] = first_horario.hora.split(":").map(x => parseInt(x));
const hora = `${horas}:${minutos.toString().padStart(2, "0")}`;
const ids = horarios.map(horario => horario.id);
const cell = document.getElementById(`hora-${hora}-${first_horario.dia}`);
if (cell == null) {
console.error(`Error: No se encontró la celda: hora-${hora}-${first_horario.dia}`);
return;
}
cell.dataset.ids = ids.join(",");
// replace the content of the cell
cell.innerHTML = `
<small class='text-danger'>
${hora}
</small>
<div class="d-flex justify-content-center align-items-center mt-4">
<div class="d-flex flex-column justify-content-center align-items-center">
<span class="ing ing-importante text-danger" style="font-size: 2rem;"></span>
<b class='text-danger'>
Empalme de ${ids.length} horarios
</b>
<hr>
<i class="text-danger">Ver horarios &#8230;</i>
</div>
</div>
`;
// Add classes and attributes
cell.classList.add("conflict", "bloque-clase");
cell.setAttribute("role", "button");
// Add event listener for the cell
cell.addEventListener("click", () => {
$("#modal-choose").modal("show");
const ids = cell.getAttribute("data-ids").split(",").map(x => parseInt(x));
const tbody = document.querySelector("#modal-choose tbody");
tbody.innerHTML = "";
horarios.filter(horario => ids.includes(horario.id)).sort((a, b) => compareHours(a.hora, b.hora)).forEach(horario => {
tbody.innerHTML += `
<tr data-ids="${horario.id}">
<td><small>${horario.hora.slice(0, -3)}-${horario.hora_final.slice(0, -3)}</small></td>
<td>${horario.materia}</td>
<td>
${horario.profesores.map(({ grado, profesor }) => `${grado ?? ''} ${profesor}`).join(", ")}
</td>
<td>${horario.salon}</td>
${edit ? `
<td class="text-center">
<button class="btn btn-sm btn-primary dismiss-editar" data-toggle="modal" data-target="#modal-editar">
<i class="ing-editar ing"></i>
</button>
</td>
<td class="text-center">
<button class="btn btn-sm btn-danger dismiss-editar" data-toggle="modal" data-target="#modal-borrar">
<i class="ing-basura ing"></i>
</button>
</td>
` : ""}
</tr>`;
});
document.querySelectorAll(".dismiss-editar").forEach(btn => {
btn.addEventListener("click", () => $("#modal-choose").modal("hide"));
});
});
function getDuration(hora_i: string, hora_f: string): number {
const [horas_i, minutos_i] = hora_i.split(":").map(x => parseInt(x));
const [horas_f, minutos_f] = hora_f.split(":").map(x => parseInt(x));
const date_i = new Date(0, 0, 0, horas_i, minutos_i);
const date_f = new Date(0, 0, 0, horas_f, minutos_f);
const diffInMilliseconds = date_f.getTime() - date_i.getTime();
const diffInMinutes = diffInMilliseconds / (1000 * 60);
const diffIn15MinuteIntervals = diffInMinutes / 15;
return Math.floor(diffIn15MinuteIntervals);
}
const maxHoraFinal = horarios.reduce((max: Date, horario: Horario) => {
const [horas, minutos] = horario.hora_final.split(":").map(x => parseInt(x));
const date = new Date(0, 0, 0, horas, minutos);
return date > max ? date : max;
}, new Date(0, 0, 0, 0, 0));
const horaFinalMax = new Date(0, 0, 0, maxHoraFinal.getHours(), maxHoraFinal.getMinutes());
const blocks = getDuration(first_horario.hora, `${horaFinalMax.getHours()}:${horaFinalMax.getMinutes()}`);
cell.setAttribute("rowSpan", blocks.toString());
removeNextCells(horas, minutos, first_horario.dia, blocks - 1);
}
const conflictBlocks = horarios.filter((horario, index, arrayHorario) =>
arrayHorario.filter((_, i) => i != index).some(horario2 =>
conflicts(horario, horario2)))
.sort((a, b) => compareHours(a.hora, b.hora));
const classes = horarios.filter(horario => !conflictBlocks.includes(horario));
const conflictBlocksPacked = []; // array of sets
conflictBlocks.forEach(horario => {
const setIndex = conflictBlocksPacked.findIndex(set => set.some(horario2 => conflicts(horario, horario2)));
if (setIndex === -1) {
conflictBlocksPacked.push([horario]);
} else {
conflictBlocksPacked[setIndex].push(horario);
}
})
classes.forEach(horario =>
newBlock(horario, write)
)
conflictBlocksPacked.forEach(horarios =>
newConflictBlock(horarios, write)
)
// remove the elements that are not in the limits
let max_hour = Math.max(...horarios.map(horario => {
const lastMoment = moment(horario.hora, "HH:mm").add(horario.bloques * 15, "minutes");
const lastHour = moment(`${lastMoment.hours()}:00`, "HH:mm");
const hourInt = parseInt(lastMoment.format("HH"));
return lastMoment.isSame(lastHour) ? hourInt - 1 : hourInt;
}));
let min_hour = Math.min(...horarios.map(horario => parseInt(horario.hora.split(":")[0])));
document.querySelectorAll("tbody#horario tr").forEach(hora => {
const hora_id = parseInt(hora.id.split("-")[1].split(":")[0]);
(hora_id < min_hour || hora_id > max_hour) ? hora.remove() : null;
})
// if there is no sábado, remove the column
if (!horarios.some(horario => horario.dia == "sábado")) {
document.querySelectorAll("tbody#horario td").forEach(td => {
if (td.id.split("-")[2] == "sábado") {
td.remove();
}
});
// remove the header (the last)
document.querySelector("#headers").lastElementChild.remove();
}
// adjust width
const ths = document.querySelectorAll("tr#headers th") as NodeListOf<HTMLTableCellElement>;
ths.forEach((th, key) =>
th.style.width = (key == 0) ? "5%" : `${95 / (ths.length - 1)}%`
);
// search item animation
const menúFlontantes = document.querySelectorAll(".menu-flotante");
menúFlontantes.forEach((element) => {
element.classList.add("d-none");
element.parentElement.addEventListener("mouseover", () =>
element.classList.remove("d-none")
);
element.parentElement.addEventListener("mouseout", (e) =>
element.classList.add("d-none")
);
});
// droppables
// forall the .bloque-elements add the event listeners for drag and drop
document.querySelectorAll(".bloque-clase").forEach(element => {
function dragStart() {
this.classList.add("dragging");
}
function dragEnd() {
this.classList.remove("dragging");
}
element.addEventListener("dragstart", dragStart);
element.addEventListener("dragend", dragEnd);
});
// forall the cells that are not .bloque-clase add the event listeners for drag and drop
document.querySelectorAll("td:not(.bloque-clase)").forEach(element => {
function dragOver(e) {
e.preventDefault();
this.classList.add("dragging-over");
}
function dragLeave() {
this.classList.remove("dragging-over");
}
function drop() {
this.classList.remove("dragging-over");
const dragging = document.querySelector(".dragging");
const id = dragging.getAttribute("data-ids");
const hora = this.id.split("-")[1];
const días = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"];
let día = this.id.split("-")[2];
día = días.indexOf(día) + 1;
// rowspan
const bloques = parseInt(dragging.getAttribute("rowspan"));
const horaMoment = moment(hora, "HH:mm");
const horaFin = horaMoment.add(bloques * 15, "minutes");
const limit = moment('22:00', 'HH:mm');
if (horaFin.isAfter(limit)) {
triggerMessage("No se puede mover el bloque a esa hora", "Error");
// scroll to the top
window.scrollTo(0, 0);
return;
}
// get the horario
// remove the horario
const bloque = document.querySelector(`.bloque-clase[data-ids="${id}"]`) as HTMLElement;
// remove all children
while (bloque.firstChild) {
bloque.removeChild(bloque.firstChild);
}
// prepend a loading child
const loading = `<div class="spinner-border" role="status" style="width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>`;
bloque.insertAdjacentHTML("afterbegin", loading);
// add style vertical-align: middle
bloque.style.verticalAlign = "middle";
bloque.classList.add("text-center");
// remove draggable
bloque.removeAttribute("draggable");
moveHorario(id, día, hora);
}
element.addEventListener("dragover", dragOver);
element.addEventListener("dragleave", dragLeave);
element.addEventListener("drop", drop);
});
}
const form = document.getElementById('form') as HTMLFormElement;
if (!(form instanceof HTMLFormElement)) {
triggerMessage('No se ha encontrado el formulario', 'Error', 'danger');
throw new Error("No se ha encontrado el formulario");
}
form.querySelector('#clave_profesor').addEventListener('input', function (e) {
const input = form.querySelector('#clave_profesor') as HTMLInputElement;
const option = form.querySelector(`option[value="${input.value}"]`) as HTMLOptionElement;
if (input.value == "") {
input.classList.remove("is-invalid", "is-valid");
return;
}
if (!option) {
input.classList.remove("is-valid");
input.classList.add("is-invalid");
}
else {
const profesor_id = form.querySelector('#profesor_id') as HTMLInputElement;
profesor_id.value = option.dataset.id;
input.classList.remove("is-invalid");
input.classList.add("is-valid");
}
});
form.addEventListener('submit', async function (e) {
e.preventDefault();
const input = form.querySelector('#clave_profesor') as HTMLInputElement;
if (input.classList.contains("is-invalid")) {
triggerMessage('El profesor no se encuentra registrado', 'Error', 'danger');
return;
}
const formData = new FormData(form);
try {
const buttons = document.querySelectorAll("button") as NodeListOf<HTMLButtonElement>;
buttons.forEach(button => {
button.disabled = true;
button.classList.add("disabled");
});
const response = await fetch('action/action_horario_profesor.php', {
method: 'POST',
body: formData,
});
const data = await response.json();
buttons.forEach(button => {
button.disabled = false;
button.classList.remove("disabled");
});
if (data.status == 'success') {
horarios = data.data;
renderHorario();
}
else {
triggerMessage(data.message, 'Error en la consulta', 'warning');
}
} catch (error) {
triggerMessage('Fallo al consutar los datos ', 'Error', 'danger');
console.log(error);
}
});
const input = form.querySelector('#clave_profesor') as HTMLInputElement;
const option = form.querySelector(`option[value="${input.value}"]`) as HTMLOptionElement;

272
ts/reposiciones.ts Normal file
View File

@@ -0,0 +1,272 @@
import { type } from "os";
declare function triggerMessage(message: string, title: string, type?: string): void;
declare const write: boolean;
declare const moment: any;
// from this 'horario_id', 'fecha', 'hora', 'duracion_id', 'descripcion', 'profesor_id', 'salon', 'unidad', 'periodo_id', 'fecha_clase' make a type of ReposicionParams
export interface ReposicionParams {
horario_id: number;
fecha: string;
hora: string;
duracion_id: number;
descripcion: string;
profesor_id: number;
salon: string;
unidad: number;
periodo_id: number;
fecha_clase: string;
}
type Horario = {
id: number;
carrera_id: number;
materia_id: number;
grupo: string;
profesores: Profesor[];
dia: string;
hora: string;
hora_final: string;
salon: string;
fecha_inicio: string;
fecha_final: string;
fecha_carga: string;
nivel_id: number;
periodo_id: number;
facultad_id: number;
materia: string;
horas: number;
minutos: number;
duracion: number;
retardo: boolean;
original_id: number;
last: boolean;
bloques: number;
};
type Profesor = {
id: number;
clave: string;
grado: string;
profesor: string;
nombre: string;
facultad_id: number;
};
// Get references to the HTML elements
const form = document.getElementById('form') as HTMLFormElement;
const steps = Array.from(form.querySelectorAll('.step')) as HTMLElement[];
const nextButton = document.getElementById('next-button') as HTMLButtonElement;
const prevButton = document.getElementById('prev-button') as HTMLButtonElement;
let currentStep = 0;
// #clave_profesor on change => show step 2
const clave_profesor = document.getElementById('clave_profesor') as HTMLInputElement;
const horario_reponer = document.getElementById('horario_reponer') as HTMLInputElement;
const fechas_clase = document.getElementById('fechas_clase') as HTMLInputElement;
const fecha_reponer = $('#fecha_reponer') as JQuery<HTMLElement>;
const hora_reponer = $('#hora_reponer') as JQuery<HTMLElement>;
const minutos_reponer = $('#minutos_reponer') as JQuery<HTMLElement>;
clave_profesor.addEventListener('change', async () => {
const step2 = document.getElementById('step-2') as HTMLElement;
clave_profesor.disabled = true;
// get option which value is the same as clave_profesor.value
const option = document.querySelector(`option[value="${clave_profesor.value}"]`) as HTMLOptionElement;
// make a form data with #form
const profesor_id = document.getElementById('profesor_id') as HTMLInputElement;
profesor_id.value = option.dataset.id;
const formData = new FormData(form);
const response = await fetch(`./action/action_horario_profesor.php`, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener los horarios del profesor."
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
const horarios = data.data as Horario[];
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione un horario';
initial.selected = true;
initial.disabled = true;
horario_reponer.innerHTML = '';
horario_reponer.appendChild(initial);
horarios.forEach((horario) => {
const dias = ['Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado', 'Domingo'];
const option = document.createElement('option');
option.value = `${horario.id}`;
// materia máx 25 caracteres, if materia.length > 25 then slice(0, 20)
const max = 25;
option.textContent = `${horario.materia.slice(0, max) + (horario.materia.length > max ? '...' : '')} - Grupo: ${horario.grupo} - ${horario.hora.slice(0, 5)}-${horario.hora_final.slice(0, 5)} - Salon: ${horario.salon} - ${horario.dia}`;
option.dataset.materia = `${horario.materia}`;
option.dataset.grupo = `${horario.grupo}`;
option.dataset.hora = `${horario.hora.slice(0, 5)}`; // slice(0, 5) => HH:MM
option.dataset.hora_final = `${horario.hora_final.slice(0, 5)}`;
option.dataset.salon = `${horario.salon}`;
option.dataset.dia = `${horario.dia}`;
option.dataset.id = `${horario.id}`;
horario_reponer.appendChild(option);
});
currentStep = 1;
step2.style.display = 'block';
prevButton.disabled = false;
});
// disable clave_profesor
// from second step to first step
prevButton.addEventListener('click', () => {
const inputs = [clave_profesor, horario_reponer, fechas_clase, fecha_reponer, hora_reponer] as HTMLInputElement[];
switch (currentStep) {
case 1:
case 2:
case 3:
const step = document.getElementById(`step-${currentStep + 1}`) as HTMLElement;
step.style.display = 'none';
inputs[currentStep - 1].disabled = false;
inputs[currentStep - 1].value = '';
if (--currentStep === 0) {
prevButton.disabled = true;
}
break;
case 4:
const step5 = document.getElementById('step-5') as HTMLElement;
step5.style.display = 'none';
fecha_reponer.prop('disabled', false);
fecha_reponer.val('');
hora_reponer.parent().removeClass('disabled');
hora_reponer.siblings('.datalist-input').text('hh');
hora_reponer.val('');
minutos_reponer.parent().removeClass('disabled');
minutos_reponer.siblings('.datalist-input').text('mm');
minutos_reponer.val('');
currentStep--;
break;
}
nextButton.disabled = true;
});
// #horario_reponer on change => show step 3
horario_reponer.addEventListener('change', async () => {
const selected = horario_reponer.querySelector(`option[value="${horario_reponer.value}"]`) as HTMLOptionElement;
horario_reponer.title = `Materia: ${selected.dataset.materia} - Grupo: ${selected.dataset.grupo} - Horario: ${selected.dataset.hora}-${selected.dataset.hora_final} - Salon: ${selected.dataset.salon} - Día: ${selected.dataset.dia}`;
const step3 = document.getElementById('step-3') as HTMLElement;
horario_reponer.disabled = true;
// make a form data with #form
const response = await fetch(`./action/action_fechas_clase.php?horario_id=${horario_reponer.value}`, {
method: 'GET',
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener las fechas de clase."
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
type Fecha = {
fecha: string;
dia_mes: number;
day: number;
month: number;
year: number;
}
const meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
const fechas = data.data as Fecha[];
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione la fecha de la falta';
initial.selected = true;
initial.disabled = true;
fechas_clase.innerHTML = '';
fechas_clase.appendChild(initial);
fechas_clase.title = 'Seleccione la fecha de la falta';
fechas.forEach((fecha) => {
const option = document.createElement('option');
option.value = `${fecha}`;
option.textContent = `${fecha.dia_mes} de ${meses[fecha.month - 1]} de ${fecha.year}`;
fechas_clase.appendChild(option);
});
step3.style.display = 'block';
currentStep = 2;
});
// #fechas_clase on change => show step 4
fechas_clase.addEventListener('change', () => {
const step4 = document.getElementById('step-4') as HTMLElement;
step4.style.display = 'block';
fechas_clase.disabled = true;
currentStep = 3;
});
// when both #fecha_reponer and #hora_reponer are selected => show step 5
const lastStep = () => {
// timeout to wait for the value to be set
setTimeout(() => {
if (fecha_reponer.val() !== '' && hora_reponer.val() !== '' && minutos_reponer.val() !== '') {
const step5 = document.getElementById('step-5') as HTMLElement;
step5.style.display = 'block';
// disable both
fecha_reponer.prop('disabled', true);
hora_reponer.parent().addClass('disabled');
minutos_reponer.parent().addClass('disabled');
const nextButton = document.getElementById('next-button') as HTMLButtonElement;
// remove property disabled
nextButton.removeAttribute('disabled');
currentStep = 4;
}
}, 100);
}
fecha_reponer.on('change', lastStep);
// on click on the sibling ul>li of #hora_reponer and #minutos_reponer
hora_reponer.siblings('ul').children('li').on('click', lastStep);
minutos_reponer.siblings('ul').children('li').on('click', lastStep);
// Initialize the form
hideSteps();
showCurrentStep();
function hideSteps() {
steps.forEach((step) => {
step.style.display = 'none';
});
}
function showCurrentStep() {
steps[currentStep].style.display = 'block';
prevButton.disabled = currentStep === 0;
}
function handleSubmit(event: Event) {
event.preventDefault();
// Handle form submission
// You can access the form data using the FormData API or serialize it manually
}

View File

@@ -1,18 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ESNext", "lib": [
"moduleResolution": "Node", "ESNext",
"target": "ES2020", "dom"
"jsx": "react", ],
"strictNullChecks": true, "outDir": "js",
"strictFunctionTypes": true, "rootDir": "ts",
"lib": ["ES2020", "DOM"], "target": "ES2022",
"rootDir": "./ts", "moduleResolution": "node",
"outDir": "./js", "module": "ESNext"
"watch": true, // ts/auditoría.ts:1:37 - error TS2307: Cannot find module 'https://unpkg.com/petite-vue?module' or its corresponding type declarations.
}, }
"exclude": [
"node_modules",
"**/node_modules/*"
]
} }