diff --git a/dashboard/models.py b/dashboard/models.py index 5227a16..c368934 100644 --- a/dashboard/models.py +++ b/dashboard/models.py @@ -3,10 +3,10 @@ from pathlib import Path from typing import List from uuid import UUID, uuid4 +from datetime import timedelta, datetime as Datetime import yaml import streamlit as st -from datetime import date from fastapi.encoders import jsonable_encoder from pydantic import BaseModel, Field, HttpUrl, EmailStr from typing_extensions import Self @@ -551,3 +551,55 @@ def create(cls, key, obj=None): awarded=awarded, date=date, ) + +class Place(CustomModel): + description: str + +class Court(CustomModel): + thesis: Thesis = None + members: List[Person] + date: Datetime = None + minutes_duration: int + place: Place = None + + def check(self): + if len(self.members) < 1: + raise ValueError("Se debe agregar los miembros del tribunal") + + if len(self.place.description) < 1: + raise ValueError("Se debe agregar un lugar") + + for court in Court.all(): + if court.uuid == self.uuid: + continue + + if court.thesis == self.thesis: + raise ValueError("Ya existe un tribual para esta tesis") + + end_time = court.date + timedelta(minutes=court.minutes_duration) + self_end_time = self.date + timedelta(minutes=self.minutes_duration) + + if court.date.date() == self.date.date(): + if court.date.time() <= self.date.time() < end_time.time() or self.date.time() <= court.date.time() < self_end_time.time(): + + # two theses in the same place and the same hour + if self.place == court.place: + return f"Ya existe una discusión de una tesis en __{court.place.description}__ a esa hora" + + # a peron in two places in the same moment + for member in self.members: + if member in court.members: + return f"__{member.name}__ ya está en otro tribunal en ese momento" + + return "" + + def print(self) -> dict: + conflicting = self.check() != "" + return { + "Tesis": ("⚠️ " if conflicting else "") + f"{self.thesis.title} - {self.thesis.authors[0]}", + "Miembros del tribunal": ", ".join([member.name for member in self.members]), + "Fecha": self.date.strftime("%Y-%m-%d"), + "Hora Inicio": self.date.strftime("%H:%M"), + "Hora Termina": (self.date + timedelta(minutes=self.minutes_duration)).strftime("%H:%M"), + "Lugar": self.place.description, + } diff --git "a/dashboard/pages/04_\360\237\216\223_tesis.py" "b/dashboard/pages/04_\360\237\216\223_tesis.py" index aacfe54..0e405c9 100644 --- "a/dashboard/pages/04_\360\237\216\223_tesis.py" +++ "b/dashboard/pages/04_\360\237\216\223_tesis.py" @@ -1,17 +1,18 @@ import collections import json +import datetime from pathlib import Path from typing import List import pandas as pd import streamlit as st -from models import Thesis +from models import Thesis, Court, Person, Place from modules.utils import generate_widget_key from modules.graph import build_advisors_graph st.set_page_config(page_title="MatCom Dashboard - Tesis", page_icon="🎓", layout="wide") -listing, create,details = st.tabs(["📃 Listado", "➕ Crear nueva Tesis", "📄 Detalles"]) +listing, create, thesis_details, courts, court_details = st.tabs(["📃 Listado", "➕ Crear nueva Tesis", "📄 Detalles - Tesis", "🤵 Tribunales", "📜 Detalles - Tribunales"]) theses: List[Thesis] = [] @@ -124,8 +125,10 @@ st.error(e) st.code(thesis.yaml(), "yaml") - -with details: + else: + st.error("Acceso de solo lectura. Vaya a la página principal para loguearse.") + +with thesis_details: thesis = st.selectbox( "Seleccione una tesis", sorted(theses, key=lambda t: t.title), @@ -153,3 +156,133 @@ ) else: st.write(f"#### No existe el pdf de la tesis") + +with courts: + if st.session_state.get('write_access', False): + selected = st.radio( + "Tipo de entrada", + ["⭐ Nuevo Tribunal"] + (["📝 Editar Tribunal"] if len(Court.all()) > 0 else []), + horizontal=True, + label_visibility="collapsed" + ) + + if selected == "📝 Editar Tribunal": + court = st.selectbox( + "Seleccione un tribunal a modificar", + sorted(Court.all(), key=lambda c: c.thesis.title), + format_func=lambda c: f"{c.thesis.title}", + ) + else: + court = Court(thesis=None, members=[], date=None, minutes_duration=60, place=None) + + left, right = st.columns([2, 1]) + + with left: + + if selected == "⭐ Nuevo Tribunal": + theses = sorted(theses, key=lambda t: t.title) + court.thesis = st.selectbox( + "Seleccione una tesis", + theses, + format_func=lambda t: f"{t.title} - {t.authors[0]}", + index=theses.index(court.thesis if court.thesis else theses[0]), + key='courts_select_thesis', + ) + + court.members = st.multiselect( + 'Seleccione los miembros de la tesis', + Person.all(), + [p for p in Person.all() if p.name in court.thesis.advisors] # selected advisors + ) + + places = sorted(Place.all(), key=lambda p: p.description) + [ Place(description="➕ Nueva entrada") ] + court.place = st.selectbox( + 'Seleccione un local', + places, + format_func=lambda p: p.description, + index=places.index(court.place if court.place else places[0]), + key='courts_select_places', + ) + if court.place.description == "➕ Nueva entrada": + court.place.description = st.text_input( + "Descripción del lugar", + key="court_place_description", + ) + + date = st.date_input( + 'Seleccione una fecha', + value=court.date.date() if court.date else datetime.date.today(), + ) + time = st.time_input( + 'Seleccione una hora', + value=court.date.time() if court.date else datetime.time(9, 0), + ) + court.date = datetime.datetime( + year=date.year, + month=date.month, + day=date.day, + hour=time.hour, + minute=time.minute + ) + + court.minutes_duration = st.number_input( + 'Introduce los minutos de duración', + step=5, + min_value=20, + value = court.minutes_duration + ) + + with right: + try: + st.warning(court.check()) + + if st.button("💾 Guardar Tribunal"): + court.save() + court.place.save() + if selected == "⭐ Nuevo Tribunal": + st.success(f"¡Tribunal de la tesis _{court.thesis.title}_ creada con éxito!") + elif selected == "📝 Editar Tribunal": + st.success(f"¡Tribunal de la tesis _{court.thesis.title}_ editada con éxito!") + + except ValueError as e: + st.error(e) + else: + st.error("Acceso de solo lectura. Vaya a la página principal para loguearse.") + + +with court_details: + st.write("##### 🏷️ Filtros") + + places = sorted(Place.all(), key=lambda p: p.description) + member_selected = st.selectbox( + "Filtrar por un miembro de tribunal", + ["Todos"] + [ p.name for p in Person.all() ], + key='court_details_select_member', + ) + + place_selected = st.selectbox( + "Filtrar por un lugar", + ["Todos"] + [ p for p in places ], + key='court_details_select_place', + ) + + data = [] + for court in Court.all(): + include = True + if member_selected != "Todos": + if member_selected not in [p.name for p in court.members]: + include = False + + if place_selected != "Todos": + if place_selected != court.place: + include = False + + if include: + data.append(court.print()) + + + if len(data) > 0: + df = pd.DataFrame(data) + st.dataframe(df) + else: + st.error("No hay datos para mostrar") \ No newline at end of file