Alejandro Rosales 2 years ago
parent
commit
6d0556cc6f
3 changed files with 289 additions and 7 deletions
  1. 43 7
      action/puesto.php
  2. 50 0
      js/puestos.js
  3. 196 0
      puestos.php

+ 43 - 7
action/puesto.php

@@ -15,9 +15,21 @@ try {
         case 'GET':
             // Fetch all puestos
             $facultad_id = $user->facultad['facultad_id'] ?? -1;
-            $puestos = $db->orderBy('puesto_id', 'desc')
-                ->where('facultad_id', $facultad_id)
-                ->get('puesto');
+            $carreras = array_map(fn($c) => $c['carrera_id'], $db->where('facultad_id', $facultad_id)->get(tableName: 'carrera', columns: 'carrera_id'));
+            $puestos = array_map(
+                fn($p) => array(
+                    ...$p,
+                    'materias' => $db->where('puesto_id', $p['puesto_id'])
+                        ->join('puesto_materia', 'puesto_materia.materia_id = materia.materia_id', 'LEFT')
+                        ->get(tableName: 'materia', columns: ['materia.materia_id', 'materia_nombre', 'clave_materia',]),
+                    'encargado' => $db->where('puesto_id', $p['puesto_id'])
+                        ->join('puesto_usuario', 'puesto_usuario.usuario_id = usuario.usuario_id', 'LEFT')
+                        ->getOne('usuario', ['usuario.usuario_id', 'usuario_nombre', 'usuario_clave']),
+                ),
+                $db->orderBy('puesto_id', 'desc')
+                    ->where('facultad_id', $facultad_id)
+                    ->get(tableName: 'puesto', numRows: count($carreras), columns: 'puesto_id, nombre'),
+            );
             echo json_encode($puestos);
             break;
 
@@ -31,21 +43,45 @@ try {
                 exit();
             }
 
-            $puesto_id = $db->insert('puestos', ['puesto_nombre' => $input_data['puesto_nombre']]);
-            echo json_encode(['msg' => 'Puesto creado exitosamente', 'puesto_id' => $puesto_id]);
+            $puesto = $db->insert('puesto', [
+                'nombre' => $input_data['puesto_nombre'],
+                'facultad_id' => $user->facultad['facultad_id'],
+            ], ['puesto_id', 'nombre', 'facultad_id']);
+
+            echo json_encode(
+                array(
+                    ...$puesto,
+                    'materias' => [],
+                    'encargado' => null,
+                ),
+            );
             break;
 
         case 'PUT':
             $raw_input = file_get_contents('php://input');
             $input_data = json_decode($raw_input, true);
 
-            if (!$input_data || !isset($input_data['puesto_id'], $input_data['puesto_nombre'])) {
+            if (!$input_data || !isset($input_data['puesto_id'], $input_data['materias'], $input_data['usuario_id'])) {
                 header('HTTP/1.1 400 Bad Request');
                 echo json_encode(['error' => 'Datos inválidos']);
                 exit();
             }
 
-            $db->where('puesto_id', $input_data['puesto_id'])->update('puestos', ['puesto_nombre' => $input_data['puesto_nombre']]);
+            $db->where('puesto_id', $input_data['puesto_id'])->delete('puesto_materia');
+            $db->where('puesto_id', $input_data['puesto_id'])->delete('puesto_usuario');
+
+            foreach ($input_data['materias'] as $materia_id) {
+                $db->insert('puesto_materia', [
+                    'puesto_id' => $input_data['puesto_id'],
+                    'materia_id' => $materia_id,
+                ]);
+            }
+
+            $db->insert('puesto_usuario', [
+                'puesto_id' => $input_data['puesto_id'],
+                'usuario_id' => $input_data['usuario_id'],
+            ]);
+
             echo json_encode(['msg' => 'Puesto actualizado exitosamente']);
             break;
 

+ 50 - 0
js/puestos.js

@@ -0,0 +1,50 @@
+import { createApp } from 'https://unpkg.com/petite-vue?module';
+const app = createApp({
+    message: null,
+    puestos: [],
+    carreras: [],
+    materias: [],
+    usuarios: [],
+    async nuevoPuesto(nuevoPuesto) {
+        try {
+            const res = await fetch('action/puesto.php', {
+                method: 'POST',
+                body: JSON.stringify({
+                    puesto_nombre: nuevoPuesto
+                })
+            });
+            const data = await res.json();
+            this.puestos.push(data);
+        }
+        catch (error) {
+            alert(`Error: ${error}`);
+        }
+    },
+    async actualizarPuesto(puesto_id, materias, usuario_id) {
+        try {
+            const res = await fetch('action/puesto.php', {
+                method: 'PUT',
+                body: JSON.stringify({
+                    puesto_id,
+                    materias: materias.map(m => m.materia_id),
+                    usuario_id
+                })
+            });
+            const data = await res.json();
+            this.message = data.msg;
+            // after 3 seconds, remove the message
+            setTimeout(() => {
+                this.message = null;
+            }, 3000);
+        }
+        catch (error) {
+            alert(`Error: ${error}`);
+        }
+    },
+    async mounted() {
+        this.puestos = await fetch('action/puesto.php').then(res => res.json());
+        this.carreras = await fetch('action/action_carreras.php').then(res => res.json());
+        this.materias = await fetch('action/action_materias.php').then(res => res.json());
+        this.usuarios = await fetch('action/usuarios.php').then(res => res.json());
+    }
+}).mount('#app');

+ 196 - 0
puestos.php

@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Auditoría asistencial</title>
+    <?php
+    include 'import/html_css_files.php';
+    ?>
+    <link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.0/dist/trix.css">
+    <style>
+        [v-cloak] {
+            display: none;
+        }
+    </style>
+    <script src="js/jquery.min.js"></script>
+    <script src="js/jquery-ui.js"></script>
+    <script src="js/bootstrap/bootstrap.min.js"></script>
+</head>
+
+<body>
+    <?
+    $redirect = $_SERVER['PHP_SELF'];
+    include "import/html_header.php";
+    global $user;
+
+    html_header(
+        "Puestos de la {$user->facultad['facultad']}",
+        "Sistema de gestión de checador",
+    );
+    ?>
+
+    <main class="container-fluid px-4 mt-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 70vh;"
+        v-scope="{new_puesto: null}">
+
+        <div class="alert alert-success" role="alert" v-if="message">
+            {{message}}
+        </div>
+        <div class="justify-content-between align-items-center d-flex mb-4">
+            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#nuevo-puesto">
+                <span class="ing-mas"></span>
+                Agregar puesto
+            </button>
+        </div>
+        <div class="d-flex flex-wrap justify-content-around">
+            <div class="col-6" v-for="puesto in puestos" :key="puesto.puesto_id">
+                <div class="card text-center"
+                    v-scope="{selected_carrera_id: -1, current_materia: null, current_encargado: null}">
+                    <div class="card-header bg-primary text-white font-weight-bold text-uppercase">
+                        {{puesto.nombre}}
+                    </div>
+                    <div class="card-body">
+                        <!-- Encargado -->
+                        <div class="form-row justify-content-around align-items-center mb-2">
+                            <label :for="`encargado-${puesto.puesto_id}`" class="col-3">
+                                Encargado del área
+                            </label>
+                            <div id="encargados" class="datalist datalist-select mb-1 col-9">
+                                <div class="datalist-input" v-if="puesto.encargado">
+                                    ({{puesto.encargado.usuario_clave}}) {{ puesto.encargado.usuario_nombre }}
+                                </div>
+                                <div class="datalist-input" v-else>
+                                    Selecciona un encargado
+                                </div>
+                                <span class="icono ing-buscar"></span>
+                                <ul style="display:none">
+                                    <li class="datalist-option" v-for="usuario in usuarios" :key="usuario.usuario_id"
+                                        :data-id="usuario.usuario_id" style=" white-space: nowrap;"
+                                        @click="puesto.encargado = usuario"
+                                        :class="{'selected': puesto.encargado.usuario_id == usuario.usuario_id}">
+                                        (<small> {{usuario.usuario_clave}} </small>) {{ usuario.usuario_nombre }}
+                                    </li>
+                                </ul>
+                                <input type="hidden" id="encargado_id" name="id">
+                            </div>
+                        </div>
+                        <hr>
+                        <fieldset class="container d-flex flex-column justify-content-center align-items-center">
+                            <legend>Materias Asignadas <span
+                                    class="badge badge-secondary">{{puesto.materias.length}}</span></legend>
+                            <ul class="list-group overflow-auto col-10" v-if="puesto.materias.length"
+                                style="max-height: 200px; overflow-y: auto;">
+                                <li class="list-group-item list-group-item-action" v-for="materia in puesto.materias"
+                                    :key="materia.materia_id"
+                                    @click="puesto.materias.splice(puesto.materias.indexOf(materia), 1); materias.push(materia)"
+                                    style="cursor: pointer;">
+                                    <div class="d-flex justify-content-center">
+                                        <div class="col-2">
+                                            <span class="icono ing-borrar text-danger"></span>
+                                        </div>
+                                        <div class="col-10 text-left">
+                                            {{materia.clave_materia}} - {{materia.materia_nombre}}
+                                        </div>
+                                    </div>
+                                </li>
+                            </ul>
+                            <div class="alert alert-light" role="alert" v-else>
+                                No hay materias asignadas
+                            </div>
+                        </fieldset>
+                        <hr>
+
+                        <div class="form-row justify-content-around align-items-center mb-2" v-show="carreras.length">
+                            <label :for="`carrera-${puesto.puesto_id}`" class="col-3">
+                                Carrera
+                            </label>
+                            <div id="dlCarreras" class="datalist datalist-select mb-1 col-9">
+                                <div class="datalist-input">
+                                    Selecciona una carrera
+                                </div>
+                                <span class="icono ing-buscar"></span>
+                                <ul style="display:none">
+                                    <li class="datalist-option" data-id="0" @click="selected_carrera_id = 0">
+                                        Todas las carreras
+                                    </li>
+                                    <li class="datalist-option" v-for="carrera in carreras" :key="carrera.carrera_id"
+                                        :data-id="carrera.carrera_id" style=" white-space: nowrap;"
+                                        @click="selected_carrera_id = carrera.carrera_id">
+                                        (<small> {{carrera.clave_carrera}} </small>) {{ carrera.carrera_nombre }}
+                                    </li>
+                                </ul>
+                                <input type="hidden" id="carrera_id" name="id">
+                            </div>
+                        </div>
+                        <div class="form-row justify-content-around align-items-center"
+                            v-scope="{to_add_materia: null}">
+                            <label :for="`materias-${puesto.puesto_id}`" class="col-3">
+                                Materias
+                            </label>
+                            <input name="materia" placeholder="Seleccione una materia" list="datalist-materias"
+                                class="form-control col-9 " v-model="current_materia" @input="to_add_materia = materias.find(m => current_materia == `${m.clave_materia} - ${m.materia_nombre}`);
+                                if (to_add_materia) {
+                                    puesto.materias.push(to_add_materia);
+                                    materias.splice(materias.indexOf(to_add_materia), 1);
+                                    current_materia = null;
+                                }" :disabled="selected_carrera_id == -1" v-model="current_materia"
+                                :id="`materias-${puesto.puesto_id}`">
+                        </div>
+                        <datalist id="datalist-materias">
+                            <option
+                                v-for="materia in materias.filter(m => selected_carrera_id == 0 || m.carrera_id == selected_carrera_id)"
+                                :value="`${materia.clave_materia} - ${materia.materia_nombre}`">
+                        </datalist>
+                    </div>
+                    <div class="card-footer text-muted">
+                        <button type="button" class="btn btn-outline-primary btn-lg btn-block" v-if="puesto.encargado"
+                            @click="actualizarPuesto(puesto.puesto_id, puesto.materias, puesto.encargado.usuario_id)">
+                            Guardar cambios
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="modal" tabindex="-1" role="dialog" accesskey="a" id="nuevo-puesto">
+            <div class="modal-dialog modal-dialog-centered" role="document">
+                <div class="modal-content">
+                    <div class="modal-header text-white">
+                        <h5 class="modal-title">Agregar un nuevo puesto</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true" class="text-white">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <fieldset>
+                            <legend>Nombre del puesto</legend>
+                            <div class="form-row">
+                                <input type="text" class="form-control" v-model="new_puesto"
+                                    placeholder="Área del puesto">
+                            </div>
+                        </fieldset>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-outline-danger" data-dismiss="modal"
+                            @click="new_puesto = null">Cancelar</button>
+                        <button type="button" class="btn btn-primary" data-dismiss="modal"
+                            @click="nuevoPuesto(new_puesto)">Guardar</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </main>
+
+    <? include "import/html_footer.php"; ?>
+
+    <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/datepicker-es.js"></script>
+    <script src="js/datalist.js"></script>
+    <script src="js/puestos.js?<?= rand(0, 2) ?>" type="module"></script>
+    <script src="js/scrollables.js"></script>
+</body>
+
+</html>