A Pen by WhiteSkull on CodePen.
Created
November 29, 2020 14:42
-
-
Save kiritodeveloper/89398e640a3ae15e78adfeb5ee9d8fde to your computer and use it in GitHub Desktop.
Prueba Práctica IV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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