|
@@ -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">×</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">×</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">×</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">×</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>
|