Skip to content

Instantly share code, notes, and snippets.

@kiritodeveloper
Created November 29, 2020 14:42
Show Gist options
  • Save kiritodeveloper/89398e640a3ae15e78adfeb5ee9d8fde to your computer and use it in GitHub Desktop.
Save kiritodeveloper/89398e640a3ae15e78adfeb5ee9d8fde to your computer and use it in GitHub Desktop.
Prueba Práctica IV
<div id="aplicacion">
<ul id="menu">
<li v-for="(tab, clave ) in tabs"><a href="javascript:void(0)" class="tablinks" v-bind:class="{ activo: tab.seleccionado }" v-on:click="seleccionaTab( clave )">{{ tab.titulo }}</a></li>
</ul>
<div v-if="tabs[0].seleccionado" class="contenedor">
<div><div class="zonaIzquierda"><select v-bind:style="{ background : colorActividadSeleccionada }" v-on:change="seleccionaActividad($event)" v-model="actividadSeleccionada"><option v-for="actividad in actividades":value="actividad.Codigo" v-bind:style="{ background : actividad.color }">{{ actividad.Denominacion }}</option></select><br><br>
<div id="listaDeProfesores"><ul><li v-for="profesor, indice in profesores" v-on:click="seleccionaProfesor( indice )" v-bind:class="{ seleccionado : seleccionados[indice] }">{{ profesor.Nombre | Capitaliza }}, {{ profesor.Apellido1 | Capitaliza }} {{ profesor.Apellido2 | Capitaliza}}</li></ul>
</div>
</div>
<div class="zonaDerecha">
<table>
<thead>
<tr><th></th><th v-for="diaSemana, dia in dias">{{ diaSemana }}</th></tr>
</thead>
<tbody>
<tr v-for="hora, indice_hora in horas">
<th><div class="popup" title="Rellena/Elimina franja horaria con actividad"><img class="botonReservaFila" v-on:click="clickReservaFila(indice_hora)"><img class="botonEliminaFila" v-on:click="clickEliminaFila(indice_hora, hora)"></div><div>{{ hora+":00" }}/{{ ((parseInt(hora)+1)+":00").length<5?"0"+((parseInt(hora)+1)+":00"):((parseInt(hora)+1)+":00") }}</div></th>
<td class="popup" v-for="dia in 5" v-on:click="clickCelda((indice_hora*5+dia)-1)" v-bind:style="{ background : colorCelda((indice_hora*5+dia)-1) }":title="popup((indice_hora*5+dia)-1)">{{ celda((indice_hora*5+dia)-1) }}</td>
</tr>
</tbody>
</table></div></div>
<div>
<input id="eliminaHorario" type="button" value="Eliminar Horario" v-on:click="clickEliminaHorario()" v-if="horasTotalesEnEsaSemana>0">
<input id="imprimeHorario" type="button" value="Imprimir Horario" v-on:click="clickImprimeHorario()" v-if="horasTotalesEnEsaSemana>0">
<br>
<div><ul><li v-for="resumenActividad, actividad in listaResumida":style="{ background : actividades[actividad].color }">{{ actividades[actividad].Denominacion }} ({{ actividad }}) tiene reservada <strong>{{ resumenActividad.contador }}</strong> hora{{ resumenActividad.contador>1 ? "s" : "" }}</li></ul><a v-if="horasTotalesEnEsaSemana>0">Tiene esta semana {{ horasTotalesEnEsaSemana }} hora{{ horasTotalesEnEsaSemana>1 ? "s" :"" }} de actividades</a></div>
</div>
</div>
<div class="contenedor" v-else>
<select v-bind:style="{ background : colorActividadSeleccionada }" v-on:change="seleccionaActividad($event)" v-model="actividadSeleccionada"><option v-for="actividad in actividades":value="actividad.Codigo" v-bind:style="{ background : actividad.color }" >{{ actividad.Denominacion }}</option></select><br><br>
<table id="tablaResumen">
<thead>
<tr><th></th><th v-for="diaSemana, dia in dias">{{ diaSemana }}</th></tr>
</thead>
<tbody>
<tr v-for="hora, indice_hora in horas">
<th>{{ hora+":00" }}/{{ ((parseInt(hora)+1)+":00").length<5?"0"+((parseInt(hora)+1)+":00"):((parseInt(hora)+1)+":00") }}</th>
<td class="popup" v-for="dia in 5" v-bind:style="{ background : colorCeldaGeneral((indice_hora*5+dia)-1) }":title="popupGeneral((indice_hora*5+dia)-1)"><div v-if="celdaGeneral((indice_hora*5+dia)-1).split(',').length>4">{{ celdaGeneral((indice_hora*5+dia)-1) }}</div><a v-else>{{ celdaGeneral((indice_hora*5+dia)-1) }}</a></td>
</tr>
</tbody>
</table>
</div>
</div>
// Version 2.0
// - Lista de profesores ordenadas
// - Modificación del diseño en la sección de horarios
// - Añadido una función "filters" para Capitalizar
// - Se añade meta código Vue para que el select mantenga el option seleccionado al cambiar de sección
// Initialize Firebase
var config = {
apiKey: "AIzaSyCC8pO_LmhTL4FhV5o6EQAWgzIkSp40Vr0",
authDomain: "pruebapracticaiv.firebaseapp.com",
databaseURL: "https://pruebapracticaiv.firebaseio.com",
storageBucket: "",
messagingSenderId: "1049865964443"
};
firebase.initializeApp(config);
/*Lean muy bien el enunciado, si tienen dudas pregunten en el foro.
Esta prueba práctica consistirá en crear una aplicación web en tiempo real (los cambios en un horario se mostrarán en todos los navegadores que tengan abierto ese horario) que permita elaborar horarios semanales de profesores y que tendrá las siguientes características:
Se mostrarán dos pestañas, una con el título Elección de horarios y otra con el título Resumen horarios.
*/
var Capitaliza = function(cadena) {
return cadena.substring(0,1).toUpperCase()+cadena.substring(1).toLowerCase();
}
var aplicacion= new Vue({
el: "#aplicacion"
, data: {
tabs :
[{ seleccionado: true, titulo: "Elección de horarios" },
{ seleccionado: false, titulo: "Resumen de horarios" }] ,
actividades : [], // listado de actividades con sus atributos
actividadSeleccionada : "",
colorActividadSeleccionada : "white",
seleccionados: [], // sirve como modificador visual de la lista de profesores
profesores : [], // listado de profesores con sus atributos
profesorSeleccionado: 0,
dias: ['Lunes','Martes','Miércoles','Jueves','Viernes'],
horas: ['08','09','10','11','12','13'],
reservas : [],
listaResumida : [], // lista resumida de las horas reservadas de cada actividad del profe actual
horasTotalesEnEsaSemana : 0,
horarios : [], // todos los malditos horarios
}, methods: {
seleccionaTab: function(clave){
for (var tab in this.tabs) this.tabs[tab].seleccionado=clave==tab;
if (clave==0) {
firebase.database().ref("/horarios").off("value");
} else {
firebase.database().ref("/horarios").on("value", function(horarios){
aplicacion.horarios=[];
for (var profesorId in horarios.val())
aplicacion.horarios[profesorId]=horarios.val()[profesorId];
});
}
},
seleccionaActividad: function(event) {
this.colorActividadSeleccionada=this.actividades[this.actividadSeleccionada=event.target.value].color;
},
// Es importante definirlas, porque se ejecuta de forma sincrona nada más cargarse la página
// luego se puede redefinir ;)
celda : function(indice) { return ""; },
colorCelda : function(indice) { return "white"; },
popup: function(indice) { },
colorCeldaGeneral : function(indice){ },
popupGeneral : function(indice){ },
celdaGeneral : function(indice){ }
// las funciones que se ejecutan de forma asincrona, se "pueden" declarar y definir fuera ;)
}, watch : {
reservas : function(nuevaReservas) { // cuando reservas se modifica, obtenemos la nueva lista resumida
var listaResumida= {}, horasTotales=0;
for (var reserva in nuevaReservas){
var clave=""+nuevaReservas[reserva];
if (listaResumida[clave]!=undefined) // existe
listaResumida[clave].contador++;
else
listaResumida[clave]={contador:1};
horasTotales++;
}
this.listaResumida=listaResumida;
this.horasTotalesEnEsaSemana=horasTotales;
},
profesorSeleccionado : function(nuevo, anterior) { // cuando cambiamos de profe desactivamos el notificador del profe anterior para no solapar las reservas en el horario
firebase.database().ref("/horarios/"+this.profesores[anterior].Codigo).off("value");
firebase.database().ref("/horarios/"+this.profesores[nuevo].Codigo).on("value", function(reservas){
aplicacion.reservas=reservas.val();
});
}
}, // fin Watch
filters : {
Capitaliza : Capitaliza
}
});
//Elección de horarios:
//1 Una lista con los tipos de actividades cada uno con su correspondiente color de fondo
firebase.database().ref("/tiposactividad").once("value",function(actividades){
aplicacion.actividades=actividades.val();
for (var actividad in aplicacion.actividades){
aplicacion.actividades[actividad].color="#"+aplicacion.actividades[actividad].color;
if (aplicacion.actividadSeleccionada=="")
aplicacion.colorActividadSeleccionada=aplicacion.actividades[aplicacion.actividadSeleccionada=actividad].color;
}
});
//2 Una lista con todos profesores, pero que se muestren en un div con una barra de desplazamiento de tal forma que el div contenga todos los profesores, pero a la vez sólo se vean unos 15 profesores.
firebase.database().ref("/profesores").orderByChild("Nombre").once("value",function(profesores){
// Resulta que la informacion no se ordena, a no ser que se pase por forEach() y lo peor de todo
// es que al pasar registro a registro usando una clave al array de profesores, el v-for lo ignora y NO renderiza (no hay mensajes de error)
// en cambio ese mismo array se puede leer con javascript puro pero como el tema era darle utilidad a todo lo visto con Vue y firebase pues...
profesores.forEach(function(profesor){
aplicacion.profesores.push({Codigo: profesor.key,Nombre: profesor.val().Nombre, Apellido1:profesor.val().Apellido1, Apellido2:profesor.val().Apellido2});
aplicacion.seleccionados[aplicacion.profesores.length-1]=false;
});
aplicacion.seleccionados[aplicacion.profesorSeleccionado]=true;
firebase.database().ref("/horarios/"+aplicacion.profesores[aplicacion.profesorSeleccionado].Codigo).on("value", function(reservas){
aplicacion.reservas=reservas.val();
});
});
//3 Un horario semanal de lunes a viernes, con franjas de una hora desde las 08:00 a las 14:00 horas
aplicacion.reservas= []; // el resto es HTML y meta codigo Vue
//4 Se seleccionará un profesor y se mostrará su horario semanal
aplicacion.seleccionaProfesor= function(clave){
this.seleccionados=[]; // No se puede modificar, porque Vue es una mierda y no detecta los cambios del array (por ende no actualiza bien)
for (var profesor in this.profesores) this.seleccionados[profesor] = profesor == (this.profesorSeleccionado=clave);
};
//5 Si se selecciona una tipo de actividad en la lista de tipos de actividades, al pulsar sobre una celda del horario aparecerá en la celda el código de la actividad y de color de fondo el color de la actividad
aplicacion.clickCelda =function(indice){
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo+"/"+indice).set(this.actividadSeleccionada);
}
aplicacion.celda =function(indice) {
try {
if (this.reservas[indice]!=undefined) return this.reservas[indice];
} catch(error) {
return "";
}
}
aplicacion.colorCelda = function(indice) {
try {
if (this.reservas[indice]!=undefined) return this.actividades[this.reservas[indice]].color;
} catch(error) {
return "white";
}
}
//6 Si se pulsa sobre una celda que ya tiene una actividad la celda se vaciará y se elimina esa hora del horario del profesor
aplicacion.clickCelda =function(indice){
try {
if (this.reservas[indice]!=undefined)
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo+"/"+indice).remove(); else throw "reserva";
} catch(reserva) { // cosas de Vue, se detiene si la excepcion no se controla y afecta al render
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo+"/"+indice).set(this.actividadSeleccionada);
}
}
//7 Si se pasa el ratón sobre una celda que no esté vacia (muestra el código de un tipo de actividad), se mostrará un pequeño popup(ventana emergente) con el nombre de esa actividad.
aplicacion.popup = function(indice) {
try {
if (this.reservas[indice]!=undefined) return this.actividades[this.reservas[indice]].Denominacion;//alert(this.actividades[this.reservas[indice]].Denominacion);
} catch(error) {
}
}
//8 Se mostrará un listado con los diferentes tipos de horas que hay en cada momento en el horario y cuantas horas hay de cada tipo de actividad
aplicacion.listaResumida = []; // se actualiza por medio de un Watch al array de Reservas del profesor seleccionado
//9 En la primera columna donde se están las horas se mostrarán un icono con un + (signo más) y al pulsar sobre esta imagen, todos los días a esa hora se colocará el tipo de actividad que esté seleccionado
aplicacion.clickReservaFila= function(indice){
for (var i=indice*5;i<(indice*5)+5;i++)
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo+"/"+i).set(this.actividadSeleccionada);
}
//10 En la primera columna donde se están las horas se mostrarán también un icono con un - (signo menos) y al pulsar sobre esta imagen, tras mostrar un mensaje de confirmación, se borrarán todas las actividades que existan en esa hora todos los días de la semana.
aplicacion.clickEliminaFila= function(indice, hora){
if (confirm("¿Desea eliminar las reservas de la franja de las "+hora+" horas?"))
for (var i=indice*5;i<(indice*5)+5;i++)
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo+"/"+i).remove();
}
//11 Existirá un botón de borrar horario, que al pulsarlo, tras mostrar un mensaje de confirmación, borrará todas las horas del horario actual
aplicacion.clickEliminaHorario= function(){
if (confirm("¿Desea eliminar el horario de actividades de '"+this.profesores[this.profesorSeleccionado].Nombre+"'?"))
firebase.database().ref("/horarios/"+this.profesores[this.profesorSeleccionado].Codigo).remove();
}
//12 Existirá un botón imprimir que la pulsarlo generará un documento pdf con el horario del profesor actual. Para imprimir se deberá utilizar una librería javascript (p.e. jsPDF)
aplicacion.clickImprimeHorario= function(){
var pdf = new jsPDF({
orientation: 'l',
unit: 'mm',
format: 'a3',
compress: true,
fontSize: 8,
lineHeight: 1,
autoSize: false,
printHeaders: true
},'','','');
pdf.fromHTML(
document.getElementsByTagName("html")[0],
50,
5,
{
'width': 315,'elementHandlers': {
'#listaDeProfesores': function (element, renderer) {
return true;
},
'#menu': function (element, renderer) {
return true;
}
}
});
pdf.save('horarioDe'+this.profesores[this.profesorSeleccionado].Codigo);
}
//Resumen de horarios:
//13 Se mostrará una lista desplegable en la parte superior de la pantalla con los tipos de actividades cada uno con su correspondiente color de fondo y al pinchar sobre un tipo de actividad se mostrará un resumen de todos los profesores que tienen ese tipo de actividad en su horario. P.e. si se pincha sobre guardia saldrá un horario semanal y en cada celda se mostrará que profesores tienen guardia a esa hora.
aplicacion.colorCeldaGeneral=function(indice){
for (var indiceProfe in this.profesores) {
try {
if (this.horarios[this.profesores[indiceProfe].Codigo][indice]==this.actividadSeleccionada)
return this.actividades[this.actividadSeleccionada].color;
} catch(error) {}
}
return "white";
}
aplicacion.popupGeneral =function(indice){
var profesores="";
for (var indiceProfe in this.profesores) {
try {
if (this.horarios[this.profesores[indiceProfe].Codigo][indice]==this.actividadSeleccionada)
profesores+=Capitaliza(this.profesores[indiceProfe].Nombre)+", "+
Capitaliza(this.profesores[indiceProfe].Apellido1)+" "+
Capitaliza(this.profesores[indiceProfe].Apellido2)+" ("+this.profesores[indiceProfe].Codigo+")\n";
} catch(error) {}
}
return profesores.length>0?profesores: null;
}
aplicacion.celdaGeneral =function(indice){
var codigosProfesores="";
for (var indiceProfe in this.profesores) {
try {
if (this.horarios[this.profesores[indiceProfe].Codigo][indice]==this.actividadSeleccionada)
codigosProfesores+=this.profesores[indiceProfe].Codigo+", ";
} catch(error) {}
}
return codigosProfesores.substring(0,codigosProfesores.length-2);
}
<script src="https://www.gstatic.com/firebasejs/3.6.2/firebase.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.2/jspdf.debug.js"></script>
body {font-family: "Lato", sans-serif;
background-color: gray;}
ul#menu {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
/*border: 1px solid #ccc;*/
background-color: gray;
}
select {
font-size: 20px;
border-radius: 5px;
}
/* Estilo para el listado de pestañas */
ul#menu li {float: left;}
/* Estilo para el contenido de las pestañas */
ul#menu li a {
display: inline-block;
color: black;
text-align: center;
padding: 14px 16px;
text-decoration: none;
transition: 0.3s;
font-size: 17px;
}
/* Cuando pasas el ratón sobre la pestaña NO seleccionada */
ul#menu li a:hover:not(.activo) {
background-color: #ddd;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
/* Cuando tiene el foco o está activa la pestaña seleccionada */
ul#menu li a:focus, .activo {
background-color: white;
border-left: 1px solid black;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-top: 1px solid black;
border-right: 1px solid black;
font-weight: bold;
}
/* estilo para el contenedor, div */
.contenedor {
padding: 6px 12px;
border: 1px solid black;
border-top: none;
background-color: white;
width: 100%;
}
.contenedor:nth-child(1) {
display: table-row;
width: 100%;
}
.zonaDerecha {
display: table-cell;
width: 55%;
}
.zonaIzquierda {
display: table-cell;
width: 40%;
}
.zonaIzquierda {
flex: 1;
}
div#listaDeProfesores {
width:100%;
}
div#listaDeProfesores ul li:nth-child(odd):not(.seleccionado) {
background-color: lightgray;
}
div#listaDeProfesores ul {
list-style-type: none;
margin: 0;
padding: 0;
height: 290px; /* con el zoom 100% */
overflow-y: auto;
}
div#listaDeProfesores ul li:hover {
background-color: gray;
color: white;
cursor: pointer;
}
div#listaDeProfesores ul li:focus, .seleccionado {
background-color: black;
color: lightgray;
font-weight: bold;
}
table {
table-layout: fixed;
}
table, td, tr {
border-radius: 6px;
}
td {
border: 1px solid lightgray;
width: 100px;
height: 50px;
text-align: center;
cursor: pointer;
}
div#listaResumida {
background-color: red;
height: 290px;
}
.botonReservaFila {
background-image : url("http://www.conexed.com/wp-content/plugins/wp-widget-sugarcrm-lead-module/image/plus-icon.png");
width: 25px;
height: 25px;
display: inline;
border-radius: 25px;
cursor: pointer;
margin-right: 25px;
}
.botonEliminaFila {
background-image: url("http://jnasolarsolutions.com/wp-content/plugins/wp-widget-sugarcrm-lead-module/image/minus-icon.png");
width: 25px;
height: 25px;
display: inline;
border-radius: 25px;
cursor: pointer;
}
input {
border-radius: 5px;
height: 30px;
cursor: pointer;
font-weight: bold;
}
input#eliminaHorario {
background-color: red;
color: white;
}
table#tablaResumen div {
height: 50px;
overflow : auto;
margin-top: auto;
}
.popup {
font-weight: normal;
text-decoration: none;
}
.popup:hover {
color: white;
position: relative;
}
.popup[title]:hover:after {
content: attr(title);
padding: 4px 8px;
color: #333;
position: absolute;
left: 100%;
/*bottom: 50%;*/
white-space: pre;
z-index: 20px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0px 0px 4px #222;
-webkit-box-shadow: 0px 0px 4px #222;
box-shadow: 0px 0px 4px #222;
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #eeeeee),color-stop(1, #cccccc));
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment