浏览代码

Gráfica de alumnos, búsqueda y comparación con los snapshots

Alejandro Rosales 1 年之前
父节点
当前提交
838bc376e8
共有 4 个文件被更改,包括 178 次插入80 次删除
  1. 37 0
      fetch/alumnos.php
  2. 7 7
      fetch/calificaciones.php
  3. 9 7
      index.php
  4. 125 66
      pages/graph.html

+ 37 - 0
fetch/alumnos.php

@@ -0,0 +1,37 @@
+<?php
+require_once "{$_SERVER['DOCUMENT_ROOT']}/dependencies.php";
+if (!isset($_SESSION['user'])) {
+  returnResponse(status: 401, error: true, message: 'No se ha iniciado sesión');
+  exit();
+}
+
+header('Content-Type: application/json');
+$params = [
+  'moodle_host_id' => $_SESSION['moodle_id'],
+];
+$alumnos = $db->query("WITH expanded_calificaciones AS (
+  SELECT DISTINCT
+    moodle_host_id,
+    jsonb_array_elements(calificaciones)->>'grupo' AS grupo,
+    jsonb_array_elements(calificaciones)->>'student_name' AS nombre,
+    jsonb_array_elements(calificaciones)->>'username' AS username
+  FROM
+    public.snapshot_calificaciones
+  WHERE
+    moodle_host_id = :moodle_host_id
+)
+SELECT
+  grupo,
+  jsonb_agg(
+    jsonb_build_object(
+      'nombre', nombre,
+      'username', username
+    ) ORDER BY nombre
+  ) AS alumnos
+FROM
+  expanded_calificaciones
+GROUP BY grupo;",
+  $params
+) ?? [];
+
+echo json_encode(array_map(fn($alumno) => ['grupo' => $alumno['grupo'], 'alumnos' => json_decode($alumno['alumnos'])], $alumnos));

+ 7 - 7
fetch/calificaciones.php

@@ -16,16 +16,16 @@ $calificaciones = $db->query("SELECT * from jsonb_array_elements(public.consulta
 )) AS calificaciones WHERE calificaciones->>'username' = :username;",
     $params
 ) ?? [];
+$calificaciones = array_column($calificaciones, 'value');
+$calificaciones = array_map(fn($calificacion) => json_decode($calificacion), $calificaciones);
 
 $timeline = $db->query('SELECT * from public.promedio_snapshot(
     moodle_host_id_param => :moodle_id,
-    username_param =>:username)',
+    username_param => :username)',
     $params
 ) ?? [];
 
-echo json_encode(
-    array(
-        'calificaciones' => $calificaciones,
-        'timeline' => $timeline
-    )
-);
+echo json_encode([
+    'calificaciones' => $calificaciones,
+    'timeline' => $timeline
+]);

+ 9 - 7
index.php

@@ -203,13 +203,15 @@ $title = "Administración de Calificaciones";
             </div>
         </div>
 
-
-        <?php
-        if (!isset($page)) {
-            throw new Exception('No se ha definido la variable $page');
-        }
-        require "{$_SERVER['DOCUMENT_ROOT']}/pages/$page.html";
-        ?>
+        
+        <div style="height: 45rem; overflow: auto;">
+            <?php
+            if (!isset($page)) {
+                throw new Exception('No se ha definido la variable $page');
+            }
+            require "{$_SERVER['DOCUMENT_ROOT']}/pages/$page.html";
+            ?>
+        </div>
 
     </div>
     <?php

+ 125 - 66
pages/graph.html

@@ -4,92 +4,151 @@
         <i class="fas fa-arrow-left"></i> Regresar
     </button>
 </form>
-<main class="container">
+<main class="container" @vue:mounted="mounted">
     <form class="mb-4">
         <div class="row mb-3 form-box">
-            <label for="item.value" class="col-sm-1 col-form-label form-group barra">Alumno</label>
+            <label for="alumno.grupo" class="col-sm-1 col-form-label form-group barra">Grupo</label>
             <div class="col-sm-10">
-                <input type="item.type" name="item.name" list="item.name" id="item.value" class="form-control col-form-label">
+                <select name="alumno.grupo" id="alumno.grupo" class="form-control col-form-label"
+                    v-model="filter.grupo">
+                    <option :value="null">Todos los grupos</option>
+                    <option v-for="alumno in alumnos.filter(alumno => alumno.grupo)" :value="alumno.grupo">
+                        {{ alumno.grupo }}
+                    </option>
+                </select>
+            </div>
+        </div>
+        <div class="row mb-3 form-box">
+            <label for="alumno" class="col-sm-1 col-form-label form-group barra">Alumno</label>
+            <div class="col-sm-10">
+                <input type="item.type" name="item.name" list="item.name" id="alumno"
+                    class="form-control col-form-label" @input="fetchDatos" v-model="filter.username">
                 <datalist id="item.name">
-                    <option value="option.id">Alumno_nombre</option>
+                    <option v-for="alumno in alumnos_por_grupo" :value="alumno.username">
+                        {{ alumno.nombre }}
+                    </option>
                 </datalist>
             </div>
-            <button type="reset" class="btn btn-danger btn-sm col-sm-1">
-                <i class="fas fa-eraser"></i>
-            </button>
         </div>
+        <button type="reset" class="btn btn-danger btn-sm">
+            <i class="fas fa-eraser"></i>
+        </button>
     </form>
 
-    <section>
+    <section v-if="datos != null">
         <h2>Gráficos</h2>
         <div class="row">
             <div class="col-md-6">
-                <canvas id="chart1" width="400" height="400"></canvas>
+                <canvas id="calificaciones" width="400" height="400" @vue:mounted="createGraph($el,
+                    datos.calificaciones.map(calificacion => calificacion.course_name),
+                    datos.calificaciones.map(calificacion => calificacion.calificación_final),
+                    'bar', 'Calificaciones')">
             </div>
             <div class="col-md-6">
-                <canvas id="chart2" width="400" height="400"></canvas>
+                <canvas id="snapshot" width="400" height="400"
+                    @vue:mounted="createGraph($el, datos.timeline.map(snapshot => snapshot.created_at), datos.timeline.map(snapshot => snapshot.promedio_calificacion_final), 'line', 'Snapshot')">
             </div>
         </div>
     </section>
 </main>
 
 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
-    <script>
-        const ctx1 = document.getElementById('chart1').getContext('2d');
-        const chart1 = new Chart(ctx1, {
-            type: 'bar',
-            data: {
-                labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
-                datasets: [{
-                    label: '# of Votes',
-                    data: [12, 19, 3, 5, 2, 3],
-                    backgroundColor: [
-                        'rgba(255, 99, 132, 0.2)',
-                        'rgba(54, 162, 235, 0.2)',
-                        'rgba(255, 206, 86, 0.2)',
-                        'rgba(75, 192, 192, 0.2)',
-                        'rgba(153, 102, 255, 0.2)',
-                        'rgba(255, 159, 64, 0.2)'
-                    ],
-                    borderColor: [
-                        'rgba(255, 99, 132, 1)',
-                        'rgba(54, 162, 235, 1)',
-                        'rgba(255, 206, 86, 1)',
-                        'rgba(75, 192, 192, 1)',
-                        'rgba(153, 102, 255, 1)',
-                        'rgba(255, 159, 64, 1)'
-                    ],
-                    borderWidth: 1
-                }]
-            },
-            options: {
-                scales: {
-                    y: {
-                        beginAtZero: true
-                    }
-                }
-            }
-        });
+<script>
+    const filter = PetiteVue.reactive({
+        grupo: null,
+        username: null,
+    });
 
-        const ctx2 = document.getElementById('chart2').getContext('2d');
-        const chart2 = new Chart(ctx2, {
-            type: 'line',
-            data: {
-                labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
-                datasets: [{
-                    label: '# of Votes',
-                    data: [12, 19, 3, 5, 2, 3],
-                    backgroundColor: 'rgba(255, 99, 132, 0.2)',
-                    borderColor: 'rgba(255, 99, 132, 1)',
-                    borderWidth: 1
-                }]
-            },
-            options: {
-                scales: {
-                    y: {
-                        beginAtZero: true
+    const graph = PetiteVue.reactive({
+        calificaciones: null,
+        snapshot: null,
+    })
+
+    PetiteVue.createApp({
+        store,
+        filter,
+        graph,
+        alumnos: [], // schema: { grupo: string, alumnos: Array<{ nombre: string, username: string }> }
+        get alumnos_por_grupo() { // returns Array<{ nombre: string, username: string }>
+            if (this.filter.grupo)
+                return this.alumnos.find(alumno => alumno.grupo === this.filter.grupo).alumnos;
+
+            return this.alumnos.flatMap(alumno => alumno.alumnos);
+        },
+        datos: null,
+        async mounted() {
+            store.loading = true;
+            const response = await fetch('fetch/alumnos.php');
+            const data = await response.json();
+            this.alumnos = data;
+            store.loading = false;
+        },
+        createGraph($el, labels, data, type, title) {
+            const backgroundColor = [
+                'rgba(255, 99, 132, 0.2)',
+                'rgba(54, 162, 235, 0.2)',
+                'rgba(255, 206, 86, 0.2)',
+                'rgba(75, 192, 192, 0.2)',
+                'rgba(153, 102, 255, 0.2)',
+                'rgba(255, 159, 64, 0.2)',
+                'rgba(255, 99, 132, 0.2)',
+                'rgba(54, 162, 235, 0.2)',
+                'rgba(255, 206, 86, 0.2)',
+                'rgba(75, 192, 192, 0.2)',
+            ];
+
+            const borderColor = [
+                'rgba(255, 99, 132, 1)',
+                'rgba(54, 162, 235, 1)',
+                'rgba(255, 206, 86, 1)',
+                'rgba(75, 192, 192, 1)',
+                'rgba(153, 102, 255, 1)',
+                'rgba(255, 159, 64, 1)',
+                'rgba(255, 99, 132, 1)',
+                'rgba(54, 162, 235, 1)',
+                'rgba(255, 206, 86, 1)',
+                'rgba(75, 192, 192, 1)',
+            ];
+            if (this.datos == null) return;
+            const ctx1 = $el.getContext('2d');
+            const chart1 = new Chart(ctx1, {
+                type: type,
+                data: {
+                    labels: labels,
+                    datasets: [{
+                        label: title,
+                        data: data,
+                        backgroundColor: backgroundColor.slice(0, data.length),
+                        borderColor: borderColor.slice(0, data.length),
+                        borderWidth: 1
+                    }]
+                },
+                options: {
+                    scales: {
+                        y: {
+                            beginAtZero: true
+                        }
                     }
                 }
-            }
-        });
-    </script>
+            });
+        },
+        async fetchDatos() {
+            if (this.filter.username == null) return;
+            if (this.alumnos_por_grupo.find(alumno => alumno.username === this.filter.username) == null) return;
+            store.loading = true;
+            // params POST (formadata [username])
+            const params = new FormData();
+            params.append('username', this.filter.username);
+
+            const response = await fetch('fetch/calificaciones.php', {
+                method: 'POST',
+                body: params,
+            });
+            const data = await response.json();
+            this.datos = data;
+            store.loading = false;
+
+            /* this.createGraph(); */
+        }
+    }).mount()
+</script>