123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 |
- <!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",
- );
- ?>
- <main class="container-fluid px-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 60vh;">
- <!-- 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 || ruta.reposiciones.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 || ruta.reposiciones.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}-${clase.profesor_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.horario_id = clase.horario_id; store.profesor_selected.profesor_id = clase.profesor_id; store.profesor_selected.es_reposicion = false"
- data-toggle="modal" data-target="#ver-detalle">
- Ver detalle <i class="ing-ojo"></i>
- </button>
- </div>
- </td>
- <td class="text-center align-middle">
- {{ clase.horario_hora?.slice(0, 5) }} - {{ clase.horario_fin?.slice(0, 5) }}
- </td>
- <td class="text-center align-middle text-nowrap">
- <!-- data-toggle="button" -->
- <div v-if="!clase.reposicion_id">
- <button class="btn text-center mx-2" v-for="estado in estados" :key="estado.id"
- @click="store.cambiarEstado(clase.horario_id, clase.profesor_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>
- </div>
- <!-- italic -->
- <div v-else class="text-muted font-italic">
- Reposición el {{ clase.reposicion_fecha }} a las
- {{ clase.reposicion_hora?.slice(0, 5) }} h en el salón {{ clase.reposicion_salon
- }}
- </div>
- </td>
- </tr>
- <tr class="table-primary" v-if="reposiciones.length > 0">
- <th colspan="5" class="text-center align-middle">Reposiciones</th>
- </tr>
- <tr v-for="clase in reposiciones"
- :key="`reposicion-${clase.horario_id}-${clase.profesor_id}`">
- <td class="text-center align-middle">{{ clase.reposicion_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.horario_id = clase.horario_id; store.profesor_selected.profesor_id = clase.profesor_id; store.profesor_selected.es_reposicion = true"
- data-toggle="modal" data-target="#ver-detalle">
- Ver detalle <i class="ing-ojo"></i>
- </button>
- </div>
- <td class="text-center align-middle">
- <fieldset>
- <legend>Hora de la reposición</legend>
- {{ clase.reposicion_hora?.slice(0, 5) }} - {{ clase.reposicion_fin?.slice(0, 5)
- }}
- </fieldset>
- <fieldset>
- <legend>Horario original</legend>
- {{ clase.horario_hora?.slice(0, 5) }} - {{ clase.horario_fin?.slice(0, 5) }} el
- día {{clase.registro_fecha_ideal}}
- </fieldset>
- </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, clase.profesor_id, estado.id === clase.estado_supervisor_id ? null : estado.id, true)"
- :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.filter(ruta => ruta.subrutas.length > 0)"
- :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>
- <!-- FREQUENT COMMENTS -->
- <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="`h${clase_vista.horario_id} - p${clase_vista.profesor_id}`">
- 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.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 class="modal-dialog modal-dialog-centered modal-xl" v-else>
- <div class="modal-content">
- <div class="modal-header">
- <h2 class="modal-title">Error al cargar la clase</h2>
- <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
- <span aria-hidden="true">×</span>
- </button>
- </div>
- <!-- cuerpo del modal -->
- </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 src="js/scrollables.js"></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,
- },
- {
- color: "primary",
- icon: "ing-ubicacion",
- id: 5,
- }
- ];
- const messages = PetiteVue.reactive({
- data: [],
- push_message(message, silent = false) {
- if (silent) {
- 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)) || (ruta => ruta.reposiciones.length > 0 && ruta.reposiciones.every(reposicion => reposicion.estado_supervisor_id)));
- const lasts = this.rutas.data.filter(ruta => ruta.horarios.length == 0 && ruta.reposiciones.length == 0);
- const notLasts = this.rutas.data.filter(ruta => ruta.horarios.some(horario => !horario.estado_supervisor_id));
- this.rutas.data = [...notLasts, ...finals, ...lasts];
- },
- // clases
- selectBloque(bloqueIndex) {
- this.bloquesHorario.selected = bloqueIndex;
- },
- // estado
- async cambiarEstado(horario_id, profesor_id, estadoId, reposicion = false) {
- const ruta = store.rutas.data.find(ruta => ruta.salon_id == this.rutas.selected);
- const clase = ruta[reposicion ? 'reposiciones' : 'horarios'].find(clase => clase.horario_id == horario_id && clase.profesor_id == profesor_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.profesor_id, clase.estado_supervisor_id);
- },
- limpiarComentario() {
- this.editor.texto = "";
- },
- profesor_selected: {
- horario_id: null,
- profesor_id: 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 ?? [];
- return clases;
- },
- get reposiciones() {
- const reposiciones = store.rutas.data.find(ruta => ruta.salon_id == store.rutas.selected)?.reposiciones ?? [];
- return reposiciones;
- },
- async guardarCambios() {
- try {
- if (!navigator.onLine)
- 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(store.rutas.data.map(ruta => ruta.horarios).flat(1).filter((clase, index, self) => clase.pendiente &&
- self.findIndex(c => c.horario_id == clase.horario_id && c.profesor_id == clase.profesor_id) === index
- ).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);
- this.header = (store.bloquesHorario.selected == -1) ? "Seleccione un horario" : "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';
- // obtener parámetros del url
- const searchParams = new URLSearchParams({
- id_espacio_sgu: id_espacio_sgu,
- bloque_horario_id: store.bloquesHorario.data[store.bloquesHorario.selected].id,
- fecha: new URL(document.location).searchParams.get('fecha') ?? null,
- });
- try {
- const rutas = await fetch(`${url}?${searchParams}`).then(res => res.json());
- store.rutas.data = rutas.filter(ruta => ruta.horarios.length > 0 || ruta.reposiciones.length > 0);
- } catch (error) {
- console.error(error);
- this.header = `Error al cargar rutas`;
- this.loading = false;
- return
- }
- 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() {
- if (!store.profesor_selected.horario_id || !(store.profesor_selected.profesor_id >= 0))
- return false;
- return store.profesor_selected.es_reposicion
- ? this.reposiciones.find(clase => clase.horario_id == store.profesor_selected.horario_id && clase.profesor_id == store.profesor_selected.profesor_id) ?? false
- : this.clases.find(clase => clase.horario_id == store.profesor_selected.horario_id && clase.profesor_id == store.profesor_selected.profesor_id) ?? false;
- },
- }).mount('#app')
- </script>
- </body>
- </html>
|