From e2a2b0f0836fc6de922c35b77b236379783e7590 Mon Sep 17 00:00:00 2001 From: viennet Date: Fri, 1 Jan 2021 20:55:44 +0100 Subject: [PATCH 001/104] Fix: Bul. XML: moyenne notes module --- sco_bulletins_xml.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sco_bulletins_xml.py b/sco_bulletins_xml.py index 7c486ca7..8c65b821 100644 --- a/sco_bulletins_xml.py +++ b/sco_bulletins_xml.py @@ -230,6 +230,7 @@ def make_xml_formsemestre_bulletinetud( value=mod_moy, min=fmt_note(modstat["min"]), max=fmt_note(modstat["max"]), + moy=fmt_note(modstat["moy"]), ) doc._pop() if context.get_preference("bul_show_mod_rangs", formsemestre_id): From 44dde2e678c650812be445605d21168ceb81520a Mon Sep 17 00:00:00 2001 From: viennet Date: Sat, 2 Jan 2021 00:18:47 +0100 Subject: [PATCH 002/104] Avoid trying to delete depts when no config file --- scotests/scointeractive.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scotests/scointeractive.sh b/scotests/scointeractive.sh index 38bf8dfa..2cecaf90 100755 --- a/scotests/scointeractive.sh +++ b/scotests/scointeractive.sh @@ -17,6 +17,7 @@ usage() { set -euo pipefail cd /opt/scodoc/Products/ScoDoc || exit 2 +source config/config.sh source config/utils.sh if [ $# -lt 1 ] @@ -38,7 +39,11 @@ shift if [ "$recreate_dept" = 1 ] then - (cd config || terminate "no config directory"; ./delete_dept.sh -n "$DEPT") || terminate "error deleting dept $DEPT" + cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg + if [ -e "$cfg_pathname" ] + then + (cd config || terminate "no config directory"; ./delete_dept.sh -n "$DEPT") || terminate "error deleting dept $DEPT" + fi (cd config || terminate "no config directory"; ./create_dept.sh -n "$DEPT") || terminate "error creating dept $DEPT" # systemctl start scodoc fi From e9e9e1c6e11c98eedb23a87df75346785d9eb716 Mon Sep 17 00:00:00 2001 From: viennet Date: Sat, 2 Jan 2021 22:07:38 +0100 Subject: [PATCH 003/104] Ajout lien saisie absence sur tableau bord module, et lien affichage notes tous --- ZAbsences.py | 2 +- ZScolar.py | 5 +---- sco_formsemestre.py | 12 ++++++++++-- sco_groups_view.py | 17 ++++++++++++++--- sco_moduleimpl_status.py | 27 ++++++++++++++++++++------- static/css/scodoc.css | 6 ++++-- 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/ZAbsences.py b/ZAbsences.py index 94b3b2c8..6ebf844e 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -936,7 +936,7 @@ class ZAbsences( moduleimpl_id = None groups_infos = sco_groups_view.DisplayedGroupsInfos( - self, group_ids, REQUEST=REQUEST + self, group_ids, moduleimpl_id=moduleimpl_id, REQUEST=REQUEST ) if not groups_infos.members: return ( diff --git a/ZScolar.py b/ZScolar.py index 133a827c..7a635570 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -764,14 +764,11 @@ UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo ' % sem) FA.append(groups_infos.get_form_elem()) - + if moduleimpl_id: + FA.append( + '' % moduleimpl_id + ) FA.append('') FA.append( diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index e60c642f..99f1f862 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -43,7 +43,7 @@ import sco_formsemestre import sco_formsemestre_status from sco_formsemestre_status import makeMenu import sco_compute_moy - +import ZAbsences # ported from old DTML code in oct 2009 @@ -224,7 +224,6 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No ) H.append("") else: - t0, t1 = "règle de calcul standard", "" H.append( 'règle de calcul standard' ) @@ -235,10 +234,21 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No ) H.append("") H.append( - 'Absences ' + 'Absences dans ce module' % moduleimpl_id ) - H.append("") + # Adapté à partir d'une suggestion de DS (Le Havre) + # Liens saisies absences seulement si permission et date courante dans le semestre + if authuser.has_permission( + ScoAbsChange, context + ) and sco_formsemestre.sem_est_courant(context, sem): + datelundi = ZAbsences.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() + H.append( + 'Saisie Absences hebdo.' + % (formsemestre_id, moduleimpl_id, datelundi) + ) + + H.append("") # if has_expression and nt.expr_diagnostics: H.append( @@ -478,7 +488,11 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No % etat ) if etat["moy"]: - H.append("%s / %g" % (etat["moy"], eval["note_max"])) + H.append("%s / %g" % (etat["moy"], eval["note_max"])) + H.append( + """  (afficher)""" + % (eval["evaluation_id"],) + ) else: H.append( """saisir notes""" @@ -488,7 +502,6 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No # if etat["nb_notes"] == 0: H.append(""" """ % tr_class) - # XXX H.append("""""") else: # il y a deja des notes saisies gr_moyennes = etat["gr_moyennes"] @@ -505,7 +518,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No if gr_moyenne["gr_nb_notes"] > 0: H.append("%(gr_moy)s" % gr_moyenne) H.append( - """  (%s notes""" + """  (%s notes""" % ( eval["evaluation_id"], gr_moyenne["group_id"], diff --git a/static/css/scodoc.css b/static/css/scodoc.css index 875361b1..7eebc65f 100644 --- a/static/css/scodoc.css +++ b/static/css/scodoc.css @@ -1223,7 +1223,7 @@ ul.ue_inscr_list li.etud { } #grouplists table { - //border: 1px solid black; + /*border: 1px solid black;*/ border-spacing: 1px; } @@ -1236,7 +1236,9 @@ div.moduleimpl_tableaubord { padding: 7px; border: 2px solid gray; } - +span.moduleimpl_abs_link { + padding-right: 2em; +} .moduleimpl_evaluations_top_links { font-size: 80%; margin-bottom: 3px; From bb70ef8e388d98d6ae115b7d38d59f1dfb0aa5d8 Mon Sep 17 00:00:00 2001 From: viennet Date: Mon, 4 Jan 2021 11:31:18 +0100 Subject: [PATCH 004/104] Fix: trombino Tours --- sco_bulletins_generator.py | 2 +- sco_bulletins_pdf.py | 2 +- sco_moduleimpl_status.py | 1 - sco_pdf.py | 2 +- sco_trombino.py | 4 ++-- sco_trombino_tours.py | 8 ++++++-- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/sco_bulletins_generator.py b/sco_bulletins_generator.py index 4e09360b..717cd9be 100644 --- a/sco_bulletins_generator.py +++ b/sco_bulletins_generator.py @@ -198,7 +198,7 @@ class BulletinGenerator: document.addPageTemplates( ScolarsPageTemplate( document, - self.context, + context=self.context, author="%s %s (E. Viennet) [%s]" % (SCONAME, SCOVERSION, self.description), title="Bulletin %s de %s" diff --git a/sco_bulletins_pdf.py b/sco_bulletins_pdf.py index 56617a57..e01dca0d 100644 --- a/sco_bulletins_pdf.py +++ b/sco_bulletins_pdf.py @@ -86,7 +86,7 @@ def pdfassemblebulletins( document.addPageTemplates( ScolarsPageTemplate( document, - context, + context=context, author="%s %s (E. Viennet)" % (SCONAME, SCOVERSION), title="Bulletin %s" % bul_title, subject="Bulletin de note", diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index 99f1f862..7a4bf1e7 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -50,7 +50,6 @@ import ZAbsences # menu evaluation dans moduleimpl def moduleimpl_evaluation_menu(context, evaluation_id, nbnotes=0, REQUEST=None): "Menu avec actions sur une evaluation" - authuser = REQUEST.AUTHENTICATED_USER E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0] modimpl = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] diff --git a/sco_pdf.py b/sco_pdf.py index 80f72f95..0d14e793 100644 --- a/sco_pdf.py +++ b/sco_pdf.py @@ -300,7 +300,7 @@ def pdf_basic_page( document.addPageTemplates( ScolarsPageTemplate( document, - context, + context=context, title=title, author="%s %s (E. Viennet)" % (SCONAME, SCOVERSION), footer_template="Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s", diff --git a/sco_trombino.py b/sco_trombino.py index f1a33856..04ffb314 100644 --- a/sco_trombino.py +++ b/sco_trombino.py @@ -366,7 +366,7 @@ def _trombino_pdf(context, groups_infos, REQUEST): document.addPageTemplates( ScolarsPageTemplate( document, - context, + context=context, preferences=context.get_preferences(sem["formsemestre_id"]), ) ) @@ -515,7 +515,7 @@ def _listeappel_photos_pdf(context, groups_infos, REQUEST): document.addPageTemplates( ScolarsPageTemplate( document, - context, + context=context, preferences=context.get_preferences(sem["formsemestre_id"]), ) ) diff --git a/sco_trombino_tours.py b/sco_trombino_tours.py index 6c0e9556..eece74eb 100644 --- a/sco_trombino_tours.py +++ b/sco_trombino_tours.py @@ -276,7 +276,9 @@ def pdf_trombino_tours( filename = "trombino-%s-%s.pdf" % (DeptName, groups_infos.groups_filename) document = BaseDocTemplate(report) document.addPageTemplates( - ScolarsPageTemplate(document, preferences=context.get_preferences()) + ScolarsPageTemplate( + document, context=context, preferences=context.get_preferences() + ) ) document.build(objects) data = report.getvalue() @@ -468,7 +470,9 @@ def pdf_feuille_releve_absences( else: document = BaseDocTemplate(report, pagesize=taille) document.addPageTemplates( - ScolarsPageTemplate(document, preferences=context.get_preferences()) + ScolarsPageTemplate( + document, context=context, preferences=context.get_preferences() + ) ) document.build(objects) data = report.getvalue() From 82f0612883b74b5184bfe49d6d16a939c304ba44 Mon Sep 17 00:00:00 2001 From: viennet Date: Mon, 4 Jan 2021 13:53:19 +0100 Subject: [PATCH 005/104] pylint --- gen_tables.py | 2 +- sco_pdf.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/gen_tables.py b/gen_tables.py index c1a84d18..fbd8309c 100644 --- a/gen_tables.py +++ b/gen_tables.py @@ -693,7 +693,7 @@ class SeqGenTable: def excel(self): """Export des genTables dans un unique fichier excel avec plusieurs feuilles tagguées""" - book = sco_excel.Workbook() # Le fichier xls en devenir + book = sco_excel.Workbook() # pylint: disable=no-member for (_, gt) in self.genTables.items(): gt.excel(wb=book) # Ecrit dans un fichier excel return book.savetostr() diff --git a/sco_pdf.py b/sco_pdf.py index 0d14e793..23e5c7ff 100644 --- a/sco_pdf.py +++ b/sco_pdf.py @@ -31,7 +31,10 @@ Tout accès à ReportLab doit donc être précédé d'un PDFLOCK.acquire() et terminé par un PDFLOCK.release() """ -import time, cStringIO +import time +import cStringIO +import re +import os from types import StringType import unicodedata import traceback @@ -49,9 +52,12 @@ from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY from reportlab.lib import styles from reportlab.lib.pagesizes import letter, A4, landscape -from sco_utils import * +import sco_utils as scu +from sco_utils import CONFIG, SCO_ENCODING, SCODOC_LOGOS_DIR, LOGOS_IMAGES_ALLOWED_TYPES from notes_log import log +from sco_exceptions import ScoGenError from SuppressAccents import suppression_diacritics +import VERSION from VERSION import SCOVERSION, SCONAME PAGE_HEIGHT = defaultPageSize[1] @@ -107,7 +113,7 @@ def makeParas(txt, style, suppress_empty=False): if suppress_empty: r = [] for para in paras: - m = re.match("\s*<\s*para.*>\s*(.*)\s*<\s*/\s*para\s*>\s*", para) + m = re.match(r"\s*<\s*para.*>\s*(.*)\s*<\s*/\s*para\s*>\s*", para) if not m: r.append(para) # not a paragraph, keep it else: @@ -229,7 +235,10 @@ class ScolarsPageTemplate(PageTemplate): # ---- Logo: a small image, positionned at top left of the page if self.logo is not None: # draws the logo if it exists - ((width, height), image) = self.logo + ( # pylint: disable=unpacking-non-sequence + (width, height), + image, + ) = self.logo canvas.drawImage(image, inch, doc.pagesize[1] - inch, width, height) # ---- Filigranne (texte en diagonal en haut a gauche de chaque page) From 3afec00b5e86ddb46a49ab36a914036a5532415c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 10 Jan 2021 18:05:20 +0100 Subject: [PATCH 006/104] Small fixes --- sco_bulletins_generator.py | 42 +++++++++++++++------ sco_report.py | 77 +++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/sco_bulletins_generator.py b/sco_bulletins_generator.py index 717cd9be..69105974 100644 --- a/sco_bulletins_generator.py +++ b/sco_bulletins_generator.py @@ -42,12 +42,22 @@ La préférence 'bul_pdf_class_name' est obsolete (inutilisée). """ +import time +import cStringIO import collections +import traceback +import reportlab +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak +from reportlab.platypus import Table, TableStyle, Image, KeepInFrame +import sco_utils +import VERSION +from sco_exceptions import NoteProcessError import sco_preferences from notes_log import log import sco_formsemestre -from sco_pdf import * +import sco_pdf +from sco_pdf import PDFLOCK BULLETIN_CLASSES = ( collections.OrderedDict() @@ -145,7 +155,7 @@ class BulletinGenerator: dt, self.infos["etud"]["nom"], ) - filename = unescape_html(filename).replace(" ", "_").replace("&", "") + filename = sco_utils.unescape_html(filename).replace(" ", "_").replace("&", "") return filename def generate(self, format="", stand_alone=True): @@ -166,8 +176,10 @@ class BulletinGenerator: def generate_html(self): """Return bulletin as an HTML string""" H = ['
'] - H.append(self.bul_table(format="html")) # table des notes - H.append(self.bul_part_below(format="html")) # infos sous la table + # table des notes: + H.append(self.bul_table(format="html")) # pylint: disable=no-member + # infos sous la table: + H.append(self.bul_part_below(format="html")) # pylint: disable=no-member H.append("
") return "\n".join(H) @@ -179,10 +191,14 @@ class BulletinGenerator: """ formsemestre_id = self.infos["formsemestre_id"] - objects = self.bul_title_pdf() # partie haute du bulletin - objects += self.bul_table(format="pdf") # table des notes - objects += self.bul_part_below(format="pdf") # infos sous la table - objects += self.bul_signatures_pdf() # signatures + # partie haute du bulletin + objects = self.bul_title_pdf() # pylint: disable=no-member + # table des notes + objects += self.bul_table(format="pdf") # pylint: disable=no-member + # infos sous la table + objects += self.bul_part_below(format="pdf") # pylint: disable=no-member + # signatures + objects += self.bul_signatures_pdf() # pylint: disable=no-member # Réduit sur une page objects = [KeepInFrame(0, 0, objects, mode="shrink")] @@ -194,13 +210,13 @@ class BulletinGenerator: # Generation du document PDF sem = sco_formsemestre.get_formsemestre(self.context, formsemestre_id) report = cStringIO.StringIO() # in-memory document, no disk file - document = BaseDocTemplate(report) + document = sco_pdf.BaseDocTemplate(report) document.addPageTemplates( - ScolarsPageTemplate( + sco_pdf.ScolarsPageTemplate( document, context=self.context, author="%s %s (E. Viennet) [%s]" - % (SCONAME, SCOVERSION, self.description), + % (VERSION.SCONAME, VERSION.SCOVERSION, self.description), title="Bulletin %s de %s" % (sem["titremois"], self.infos["etud"]["nomprenom"]), subject="Bulletin de note", @@ -222,7 +238,9 @@ class BulletinGenerator: """ try: # put each table cell in a Paragraph - Pt = [[Paragraph(SU(x), self.CellStyle) for x in line] for line in P] + Pt = [ + [Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P + ] except: # enquête sur exception intermittente... log("*** bug in PDF buildTableObject:") diff --git a/sco_report.py b/sco_report.py index 4e1d13ee..3f4a69b0 100644 --- a/sco_report.py +++ b/sco_report.py @@ -29,16 +29,22 @@ - statistiques decisions - suivi cohortes """ +import os +import tempfile +import urllib +import re +import time +import mx import mx.DateTime from mx.DateTime import DateTime as mxDateTime -import tempfile, urllib, re - -from notesdb import * -from sco_utils import * +import sco_utils as scu +import VERSION from notes_log import log from gen_tables import GenTable import sco_excel, sco_pdf +from notesdb import DateDMYtoISO +from sco_exceptions import ScoValueError import sco_codes_parcours from sco_codes_parcours import code_semestre_validant import sco_parcours_dut @@ -129,7 +135,7 @@ def _results_by_category( if Count.has_key(etud[category]): Count[etud[category]][etud[result]] += 1 else: - Count[etud[category]] = DictDefault(kv_dict={etud[result]: 1}) + Count[etud[category]] = scu.DictDefault(kv_dict={etud[result]: 1}) # conversion en liste de dict C = [Count[cat] for cat in categories] # Totaux par lignes et colonnes @@ -214,9 +220,9 @@ def formsemestre_report( formsemestre_id=formsemestre_id, ) # - tab.filename = make_filename("stats " + sem["titreannee"]) + tab.filename = scu.make_filename("stats " + sem["titreannee"]) - tab.origin = "Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "" + tab.origin = "Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "" tab.caption = "Répartition des résultats par %s, semestre %s" % ( category_name, sem["titreannee"], @@ -259,7 +265,7 @@ def formsemestre_report_counts( Tableau comptage avec choix des categories """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) - category_name = strcapitalize(category) + category_name = scu.strcapitalize(category) title = "Comptages " + category_name etuds = formsemestre_etuds_stats(context, sem, only_primo=only_primo) tab = formsemestre_report( @@ -484,7 +490,7 @@ def table_suivi_cohorte( indices_sems.sort() for p in P: p.nb_etuds = 0 # nombre total d'etudiants dans la periode - p.sems_by_id = DictDefault(defaultvalue=[]) + p.sems_by_id = scu.DictDefault(defaultvalue=[]) for s in p.sems: p.sems_by_id[s["semestre_id"]].append(s) p.nb_etuds += len(s["members"]) @@ -623,8 +629,8 @@ def table_suivi_cohorte( rows=L, html_col_width="4em", html_sortable=True, - filename=make_filename("cohorte " + sem["titreannee"]), - origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "", + filename=scu.make_filename("cohorte " + sem["titreannee"]), + origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "", caption="Suivi cohorte " + pp + sem["titreannee"] + dbac, page_title="Suivi cohorte " + sem["titreannee"], html_class="table_cohorte", @@ -666,7 +672,6 @@ def formsemestre_suivi_cohorte( ): """Affiche suivi cohortes par numero de semestre""" percent = int(percent) - sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) tab, expl, bacs, bacspecialites, annee_bacs, sexes, statuts = table_suivi_cohorte( context, formsemestre_id, @@ -1049,7 +1054,7 @@ def table_suivi_parcours( etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list( context, formsemestre_id, only_primo=only_primo ) - codes_etuds = DictDefault(defaultvalue=[]) + codes_etuds = scu.DictDefault(defaultvalue=[]) for etud in etuds: etud["codeparcours"], etud["decisions_jury"] = get_codeparcoursetud( context, etud @@ -1105,7 +1110,7 @@ def table_suivi_parcours( columns_ids=columns_ids, rows=L, titles=titles, - origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "", + origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "", caption="Parcours suivis, étudiants %s semestre " % primostr + sem["titreannee"], page_title="Parcours " + sem["titreannee"], @@ -1164,7 +1169,6 @@ def formsemestre_suivi_parcours( REQUEST=None, ): """Effectifs dans les differents parcours possibles.""" - sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) tab = table_suivi_parcours( context, formsemestre_id, @@ -1211,9 +1215,8 @@ def graph_parcours( statut="", ): """""" - if not WITH_PYDOT: + if not scu.WITH_PYDOT: raise ScoValueError("pydot module is not installed") - sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list( context, formsemestre_id, @@ -1227,12 +1230,12 @@ def graph_parcours( # log('graph_parcours: %s etuds (only_primo=%s)' % (len(etuds), only_primo)) if not etuds: return "", etuds, bacs, bacspecialites, annee_bacs, sexes, statuts - edges = DictDefault( + edges = scu.DictDefault( defaultvalue=set() ) # {(formsemestre_id_origin, formsemestre_id_dest) : etud_set} sems = {} - effectifs = DictDefault(defaultvalue=set()) # formsemestre_id : etud_set - decisions = DictDefault(defaultvalue={}) # formsemestre_id : { code : nb_etud } + effectifs = scu.DictDefault(defaultvalue=set()) # formsemestre_id : etud_set + decisions = scu.DictDefault(defaultvalue={}) # formsemestre_id : { code : nb_etud } isolated_nodes = [] connected_nodes = set() diploma_nodes = [] @@ -1279,7 +1282,11 @@ def graph_parcours( dem_nodes[s["formsemestre_id"]] = nid edges[(s["formsemestre_id"], nid)].add(etudid) # ajout noeud pour NAR (seulement pour noeud de depart) - if s["formsemestre_id"] == formsemestre_id and dec and dec["code"] == NAR: + if ( + s["formsemestre_id"] == formsemestre_id + and dec + and dec["code"] == sco_codes_parcours.NAR + ): nid = "_nar_" + s["formsemestre_id"] nar_nodes[s["formsemestre_id"]] = nid edges[(s["formsemestre_id"], nid)].add(etudid) @@ -1295,10 +1302,10 @@ def graph_parcours( edges[(s["formsemestre_id"], nid)].add(etudid) diploma_nodes.append(nid) # - g = pydot.graph_from_edges(edges.keys()) + g = scu.pydot.graph_from_edges(edges.keys()) for fid in isolated_nodes: if not fid in connected_nodes: - n = pydot.Node(name=fid) + n = scu.pydot.Node(name=fid) g.add_node(n) g.set("rankdir", "LR") # left to right g.set_fontname("Helvetica") @@ -1306,7 +1313,7 @@ def graph_parcours( g.set_bgcolor("#fffff0") # ou 'transparent' # titres des semestres: for s in sems.values(): - n = pydot_get_node(g, s["formsemestre_id"]) + n = scu.pydot_get_node(g, s["formsemestre_id"]) log("s['formsemestre_id'] = %s" % s["formsemestre_id"]) log("n=%s" % n) log("get=%s" % g.get_node(s["formsemestre_id"])) @@ -1324,31 +1331,31 @@ def graph_parcours( s["annee_fin"][2:], len(effectifs[s["formsemestre_id"]]), ) - n.set("label", suppress_accents(label)) + n.set("label", scu.suppress_accents(label)) n.set_fontname("Helvetica") n.set_fontsize(8.0) n.set_width(1.2) n.set_shape("box") n.set_URL("formsemestre_status?formsemestre_id=" + s["formsemestre_id"]) # semestre de depart en vert - n = pydot_get_node(g, formsemestre_id) + n = scu.pydot_get_node(g, formsemestre_id) n.set_color("green") # demissions en rouge, octagonal for nid in dem_nodes.values(): - n = pydot_get_node(g, nid) + n = scu.pydot_get_node(g, nid) n.set_color("red") n.set_shape("octagon") n.set("label", "Dem.") # NAR en rouge, Mcircle for nid in nar_nodes.values(): - n = pydot_get_node(g, nid) + n = scu.pydot_get_node(g, nid) n.set_color("red") n.set_shape("Mcircle") - n.set("label", NAR) + n.set("label", sco_codes_parcours.NAR) # diplomes: for nid in diploma_nodes: - n = pydot_get_node(g, nid) + n = scu.pydot_get_node(g, nid) n.set_color("red") n.set_shape("ellipse") n.set("label", "Diplome") # bug si accent (pas compris pourquoi) @@ -1367,7 +1374,7 @@ def graph_parcours( bubbles[src_id + ":" + dst_id] = etud_descr e.set_URL("__xxxetudlist__?" + src_id + ":" + dst_id) # Genere graphe - f, path = tempfile.mkstemp(".gr") + _, path = tempfile.mkstemp(".gr") g.write(path=path, format=format) data = open(path, "r").read() log("dot generated %d bytes in %s format" % (len(data), format)) @@ -1403,7 +1410,7 @@ def graph_parcours( ) return ( ' Date: Sun, 10 Jan 2021 18:54:39 +0100 Subject: [PATCH 007/104] code refactoring: sco_abs --- ZAbsences.py | 602 ++------------------------------- ZEntreprises.py | 86 ++--- ZScolar.py | 17 +- html_sidebar.py | 2 +- sco_abs.py | 595 ++++++++++++++++++++++++++++++++ sco_abs_views.py | 4 +- sco_bulletins.py | 4 +- sco_bulletins_json.py | 4 +- sco_bulletins_xml.py | 4 +- sco_compute_moy.py | 4 +- sco_evaluations.py | 4 +- sco_formsemestre_validation.py | 2 +- sco_groups_view.py | 6 +- sco_moduleimpl_status.py | 4 +- sco_poursuite_dut.py | 4 +- sco_prepajury.py | 2 +- sco_trombino_tours.py | 6 +- 17 files changed, 699 insertions(+), 651 deletions(-) create mode 100644 sco_abs.py diff --git a/ZAbsences.py b/ZAbsences.py index 6ebf844e..8e50e72a 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -48,6 +48,13 @@ import urllib import datetime import jaxml import cgi +import string +import re +import time +import calendar + +from mx.DateTime import DateTime as mxDateTime +from mx.DateTime.ISO import ParseDateTimeUTC # --------------- from sco_zope import * @@ -68,10 +75,8 @@ import sco_groups_view import sco_excel import sco_abs_notification, sco_abs_views import sco_compute_moy -import string, re -import time, calendar -from mx.DateTime import DateTime as mxDateTime -from mx.DateTime.ISO import ParseDateTimeUTC +import sco_abs +from sco_abs import ddmmyyyy def _toboolean(x): @@ -84,189 +89,6 @@ def _toboolean(x): return False -def MonthNbDays(month, year): - "returns nb of days in month" - if month > 7: - month = month + 1 - if month % 2: - return 31 - elif month == 2: - if calendar.isleap(year): - return 29 - else: - return 28 - else: - return 30 - - -class ddmmyyyy: - """immutable dates""" - - def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False): - self.work_saturday = work_saturday - if date is None: - return - try: - if fmt == "ddmmyyyy": - self.day, self.month, self.year = string.split(date, "/") - elif fmt == "iso": - self.year, self.month, self.day = string.split(date, "-") - else: - raise ValueError("invalid format spec. (%s)" % fmt) - self.year = string.atoi(self.year) - self.month = string.atoi(self.month) - self.day = string.atoi(self.day) - except: - raise ScoValueError("date invalide: %s" % date) - # accept years YYYY or YY, uses 1970 as pivot - if self.year < 1970: - if self.year > 100: - raise ScoInvalidDateError("Année invalide: %s" % self.year) - if self.year < 70: - self.year = self.year + 2000 - else: - self.year = self.year + 1900 - if self.month < 1 or self.month > 12: - raise ScoInvalidDateError("Mois invalide: %s" % self.month) - - if self.day < 1 or self.day > MonthNbDays(self.month, self.year): - raise ScoInvalidDateError("Jour invalide: %s" % self.day) - - # weekday in 0-6, where 0 is monday - self.weekday = calendar.weekday(self.year, self.month, self.day) - - self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0)) - - def iswork(self): - "returns true if workable day" - if self.work_saturday: - nbdays = 6 - else: - nbdays = 5 - if ( - self.weekday >= 0 and self.weekday < nbdays - ): # monday-friday or monday-saturday - return 1 - else: - return 0 - - def __repr__(self): - return "'%02d/%02d/%04d'" % (self.day, self.month, self.year) - - def __str__(self): - return "%02d/%02d/%04d" % (self.day, self.month, self.year) - - def ISO(self): - "iso8601 representation of the date" - return "%04d-%02d-%02d" % (self.year, self.month, self.day) - - def next(self, days=1): - "date for the next day (nota: may be a non workable day)" - day = self.day + days - month = self.month - year = self.year - - while day > MonthNbDays(month, year): - day = day - MonthNbDays(month, year) - month = month + 1 - if month > 12: - month = 1 - year = year + 1 - return self.__class__( - "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday - ) - - def prev(self, days=1): - "date for previous day" - day = self.day - days - month = self.month - year = self.year - while day <= 0: - month = month - 1 - if month == 0: - month = 12 - year = year - 1 - day = day + MonthNbDays(month, year) - - return self.__class__( - "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday - ) - - def next_monday(self): - "date of next monday" - return self.next((7 - self.weekday) % 7) - - def prev_monday(self): - "date of last monday, but on sunday, pick next monday" - if self.weekday == 6: - return self.next_monday() - else: - return self.prev(self.weekday) - - def __cmp__(self, other): - """return a negative integer if self < other, - zero if self == other, a positive integer if self > other""" - return int(self.time - other.time) - - def __hash__(self): - "we are immutable !" - return hash(self.time) ^ hash(str(self)) - - -# d = ddmmyyyy( '21/12/99' ) - - -def YearTable( - context, - year, - events=[], - firstmonth=9, - lastmonth=7, - halfday=0, - dayattributes="", - pad_width=8, -): - """Generate a calendar table - events = list of tuples (date, text, color, href [,halfday]) - where date is a string in ISO format (yyyy-mm-dd) - halfday is boolean (true: morning, false: afternoon) - text = text to put in calendar (must be short, 1-5 cars) (optional) - if halfday, generate 2 cells per day (morning, afternoon) - """ - T = [ - '' - ] - T.append("") - month = firstmonth - while 1: - T.append('") - if month == lastmonth: - break - month = month + 1 - if month > 12: - month = 1 - year = year + 1 - T.append("
') - T.append(MonthTableHead(month)) - T.append( - MonthTableBody( - month, - year, - events, - halfday, - dayattributes, - context.is_work_saturday(), - pad_width=pad_width, - ) - ) - T.append(MonthTableTail()) - T.append("
") - return string.join(T, "\n") - - -# --------------- - - class ZAbsences( ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit ): @@ -373,7 +195,7 @@ class ZAbsences( % vars(), ) cnx.commit() - invalidateAbsEtudDate(self, etudid, jour) + sco_abs.invalidateAbsEtudDate(self, etudid, jour) sco_abs_notification.abs_notify(self, etudid, jour) def _AddJustif(self, etudid, jour, matin, REQUEST, description=None): @@ -396,7 +218,7 @@ class ZAbsences( msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), ) cnx.commit() - invalidateAbsEtudDate(self, etudid, jour) + sco_abs.invalidateAbsEtudDate(self, etudid, jour) def _AnnuleAbsence(self, etudid, jour, matin, moduleimpl_id=None, REQUEST=None): """Annule une absence ds base @@ -419,7 +241,7 @@ class ZAbsences( % vars(), ) cnx.commit() - invalidateAbsEtudDate(self, etudid, jour) + sco_abs.invalidateAbsEtudDate(self, etudid, jour) def _AnnuleJustif(self, etudid, jour, matin, REQUEST=None): "Annule un justificatif" @@ -443,7 +265,7 @@ class ZAbsences( msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), ) cnx.commit() - invalidateAbsEtudDate(self, etudid, jour) + sco_abs.invalidateAbsEtudDate(self, etudid, jour) # Fonction inutile à supprimer (gestion moduleimpl_id incorrecte): # def _AnnuleAbsencesPeriodNoJust(self, etudid, datedebut, datefin, @@ -462,8 +284,8 @@ class ZAbsences( # logdb(REQUEST, cnx, 'AnnuleAbsencesPeriodNoJust', etudid=etudid, # msg='%(datedebut)s - %(datefin)s - (moduleimpl_id)s'%vars()) # cnx.commit() - # invalidateAbsEtudDate(self, etudid, datedebut) - # invalidateAbsEtudDate(self, etudid, datefin) # si un semestre commence apres datedebut et termine avant datefin, il ne sera pas invalide. Tant pis ;-) + # sco_abs.invalidateAbsEtudDate(self, etudid, datedebut) + # sco_abs.invalidateAbsEtudDate(self, etudid, datefin) # si un semestre commence apres datedebut et termine avant datefin, il ne sera pas invalide. Tant pis ;-) security.declareProtected(ScoAbsChange, "AnnuleAbsencesDatesNoJust") @@ -497,7 +319,7 @@ class ZAbsences( "delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s", vars(), ) - invalidateAbsEtudDate(self, etudid, date) + sco_abs.invalidateAbsEtudDate(self, etudid, date) # s'assure que les justificatifs ne sont pas "absents" for date in dates: cursor.execute( @@ -855,7 +677,7 @@ class ZAbsences( js = "" else: js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' - C = YearTable(self, int(year), dayattributes=js) + C = sco_abs.YearTable(self, int(year), dayattributes=js) return C # --- Misc tools.... ------------------ @@ -1352,7 +1174,7 @@ class ZAbsences( else: checked = "" # bulle lors du passage souris - coljour = DAYNAMES[ + coljour = sco_abs.DAYNAMES[ (calendar.weekday(int(date[:4]), int(date[5:7]), int(date[8:]))) ] datecol = coljour + " " + date[8:] + "/" + date[5:7] + "/" + date[:4] @@ -1798,7 +1620,7 @@ ou entrez une date pour visualiser les absents un jour donné : justified = int(justified) # cnx = self.GetDBConnexion() - billet_id = billet_absence_create( + billet_id = sco_abs.billet_absence_create( cnx, { "etudid": etud["etudid"], @@ -1814,7 +1636,7 @@ ou entrez une date pour visualiser les absents un jour donné : if REQUEST: REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) - billets = billet_absence_list(cnx, {"billet_id": billet_id}) + billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) tab = self._tableBillets(billets, etud=etud) log( "AddBilletAbsence: new billet_id=%s (%gs)" @@ -1940,7 +1762,7 @@ ou entrez une date pour visualiser les absents un jour donné : etud = etuds[0] cnx = self.GetDBConnexion() - billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) + billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]}) tab = self._tableBillets(billets, etud=etud) return tab.make_page(self, REQUEST=REQUEST, format=format) @@ -1960,7 +1782,7 @@ ou entrez une date pour visualiser les absents un jour donné : def listeBillets(self, REQUEST=None): """Page liste des billets non traités et formulaire recherche d'un billet""" cnx = self.GetDBConnexion() - billets = billet_absence_list(cnx, {"etat": 0}) + billets = sco_abs.billet_absence_list(cnx, {"etat": 0}) tab = self._tableBillets(billets) T = tab.html() H = [ @@ -1986,7 +1808,7 @@ ou entrez une date pour visualiser les absents un jour donné : def deleteBilletAbsence(self, billet_id, REQUEST=None, dialog_confirmed=False): """Supprime un billet.""" cnx = self.GetDBConnexion() - billets = billet_absence_list(cnx, {"billet_id": billet_id}) + billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) if not billets: return REQUEST.RESPONSE.redirect( "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id @@ -2001,7 +1823,7 @@ ou entrez une date pour visualiser les absents un jour donné : parameters={"billet_id": billet_id}, ) - billet_absence_delete(cnx, billet_id) + sco_abs.billet_absence_delete(cnx, billet_id) return REQUEST.RESPONSE.redirect("listeBillets?head_message=Billet%20supprimé") @@ -2050,7 +1872,7 @@ ou entrez une date pour visualiser les absents un jour donné : n += 2 # 2- change etat du billet - billet_absence_edit(cnx, {"billet_id": billet["billet_id"], "etat": 1}) + sco_abs.billet_absence_edit(cnx, {"billet_id": billet["billet_id"], "etat": 1}) return n @@ -2059,7 +1881,7 @@ ou entrez une date pour visualiser les absents un jour donné : def ProcessBilletAbsenceForm(self, billet_id, REQUEST=None): """Formulaire traitement d'un billet""" cnx = self.GetDBConnexion() - billets = billet_absence_list(cnx, {"billet_id": billet_id}) + billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) if not billets: return REQUEST.RESPONSE.redirect( "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id @@ -2134,7 +1956,7 @@ ou entrez une date pour visualiser les absents un jour donné : '

Autre billets en attente

Billets déclarés par %s

' % (etud["nomprenom"]) ) - billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) + billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]}) tab = self._tableBillets(billets, etud=etud) H.append(tab.html()) return "\n".join(H) + self.sco_footer(REQUEST) @@ -2172,258 +1994,6 @@ ou entrez une date pour visualiser les absents un jour donné : return repr(doc) -_billet_absenceEditor = notesdb.EditableTable( - "billet_absence", - "billet_id", - ( - "billet_id", - "etudid", - "abs_begin", - "abs_end", - "description", - "etat", - "entry_date", - "justified", - ), - sortkey="entry_date desc", -) - -billet_absence_create = _billet_absenceEditor.create -billet_absence_delete = _billet_absenceEditor.delete -billet_absence_list = _billet_absenceEditor.list -billet_absence_edit = _billet_absenceEditor.edit - -# ------ HTML Calendar functions (see YearTable function) - -# MONTH/DAY NAMES: - -MONTHNAMES = ( - "Janvier", - "Février", - "Mars", - "Avril", - "Mai", - "Juin", - "Juillet", - "Aout", - "Septembre", - "Octobre", - "Novembre", - "Décembre", -) - -MONTHNAMES_ABREV = ( - "Jan.", - "Fév.", - "Mars", - "Avr.", - "Mai ", - "Juin", - "Juil", - "Aout", - "Sept", - "Oct.", - "Nov.", - "Déc.", -) - -DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche") - -DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D") - -# COLORS: - -WHITE = "#FFFFFF" -GRAY1 = "#EEEEEE" -GREEN3 = "#99CC99" -WEEKDAYCOLOR = GRAY1 -WEEKENDCOLOR = GREEN3 - - -def MonthTableHead(month): - color = WHITE - return """ - \n""" % ( - color, - MONTHNAMES_ABREV[month - 1], - ) - - -def MonthTableTail(): - return "
%s
\n" - - -def MonthTableBody( - month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8 -): - firstday, nbdays = calendar.monthrange(year, month) - localtime = time.localtime() - current_weeknum = time.strftime("%U", localtime) - current_year = localtime[0] - T = [] - # cherche date du lundi de la 1ere semaine de ce mois - monday = ddmmyyyy("1/%d/%d" % (month, year)) - while monday.weekday != 0: - monday = monday.prev() - - if work_saturday: - weekend = ("D",) - else: - weekend = ("S", "D") - - if not halfday: - for d in range(1, nbdays + 1): - weeknum = time.strftime( - "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") - ) - day = DAYNAMES_ABREV[(firstday + d - 1) % 7] - if day in weekend: - bgcolor = WEEKENDCOLOR - weekclass = "wkend" - attrs = "" - else: - bgcolor = WEEKDAYCOLOR - weekclass = "wk" + str(monday).replace("/", "_") - attrs = trattributes - color = None - legend = "" - href = "" - descr = "" - # event this day ? - # each event is a tuple (date, text, color, href) - # where date is a string in ISO format (yyyy-mm-dd) - for ev in events: - ev_year = int(ev[0][:4]) - ev_month = int(ev[0][5:7]) - ev_day = int(ev[0][8:10]) - if year == ev_year and month == ev_month and ev_day == d: - if ev[1]: - legend = ev[1] - if ev[2]: - color = ev[2] - if ev[3]: - href = ev[3] - if len(ev) > 4 and ev[4]: - descr = ev[4] - # - cc = [] - if color != None: - cc.append('' % color) - else: - cc.append('') - - if href: - href = 'href="%s"' % href - if descr: - descr = 'title="%s"' % cgi.escape(descr, quote=True) - if href or descr: - cc.append("" % (href, descr)) - - if legend or d == 1: - if pad_width != None: - n = pad_width - len(legend) # pad to 8 cars - if n > 0: - legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) - else: - legend = " " # empty cell - cc.append(legend) - if href or descr: - cc.append("") - cc.append("") - cell = string.join(cc, "") - if day == "D": - monday = monday.next(7) - if ( - weeknum == current_weeknum - and current_year == year - and weekclass != "wkend" - ): - weekclass += " currentweek" - T.append( - '%d%s%s' - % (bgcolor, weekclass, attrs, d, day, cell) - ) - else: - # Calendar with 2 cells / day - for d in range(1, nbdays + 1): - weeknum = time.strftime( - "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") - ) - day = DAYNAMES_ABREV[(firstday + d - 1) % 7] - if day in weekend: - bgcolor = WEEKENDCOLOR - weekclass = "wkend" - attrs = "" - else: - bgcolor = WEEKDAYCOLOR - weekclass = "wk" + str(monday).replace("/", "_") - attrs = trattributes - if ( - weeknum == current_weeknum - and current_year == year - and weekclass != "wkend" - ): - weeknum += " currentweek" - - if day == "D": - monday = monday.next(7) - T.append( - '%d%s' - % (bgcolor, weekclass, attrs, d, day) - ) - cc = [] - for morning in (1, 0): - color = None - legend = "" - href = "" - descr = "" - for ev in events: - ev_year = int(ev[0][:4]) - ev_month = int(ev[0][5:7]) - ev_day = int(ev[0][8:10]) - if ev[4] != None: - ev_half = int(ev[4]) - else: - ev_half = 0 - if ( - year == ev_year - and month == ev_month - and ev_day == d - and morning == ev_half - ): - if ev[1]: - legend = ev[1] - if ev[2]: - color = ev[2] - if ev[3]: - href = ev[3] - if len(ev) > 5 and ev[5]: - descr = ev[5] - # - if color != None: - cc.append('' % (color)) - else: - cc.append('') - if href: - href = 'href="%s"' % href - if descr: - descr = 'title="%s"' % cgi.escape(descr, quote=True) - if href or descr: - cc.append("" % (href, descr)) - if legend or d == 1: - n = 3 - len(legend) # pad to 3 cars - if n > 0: - legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) - else: - legend = "   " # empty cell - cc.append(legend) - if href or descr: - cc.append("") - cc.append("\n") - T.append(string.join(cc, "") + "") - return string.join(T, "\n") - - # -------------------------------------------------------------------- # # Zope Product Administration @@ -2441,121 +2011,3 @@ def manage_addZAbsences( # The form used to get the instance id from the user. # manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) - - -# -------------------------------------------------------------------- -# -# Cache absences -# -# On cache simplement (à la demande) le nombre d'absences de chaque etudiant -# dans un semestre donné. -# Toute modification du semestre (invalidation) invalide le cache -# (simple mécanisme de "listener" sur le cache de semestres) -# Toute modification des absences d'un étudiant invalide les caches des semestres -# concernés à cette date (en général un seul semestre) -# -# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier, -# absences à une date donnée). -# -# -------------------------------------------------------------------- -class CAbsSemEtud: - """Comptes d'absences d'un etudiant dans un semestre""" - - def __init__(self, context, sem, etudid): - self.context = context - self.sem = sem - self.etudid = etudid - self._loaded = False - formsemestre_id = sem["formsemestre_id"] - context.Notes._getNotesCache().add_listener( - self.invalidate, formsemestre_id, (etudid, formsemestre_id) - ) - - def CountAbs(self): - if not self._loaded: - self.load() - return self._CountAbs - - def CountAbsJust(self): - if not self._loaded: - self.load() - return self._CountAbsJust - - def load(self): - "Load state from DB" - # log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) - # Reload sem, it may have changed - self.sem = sco_formsemestre.get_formsemestre( - self.context, self.sem["formsemestre_id"] - ) - debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"]) - fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"]) - - self._CountAbs = self.context.Absences.CountAbs( - etudid=self.etudid, debut=debut_sem, fin=fin_sem - ) - self._CountAbsJust = self.context.Absences.CountAbsJust( - etudid=self.etudid, debut=debut_sem, fin=fin_sem - ) - self._loaded = True - - def invalidate(self, args=None): - "Notify me that DB has been modified" - # log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) - self._loaded = False - - -# Accès au cache des absences -ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } } - - -def getAbsSemEtud(context, sem, etudid): - AbsSemEtuds = getAbsSemEtuds(context, sem) - if not etudid in AbsSemEtuds: - AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid) - return AbsSemEtuds[etudid] - - -def getAbsSemEtuds(context, sem): - u = context.GetDBConnexionString() # identifie le dept de facon fiable - if not u in ABS_CACHE_INST: - ABS_CACHE_INST[u] = {} - C = ABS_CACHE_INST[u] - if sem["formsemestre_id"] not in C: - C[sem["formsemestre_id"]] = {} - return C[sem["formsemestre_id"]] - - -def invalidateAbsEtudDate(context, etudid, date): - """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date. - Invalide cache absence et PDF bulletins si nécessaire. - date: date au format ISO - """ - # Semestres a cette date: - etud = context.getEtudInfo(etudid=etudid, filled=True)[0] - sems = [ - sem - for sem in etud["sems"] - if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date - ] - - # Invalide les PDF et les abscences: - for sem in sems: - # Inval cache bulletin et/ou note_table - if sco_compute_moy.formsemestre_expressions_use_abscounts( - context, sem["formsemestre_id"] - ): - pdfonly = False # seules certaines formules utilisent les absences - else: - pdfonly = ( - True # efface toujours le PDF car il affiche en général les absences - ) - - context.Notes._inval_cache( - pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"] - ) - - # Inval cache compteurs absences: - AbsSemEtuds = getAbsSemEtuds(context, sem) - if etudid in AbsSemEtuds: - AbsSemEtuds[etudid].invalidate() diff --git a/ZEntreprises.py b/ZEntreprises.py index e835c80d..3e8a4468 100644 --- a/ZEntreprises.py +++ b/ZEntreprises.py @@ -107,7 +107,7 @@ class EntreprisesEditor(EditableTable): "select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom", args, ) - titles, res = [x[0] for x in cursor.description], cursor.dictfetchall() + _, res = [x[0] for x in cursor.description], cursor.dictfetchall() R = [] for r in res: r["etud_prenom"] = r["etud_prenom"] or "" @@ -450,7 +450,6 @@ class ZEntreprises( def do_entreprise_correspondant_listnames(self, args={}): "-> liste des noms des correspondants (pour affichage menu)" - cnx = self.GetDBConnexion() C = self.do_entreprise_correspondant_list(args=args) return [ (x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C @@ -538,43 +537,48 @@ class ZEntreprises( # (fonction ad-hoc car requete sur plusieurs tables) raise NotImplementedError # XXXXX fonction non achevee , non testee... - cnx = self.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ScoDocCursor) - vals = dictfilter(args, self.dbfields) - # DBSelect - what = ["*"] - operator = " " + operator + " " - cond = " E.entreprise_id = C.entreprise_id " - if vals: - cond += " where " + operator.join( - ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None] - ) - cnuls = " and ".join( - ["%s is NULL" % x for x in vals.keys() if vals[x] is None] - ) - if cnuls: - cond = cond + " and " + cnuls - else: - cond += "" - cursor.execute( - "select distinct" - + ", ".join(what) - + " from entreprises E, entreprise_contact C " - + cond - + orderby, - vals, - ) - titles, res = [x[0] for x in cursor.description], cursor.fetchall() - # - R = [] - for r in res: - d = {} - for i in range(len(titles)): - v = r[i] - # value not formatted ! (see EditableTable.list()) - d[titles[i]] = v - R.append(d) - return R + + # cnx = self.GetDBConnexion() + # cursor = cnx.cursor(cursor_factory=ScoDocCursor) + # if sortkey: + # orderby = " order by " + sortkey + # else: + # orderby = "" + # vals = dictfilter(args, self.dbfields) + # # DBSelect + # what = ["*"] + # operator = " " + operator + " " + # cond = " E.entreprise_id = C.entreprise_id " + # if vals: + # cond += " where " + operator.join( + # ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None] + # ) + # cnuls = " and ".join( + # ["%s is NULL" % x for x in vals.keys() if vals[x] is None] + # ) + # if cnuls: + # cond = cond + " and " + cnuls + # else: + # cond += "" + # cursor.execute( + # "select distinct" + # + ", ".join(what) + # + " from entreprises E, entreprise_contact C " + # + cond + # + orderby, + # vals, + # ) + # titles, res = [x[0] for x in cursor.description], cursor.fetchall() + # # + # R = [] + # for r in res: + # d = {} + # for i in range(len(titles)): + # v = r[i] + # # value not formatted ! (see EditableTable.list()) + # d[titles[i]] = v + # R.append(d) + # return R # -------- Formulaires: traductions du DTML security.declareProtected(ScoEntrepriseChange, "entreprise_create") @@ -892,7 +896,3 @@ def manage_addZEntreprises( if REQUEST is not None: return self.manage_main(self, REQUEST) # return self.manage_editForm(self, REQUEST) - - -# The form used to get the instance id from the user. -# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) diff --git a/ZScolar.py b/ZScolar.py index 7a635570..d09e2ac5 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -89,6 +89,7 @@ import ZEntreprises import ZScoUsers import sco_modalites import ImportScolars +import sco_abs import sco_portal_apogee import sco_synchro_etuds import sco_page_etud @@ -274,13 +275,13 @@ UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo """ - return ( - self.sco_header(REQUEST) - + """
%s
""" % x - + self.sco_footer(REQUEST) - ) - b = "

Hello, World !


" - raise ValueError("essai exception") + # return ( + # self.sco_header(REQUEST) + # + """
%s
""" % x + # + self.sco_footer(REQUEST) + # ) + # b = "

Hello, World !


" + # raise ValueError("essai exception") # raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST) # cursor = cnx.cursor(cursor_factory=ScoDocCursor) @@ -592,7 +593,7 @@ UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo
' diff --git a/html_sidebar.py b/html_sidebar.py index ea05e71c..df7bb59b 100644 --- a/html_sidebar.py +++ b/html_sidebar.py @@ -26,7 +26,7 @@ ############################################################################## from sco_utils import * -from ZAbsences import getAbsSemEtud +from sco_abs import getAbsSemEtud """ Génération de la "sidebar" (marge gauche des pages HTML) diff --git a/sco_abs.py b/sco_abs.py new file mode 100644 index 00000000..8d97d949 --- /dev/null +++ b/sco_abs.py @@ -0,0 +1,595 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Fonctions sur les absences +""" + +# Anciennement dans ZAbscences.py, séparé pour migration + +import string +import datetime +import re +import time +import calendar +import cgi + +import notesdb +from sco_exceptions import ScoValueError, ScoInvalidDateError +import sco_formsemestre +import sco_compute_moy + + +def MonthNbDays(month, year): + "returns nb of days in month" + if month > 7: + month = month + 1 + if month % 2: + return 31 + elif month == 2: + if calendar.isleap(year): + return 29 + else: + return 28 + else: + return 30 + + +class ddmmyyyy: + """immutable dates""" + + def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False): + self.work_saturday = work_saturday + if date is None: + return + try: + if fmt == "ddmmyyyy": + self.day, self.month, self.year = string.split(date, "/") + elif fmt == "iso": + self.year, self.month, self.day = string.split(date, "-") + else: + raise ValueError("invalid format spec. (%s)" % fmt) + self.year = string.atoi(self.year) + self.month = string.atoi(self.month) + self.day = string.atoi(self.day) + except: + raise ScoValueError("date invalide: %s" % date) + # accept years YYYY or YY, uses 1970 as pivot + if self.year < 1970: + if self.year > 100: + raise ScoInvalidDateError("Année invalide: %s" % self.year) + if self.year < 70: + self.year = self.year + 2000 + else: + self.year = self.year + 1900 + if self.month < 1 or self.month > 12: + raise ScoInvalidDateError("Mois invalide: %s" % self.month) + + if self.day < 1 or self.day > MonthNbDays(self.month, self.year): + raise ScoInvalidDateError("Jour invalide: %s" % self.day) + + # weekday in 0-6, where 0 is monday + self.weekday = calendar.weekday(self.year, self.month, self.day) + + self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0)) + + def iswork(self): + "returns true if workable day" + if self.work_saturday: + nbdays = 6 + else: + nbdays = 5 + if ( + self.weekday >= 0 and self.weekday < nbdays + ): # monday-friday or monday-saturday + return 1 + else: + return 0 + + def __repr__(self): + return "'%02d/%02d/%04d'" % (self.day, self.month, self.year) + + def __str__(self): + return "%02d/%02d/%04d" % (self.day, self.month, self.year) + + def ISO(self): + "iso8601 representation of the date" + return "%04d-%02d-%02d" % (self.year, self.month, self.day) + + def next(self, days=1): + "date for the next day (nota: may be a non workable day)" + day = self.day + days + month = self.month + year = self.year + + while day > MonthNbDays(month, year): + day = day - MonthNbDays(month, year) + month = month + 1 + if month > 12: + month = 1 + year = year + 1 + return self.__class__( + "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday + ) + + def prev(self, days=1): + "date for previous day" + day = self.day - days + month = self.month + year = self.year + while day <= 0: + month = month - 1 + if month == 0: + month = 12 + year = year - 1 + day = day + MonthNbDays(month, year) + + return self.__class__( + "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday + ) + + def next_monday(self): + "date of next monday" + return self.next((7 - self.weekday) % 7) + + def prev_monday(self): + "date of last monday, but on sunday, pick next monday" + if self.weekday == 6: + return self.next_monday() + else: + return self.prev(self.weekday) + + def __cmp__(self, other): + """return a negative integer if self < other, + zero if self == other, a positive integer if self > other""" + return int(self.time - other.time) + + def __hash__(self): + "we are immutable !" + return hash(self.time) ^ hash(str(self)) + + +# d = ddmmyyyy( '21/12/99' ) + + +def YearTable( + context, + year, + events=[], + firstmonth=9, + lastmonth=7, + halfday=0, + dayattributes="", + pad_width=8, +): + """Generate a calendar table + events = list of tuples (date, text, color, href [,halfday]) + where date is a string in ISO format (yyyy-mm-dd) + halfday is boolean (true: morning, false: afternoon) + text = text to put in calendar (must be short, 1-5 cars) (optional) + if halfday, generate 2 cells per day (morning, afternoon) + """ + T = [ + '' + ] + T.append("") + month = firstmonth + while 1: + T.append('") + if month == lastmonth: + break + month = month + 1 + if month > 12: + month = 1 + year = year + 1 + T.append("
') + T.append(MonthTableHead(month)) + T.append( + MonthTableBody( + month, + year, + events, + halfday, + dayattributes, + context.is_work_saturday(), + pad_width=pad_width, + ) + ) + T.append(MonthTableTail()) + T.append("
") + return string.join(T, "\n") + + +# ---- BILLETS + +_billet_absenceEditor = notesdb.EditableTable( + "billet_absence", + "billet_id", + ( + "billet_id", + "etudid", + "abs_begin", + "abs_end", + "description", + "etat", + "entry_date", + "justified", + ), + sortkey="entry_date desc", +) + +billet_absence_create = _billet_absenceEditor.create +billet_absence_delete = _billet_absenceEditor.delete +billet_absence_list = _billet_absenceEditor.list +billet_absence_edit = _billet_absenceEditor.edit + +# ------ HTML Calendar functions (see YearTable function) + +# MONTH/DAY NAMES: + +MONTHNAMES = ( + "Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Aout", + "Septembre", + "Octobre", + "Novembre", + "Décembre", +) + +MONTHNAMES_ABREV = ( + "Jan.", + "Fév.", + "Mars", + "Avr.", + "Mai ", + "Juin", + "Juil", + "Aout", + "Sept", + "Oct.", + "Nov.", + "Déc.", +) + +DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche") + +DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D") + +# COLORS: + +WHITE = "#FFFFFF" +GRAY1 = "#EEEEEE" +GREEN3 = "#99CC99" +WEEKDAYCOLOR = GRAY1 +WEEKENDCOLOR = GREEN3 + + +def MonthTableHead(month): + color = WHITE + return """ + \n""" % ( + color, + MONTHNAMES_ABREV[month - 1], + ) + + +def MonthTableTail(): + return "
%s
\n" + + +def MonthTableBody( + month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8 +): + firstday, nbdays = calendar.monthrange(year, month) + localtime = time.localtime() + current_weeknum = time.strftime("%U", localtime) + current_year = localtime[0] + T = [] + # cherche date du lundi de la 1ere semaine de ce mois + monday = ddmmyyyy("1/%d/%d" % (month, year)) + while monday.weekday != 0: + monday = monday.prev() + + if work_saturday: + weekend = ("D",) + else: + weekend = ("S", "D") + + if not halfday: + for d in range(1, nbdays + 1): + weeknum = time.strftime( + "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") + ) + day = DAYNAMES_ABREV[(firstday + d - 1) % 7] + if day in weekend: + bgcolor = WEEKENDCOLOR + weekclass = "wkend" + attrs = "" + else: + bgcolor = WEEKDAYCOLOR + weekclass = "wk" + str(monday).replace("/", "_") + attrs = trattributes + color = None + legend = "" + href = "" + descr = "" + # event this day ? + # each event is a tuple (date, text, color, href) + # where date is a string in ISO format (yyyy-mm-dd) + for ev in events: + ev_year = int(ev[0][:4]) + ev_month = int(ev[0][5:7]) + ev_day = int(ev[0][8:10]) + if year == ev_year and month == ev_month and ev_day == d: + if ev[1]: + legend = ev[1] + if ev[2]: + color = ev[2] + if ev[3]: + href = ev[3] + if len(ev) > 4 and ev[4]: + descr = ev[4] + # + cc = [] + if color != None: + cc.append('' % color) + else: + cc.append('') + + if href: + href = 'href="%s"' % href + if descr: + descr = 'title="%s"' % cgi.escape(descr, quote=True) + if href or descr: + cc.append("" % (href, descr)) + + if legend or d == 1: + if pad_width != None: + n = pad_width - len(legend) # pad to 8 cars + if n > 0: + legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) + else: + legend = " " # empty cell + cc.append(legend) + if href or descr: + cc.append("") + cc.append("") + cell = string.join(cc, "") + if day == "D": + monday = monday.next(7) + if ( + weeknum == current_weeknum + and current_year == year + and weekclass != "wkend" + ): + weekclass += " currentweek" + T.append( + '%d%s%s' + % (bgcolor, weekclass, attrs, d, day, cell) + ) + else: + # Calendar with 2 cells / day + for d in range(1, nbdays + 1): + weeknum = time.strftime( + "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") + ) + day = DAYNAMES_ABREV[(firstday + d - 1) % 7] + if day in weekend: + bgcolor = WEEKENDCOLOR + weekclass = "wkend" + attrs = "" + else: + bgcolor = WEEKDAYCOLOR + weekclass = "wk" + str(monday).replace("/", "_") + attrs = trattributes + if ( + weeknum == current_weeknum + and current_year == year + and weekclass != "wkend" + ): + weeknum += " currentweek" + + if day == "D": + monday = monday.next(7) + T.append( + '%d%s' + % (bgcolor, weekclass, attrs, d, day) + ) + cc = [] + for morning in (1, 0): + color = None + legend = "" + href = "" + descr = "" + for ev in events: + ev_year = int(ev[0][:4]) + ev_month = int(ev[0][5:7]) + ev_day = int(ev[0][8:10]) + if ev[4] != None: + ev_half = int(ev[4]) + else: + ev_half = 0 + if ( + year == ev_year + and month == ev_month + and ev_day == d + and morning == ev_half + ): + if ev[1]: + legend = ev[1] + if ev[2]: + color = ev[2] + if ev[3]: + href = ev[3] + if len(ev) > 5 and ev[5]: + descr = ev[5] + # + if color != None: + cc.append('' % (color)) + else: + cc.append('') + if href: + href = 'href="%s"' % href + if descr: + descr = 'title="%s"' % cgi.escape(descr, quote=True) + if href or descr: + cc.append("" % (href, descr)) + if legend or d == 1: + n = 3 - len(legend) # pad to 3 cars + if n > 0: + legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) + else: + legend = "   " # empty cell + cc.append(legend) + if href or descr: + cc.append("") + cc.append("\n") + T.append(string.join(cc, "") + "") + return string.join(T, "\n") + + +# -------------------------------------------------------------------- +# +# Cache absences +# +# On cache simplement (à la demande) le nombre d'absences de chaque etudiant +# dans un semestre donné. +# Toute modification du semestre (invalidation) invalide le cache +# (simple mécanisme de "listener" sur le cache de semestres) +# Toute modification des absences d'un étudiant invalide les caches des semestres +# concernés à cette date (en général un seul semestre) +# +# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier, +# absences à une date donnée). +# +# -------------------------------------------------------------------- +class CAbsSemEtud: + """Comptes d'absences d'un etudiant dans un semestre""" + + def __init__(self, context, sem, etudid): + self.context = context + self.sem = sem + self.etudid = etudid + self._loaded = False + formsemestre_id = sem["formsemestre_id"] + context.Notes._getNotesCache().add_listener( + self.invalidate, formsemestre_id, (etudid, formsemestre_id) + ) + + def CountAbs(self): + if not self._loaded: + self.load() + return self._CountAbs + + def CountAbsJust(self): + if not self._loaded: + self.load() + return self._CountAbsJust + + def load(self): + "Load state from DB" + # log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) + # Reload sem, it may have changed + self.sem = sco_formsemestre.get_formsemestre( + self.context, self.sem["formsemestre_id"] + ) + debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"]) + fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"]) + + self._CountAbs = self.context.Absences.CountAbs( + etudid=self.etudid, debut=debut_sem, fin=fin_sem + ) + self._CountAbsJust = self.context.Absences.CountAbsJust( + etudid=self.etudid, debut=debut_sem, fin=fin_sem + ) + self._loaded = True + + def invalidate(self, args=None): + "Notify me that DB has been modified" + # log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) + self._loaded = False + + +# Accès au cache des absences +ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } } + + +def getAbsSemEtud(context, sem, etudid): + AbsSemEtuds = getAbsSemEtuds(context, sem) + if not etudid in AbsSemEtuds: + AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid) + return AbsSemEtuds[etudid] + + +def getAbsSemEtuds(context, sem): + u = context.GetDBConnexionString() # identifie le dept de facon fiable + if not u in ABS_CACHE_INST: + ABS_CACHE_INST[u] = {} + C = ABS_CACHE_INST[u] + if sem["formsemestre_id"] not in C: + C[sem["formsemestre_id"]] = {} + return C[sem["formsemestre_id"]] + + +def invalidateAbsEtudDate(context, etudid, date): + """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date. + Invalide cache absence et PDF bulletins si nécessaire. + date: date au format ISO + """ + # Semestres a cette date: + etud = context.getEtudInfo(etudid=etudid, filled=True)[0] + sems = [ + sem + for sem in etud["sems"] + if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date + ] + + # Invalide les PDF et les abscences: + for sem in sems: + # Inval cache bulletin et/ou note_table + if sco_compute_moy.formsemestre_expressions_use_abscounts( + context, sem["formsemestre_id"] + ): + pdfonly = False # seules certaines formules utilisent les absences + else: + pdfonly = ( + True # efface toujours le PDF car il affiche en général les absences + ) + + context.Notes._inval_cache( + pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"] + ) + + # Inval cache compteurs absences: + AbsSemEtuds = getAbsSemEtuds(context, sem) + if etudid in AbsSemEtuds: + AbsSemEtuds[etudid].invalidate() diff --git a/sco_abs_views.py b/sco_abs_views.py index af23bfd0..9ede1d38 100644 --- a/sco_abs_views.py +++ b/sco_abs_views.py @@ -40,7 +40,7 @@ import sco_find_etud import sco_formsemestre import sco_photos -import ZAbsences +import sco_abs def doSignaleAbsence( @@ -631,7 +631,7 @@ def CalAbs(context, REQUEST=None): # etud implied events.append( (str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"]) ) - CalHTML = ZAbsences.YearTable(context, anneescolaire, events=events, halfday=1) + CalHTML = sco_abs.YearTable(context, anneescolaire, events=events, halfday=1) # H = [ diff --git a/sco_bulletins.py b/sco_bulletins.py index ab6e2161..1a8fe6d6 100644 --- a/sco_bulletins.py +++ b/sco_bulletins.py @@ -44,7 +44,7 @@ import sco_groups import sco_pvjury import sco_formsemestre_status import sco_photos -import ZAbsences +import sco_abs import sco_abs_views import sco_preferences import sco_codes_parcours @@ -150,7 +150,7 @@ def formsemestre_bulletinetud_dict( context, pid ) # --- Absences - AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid) + AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid) I["nbabs"] = AbsSemEtud.CountAbs() I["nbabsjust"] = AbsSemEtud.CountAbsJust() diff --git a/sco_bulletins_json.py b/sco_bulletins_json.py index d284ce82..1d19a8c0 100644 --- a/sco_bulletins_json.py +++ b/sco_bulletins_json.py @@ -34,7 +34,7 @@ from notes_table import * import sco_formsemestre import sco_groups import sco_photos -import ZAbsences +import sco_abs import sco_bulletins # -------- Bulletin en JSON @@ -321,7 +321,7 @@ def formsemestre_bulletinetud_published_dict( if context.get_preference("bul_show_abs", formsemestre_id): debut_sem = DateDMYtoISO(sem["date_debut"]) fin_sem = DateDMYtoISO(sem["date_fin"]) - AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid) + AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid) nbabs = AbsEtudSem.CountAbs() nbabsjust = AbsEtudSem.CountAbsJust() diff --git a/sco_bulletins_xml.py b/sco_bulletins_xml.py index 8c65b821..7edd20ae 100644 --- a/sco_bulletins_xml.py +++ b/sco_bulletins_xml.py @@ -41,7 +41,7 @@ from notes_table import * import sco_formsemestre import sco_groups import sco_photos -import ZAbsences +import sco_abs import sco_bulletins # -------- Bulletin en XML @@ -323,7 +323,7 @@ def make_xml_formsemestre_bulletinetud( if context.get_preference("bul_show_abs", formsemestre_id): debut_sem = DateDMYtoISO(sem["date_debut"]) fin_sem = DateDMYtoISO(sem["date_fin"]) - AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid) + AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid) nbabs = AbsEtudSem.CountAbs() nbabsjust = AbsEtudSem.CountAbsJust() doc._push() diff --git a/sco_compute_moy.py b/sco_compute_moy.py index 57a74dab..108f764f 100644 --- a/sco_compute_moy.py +++ b/sco_compute_moy.py @@ -37,7 +37,7 @@ import sco_formsemestre import sco_groups import sco_evaluations from sco_formulas import * -import ZAbsences +import sco_abs def moduleimpl_has_expression(context, mod): @@ -124,7 +124,7 @@ def compute_user_formula( Retourne moy, et en cas d'erreur met à jour diag_info (msg) """ if use_abs: - AbsSemEtud = ZAbsences.getAbsSemEtud(context, sem, etudid) + AbsSemEtud = sco_abs.getAbsSemEtud(context, sem, etudid) nbabs = AbsSemEtud.CountAbs() nbabs_just = AbsSemEtud.CountAbsJust() else: diff --git a/sco_evaluations.py b/sco_evaluations.py index e3db6d00..e877b62c 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -42,7 +42,7 @@ from TrivialFormulator import TrivialFormulator import sco_news import sco_formsemestre import sco_groups -import ZAbsences +import sco_abs import sco_evaluations # -------------------------------------------------------------------- @@ -487,7 +487,7 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None): if day > today: e[2] = color_futur - CalHTML = ZAbsences.YearTable( + CalHTML = sco_abs.YearTable( context.Absences, year, events=events.values(), halfday=False, pad_width=None ) diff --git a/sco_formsemestre_validation.py b/sco_formsemestre_validation.py index afee3e8c..09739779 100644 --- a/sco_formsemestre_validation.py +++ b/sco_formsemestre_validation.py @@ -35,7 +35,7 @@ from notes_log import log from scolog import logdb from notes_table import * import notes_table -from ZAbsences import getAbsSemEtud +from sco_abs import getAbsSemEtud import sco_formsemestre import sco_formsemestre_edit diff --git a/sco_groups_view.py b/sco_groups_view.py index 1ae5387d..152ceb98 100644 --- a/sco_groups_view.py +++ b/sco_groups_view.py @@ -35,7 +35,7 @@ from sco_utils import * import html_sco_header from gen_tables import GenTable import scolars -import ZAbsences +import sco_abs import sco_excel import sco_formsemestre import sco_groups @@ -870,7 +870,7 @@ def form_choix_jour_saisie_hebdo( if not authuser.has_permission(ScoAbsChange, context): return "" sem = groups_infos.formsemestre - first_monday = ZAbsences.ddmmyyyy(sem["date_debut"]).prev_monday() + first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday() today_idx = datetime.date.today().weekday() FA = [] # formulaire avec menu saisi absences @@ -923,7 +923,7 @@ def form_choix_saisie_semaine(context, groups_infos, REQUEST=None): ) # car ici utilisee dans un format string ! DateJour = time.strftime("%d/%m/%Y") - datelundi = ZAbsences.ddmmyyyy(DateJour).prev_monday() + datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday() FA = [] # formulaire avec menu saisi hebdo des absences FA.append('') FA.append('' % datelundi) diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index 7a4bf1e7..08c32bfc 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -43,7 +43,7 @@ import sco_formsemestre import sco_formsemestre_status from sco_formsemestre_status import makeMenu import sco_compute_moy -import ZAbsences +import sco_abs # ported from old DTML code in oct 2009 @@ -241,7 +241,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No if authuser.has_permission( ScoAbsChange, context ) and sco_formsemestre.sem_est_courant(context, sem): - datelundi = ZAbsences.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() + datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() H.append( 'Saisie Absences hebdo.' % (formsemestre_id, moduleimpl_id, datelundi) diff --git a/sco_poursuite_dut.py b/sco_poursuite_dut.py index 46334dee..c845ea04 100644 --- a/sco_poursuite_dut.py +++ b/sco_poursuite_dut.py @@ -36,7 +36,7 @@ from notes_log import log from gen_tables import GenTable import sco_formsemestre import sco_groups -import ZAbsences +import sco_abs from sco_codes_parcours import code_semestre_validant, code_semestre_attente @@ -90,7 +90,7 @@ def etud_get_poursuite_info(context, sem, etud): rangs.append(["rang_" + codeModule, rangModule]) # Absences - AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid) + AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid) NbAbs = AbsSemEtud.CountAbs() NbAbsJust = AbsSemEtud.CountAbsJust() if ( diff --git a/sco_prepajury.py b/sco_prepajury.py index 0df0063d..11f33a54 100644 --- a/sco_prepajury.py +++ b/sco_prepajury.py @@ -38,7 +38,7 @@ import sco_formsemestre import sco_parcours_dut import sco_codes_parcours from scolars import format_nom, format_prenom, format_sexe, format_lycee -from ZAbsences import getAbsSemEtud +from sco_abs import getAbsSemEtud def feuille_preparation_jury(context, formsemestre_id, REQUEST): diff --git a/sco_trombino_tours.py b/sco_trombino_tours.py index eece74eb..1b5a44ed 100644 --- a/sco_trombino_tours.py +++ b/sco_trombino_tours.py @@ -40,7 +40,7 @@ import tempfile from notes_log import log from sco_utils import * -import ZAbsences +import sco_abs import scolars import sco_photos import sco_formsemestre @@ -301,9 +301,9 @@ def pdf_feuille_releve_absences( NB_CELL_PM = context.get_preference("feuille_releve_abs_PM") COLWIDTH = 0.85 * cm if context.get_preference("feuille_releve_abs_samedi"): - days = ZAbsences.DAYNAMES[:6] # Lundi, ..., Samedi + days = sco_abs.DAYNAMES[:6] # Lundi, ..., Samedi else: - days = ZAbsences.DAYNAMES[:5] # Lundi, ..., Vendredi + days = sco_abs.DAYNAMES[:5] # Lundi, ..., Vendredi nb_days = len(days) # Informations sur les groupes à afficher: From acdda802ab26f3bd2b7b3205e59a614b158b9c96 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 10 Jan 2021 22:31:00 +0100 Subject: [PATCH 008/104] Fix: declareProtected --- ZAbsences.py | 13 +++++++------ ZEntreprises.py | 14 +++++++++----- ZNotes.py | 10 ++++++++-- ZScoUsers.py | 16 +++++++++------- ZScolar.py | 6 ++++++ 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/ZAbsences.py b/ZAbsences.py index 8e50e72a..afdcb396 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -117,11 +117,11 @@ class ZAbsences( self.title = title # The form used to edit this object - def manage_editZAbsences(self, title, RESPONSE=None): - "Changes the instance values" - self.title = title - self._p_changed = 1 - RESPONSE.redirect("manage_editForm") + # def manage_editZAbsences(self, title, RESPONSE=None): + # "Changes the instance values" + # self.title = title + # self._p_changed = 1 + # RESPONSE.redirect("manage_editForm") # -------------------------------------------------------------------- # @@ -495,7 +495,7 @@ class ZAbsences( a["description"] = self._GetAbsDescription(a, cursor=cursor) return A - security.declareProtected(ScoView, "ListeAbsJust") + security.declareProtected(ScoView, "ListeJustifs") def ListeJustifs(self, etudid, datedebut, datefin=None, only_no_abs=False): """Liste des justificatifs (sans absence relevée) à partir d'une date, @@ -696,6 +696,7 @@ class ZAbsences( "Vrai si le samedi est travaillé" return int(self.get_preference("work_saturday")) + security.declareProtected(ScoView, "day_names") def day_names(self): """Returns week day names. If work_saturday property is set, include saturday diff --git a/ZEntreprises.py b/ZEntreprises.py index 3e8a4468..3f138977 100644 --- a/ZEntreprises.py +++ b/ZEntreprises.py @@ -233,11 +233,11 @@ class ZEntreprises( self.title = title # The form used to edit this object - def manage_editZEntreprises(self, title, RESPONSE=None): - "Changes the instance values" - self.title = title - self._p_changed = 1 - RESPONSE.redirect("manage_editForm") + # def manage_editZEntreprises(self, title, RESPONSE=None): + # "Changes the instance values" + # self.title = title + # self._p_changed = 1 + # RESPONSE.redirect("manage_editForm") # Ajout (dans l'instance) d'un dtml modifiable par Zope def defaultDocFile(self, id, title, file): @@ -448,6 +448,10 @@ class ZEntreprises( cnx = self.GetDBConnexion() _entreprise_correspEditor.edit(cnx, *args, **kw) + security.declareProtected( + ScoEntrepriseView, "do_entreprise_correspondant_listnames" + ) + def do_entreprise_correspondant_listnames(self, args={}): "-> liste des noms des correspondants (pour affichage menu)" C = self.do_entreprise_correspondant_list(args=args) diff --git a/ZNotes.py b/ZNotes.py index ae180b3e..b1e4fe92 100644 --- a/ZNotes.py +++ b/ZNotes.py @@ -138,6 +138,8 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl self.title = title # The form used to edit this object + security.declareProtected(ScoView, "manage_editZNotes") + def manage_editZNotes(self, title, RESPONSE=None): "Changes the instance values" self.title = title @@ -992,6 +994,8 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl if redirect: return REQUEST.RESPONSE.redirect("ue_list?formation_id=" + formation_id) + security.declareProtected(ScoChangeFormation, "ue_move") + def ue_move(self, ue_id, after=0, REQUEST=None, redirect=1): """Move UE before/after previous one (decrement/increment numero)""" o = self.do_ue_list({"ue_id": ue_id})[0] @@ -2083,6 +2087,8 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl + self.sco_footer(REQUEST) ) + security.declareProtected(ScoImplement, "do_formsemestre_desinscription") + def do_formsemestre_desinscription(self, etudid, formsemestre_id, REQUEST=None): """Désinscription d'un étudiant. Si semestre extérieur et dernier inscrit, suppression de ce semestre. @@ -2960,7 +2966,7 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl + self.sco_footer(REQUEST) ) - security.declareProtected(ScoView, "formsemestre_bulletins_mailetuds") + security.declareProtected(ScoView, "external_ue_create_form") external_ue_create_form = sco_ue_external.external_ue_create_form security.declareProtected(ScoEnsView, "appreciation_add_form") @@ -3282,7 +3288,7 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl self, formsemestre_id, REQUEST ) - security.declareProtected(ScoView, "formsemestre_validation_auto") + security.declareProtected(ScoView, "do_formsemestre_validation_auto") def do_formsemestre_validation_auto(self, formsemestre_id, REQUEST): "Formulaire saisie automatisee des decisions d'un semestre" diff --git a/ZScoUsers.py b/ZScoUsers.py index 3d435335..c62d74f2 100644 --- a/ZScoUsers.py +++ b/ZScoUsers.py @@ -106,11 +106,11 @@ class ZScoUsers( self.title = title # The form used to edit this object - def manage_editZScousers(self, title, RESPONSE=None): - "Changes the instance values" - self.title = title - self._p_changed = 1 - RESPONSE.redirect("manage_editForm") + # def manage_editZScousers(self, title, RESPONSE=None): + # "Changes the instance values" + # self.title = title + # self._p_changed = 1 + # RESPONSE.redirect("manage_editForm") # Ajout (dans l'instance) d'un dtml modifiable par Zope def defaultDocFile(self, id, title, file): @@ -378,7 +378,7 @@ class ZScoUsers( def do_change_password(self, user_name, password): user = self._user_list(args={"user_name": user_name}) - assert len(user) == 1, "database inconsistency: len(r)=%d" % len(r) + assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user) # should not occur, already tested in _can_handle_passwd cnx = self.GetUsersDBConnexion() # en mode autocommit cursor = cnx.cursor(cursor_factory=ScoDocCursor) @@ -408,7 +408,7 @@ class ZScoUsers( # access denied log( "change_password: access denied (authuser=%s, user_name=%s, ip=%s)" - % (authuser, user_name, REQUEST.REMOTE_ADDR) + % (REQUEST.AUTHENTICATED_USER, user_name, REQUEST.REMOTE_ADDR) ) raise AccessDenied( "vous n'avez pas la permission de changer ce mot de passe" @@ -1089,6 +1089,8 @@ class ZScoUsers( self._user_delete(user_name) REQUEST.RESPONSE.redirect(REQUEST.URL1) + security.declareProtected(ScoView, "list_users") + def list_users( self, dept, diff --git a/ZScolar.py b/ZScolar.py index d09e2ac5..e42397c8 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -184,6 +184,8 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp self.manage_addProperty("roles_initialized", "0", "string") # The for used to edit this object + security.declareProtected(ScoView, "manage_editZScolar") + def manage_editZScolar(self, title, RESPONSE=None): "Changes the instance values" self.title = title @@ -538,6 +540,8 @@ UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo (code UCOD46, 16 ECTS, Apo Date: Fri, 15 Jan 2021 23:08:27 +0100 Subject: [PATCH 009/104] Port all Entreprise DTML methods to Python --- ZEntreprises.py | 921 ++++++++++++++++++++++++++++++++++++++---- notesdb.py | 24 +- static/css/scodoc.css | 20 + 3 files changed, 885 insertions(+), 80 deletions(-) diff --git a/ZEntreprises.py b/ZEntreprises.py index 3f138977..5eb610aa 100644 --- a/ZEntreprises.py +++ b/ZEntreprises.py @@ -26,23 +26,33 @@ ############################################################################## """ Gestion des relations avec les entreprises + +Note: Code très ancien, porté de Zope/DTML, peu utilisable + +=> Voir si des départements utilisent encore ce module et envisager de le supprimer. + """ import urllib +import string +import re +import time +import calendar from sco_zope import * +from sco_permissions import ScoEntrepriseView, ScoEntrepriseChange # --------------- from notesdb import * from notes_log import log from scolog import logdb -from sco_utils import * +from sco_utils import SCO_ENCODING +import sco_utils as scu import html_sidebar - +import VERSION +from gen_tables import GenTable from TrivialFormulator import TrivialFormulator, TF import scolars -import string, re -import time, calendar def _format_nom(nom): @@ -78,10 +88,19 @@ class EntreprisesEditor(EditableTable): sortkey=None, sort_on_contact=False, ZEntrepriseInstance=None, + limit="", + offset="", ): # list, then sort on date of last contact R = EditableTable.list( - self, cnx, args=args, operator=operator, test=test, sortkey=sortkey + self, + cnx, + args=args, + operator=operator, + test=test, + sortkey=sortkey, + limit=limit, + offset=offset, ) if sort_on_contact: for r in R: @@ -323,7 +342,7 @@ class ZEntreprises( H.append("") # - H.append("""

%s""" % icontag("entreprise_side_img")) + H.append("""

%s""" % scu.icontag("entreprise_side_img")) if REQUEST["_read_only"]: H.append("""
(Lecture seule)""") H.append(""" """) @@ -331,55 +350,835 @@ class ZEntreprises( # -------------------------------------------------------------------- # - # Entreprises : Methodes en DTML + # Entreprises : Vues # # -------------------------------------------------------------------- - # used to view content of the object security.declareProtected(ScoEntrepriseView, "index_html") - index_html = DTMLFile("dtml/entreprises/index_html", globals()) + + def index_html( + self, REQUEST=None, etud_nom=None, limit=50, offset="", format="html" + ): + """Accueil module entreprises""" + # Traduit du DTML - utilise table standard + if limit: + limit = int(limit) + if offset: + offset = int(offset or 0) + + if etud_nom: + entreprises = self.do_entreprise_list_by_etud( + args=REQUEST.form, sort_on_contact=True + ) + table_navigation = "" + else: + entreprises = self.do_entreprise_list( + args=REQUEST.form, + test="~*", + sort_on_contact=True, + limit=limit, + offset=offset, + ) + # Liens navigation précédent/suivant + webparams = {"limit": limit} + if offset: + webparams["offset"] = max((offset or 0) - limit, 0) + prev_lnk = 'précédentes' % ( + REQUEST.URL0 + "?" + urllib.urlencode(webparams) + ) + else: + prev_lnk = "" + if len(entreprises) >= limit: + webparams["offset"] = (offset or 0) + limit + next_lnk = 'suivantes' % ( + REQUEST.URL0 + "?" + urllib.urlencode(webparams) + ) + else: + next_lnk = "" + table_navigation = ( + '
' + + prev_lnk + + '' + + next_lnk + + "
" + ) + # Ajout des liens sur la table: + for e in entreprises: + e["_nom_target"] = "entreprise_edit?entreprise_id=%(entreprise_id)s" % e + e["correspondants"] = self.do_entreprise_correspondant_list( + args={"entreprise_id": e["entreprise_id"]} + ) + e["nbcorr"] = "%d corr." % len(e["correspondants"]) + e["_nbcorr_target"] = ( + "entreprise_correspondant_list?entreprise_id=%(entreprise_id)s" % e + ) + e["contacts"] = self.do_entreprise_contact_list( + args={"entreprise_id": e["entreprise_id"]} + ) + e["nbcontact"] = "%d contacts." % len(e["contacts"]) + e["_nbcontact_target"] = ( + "entreprise_contact_list?entreprise_id=%(entreprise_id)s" % e + ) + tab = GenTable( + rows=entreprises, + columns_ids=("nom", "ville", "secteur", "nbcorr", "nbcontact"), + titles={ + "nom": "Entreprise", + "ville": "Ville", + "secteur": "Secteur", + "nbcorr": "Corresp.", + "contacts": "Contacts", + }, + origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr(), + filename=scu.make_filename( + "entreprises_%s" % self.get_preference("DeptName") + ), + caption="Entreprises du département %s" % self.get_preference("DeptName"), + html_sortable=True, + html_class="entreprise_list table_leftalign", + html_with_td_classes=True, + html_next_section=table_navigation, + base_url=REQUEST.URL0 + "?", + preferences=self.get_preferences(), + ) + if format != "html": + return tab.make_page(self, format=format, REQUEST=REQUEST) + else: + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Suivi relations entreprises

""", + """
""", + tab.html(), + """
""", + self.entreprise_footer(REQUEST), + ] + return "\n".join(H) security.declareProtected(ScoEntrepriseView, "entreprise_contact_list") - entreprise_contact_list = DTMLFile( - "dtml/entreprises/entreprise_contact_list", globals() - ) - security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_list") - entreprise_correspondant_list = DTMLFile( - "dtml/entreprises/entreprise_correspondant_list", globals() - ) - # les methodes "edit" sont aussi en ScoEntrepriseView car elles permettent - # la visualisation (via variable _read_only positionnee dans entreprise_header) - security.declareProtected(ScoEntrepriseView, "entreprise_contact_edit") - entreprise_contact_edit = DTMLFile( - "dtml/entreprises/entreprise_contact_edit", globals() - ) - security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_edit") - entreprise_correspondant_edit = DTMLFile( - "dtml/entreprises/entreprise_correspondant_edit", globals() - ) - # Acces en modification: + def entreprise_contact_list(self, entreprise_id=None, format="html", REQUEST=None): + """Liste des contacts de l'entreprise""" + H = [self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises")] + if entreprise_id: + E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + C = self.do_entreprise_contact_list(args={"entreprise_id": entreprise_id}) + H.append( + """

Listes des contacts avec l'entreprise %(nom)s

+ """ + % E + ) + else: + C = self.do_entreprise_contact_list(args={}) + H.append( + """

Listes des contacts

+ """ + ) + for c in C: + c[ + "_date_target" + ] = "%s/entreprise_contact_edit?entreprise_contact_id=%s" % ( + REQUEST.URL1, + c["entreprise_contact_id"], + ) + c["entreprise"] = self.do_entreprise_list( + args={"entreprise_id": c["entreprise_id"]} + )[0] + if c["etudid"]: + c["etud"] = self.getEtudInfo(etudid=c["etudid"], filled=1)[0] + c["etudnom"] = c["etud"]["nomprenom"] + c["_etudnom_target"] = "%s/ficheEtud?etudid=%s" % ( + REQUEST.URL1, + c["etudid"], + ) + else: + c["etud"] = None + c["etudnom"] = "" + + tab = GenTable( + rows=C, + columns_ids=("date", "type_contact", "etudnom", "description"), + titles={ + "date": "Date", + "type_contact": "Object", + "etudnom": "Étudiant", + "description": "Description", + }, + origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr(), + filename=scu.make_filename("contacts_%s" % self.get_preference("DeptName")), + caption="", + html_sortable=True, + html_class="contact_list table_leftalign", + html_with_td_classes=True, + base_url=REQUEST.URL0 + "?", + preferences=self.get_preferences(), + ) + if format != "html": + return tab.make_page(self, format=format, REQUEST=REQUEST) + + H.append(tab.html()) + + if not REQUEST["_read_only"]: # portage DTML, à modifier + if entreprise_id: + H.append( + """

nouveau "contact"

+ """ + % E + ) + + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + + security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_list") + + def entreprise_correspondant_list( + self, + entreprise_id=None, + format="html", + REQUEST=None, + ): + """Liste des correspondants de l'entreprise""" + E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """ +

Listes des correspondants dans l'entreprise %(nom)s

+ """ + % E, + ] + correspondants = self.do_entreprise_correspondant_list( + args={"entreprise_id": entreprise_id} + ) + for c in correspondants: + c["nomprenom"] = c["nom"].upper() + " " + c["nom"].capitalize() + c["_nomprenom_target"] = ( + "%s/entreprise_correspondant_edit?entreprise_corresp_id=%s" + % (REQUEST.URL1, c["entreprise_corresp_id"]), + ) + c["nom_entreprise"] = E["nom"] + l = [] + if c["phone1"]: + l.append(c["phone1"]) + if c["phone2"]: + l.append(c["phone2"]) + if c["mobile"]: + l.append(c["mobile"]) + c["telephones"] = " / ".join(l) + c["mails"] = " ".join( + [ + '%s' % (c["mail1"], c["mail1"]) + if c["mail1"] + else "", + '%s' % (c["mail2"], c["mail2"]) + if c["mail2"] + else "", + ] + ) + c["modifier"] = ( + 'modifier' + % c["entreprise_corresp_id"] + ) + c["supprimer"] = ( + 'supprimer' + % c["entreprise_corresp_id"] + ) + tab = GenTable( + rows=correspondants, + columns_ids=( + "nomprenom", + "nom_entreprise", + "fonction", + "telephones", + "mails", + "note", + "modifier", + "supprimer", + ), + titles={ + "nomprenom": "Nom", + "nom_entreprise": "Entreprise", + "fonction": "Fonction", + "telephones": "Téléphone", + "mails": "Mail", + "note": "Note", + "modifier": "", + "supprimer": "", + }, + origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr(), + filename=scu.make_filename( + "correspondants_%s_%s" % (E["nom"], self.get_preference("DeptName")) + ), + caption="", + html_sortable=True, + html_class="contact_list table_leftalign", + html_with_td_classes=True, + base_url=REQUEST.URL0 + "?", + preferences=self.get_preferences(), + ) + if format != "html": + return tab.make_page(self, format=format, REQUEST=REQUEST) + + H.append(tab.html()) + + if not REQUEST["_read_only"]: # portage DTML, à modifier + H.append( + """

Ajouter un correspondant dans l'entreprise %(nom)s

+ """ + % E + ) + + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + + security.declareProtected(ScoEntrepriseView, "entreprise_contact_edit") + + def entreprise_contact_edit(self, entreprise_contact_id, REQUEST=None): + """Form edit contact""" + c = self.do_entreprise_contact_list( + args={"entreprise_contact_id": entreprise_contact_id} + )[0] + link_create_corr = ( + 'créer un nouveau correspondant' + % (REQUEST.URL1, c["entreprise_id"]) + ) + E = self.do_entreprise_list(args={"entreprise_id": c["entreprise_id"]})[0] + correspondants = self.do_entreprise_correspondant_listnames( + args={"entreprise_id": c["entreprise_id"]} + ) + [("inconnu", "")] + + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Suivi entreprises

+

Contact avec entreprise %(nom)s

""" + % E, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ( + "entreprise_contact_id", + {"default": entreprise_contact_id, "input_type": "hidden"}, + ), + ( + "entreprise_id", + {"input_type": "hidden", "default": c["entreprise_id"]}, + ), + ( + "type_contact", + { + "input_type": "menu", + "title": "Objet", + "allowed_values": ( + "Prospection", + "Stage étudiant", + "Contrat Apprentissage", + "Projet", + "Autre", + ), + }, + ), + ( + "date", + { + "size": 12, + "title": "Date du contact (j/m/a)", + "allow_null": False, + }, + ), + ( + "entreprise_corresp_id", + { + "input_type": "menu", + "title": "Correspondant entreprise", + "explanation": link_create_corr, + "allow_null": True, + "labels": [x[0] for x in correspondants], + "allowed_values": [x[1] for x in correspondants], + }, + ), + ( + "etudiant", + { + "size": 16, + "title": "Etudiant concerné", + "allow_null": True, + "default": c["etudid"], + "explanation": "nom (si pas ambigu) ou code", + }, + ), + ( + "enseignant", + {"size": 16, "title": "Enseignant (tuteur)", "allow_null": True}, + ), + ( + "description", + { + "input_type": "textarea", + "rows": 3, + "cols": 40, + "title": "Description", + }, + ), + ), + cancelbutton="Annuler", + initvalues=c, + submitlabel="Modifier les valeurs", + readonly=REQUEST["_read_only"], + ) + + if tf[0] == 0: + H.append(tf[1]) + if not REQUEST["_read_only"]: # portage DTML, à modifier + H.append( + """

Supprimer ce contact

""" + % entreprise_contact_id + ) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + etudok = self.do_entreprise_check_etudiant(tf[2]["etudiant"]) + if etudok[0] == 0: + H.append("""

%s

""" % etudok[1]) + else: + tf[2].update({"etudid": etudok[1]}) + self.do_entreprise_contact_edit(tf[2]) + REQUEST.RESPONSE.redirect( + REQUEST.URL1 + + "/entreprise_contact_list?entreprise_id=" + + str(c["entreprise_id"]) + ) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + + security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_edit") + + def entreprise_correspondant_edit(self, entreprise_corresp_id, REQUEST=None): + """Form édition d'un correspondant""" + # F -> c + c = self.do_entreprise_correspondant_list( + args={"entreprise_corresp_id": entreprise_corresp_id} + )[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Édition contact entreprise

""", + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ( + "entreprise_corresp_id", + {"default": entreprise_corresp_id, "input_type": "hidden"}, + ), + ( + "civilite", + { + "input_type": "menu", + "labels": ["M.", "Mme"], + "allowed_values": ["M.", "Mme"], + }, + ), + ("nom", {"size": 25, "title": "Nom", "allow_null": False}), + ("prenom", {"size": 25, "title": "Prénom"}), + ( + "fonction", + { + "input_type": "menu", + "allowed_values": ( + "Directeur", + "RH", + "Resp. Administratif", + "Tuteur", + "Autre", + ), + "explanation": "fonction via à vis de l'IUT", + }, + ), + ( + "phone1", + { + "size": 14, + "title": "Téléphone 1", + }, + ), + ( + "phone2", + { + "size": 14, + "title": "Téléphone 2", + }, + ), + ( + "mobile", + { + "size": 14, + "title": "Tél. mobile", + }, + ), + ( + "fax", + { + "size": 14, + "title": "Fax", + }, + ), + ( + "mail1", + { + "size": 25, + "title": "e-mail", + }, + ), + ( + "mail2", + { + "size": 25, + "title": "e-mail 2", + }, + ), + ( + "note", + {"input_type": "textarea", "rows": 3, "cols": 40, "title": "Note"}, + ), + ), + cancelbutton="Annuler", + initvalues=c, + submitlabel="Modifier les valeurs", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect( + "%s/entreprise_correspondant_list?entreprise_id=%s" + % (REQUEST.URL1, c["entreprise_id"]) + ) + else: + self.do_entreprise_correspondant_edit(tf[2]) + REQUEST.RESPONSE.redirect( + "%s/entreprise_correspondant_list?entreprise_id=%s" + % (REQUEST.URL1, c["entreprise_id"]) + ) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + security.declareProtected(ScoEntrepriseChange, "entreprise_contact_create") - entreprise_contact_create = DTMLFile( - "dtml/entreprises/entreprise_contact_create", globals() - ) + + def entreprise_contact_create(self, entreprise_id, REQUEST=None): + """Form création contact""" + E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + correspondants = self.do_entreprise_correspondant_listnames( + args={"entreprise_id": entreprise_id} + ) + if not correspondants: + correspondants = [("inconnu", "")] + curtime = time.strftime("%d/%m/%Y") + link_create_corr = ( + 'créer un nouveau correspondant' + % (REQUEST.URL1, entreprise_id) + ) + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Nouveau "contact" avec l'entreprise %(nom)s

""" + % E, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("entreprise_id", {"input_type": "hidden", "default": entreprise_id}), + ( + "type_contact", + { + "input_type": "menu", + "title": "Objet", + "allowed_values": ( + "Prospection", + "Stage étudiant", + "Contrat Apprentissage DUT GTR1", + "Contrat Apprentissage DUT GTR2", + "Contrat Apprentissage Licence SQRT", + "Projet", + "Autre", + ), + "default": "Stage étudiant", + }, + ), + ( + "date", + { + "size": 12, + "title": "Date du contact (j/m/a)", + "allow_null": False, + "default": curtime, + }, + ), + ( + "entreprise_corresp_id", + { + "input_type": "menu", + "title": "Correspondant entreprise", + "explanation": link_create_corr, + "allow_null": True, + "labels": [x[0] for x in correspondants], + "allowed_values": [x[1] for x in correspondants], + }, + ), + ( + "etudiant", + { + "size": 16, + "title": "Etudiant concerné", + "allow_null": True, + "explanation": "nom (si pas ambigu) ou code", + }, + ), + ( + "enseignant", + {"size": 16, "title": "Enseignant (tuteur)", "allow_null": True}, + ), + ( + "description", + { + "input_type": "textarea", + "rows": 3, + "cols": 40, + "title": "Description", + }, + ), + ), + cancelbutton="Annuler", + submitlabel="Ajouter ce contact", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + etudok = self.do_entreprise_check_etudiant(tf[2]["etudiant"]) + if etudok[0] == 0: + H.append("""

%s

""" % etudok[1]) + else: + tf[2].update({"etudid": etudok[1]}) + self.do_entreprise_contact_create(tf[2]) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + security.declareProtected(ScoEntrepriseChange, "entreprise_contact_delete") - entreprise_contact_delete = DTMLFile( - "dtml/entreprises/entreprise_contact_delete", globals() - ) + + def entreprise_contact_delete(self, entreprise_contact_id, REQUEST=None): + """Form delete contact""" + c = self.do_entreprise_contact_list( + args={"entreprise_contact_id": entreprise_contact_id} + )[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Suppression du contact

""", + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("entreprise_contact_id", {"input_type": "hidden"}),), + initvalues=c, + submitlabel="Confirmer la suppression", + cancelbutton="Annuler", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + self.do_entreprise_contact_delete(c["entreprise_contact_id"]) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_create") - entreprise_correspondant_create = DTMLFile( - "dtml/entreprises/entreprise_correspondant_create", globals() - ) + + def entreprise_correspondant_create(self, entreprise_id, REQUEST=None): + """Form création correspondant""" + E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Nouveau correspondant l'entreprise %(nom)s

""" + % E, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("entreprise_id", {"input_type": "hidden", "default": entreprise_id}), + ( + "civilite", + { + "input_type": "menu", + "labels": ["M.", "Mme"], + "allowed_values": ["M.", "Mme"], + }, + ), + ("nom", {"size": 25, "title": "Nom", "allow_null": False}), + ("prenom", {"size": 25, "title": "Prénom"}), + ( + "fonction", + { + "input_type": "menu", + "allowed_values": ( + "Directeur", + "RH", + "Resp. Administratif", + "Tuteur", + "Autre", + ), + "default": "Tuteur", + "explanation": "fonction via à vis de l'IUT", + }, + ), + ( + "phone1", + { + "size": 14, + "title": "Téléphone 1", + }, + ), + ( + "phone2", + { + "size": 14, + "title": "Téléphone 2", + }, + ), + ( + "mobile", + { + "size": 14, + "title": "Tél. mobile", + }, + ), + ( + "fax", + { + "size": 14, + "title": "Fax", + }, + ), + ( + "mail1", + { + "size": 25, + "title": "e-mail", + }, + ), + ( + "mail2", + { + "size": 25, + "title": "e-mail 2", + }, + ), + ( + "note", + {"input_type": "textarea", "rows": 3, "cols": 40, "title": "Note"}, + ), + ), + cancelbutton="Annuler", + submitlabel="Ajouter ce correspondant", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + self.do_entreprise_correspondant_create(tf[2]) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_delete") - entreprise_correspondant_delete = DTMLFile( - "dtml/entreprises/entreprise_correspondant_delete", globals() - ) + + def entreprise_correspondant_delete(self, entreprise_corresp_id, REQUEST=None): + """Form delete correspondant""" + c = self.do_entreprise_correspondant_list( + args={"entreprise_corresp_id": entreprise_corresp_id} + )[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Suppression du correspondant %(nom)s %(prenom)s

""" % c, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("entreprise_corresp_id", {"input_type": "hidden"}),), + initvalues=c, + submitlabel="Confirmer la suppression", + cancelbutton="Annuler", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + self.do_entreprise_correspondant_delete(c["entreprise_corresp_id"]) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) + security.declareProtected(ScoEntrepriseChange, "entreprise_delete") - entreprise_delete = DTMLFile("dtml/entreprises/entreprise_delete", globals()) + + def entreprise_delete(self, entreprise_id, REQUEST=None): + """Form delete entreprise""" + E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + H = [ + self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + """

Suppression de l'entreprise %(nom)s

+

Attention: supression définitive de l'entreprise, de ses correspondants et contacts. +

""" + % E, + ] + Cl = self.do_entreprise_correspondant_list( + args={"entreprise_id": entreprise_id} + ) + if Cl: + H.append( + """

Correspondants dans l'entreprise qui seront supprimés:

    """ + ) + for c in Cl: + H.append("""
  • %(nom)s %(prenom)s (%(fonction)s)
  • """ % c) + H.append("""
""") + + Cts = self.do_entreprise_contact_list(args={"entreprise_id": entreprise_id}) + if Cts: + H.append( + """

Contacts avec l'entreprise qui seront supprimés:

    """ + ) + for c in Cts: + H.append("""
  • %(date)s %(description)s
  • """ % c) + H.append("""
""") + tf = self.TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("entreprise_id", {"input_type": "hidden"}),), + initvalues=E, + submitlabel="Confirmer la suppression", + cancelbutton="Annuler", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + H.append(tf[1]) + elif tf[0] == -1: + REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + self.do_entreprise_delete(E["entreprise_id"]) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + H.append(self.entreprise_footer(REQUEST)) + return "\n".join(H) # -------------------------------------------------------------------- # - # Entreprises : Methodes en Python + # Entreprises : Actions # # -------------------------------------------------------------------- security.declareProtected(ScoEntrepriseChange, "do_entreprise_create") @@ -521,7 +1320,7 @@ class ZEntreprises( for x in r: e.append( "
  • %s %s (code %s)
  • " - % (strupper(x[1]), x[2] or "", x[0].strip()) + % (scu.strupper(x[1]), x[2] or "", x[0].strip()) ) e.append("") return ( @@ -780,7 +1579,7 @@ class ZEntreprises( H.append( """

    %s Supprimer cette entreprise

    """ % ( - icontag("delete_img", title="delete", border="0"), + scu.icontag("delete_img", title="delete", border="0"), F["entreprise_id"], ) ) @@ -850,42 +1649,6 @@ class ZEntreprises( self.do_entreprise_edit(tf[2]) return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) - # --- Misc tools.... ------------------ - security.declareProtected(ScoEntrepriseView, "str_abbrev") - - def str_abbrev(self, s, maxlen): - "abreviation" - if s == None: - return "?" - if len(s) < maxlen: - return s - return s[: maxlen - 3] + "..." - - security.declareProtected(ScoEntrepriseView, "setPageSizeCookie") - - def setPageSizeCookie(self, REQUEST=None): - "set page size cookie" - RESPONSE = REQUEST.RESPONSE - # - if REQUEST.form.has_key("entreprise_page_size"): - RESPONSE.setCookie( - "entreprise_page_size", - REQUEST.form["entreprise_page_size"], - path="/", - expires="Wed, 31-Dec-2025 23:55:00 GMT", - ) - RESPONSE.redirect(REQUEST.form["target_url"]) - - security.declareProtected(ScoEntrepriseView, "make_link_create_corr") - - def make_link_create_corr(self, entreprise_id): - "yet another stupid code snippet" - return ( - 'créer un nouveau correspondant' - ) - # -------------------------------------------------------------------- # diff --git a/notesdb.py b/notesdb.py index 5bdbcf40..09e51a3b 100644 --- a/notesdb.py +++ b/notesdb.py @@ -141,6 +141,8 @@ def DBSelectArgs( distinct=True, aux_tables=[], id_name=None, + limit="", + offset="", ): """Select * from table where values match dict vals. Returns cnx, columns_names, list of tuples @@ -155,6 +157,12 @@ def DBSelectArgs( distinct = " distinct " else: distinct = "" + if limit != "": + limit = " LIMIT %d" % limit + if not offset: + offset = "" + if offset != "": + offset = " OFFSET %d" % offset operator = " " + operator + " " # liste des tables (apres "from") tables = [table] + [x[0] for x in aux_tables] @@ -195,7 +203,17 @@ def DBSelectArgs( if cond: cond = " where " + cond # - req = "select " + distinct + ", ".join(what) + " from " + tables + cond + orderby + req = ( + "select " + + distinct + + ", ".join(what) + + " from " + + tables + + cond + + orderby + + limit + + offset + ) # open('/tmp/select.log','a').write( req % vals + '\n' ) try: cursor.execute(req, vals) @@ -329,6 +347,8 @@ class EditableTable: test="=", sortkey=None, disable_formatting=False, + limit="", + offset="", ): "returns list of dicts" # REQLOG.write('%s: %s by %s (%s) %d\n'%(self.table_name,args,sys._getframe(1).f_code.co_name, sys._getframe(2).f_code.co_name, REQN)) @@ -347,6 +367,8 @@ class EditableTable: operator=operator, aux_tables=self.aux_tables, id_name=self.id_name, + limit=limit, + offset=offset, ) for r in res: self.format_output(r, disable_formatting=disable_formatting) diff --git a/static/css/scodoc.css b/static/css/scodoc.css index 7eebc65f..48dd4df8 100644 --- a/static/css/scodoc.css +++ b/static/css/scodoc.css @@ -2039,7 +2039,14 @@ table.entreprise_list, table.corr_list, table.contact_list { /* border-style: solid; */ border-spacing: 0px 0px; padding: 0px; + margin-left: 0px; } + +table.entreprise_list td.nbcorr a, table.entreprise_list td.nbcontact a, table.contact_list td.etudnom a, table.contact_list td a { + color: navy; + text-decoration: underline; +} + tr.entreprise_list_even, tr.corr_list_even, tr.contact_list_even { background-color: rgb(85%,85%,95%); } @@ -2047,6 +2054,19 @@ tr.entreprise_list_odd, tr.corr_list_odd, tr.contact_list_odd { background-color: rgb(90%,90%, 90%); } +span.table_nav_mid { + flex-grow: 1; /* Set the middle element to grow and stretch */ +} +span.table_nav_prev, span.table_nav_next { + width: 11em; /* A fixed width as the default */ +} + +div.table_nav { + width: 100%; + display: flex; + justify-content: space-between; +} + td.entreprise_descr, td.corr_descr, td.contact_descr { padding-left: 2em; } From 9b9ed47e9663e404eace7c390d1bde09f1caabaa Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 16 Jan 2021 10:44:02 +0100 Subject: [PATCH 010/104] Removed more Zope archaeological remains --- ZAbsences.py | 20 +------------------- ZEntreprises.py | 29 ----------------------------- ZNotes.py | 19 ------------------- ZScoDoc.py | 8 +------- ZScoUsers.py | 14 -------------- ZScolar.py | 18 +----------------- __init__.py | 18 ------------------ 7 files changed, 3 insertions(+), 123 deletions(-) diff --git a/ZAbsences.py b/ZAbsences.py index afdcb396..33e09b6b 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -697,6 +697,7 @@ class ZAbsences( return int(self.get_preference("work_saturday")) security.declareProtected(ScoView, "day_names") + def day_names(self): """Returns week day names. If work_saturday property is set, include saturday @@ -1993,22 +1994,3 @@ ou entrez une date pour visualiser les absents un jour donné : doc._pop() log("XMLgetAbsEtud (%gs)" % (time.time() - t0)) return repr(doc) - - -# -------------------------------------------------------------------- -# -# Zope Product Administration -# -# -------------------------------------------------------------------- -def manage_addZAbsences( - self, id="id_ZAbsences", title="The Title for ZAbsences Object", REQUEST=None -): - "Add a ZAbsences instance to a folder." - self._setObject(id, ZAbsences(id, title)) - if REQUEST is not None: - return self.manage_main(self, REQUEST) - # return self.manage_editForm(self, REQUEST) - - -# The form used to get the instance id from the user. -# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) diff --git a/ZEntreprises.py b/ZEntreprises.py index 5eb610aa..53d287a0 100644 --- a/ZEntreprises.py +++ b/ZEntreprises.py @@ -251,20 +251,6 @@ class ZEntreprises( self.id = id self.title = title - # The form used to edit this object - # def manage_editZEntreprises(self, title, RESPONSE=None): - # "Changes the instance values" - # self.title = title - # self._p_changed = 1 - # RESPONSE.redirect("manage_editForm") - - # Ajout (dans l'instance) d'un dtml modifiable par Zope - def defaultDocFile(self, id, title, file): - f = open(file_path + "/dtml-editable/" + file + ".dtml") - file = f.read() - f.close() - self.manage_addDTMLMethod(id, title, file) - security.declareProtected(ScoEntrepriseView, "entreprise_header") def entreprise_header(self, REQUEST=None, page_title=""): @@ -1648,18 +1634,3 @@ class ZEntreprises( else: self.do_entreprise_edit(tf[2]) return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) - - -# -------------------------------------------------------------------- -# -# Zope Product Administration -# -# -------------------------------------------------------------------- -def manage_addZEntreprises( - self, id="id_ZEntreprises", title="The Title for ZEntreprises Object", REQUEST=None -): - "Add a ZEntreprises instance to a folder." - self._setObject(id, ZEntreprises(id, title)) - if REQUEST is not None: - return self.manage_main(self, REQUEST) - # return self.manage_editForm(self, REQUEST) diff --git a/ZNotes.py b/ZNotes.py index b1e4fe92..d02d384e 100644 --- a/ZNotes.py +++ b/ZNotes.py @@ -3631,22 +3631,3 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl ) # -------------------------------------------------------------------- - - -# -------------------------------------------------------------------- -# -# Zope Product Administration -# -# -------------------------------------------------------------------- -def manage_addZNotes( - self, id="id_ZNotes", title="The Title for ZNotes Object", REQUEST=None -): - "Add a ZNotes instance to a folder." - self._setObject(id, ZNotes(id, title)) - if REQUEST is not None: - return self.manage_main(self, REQUEST) - # return self.manage_editForm(self, REQUEST) - - -# The form used to get the instance id from the user. -manage_addZNotesForm = DTMLFile("dtml/manage_addZNotesForm", globals()) diff --git a/ZScoDoc.py b/ZScoDoc.py index c2c7094c..76827bd8 100644 --- a/ZScoDoc.py +++ b/ZScoDoc.py @@ -77,13 +77,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp manage_options = ( ({"label": "Contents", "action": "manage_main"},) + PropertyManager.manage_options # add the 'Properties' tab - + ( - # this line is kept as an example with the files : - # dtml/manage_editZScolarForm.dtml - # html/ZScolar-edit.stx - # {'label': 'Properties', 'action': 'manage_editForm',}, - {"label": "View", "action": "index_html"}, - ) + + ({"label": "View", "action": "index_html"},) + Item.manage_options # add the 'Undo' & 'Owner' tab + RoleManager.manage_options # add the 'Security' tab ) diff --git a/ZScoUsers.py b/ZScoUsers.py index c62d74f2..0e03a39e 100644 --- a/ZScoUsers.py +++ b/ZScoUsers.py @@ -105,20 +105,6 @@ class ZScoUsers( self.id = id self.title = title - # The form used to edit this object - # def manage_editZScousers(self, title, RESPONSE=None): - # "Changes the instance values" - # self.title = title - # self._p_changed = 1 - # RESPONSE.redirect("manage_editForm") - - # Ajout (dans l'instance) d'un dtml modifiable par Zope - def defaultDocFile(self, id, title, file): - f = open(file_path + "/dtml-editable/" + file + ".dtml") - file = f.read() - f.close() - self.manage_addDTMLMethod(id, title, file) - # Connexion to SQL database of users: # Ugly but necessary during transition out of Zope: diff --git a/ZScolar.py b/ZScolar.py index e42397c8..c39d8bdc 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -133,13 +133,7 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp manage_options = ( ({"label": "Contents", "action": "manage_main"},) + PropertyManager.manage_options # add the 'Properties' tab - + ( - # this line is kept as an example with the files : - # dtml/manage_editZScolarForm.dtml - # html/ZScolar-edit.stx - # {'label': 'Properties', 'action': 'manage_editForm',}, - {"label": "View", "action": "index_html"}, - ) + + ({"label": "View", "action": "index_html"},) + Item.manage_options # add the 'Undo' & 'Owner' tab + RoleManager.manage_options # add the 'Security' tab ) @@ -152,17 +146,7 @@ class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp self.title = title self._db_cnx_string = db_cnx_string self._cnx = None - # --- add editable DTML documents: - # self.defaultDocFile('sidebar_dept', - # 'barre gauche (partie haute)', - # 'sidebar_dept') - # --- add DB connector - # id = 'DB' - # da = ZopeDA.Connection( - # id, 'DB connector', db_cnx_string, False, - # check=1, tilevel=2, encoding='utf-8') - # self._setObject(id, da) # --- add Scousers instance id = "Users" obj = ZScoUsers.ZScoUsers(id, "Gestion utilisateurs zope") diff --git a/__init__.py b/__init__.py index a7ebcf4b..98fd4023 100644 --- a/__init__.py +++ b/__init__.py @@ -27,8 +27,6 @@ from ZScolar import ZScolar, manage_addZScolarForm, manage_addZScolar -# from ZNotes import ZNotes, manage_addZNotesForm, manage_addZNotes - from ZScoDoc import ZScoDoc, manage_addZScoDoc # from sco_zope import * @@ -53,23 +51,7 @@ def initialize(context): icon="static/icons/sco_icon.png", ) - # context.registerHelp() - # context.registerHelpTitle("ZScolar") - # --- ZScoDoc context.registerClass( ZScoDoc, constructors=(manage_addZScoDoc,), icon="static/icons/sco_icon.png" ) - - # --- ZNotes - # context.registerClass( - # ZNotes, - # constructors = ( - # manage_addZNotesForm, - # manage_addZNotes - # ), - # icon = 'static/icons/notes_icon.png' - # ) - - # context.registerHelp() - # context.registerHelpTitle("ZNotes") From 09f9124b01b07ddaecc5e78a43b039238b1c51f0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 16 Jan 2021 10:53:15 +0100 Subject: [PATCH 011/104] removed (last) dtml methods: entreprises --- .../entreprise_contact_create.dtml | 62 -------- .../entreprise_contact_delete.dtml | 27 ---- dtml/entreprises/entreprise_contact_edit.dtml | 62 -------- dtml/entreprises/entreprise_contact_list.dtml | 54 ------- .../entreprise_correspondant_create.dtml | 43 ------ .../entreprise_correspondant_delete.dtml | 27 ---- .../entreprise_correspondant_edit.dtml | 41 ------ .../entreprise_correspondant_list.dtml | 52 ------- dtml/entreprises/entreprise_delete.dtml | 60 -------- dtml/entreprises/index_html.dtml | 139 ------------------ 10 files changed, 567 deletions(-) delete mode 100644 dtml/entreprises/entreprise_contact_create.dtml delete mode 100644 dtml/entreprises/entreprise_contact_delete.dtml delete mode 100644 dtml/entreprises/entreprise_contact_edit.dtml delete mode 100644 dtml/entreprises/entreprise_contact_list.dtml delete mode 100644 dtml/entreprises/entreprise_correspondant_create.dtml delete mode 100644 dtml/entreprises/entreprise_correspondant_delete.dtml delete mode 100644 dtml/entreprises/entreprise_correspondant_edit.dtml delete mode 100644 dtml/entreprises/entreprise_correspondant_list.dtml delete mode 100644 dtml/entreprises/entreprise_delete.dtml delete mode 100644 dtml/entreprises/index_html.dtml diff --git a/dtml/entreprises/entreprise_contact_create.dtml b/dtml/entreprises/entreprise_contact_create.dtml deleted file mode 100644 index e0c3ab05..00000000 --- a/dtml/entreprises/entreprise_contact_create.dtml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - -

    Nouveau "contact" avec l'entreprise

    - - - - - - - - -

    -
    - - - - - - - -
    - - \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_delete.dtml b/dtml/entreprises/entreprise_contact_delete.dtml deleted file mode 100644 index df73659b..00000000 --- a/dtml/entreprises/entreprise_contact_delete.dtml +++ /dev/null @@ -1,27 +0,0 @@ - - - - -

    Suppression du contact

    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_edit.dtml b/dtml/entreprises/entreprise_contact_edit.dtml deleted file mode 100644 index 5fe437f1..00000000 --- a/dtml/entreprises/entreprise_contact_edit.dtml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - -

    - - -

    Contact avec entreprise

    - - - - - - - -

    -
    - - - -

    Supprimer ce contact

    -
    - - - - - - -
    - - - \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_list.dtml b/dtml/entreprises/entreprise_contact_list.dtml deleted file mode 100644 index cffeb351..00000000 --- a/dtml/entreprises/entreprise_contact_list.dtml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -

    Listes des contacts avec l'entreprise

    - - -

    Listes des contacts

    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    DateObjetEntrepriseEtudiantDescription
    "> - - - - - "> - - -
    Aucun contact !
    - - - -

    -">nouveau "contact" -

    -
    -
    - - \ No newline at end of file diff --git a/dtml/entreprises/entreprise_correspondant_create.dtml b/dtml/entreprises/entreprise_correspondant_create.dtml deleted file mode 100644 index 2ffdbba4..00000000 --- a/dtml/entreprises/entreprise_correspondant_create.dtml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - -

    dans l'entreprise

    - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dtml/entreprises/entreprise_correspondant_delete.dtml b/dtml/entreprises/entreprise_correspondant_delete.dtml deleted file mode 100644 index ac50550e..00000000 --- a/dtml/entreprises/entreprise_correspondant_delete.dtml +++ /dev/null @@ -1,27 +0,0 @@ - - - - -

    Suppression du correspondant

    - - - - - - - - - - - - - - - - diff --git a/dtml/entreprises/entreprise_correspondant_edit.dtml b/dtml/entreprises/entreprise_correspondant_edit.dtml deleted file mode 100644 index 2b254568..00000000 --- a/dtml/entreprises/entreprise_correspondant_edit.dtml +++ /dev/null @@ -1,41 +0,0 @@ - - - - -

    - - - - - - - - - - - - - - - diff --git a/dtml/entreprises/entreprise_correspondant_list.dtml b/dtml/entreprises/entreprise_correspondant_list.dtml deleted file mode 100644 index 085d4375..00000000 --- a/dtml/entreprises/entreprise_correspondant_list.dtml +++ /dev/null @@ -1,52 +0,0 @@ - - - - -

    Listes des correspondants dans l'entreprise

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NomEntrepriseFonctionTéléphoneMailNote
    "> - - - - - - / - / - - "> -
    ">
    -
    ">modifier ">supprimer
    Aucun correspondant dans cette entreprise !
    - -

    - - - diff --git a/dtml/entreprises/entreprise_delete.dtml b/dtml/entreprises/entreprise_delete.dtml deleted file mode 100644 index 7f7db5f8..00000000 --- a/dtml/entreprises/entreprise_delete.dtml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - -

    Suppression de l'entreprise

    - -

    Attention: supression définitive de l'entreprise, de ses correspondants et contacts. -

    - - - - -

    Correspondants dans l'entreprise (seront supprimés):

    -
      - - -
    • - - () -
    • -
      -
      -
    -
    - - - -

    Contacts avec l'entreprise (seront supprimés):

    -
      - - -
    • - () -
    • -
      -
      -
    -
    - - - - - - - - - - - - - - - diff --git a/dtml/entreprises/index_html.dtml b/dtml/entreprises/index_html.dtml deleted file mode 100644 index 1e2fbc25..00000000 --- a/dtml/entreprises/index_html.dtml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - -

    - - -

    -Attention: version test préliminaire. Signaler les problèmes à Emmanuel -

    -
    - - - - - - - - - - - - - - -

    valeur invalide pour 'sort_type' !

    -
    - - - - - - - - - - - - - - - - - - - -
    nomvilleétudiant     Tri par:
    - - - - - - -
    - - - -
    EntreprisesRésultats - sur -
    - - - - - - - - - - - - - - - - - - - - - - -
    &start=">
     
    - - -

    Aucune entreprise !

    - - - -

    -

    - "> - - - - - - page précédente     - - - - - - page suivante - - - -   Résultats par page : - -
    - -

    - From 1476df8ecf0d6f352b3b2ff01c3cc0a4b3cb84b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 16 Jan 2021 11:49:02 +0100 Subject: [PATCH 012/104] Fix comments on published methods --- ZAbsences.py | 1 + ZNotes.py | 2 ++ ZScoDoc.py | 1 + sco_edit_ue.py | 2 +- sco_formsemestre.py | 2 ++ 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ZAbsences.py b/ZAbsences.py index 33e09b6b..e10afce5 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -380,6 +380,7 @@ class ZAbsences( security.declareProtected(ScoView, "CountAbsJust") def CountAbsJust(self, etudid, debut, fin, matin=None, moduleimpl_id=None): + "Count just. abs" if matin != None: matin = _toboolean(matin) ismatin = " AND A.MATIN = %(matin)s " diff --git a/ZNotes.py b/ZNotes.py index d02d384e..7530d7cd 100644 --- a/ZNotes.py +++ b/ZNotes.py @@ -1068,6 +1068,8 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl ) return formsemestre_id + security.declareProtected(ScoView, "formsemestre_list") + def formsemestre_list( self, format=None, diff --git a/ZScoDoc.py b/ZScoDoc.py index 76827bd8..08aaab66 100644 --- a/ZScoDoc.py +++ b/ZScoDoc.py @@ -592,6 +592,7 @@ E. Viennet (Université Paris 13).

    security.declareProtected("View", "standard_html_footer") def standard_html_footer(self, REQUEST=None): + """Le pied de page HTML de la page d'accueil.""" return """

    Problèmes et suggestions sur le logiciel: %s

    diff --git a/sco_edit_ue.py b/sco_edit_ue.py index 5e9f16b1..b29c613c 100644 --- a/sco_edit_ue.py +++ b/sco_edit_ue.py @@ -779,7 +779,7 @@ def edit_ue_set_code_apogee(context, id=None, value=None, REQUEST=None): # ---- Table recap formation def formation_table_recap(context, formation_id, format="html", REQUEST=None): - """""" + """Table recapitulant formation.""" F = context.formation_list(args={"formation_id": formation_id}) if not F: raise ScoValueError("invalid formation_id") diff --git a/sco_formsemestre.py b/sco_formsemestre.py index e4c574f8..55c88ece 100644 --- a/sco_formsemestre.py +++ b/sco_formsemestre.py @@ -213,6 +213,8 @@ def etapes_apo_str(etapes): def do_formsemestre_edit(context, sem, cnx=None, **kw): + """Apply modifications to formsemestre. + Update etapes and resps. Invalidate cache.""" if not cnx: cnx = context.GetDBConnexion() From 76d7a21ec7fbdcb0f2b4c4d12ced34a6b4d3c81b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 16 Jan 2021 14:02:18 +0100 Subject: [PATCH 013/104] Begins separation of user management functions --- ZScoDoc.py | 4 ++-- ZScoUsers.py | 27 +++----------------------- sco_users.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 sco_users.py diff --git a/ZScoDoc.py b/ZScoDoc.py index 08aaab66..4b567877 100644 --- a/ZScoDoc.py +++ b/ZScoDoc.py @@ -59,7 +59,7 @@ except: from sco_utils import * from notes_log import log import sco_find_etud -from ZScoUsers import pwdFascistCheck +import sco_users class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): @@ -201,7 +201,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") log("trying to change admin password") # 1-- check strong password - if pwdFascistCheck(password) != None: + if not sco_users.is_valid_password(password): log("refusing weak password") return REQUEST.RESPONSE.redirect( "change_admin_user_form?message=Mot%20de%20passe%20trop%20simple,%20recommencez" diff --git a/ZScoUsers.py b/ZScoUsers.py index 0e03a39e..9e9dd924 100644 --- a/ZScoUsers.py +++ b/ZScoUsers.py @@ -47,24 +47,7 @@ from TrivialFormulator import TrivialFormulator, TF from gen_tables import GenTable import scolars import sco_cache - -# ----------------- password checking -import cracklib - - -def pwdFascistCheck(cleartxt): - "returns None if OK" - if ( - hasattr(CONFIG, "MIN_PASSWORD_LENGTH") - and CONFIG.MIN_PASSWORD_LENGTH > 0 - and len(cleartxt) < CONFIG.MIN_PASSWORD_LENGTH - ): - return True # invalid - try: - x = cracklib.FascistCheck(cleartxt) - return None - except ValueError as e: - return str(e) +import sco_users # --------------- @@ -358,10 +341,6 @@ class ZScoUsers( else: return False - def _is_valid_passwd(self, passwd): - "check if passwd is secure enough" - return not pwdFascistCheck(passwd) - def do_change_password(self, user_name, password): user = self._user_list(args={"user_name": user_name}) assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user) @@ -407,7 +386,7 @@ class ZScoUsers( % user_name ) else: - if not self._is_valid_passwd(password): + if not sco_users.is_valid_password(password): H.append( """

    ce mot de passe n\'est pas assez compliqué !
    (oui, il faut un mot de passe vraiment compliqué !)

    Recommencer

    @@ -890,7 +869,7 @@ class ZScoUsers( """Les deux mots de passes ne correspondent pas !""" ) return "\n".join(H) + msg + "\n" + tf[1] + F - if not self._is_valid_passwd(vals["passwd"]): + if not sco_users.is_valid_password(vals["passwd"]): msg = tf_error_message( """Mot de passe trop simple, recommencez !""" ) diff --git a/sco_users.py b/sco_users.py new file mode 100644 index 00000000..703d6b18 --- /dev/null +++ b/sco_users.py @@ -0,0 +1,53 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Fonctions sur les utilisateurs +""" + +# Anciennement dans ZScoUsers.py, séparé pour migration + +import cracklib # pylint: disable=import-error + +import sco_utils as scu +from sco_utils import CONFIG, SCO_ENCODING + + +def is_valid_password(cleartxt): + """Check password. + returns True if OK. + """ + if ( + hasattr(CONFIG, "MIN_PASSWORD_LENGTH") + and CONFIG.MIN_PASSWORD_LENGTH > 0 + and len(cleartxt) < CONFIG.MIN_PASSWORD_LENGTH + ): + return False # invalid: too short + try: + _ = cracklib.FascistCheck(cleartxt) + return True + except ValueError: + return False From d6fa3e57de99c7f0768efd2c3eb0db78ba2d19fb Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 17 Jan 2021 09:37:11 +0100 Subject: [PATCH 014/104] Refactoring: remove useless public zmethods --- ZAbsences.py | 94 +------ ZEntreprises.py | 643 +++++++++++---------------------------------- ZScolar.py | 2 +- sco_abs.py | 40 ++- sco_abs_views.py | 41 ++- sco_entreprises.py | 326 +++++++++++++++++++++++ sco_groups_view.py | 15 +- 7 files changed, 570 insertions(+), 591 deletions(-) create mode 100644 sco_entreprises.py diff --git a/ZAbsences.py b/ZAbsences.py index e10afce5..ab194f5a 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -666,21 +666,6 @@ class ZAbsences( if self.CountAbs(etudid, jour, jour, matin, moduleimpl_id) == 0: self._AddAbsence(etudid, jour, matin, 0, REQUEST, "", moduleimpl_id) - # - security.declareProtected(ScoView, "CalSelectWeek") - - def CalSelectWeek(self, year=None, REQUEST=None): - "display calendar allowing week selection" - if not year: - year = scu.AnneeScolaire(REQUEST) - sems = sco_formsemestre.do_formsemestre_list(self) - if not sems: - js = "" - else: - js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' - C = sco_abs.YearTable(self, int(year), dayattributes=js) - return C - # --- Misc tools.... ------------------ def _isFarFutur(self, jour): @@ -691,65 +676,6 @@ class ZAbsences( # 6 mois ~ 182 jours: return j - datetime.date.today() > datetime.timedelta(182) - security.declareProtected(ScoView, "is_work_saturday") - - def is_work_saturday(self): - "Vrai si le samedi est travaillé" - return int(self.get_preference("work_saturday")) - - security.declareProtected(ScoView, "day_names") - - def day_names(self): - """Returns week day names. - If work_saturday property is set, include saturday - """ - if self.is_work_saturday(): - return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"] - else: - return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"] - - security.declareProtected(ScoView, "ListMondays") - - def ListMondays(self, year=None, REQUEST=None): - """return list of mondays (ISO dates), from september to june""" - if not year: - year = scu.AnneeScolaire(REQUEST) - d = ddmmyyyy("1/9/%d" % year, work_saturday=self.is_work_saturday()) - while d.weekday != 0: - d = d.next() - end = ddmmyyyy("1/7/%d" % (year + 1), work_saturday=self.is_work_saturday()) - L = [d] - while d < end: - d = d.next(days=7) - L.append(d) - return map(lambda x: x.ISO(), L) - - security.declareProtected(ScoView, "NextISODay") - - def NextISODay(self, date): - "return date after date" - d = ddmmyyyy(date, fmt="iso", work_saturday=self.is_work_saturday()) - return d.next().ISO() - - security.declareProtected(ScoView, "DateRangeISO") - - def DateRangeISO(self, date_beg, date_end, workable=1): - """returns list of dates in [date_beg,date_end] - workable = 1 => keeps only workable days""" - if not date_beg: - raise ScoValueError("pas de date spécifiée !") - if not date_end: - date_end = date_beg - r = [] - cur = ddmmyyyy(date_beg, work_saturday=self.is_work_saturday()) - end = ddmmyyyy(date_end, work_saturday=self.is_work_saturday()) - while cur <= end: - if (not workable) or cur.iswork(): - r.append(cur) - cur = cur.next() - - return map(lambda x: x.ISO(), r) - # ------------ HTML Interfaces security.declareProtected(ScoAbsChange, "SignaleAbsenceGrHebdo") @@ -801,8 +727,8 @@ class ZAbsences( # calcule dates jours de cette semaine # liste de dates iso "yyyy-mm-dd" datessem = [notesdb.DateDMYtoISO(datelundi)] - for _ in self.day_names()[1:]: - datessem.append(self.NextISODay(datessem[-1])) + for _ in sco_abs.day_names(self)[1:]: + datessem.append(sco_abs.next_iso_day(self, datessem[-1])) # if groups_infos.tous_les_etuds_du_sem: gr_tit = "en" @@ -944,12 +870,12 @@ class ZAbsences( sem = sco_formsemestre.do_formsemestre_list( self, {"formsemestre_id": formsemestre_id} )[0] - - jourdebut = ddmmyyyy(datedebut, work_saturday=self.is_work_saturday()) - jourfin = ddmmyyyy(datefin, work_saturday=self.is_work_saturday()) + work_saturday = sco_abs.is_work_saturday(self) + jourdebut = ddmmyyyy(datedebut, work_saturday=work_saturday) + jourfin = ddmmyyyy(datefin, work_saturday=work_saturday) today = ddmmyyyy( time.strftime("%d/%m/%Y", time.localtime()), - work_saturday=self.is_work_saturday(), + work_saturday=work_saturday, ) today.next() if jourfin > today: # ne propose jamais les semaines dans le futur @@ -964,7 +890,7 @@ class ZAbsences( ) # calcule dates dates = [] # ddmmyyyy instances - d = ddmmyyyy(datedebut, work_saturday=self.is_work_saturday()) + d = ddmmyyyy(datedebut, work_saturday=work_saturday) while d <= jourfin: dates.append(d) d = d.next(7) # avance d'une semaine @@ -982,7 +908,7 @@ class ZAbsences( url_link_semaines += "&moduleimpl_id=" + moduleimpl_id # dates = [x.ISO() for x in dates] - dayname = self.day_names()[jourdebut.weekday] + dayname = sco_abs.day_names(self)[jourdebut.weekday] if groups_infos.tous_les_etuds_du_sem: gr_tit = "en" @@ -1119,7 +1045,7 @@ class ZAbsences( odates = [datetime.date(*[int(x) for x in d.split("-")]) for d in dates] # Titres colonnes noms_jours = [] # eg [ "Lundi", "mardi", "Samedi", ... ] - jn = self.day_names() + jn = sco_abs.day_names(self) for d in odates: idx_jour = d.weekday() noms_jours.append(jn[idx_jour]) @@ -1844,7 +1770,7 @@ ou entrez une date pour visualiser les absents un jour donné : # 1-- ajout des absences (et justifs) datedebut = billet["abs_begin"].strftime("%d/%m/%Y") datefin = billet["abs_end"].strftime("%d/%m/%Y") - dates = self.DateRangeISO(datedebut, datefin) + dates = sco_abs.DateRangeISO(self, datedebut, datefin) # commence après-midi ? if dates and billet["abs_begin"].hour > 11: self._AddAbsence( diff --git a/ZEntreprises.py b/ZEntreprises.py index 53d287a0..56f16eaa 100644 --- a/ZEntreprises.py +++ b/ZEntreprises.py @@ -43,7 +43,6 @@ from sco_permissions import ScoEntrepriseView, ScoEntrepriseChange # --------------- -from notesdb import * from notes_log import log from scolog import logdb from sco_utils import SCO_ENCODING @@ -53,169 +52,17 @@ import VERSION from gen_tables import GenTable from TrivialFormulator import TrivialFormulator, TF import scolars +import sco_entreprises -def _format_nom(nom): - "formatte nom (filtre en entree db) d'une entreprise" - if not nom: - return nom - nom = nom.decode(SCO_ENCODING) - return (nom[0].upper() + nom[1:]).encode(SCO_ENCODING) +def entreprise_header(context, REQUEST=None, page_title=""): + "common header for all Entreprises pages" + return context.sco_header(REQUEST, container=context, page_title=page_title) -class EntreprisesEditor(EditableTable): - def delete(self, cnx, oid): - "delete correspondants and contacts, then self" - # first, delete all correspondants and contacts - cursor = cnx.cursor(cursor_factory=ScoDocCursor) - cursor.execute( - "delete from entreprise_contact where entreprise_id=%(entreprise_id)s", - {"entreprise_id": oid}, - ) - cursor.execute( - "delete from entreprise_correspondant where entreprise_id=%(entreprise_id)s", - {"entreprise_id": oid}, - ) - cnx.commit() - EditableTable.delete(self, cnx, oid) - - def list( - self, - cnx, - args={}, - operator="and", - test="=", - sortkey=None, - sort_on_contact=False, - ZEntrepriseInstance=None, - limit="", - offset="", - ): - # list, then sort on date of last contact - R = EditableTable.list( - self, - cnx, - args=args, - operator=operator, - test=test, - sortkey=sortkey, - limit=limit, - offset=offset, - ) - if sort_on_contact: - for r in R: - c = ZEntrepriseInstance.do_entreprise_contact_list( - args={"entreprise_id": r["entreprise_id"]}, disable_formatting=True - ) - if c: - r["date"] = max([x["date"] or datetime.date.min for x in c]) - else: - r["date"] = datetime.date.min - # sort - R.sort(lambda r1, r2: cmp(r2["date"], r1["date"])) - for r in R: - r["date"] = DateISOtoDMY(r["date"]) - return R - - def list_by_etud( - self, cnx, args={}, sort_on_contact=False, disable_formatting=False - ): - "cherche rentreprise ayant eu contact avec etudiant" - cursor = cnx.cursor(cursor_factory=ScoDocCursor) - cursor.execute( - "select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom", - args, - ) - _, res = [x[0] for x in cursor.description], cursor.dictfetchall() - R = [] - for r in res: - r["etud_prenom"] = r["etud_prenom"] or "" - d = {} - for key in r: - v = r[key] - # format value - if not disable_formatting and self.output_formators.has_key(key): - v = self.output_formators[key](v) - d[key] = v - R.append(d) - # sort - if sort_on_contact: - R.sort( - lambda r1, r2: cmp( - r2["date"] or datetime.date.min, r1["date"] or datetime.date.min - ) - ) - for r in R: - r["date"] = DateISOtoDMY(r["date"] or datetime.date.min) - return R - - -_entreprisesEditor = EntreprisesEditor( - "entreprises", - "entreprise_id", - ( - "entreprise_id", - "nom", - "adresse", - "ville", - "codepostal", - "pays", - "contact_origine", - "secteur", - "privee", - "localisation", - "qualite_relation", - "plus10salaries", - "note", - "date_creation", - ), - sortkey="nom", - input_formators={"nom": _format_nom}, -) - -# ----------- Correspondants -_entreprise_correspEditor = EditableTable( - "entreprise_correspondant", - "entreprise_corresp_id", - ( - "entreprise_corresp_id", - "entreprise_id", - "civilite", - "nom", - "prenom", - "fonction", - "phone1", - "phone2", - "mobile", - "fax", - "mail1", - "mail2", - "note", - ), - sortkey="nom", -) - - -# ----------- Contacts -_entreprise_contactEditor = EditableTable( - "entreprise_contact", - "entreprise_contact_id", - ( - "entreprise_contact_id", - "date", - "type_contact", - "entreprise_id", - "entreprise_corresp_id", - "etudid", - "description", - "enseignant", - ), - sortkey="date", - output_formators={"date": DateISOtoDMY}, - input_formators={"date": DateDMYtoISO}, -) - -# --------------- +def entreprise_footer(context, REQUEST): + "common entreprise footer" + return context.sco_footer(REQUEST) class ZEntreprises( @@ -251,42 +98,22 @@ class ZEntreprises( self.id = id self.title = title - security.declareProtected(ScoEntrepriseView, "entreprise_header") - - def entreprise_header(self, REQUEST=None, page_title=""): - "common header for all Entreprises pages" - authuser = REQUEST.AUTHENTICATED_USER - # _read_only is used to modify pages properties (links, buttons) - # Python methods (do_xxx in this class) are also protected individualy) - if authuser.has_permission(ScoEntrepriseChange, self): - REQUEST.set("_read_only", False) - else: - REQUEST.set("_read_only", True) - return self.sco_header(REQUEST, container=self, page_title=page_title) - - security.declareProtected(ScoEntrepriseView, "entreprise_footer") - - def entreprise_footer(self, REQUEST): - "common entreprise footer" - return self.sco_footer(REQUEST) - security.declareProtected(ScoEntrepriseView, "sidebar") def sidebar(self, REQUEST): "barre gauche (overide std sco sidebar)" # rewritten from legacy DTML code - context = self - params = {"ScoURL": context.ScoURL()} - + # XXX rare cas restant d'utilisation de l'acquisition Zope2: à revoir + params = {"ScoURL": self.ScoURL()} H = [ """ """) return "".join(H) @@ -352,12 +181,13 @@ class ZEntreprises( offset = int(offset or 0) if etud_nom: - entreprises = self.do_entreprise_list_by_etud( - args=REQUEST.form, sort_on_contact=True + entreprises = sco_entreprises.do_entreprise_list_by_etud( + self, args=REQUEST.form, sort_on_contact=True ) table_navigation = "" else: - entreprises = self.do_entreprise_list( + entreprises = sco_entreprises.do_entreprise_list( + self, args=REQUEST.form, test="~*", sort_on_contact=True, @@ -390,15 +220,15 @@ class ZEntreprises( # Ajout des liens sur la table: for e in entreprises: e["_nom_target"] = "entreprise_edit?entreprise_id=%(entreprise_id)s" % e - e["correspondants"] = self.do_entreprise_correspondant_list( - args={"entreprise_id": e["entreprise_id"]} + e["correspondants"] = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_id": e["entreprise_id"]} ) e["nbcorr"] = "%d corr." % len(e["correspondants"]) e["_nbcorr_target"] = ( "entreprise_correspondant_list?entreprise_id=%(entreprise_id)s" % e ) - e["contacts"] = self.do_entreprise_contact_list( - args={"entreprise_id": e["entreprise_id"]} + e["contacts"] = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_id": e["entreprise_id"]} ) e["nbcontact"] = "%d contacts." % len(e["contacts"]) e["_nbcontact_target"] = ( @@ -430,12 +260,14 @@ class ZEntreprises( return tab.make_page(self, format=format, REQUEST=REQUEST) else: H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header( + self, REQUEST=REQUEST, page_title="Suivi entreprises" + ), """

    Suivi relations entreprises

    """, """
    """, tab.html(), """
    """, - self.entreprise_footer(REQUEST), + entreprise_footer(self, REQUEST), ] return "\n".join(H) @@ -443,17 +275,21 @@ class ZEntreprises( def entreprise_contact_list(self, entreprise_id=None, format="html", REQUEST=None): """Liste des contacts de l'entreprise""" - H = [self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises")] + H = [entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises")] if entreprise_id: - E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] - C = self.do_entreprise_contact_list(args={"entreprise_id": entreprise_id}) + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] + C = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_id": entreprise_id} + ) H.append( """

    Listes des contacts avec l'entreprise %(nom)s

    """ % E ) else: - C = self.do_entreprise_contact_list(args={}) + C = sco_entreprises.do_entreprise_contact_list(self, args={}) H.append( """

    Listes des contacts

    """ @@ -465,8 +301,8 @@ class ZEntreprises( REQUEST.URL1, c["entreprise_contact_id"], ) - c["entreprise"] = self.do_entreprise_list( - args={"entreprise_id": c["entreprise_id"]} + c["entreprise"] = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": c["entreprise_id"]} )[0] if c["etudid"]: c["etud"] = self.getEtudInfo(etudid=c["etudid"], filled=1)[0] @@ -502,7 +338,7 @@ class ZEntreprises( H.append(tab.html()) - if not REQUEST["_read_only"]: # portage DTML, à modifier + if REQUEST.AUTHENTICATED_USER.has_permission(ScoEntrepriseChange, self): if entreprise_id: H.append( """

    nouveau "contact"

    @@ -510,7 +346,7 @@ class ZEntreprises( % E ) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_list") @@ -522,23 +358,28 @@ class ZEntreprises( REQUEST=None, ): """Liste des correspondants de l'entreprise""" - E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Listes des correspondants dans l'entreprise %(nom)s

    """ % E, ] - correspondants = self.do_entreprise_correspondant_list( - args={"entreprise_id": entreprise_id} + correspondants = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_id": entreprise_id} ) for c in correspondants: c["nomprenom"] = c["nom"].upper() + " " + c["nom"].capitalize() - c["_nomprenom_target"] = ( - "%s/entreprise_correspondant_edit?entreprise_corresp_id=%s" - % (REQUEST.URL1, c["entreprise_corresp_id"]), + c[ + "_nomprenom_target" + ] = "%s/entreprise_correspondant_edit?entreprise_corresp_id=%s" % ( + REQUEST.URL1, + c["entreprise_corresp_id"], ) + c["nom_entreprise"] = E["nom"] l = [] if c["phone1"]: @@ -559,11 +400,11 @@ class ZEntreprises( ] ) c["modifier"] = ( - 'modifier' + 'modifier' % c["entreprise_corresp_id"] ) c["supprimer"] = ( - 'supprimer' + 'supprimer' % c["entreprise_corresp_id"] ) tab = GenTable( @@ -604,34 +445,36 @@ class ZEntreprises( H.append(tab.html()) - if not REQUEST["_read_only"]: # portage DTML, à modifier + if REQUEST.AUTHENTICATED_USER.has_permission(ScoEntrepriseChange, self): H.append( """

    Ajouter un correspondant dans l'entreprise %(nom)s

    """ % E ) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseView, "entreprise_contact_edit") def entreprise_contact_edit(self, entreprise_contact_id, REQUEST=None): """Form edit contact""" - c = self.do_entreprise_contact_list( - args={"entreprise_contact_id": entreprise_contact_id} + c = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_contact_id": entreprise_contact_id} )[0] link_create_corr = ( 'créer un nouveau correspondant' % (REQUEST.URL1, c["entreprise_id"]) ) - E = self.do_entreprise_list(args={"entreprise_id": c["entreprise_id"]})[0] - correspondants = self.do_entreprise_correspondant_listnames( - args={"entreprise_id": c["entreprise_id"]} + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": c["entreprise_id"]} + )[0] + correspondants = sco_entreprises.do_entreprise_correspondant_listnames( + self, args={"entreprise_id": c["entreprise_id"]} ) + [("inconnu", "")] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Suivi entreprises

    Contact avec entreprise %(nom)s

    """ % E, @@ -708,12 +551,14 @@ class ZEntreprises( cancelbutton="Annuler", initvalues=c, submitlabel="Modifier les valeurs", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) - if not REQUEST["_read_only"]: # portage DTML, à modifier + if REQUEST.AUTHENTICATED_USER.has_permission(ScoEntrepriseChange, self): H.append( """

    Supprimer ce contact

    """ % entreprise_contact_id @@ -721,30 +566,31 @@ class ZEntreprises( elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - etudok = self.do_entreprise_check_etudiant(tf[2]["etudiant"]) + etudok = sco_entreprises.do_entreprise_check_etudiant( + self, tf[2]["etudiant"] + ) if etudok[0] == 0: H.append("""

    %s

    """ % etudok[1]) else: tf[2].update({"etudid": etudok[1]}) - self.do_entreprise_contact_edit(tf[2]) + sco_entreprises.do_entreprise_contact_edit(self, tf[2]) REQUEST.RESPONSE.redirect( REQUEST.URL1 + "/entreprise_contact_list?entreprise_id=" + str(c["entreprise_id"]) ) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_edit") def entreprise_correspondant_edit(self, entreprise_corresp_id, REQUEST=None): """Form édition d'un correspondant""" - # F -> c - c = self.do_entreprise_correspondant_list( - args={"entreprise_corresp_id": entreprise_corresp_id} + c = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_corresp_id": entreprise_corresp_id} )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Édition contact entreprise

    """, ] tf = TrivialFormulator( @@ -829,7 +675,9 @@ class ZEntreprises( cancelbutton="Annuler", initvalues=c, submitlabel="Modifier les valeurs", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) @@ -839,21 +687,23 @@ class ZEntreprises( % (REQUEST.URL1, c["entreprise_id"]) ) else: - self.do_entreprise_correspondant_edit(tf[2]) + sco_entreprises.do_entreprise_correspondant_edit(self, tf[2]) REQUEST.RESPONSE.redirect( "%s/entreprise_correspondant_list?entreprise_id=%s" % (REQUEST.URL1, c["entreprise_id"]) ) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseChange, "entreprise_contact_create") def entreprise_contact_create(self, entreprise_id, REQUEST=None): """Form création contact""" - E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] - correspondants = self.do_entreprise_correspondant_listnames( - args={"entreprise_id": entreprise_id} + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] + correspondants = sco_entreprises.do_entreprise_correspondant_listnames( + self, args={"entreprise_id": entreprise_id} ) if not correspondants: correspondants = [("inconnu", "")] @@ -863,7 +713,7 @@ class ZEntreprises( % (REQUEST.URL1, entreprise_id) ) H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Nouveau "contact" avec l'entreprise %(nom)s

    """ % E, ] @@ -934,32 +784,36 @@ class ZEntreprises( ), cancelbutton="Annuler", submitlabel="Ajouter ce contact", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - etudok = self.do_entreprise_check_etudiant(tf[2]["etudiant"]) + etudok = sco_entreprises.do_entreprise_check_etudiant( + self, tf[2]["etudiant"] + ) if etudok[0] == 0: H.append("""

    %s

    """ % etudok[1]) else: tf[2].update({"etudid": etudok[1]}) - self.do_entreprise_contact_create(tf[2]) + sco_entreprises.do_entreprise_contact_create(self, tf[2]) REQUEST.RESPONSE.redirect(REQUEST.URL1) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseChange, "entreprise_contact_delete") def entreprise_contact_delete(self, entreprise_contact_id, REQUEST=None): """Form delete contact""" - c = self.do_entreprise_contact_list( - args={"entreprise_contact_id": entreprise_contact_id} + c = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_contact_id": entreprise_contact_id} )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Suppression du contact

    """, ] tf = TrivialFormulator( @@ -969,25 +823,31 @@ class ZEntreprises( initvalues=c, submitlabel="Confirmer la suppression", cancelbutton="Annuler", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - self.do_entreprise_contact_delete(c["entreprise_contact_id"]) + sco_entreprises.do_entreprise_contact_delete( + self, c["entreprise_contact_id"] + ) REQUEST.RESPONSE.redirect(REQUEST.URL1) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_create") def entreprise_correspondant_create(self, entreprise_id, REQUEST=None): """Form création correspondant""" - E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Nouveau correspondant l'entreprise %(nom)s

    """ % E, ] @@ -1070,27 +930,29 @@ class ZEntreprises( ), cancelbutton="Annuler", submitlabel="Ajouter ce correspondant", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - self.do_entreprise_correspondant_create(tf[2]) + sco_entreprises.do_entreprise_correspondant_create(self, tf[2]) REQUEST.RESPONSE.redirect(REQUEST.URL1) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_delete") def entreprise_correspondant_delete(self, entreprise_corresp_id, REQUEST=None): """Form delete correspondant""" - c = self.do_entreprise_correspondant_list( - args={"entreprise_corresp_id": entreprise_corresp_id} + c = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_corresp_id": entreprise_corresp_id} )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Suppression du correspondant %(nom)s %(prenom)s

    """ % c, ] tf = TrivialFormulator( @@ -1100,32 +962,38 @@ class ZEntreprises( initvalues=c, submitlabel="Confirmer la suppression", cancelbutton="Annuler", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - self.do_entreprise_correspondant_delete(c["entreprise_corresp_id"]) + sco_entreprises.do_entreprise_correspondant_delete( + self, c["entreprise_corresp_id"] + ) REQUEST.RESPONSE.redirect(REQUEST.URL1) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) security.declareProtected(ScoEntrepriseChange, "entreprise_delete") def entreprise_delete(self, entreprise_id, REQUEST=None): """Form delete entreprise""" - E = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + E = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] H = [ - self.entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"), + entreprise_header(self, REQUEST=REQUEST, page_title="Suivi entreprises"), """

    Suppression de l'entreprise %(nom)s

    Attention: supression définitive de l'entreprise, de ses correspondants et contacts.

    """ % E, ] - Cl = self.do_entreprise_correspondant_list( - args={"entreprise_id": entreprise_id} + Cl = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_id": entreprise_id} ) if Cl: H.append( @@ -1135,7 +1003,9 @@ class ZEntreprises( H.append("""
  • %(nom)s %(prenom)s (%(fonction)s)
  • """ % c) H.append("""""") - Cts = self.do_entreprise_contact_list(args={"entreprise_id": entreprise_id}) + Cts = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_id": entreprise_id} + ) if Cts: H.append( """

    Contacts avec l'entreprise qui seront supprimés:

      """ @@ -1150,233 +1020,27 @@ class ZEntreprises( initvalues=E, submitlabel="Confirmer la suppression", cancelbutton="Annuler", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: H.append(tf[1]) elif tf[0] == -1: REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - self.do_entreprise_delete(E["entreprise_id"]) + sco_entreprises.do_entreprise_delete(self, E["entreprise_id"]) REQUEST.RESPONSE.redirect(REQUEST.URL1) - H.append(self.entreprise_footer(REQUEST)) + H.append(entreprise_footer(self, REQUEST)) return "\n".join(H) - # -------------------------------------------------------------------- - # - # Entreprises : Actions - # - # -------------------------------------------------------------------- - security.declareProtected(ScoEntrepriseChange, "do_entreprise_create") - - def do_entreprise_create(self, args): - "entreprise_create" - cnx = self.GetDBConnexion() - r = _entreprisesEditor.create(cnx, args) - return r - - security.declareProtected(ScoEntrepriseChange, "do_entreprise_delete") - - def do_entreprise_delete(self, oid): - "entreprise_delete" - cnx = self.GetDBConnexion() - _entreprisesEditor.delete(cnx, oid) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_list") - - def do_entreprise_list(self, **kw): - "entreprise_list" - cnx = self.GetDBConnexion() - kw["ZEntrepriseInstance"] = self - return _entreprisesEditor.list(cnx, **kw) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_list_by_etud") - - def do_entreprise_list_by_etud(self, **kw): - "entreprise_list_by_etud" - cnx = self.GetDBConnexion() - return _entreprisesEditor.list_by_etud(cnx, **kw) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_edit") - - def do_entreprise_edit(self, *args, **kw): - "entreprise_edit" - cnx = self.GetDBConnexion() - _entreprisesEditor.edit(cnx, *args, **kw) - - security.declareProtected(ScoEntrepriseChange, "do_entreprise_correspondant_create") - - def do_entreprise_correspondant_create(self, args): - "entreprise_correspondant_create" - cnx = self.GetDBConnexion() - r = _entreprise_correspEditor.create(cnx, args) - return r - - security.declareProtected(ScoEntrepriseChange, "do_entreprise_correspondant_delete") - - def do_entreprise_correspondant_delete(self, oid): - "entreprise_correspondant_delete" - cnx = self.GetDBConnexion() - _entreprise_correspEditor.delete(cnx, oid) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_correspondant_list") - - def do_entreprise_correspondant_list(self, **kw): - "entreprise_correspondant_list" - cnx = self.GetDBConnexion() - return _entreprise_correspEditor.list(cnx, **kw) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_correspondant_edit") - - def do_entreprise_correspondant_edit(self, *args, **kw): - "entreprise_correspondant_edit" - cnx = self.GetDBConnexion() - _entreprise_correspEditor.edit(cnx, *args, **kw) - - security.declareProtected( - ScoEntrepriseView, "do_entreprise_correspondant_listnames" - ) - - def do_entreprise_correspondant_listnames(self, args={}): - "-> liste des noms des correspondants (pour affichage menu)" - C = self.do_entreprise_correspondant_list(args=args) - return [ - (x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C - ] - - security.declareProtected(ScoEntrepriseChange, "do_entreprise_contact_create") - - def do_entreprise_contact_create(self, args): - "entreprise_contact_create" - cnx = self.GetDBConnexion() - r = _entreprise_contactEditor.create(cnx, args) - return r - - security.declareProtected(ScoEntrepriseChange, "do_entreprise_contact_delete") - - def do_entreprise_contact_delete(self, oid): - "entreprise_contact_delete" - cnx = self.GetDBConnexion() - _entreprise_contactEditor.delete(cnx, oid) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_contact_list") - - def do_entreprise_contact_list(self, **kw): - "entreprise_contact_list" - cnx = self.GetDBConnexion() - return _entreprise_contactEditor.list(cnx, **kw) - - security.declareProtected(ScoEntrepriseView, "do_entreprise_contact_edit") - - def do_entreprise_contact_edit(self, *args, **kw): - "entreprise_contact_edit" - cnx = self.GetDBConnexion() - _entreprise_contactEditor.edit(cnx, *args, **kw) - - # - security.declareProtected(ScoEntrepriseView, "do_entreprise_check_etudiant") - - def do_entreprise_check_etudiant(self, etudiant): - """Si etudiant est vide, ou un ETUDID valide, ou un nom unique, - retourne (1, ETUDID). - Sinon, retourne (0, 'message explicatif') - """ - etudiant = etudiant.strip().translate( - None, "'()" - ) # suppress parens and quote from name - if not etudiant: - return 1, None - cnx = self.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ScoDocCursor) - cursor.execute( - "select etudid, nom, prenom from identite where upper(nom) ~ upper(%(etudiant)s) or etudid=%(etudiant)s", - {"etudiant": etudiant}, - ) - r = cursor.fetchall() - if len(r) < 1: - return 0, 'Aucun etudiant ne correspond à "%s"' % etudiant - elif len(r) > 10: - return ( - 0, - "%d etudiants correspondent à ce nom (utilisez le code)" - % len(r), - ) - elif len(r) > 1: - e = ['
        '] - for x in r: - e.append( - "
      • %s %s (code %s)
      • " - % (scu.strupper(x[1]), x[2] or "", x[0].strip()) - ) - e.append("
      ") - return ( - 0, - "Les étudiants suivants correspondent: préciser le nom complet ou le code\n" - + "\n".join(e), - ) - else: # une seule reponse ! - return 1, r[0][0].strip() - - security.declareProtected(ScoEntrepriseView, "do_entreprise_list_by_contact") - - def do_entreprise_list_by_contact( - self, args={}, operator="and", test="=", sortkey=None - ): - """Recherche dans entreprises, avec date de contact""" - # (fonction ad-hoc car requete sur plusieurs tables) - raise NotImplementedError - # XXXXX fonction non achevee , non testee... - - # cnx = self.GetDBConnexion() - # cursor = cnx.cursor(cursor_factory=ScoDocCursor) - # if sortkey: - # orderby = " order by " + sortkey - # else: - # orderby = "" - # vals = dictfilter(args, self.dbfields) - # # DBSelect - # what = ["*"] - # operator = " " + operator + " " - # cond = " E.entreprise_id = C.entreprise_id " - # if vals: - # cond += " where " + operator.join( - # ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None] - # ) - # cnuls = " and ".join( - # ["%s is NULL" % x for x in vals.keys() if vals[x] is None] - # ) - # if cnuls: - # cond = cond + " and " + cnuls - # else: - # cond += "" - # cursor.execute( - # "select distinct" - # + ", ".join(what) - # + " from entreprises E, entreprise_contact C " - # + cond - # + orderby, - # vals, - # ) - # titles, res = [x[0] for x in cursor.description], cursor.fetchall() - # # - # R = [] - # for r in res: - # d = {} - # for i in range(len(titles)): - # v = r[i] - # # value not formatted ! (see EditableTable.list()) - # d[titles[i]] = v - # R.append(d) - # return R - # -------- Formulaires: traductions du DTML security.declareProtected(ScoEntrepriseChange, "entreprise_create") def entreprise_create(self, REQUEST=None): """Form. création entreprise""" - context = self H = [ - self.entreprise_header(REQUEST, page_title="Création d'une entreprise"), + entreprise_header(self, REQUEST, page_title="Création d'une entreprise"), """

      Création d'une entreprise

      """, ] tf = TrivialFormulator( @@ -1451,26 +1115,29 @@ class ZEntreprises( ), cancelbutton="Annuler", submitlabel="Ajouter cette entreprise", - readonly=REQUEST["_read_only"], + readonly=not REQUEST.AUTHENTICATED_USER.has_permission( + ScoEntrepriseChange, self + ), ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.entreprise_footer(REQUEST) + return "\n".join(H) + tf[1] + entreprise_footer(self, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(REQUEST.URL1) else: - self.do_entreprise_create(tf[2]) + sco_entreprises.do_entreprise_create(self, tf[2]) return REQUEST.RESPONSE.redirect(REQUEST.URL1) security.declareProtected(ScoEntrepriseView, "entreprise_edit") def entreprise_edit(self, entreprise_id, REQUEST=None, start=1): """Form. edit entreprise""" - context = self authuser = REQUEST.AUTHENTICATED_USER readonly = not authuser.has_permission(ScoEntrepriseChange, self) - F = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + F = sco_entreprises.do_entreprise_list( + self, args={"entreprise_id": entreprise_id} + )[0] H = [ - self.entreprise_header(REQUEST, page_title="Entreprise"), + entreprise_header(self, REQUEST, page_title="Entreprise"), """

      %(nom)s

      """ % F, ] tf = TrivialFormulator( @@ -1555,11 +1222,11 @@ class ZEntreprises( if tf[0] == 0: H.append(tf[1]) - Cl = self.do_entreprise_correspondant_list( - args={"entreprise_id": F["entreprise_id"]} + Cl = sco_entreprises.do_entreprise_correspondant_list( + self, args={"entreprise_id": F["entreprise_id"]} ) - Cts = self.do_entreprise_contact_list( - args={"entreprise_id": F["entreprise_id"]} + Cts = sco_entreprises.do_entreprise_contact_list( + self, args={"entreprise_id": F["entreprise_id"]} ) if not readonly: H.append( @@ -1628,9 +1295,9 @@ class ZEntreprises( H.append("(%s)" % c["description"]) H.append("") H.append("
    ") - return "\n".join(H) + context.entreprise_footer(REQUEST) + return "\n".join(H) + entreprise_footer(self, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) else: - self.do_entreprise_edit(tf[2]) + sco_entreprises.do_entreprise_edit(self, tf[2]) return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) diff --git a/ZScolar.py b/ZScolar.py index c39d8bdc..8d430780 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -599,7 +599,7 @@ UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo ') FA.append('") diff --git a/sco_abs.py b/sco_abs.py index 8d97d949..21538e67 100644 --- a/sco_abs.py +++ b/sco_abs.py @@ -43,6 +43,11 @@ import sco_formsemestre import sco_compute_moy +def is_work_saturday(context): + "Vrai si le samedi est travaillé" + return int(context.get_preference("work_saturday")) + + def MonthNbDays(month, year): "returns nb of days in month" if month > 7: @@ -173,6 +178,39 @@ class ddmmyyyy: # d = ddmmyyyy( '21/12/99' ) +def DateRangeISO(context, date_beg, date_end, workable=1): + """returns list of dates in [date_beg,date_end] + workable = 1 => keeps only workable days""" + if not date_beg: + raise ScoValueError("pas de date spécifiée !") + if not date_end: + date_end = date_beg + r = [] + work_saturday = is_work_saturday(context) + cur = ddmmyyyy(date_beg, work_saturday=work_saturday) + end = ddmmyyyy(date_end, work_saturday=work_saturday) + while cur <= end: + if (not workable) or cur.iswork(): + r.append(cur) + cur = cur.next() + + return map(lambda x: x.ISO(), r) + + +def day_names(context): + """Returns week day names. + If work_saturday property is set, include saturday + """ + if is_work_saturday(context): + return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"] + else: + return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"] + + +def next_iso_day(context, date): + "return date after date" + d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday(context)) + return d.next().ISO() def YearTable( @@ -207,7 +245,7 @@ def YearTable( events, halfday, dayattributes, - context.is_work_saturday(), + is_work_saturday(context), pad_width=pad_width, ) ) diff --git a/sco_abs_views.py b/sco_abs_views.py index 9ede1d38..a76e225a 100644 --- a/sco_abs_views.py +++ b/sco_abs_views.py @@ -28,12 +28,16 @@ """Pages HTML gestion absences (la plupart portées du DTML) """ +import datetime from stripogram import html2text, html2safehtml + from gen_tables import GenTable -from notesdb import * -from sco_utils import * +from notesdb import DateISOtoDMY +import sco_utils as scu +from sco_exceptions import ScoValueError +from sco_permissions import ScoAbsChange from notes_log import log import sco_groups import sco_find_etud @@ -58,7 +62,7 @@ def doSignaleAbsence( etudid = etud["etudid"] description_abs = description - dates = context.DateRangeISO(datedebut, datefin) + dates = sco_abs.DateRangeISO(context, datedebut, datefin) nbadded = 0 for jour in dates: if demijournee == "2": @@ -248,7 +252,7 @@ def doJustifAbsence( etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etudid = etud["etudid"] description_abs = description - dates = context.DateRangeISO(datedebut, datefin) + dates = sco_abs.DateRangeISO(context, datedebut, datefin) nbadded = 0 for jour in dates: if demijournee == "2": @@ -371,7 +375,7 @@ def doAnnuleAbsence( etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etudid = etud["etudid"] - dates = context.DateRangeISO(datedebut, datefin) + dates = sco_abs.DateRangeISO(context, datedebut, datefin) nbadded = 0 for jour in dates: if demijournee == "2": @@ -506,7 +510,7 @@ def doAnnuleJustif( """Annulation d'une justification""" etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etudid = etud["etudid"] - dates = context.DateRangeISO(datedebut0, datefin0) + dates = sco_abs.DateRangeISO(context, datedebut0, datefin0) nbadded = 0 for jour in dates: # Attention: supprime matin et après-midi @@ -571,7 +575,7 @@ def EtatAbsences(context, REQUEST=None): """ - % (AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")), + % (scu.AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")), context.sco_footer(REQUEST), ] return "\n".join(H) @@ -610,7 +614,7 @@ def CalAbs(context, REQUEST=None): # etud implied # crude portage from 1999 DTML etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etudid = etud["etudid"] - anneescolaire = int(AnneeScolaire(REQUEST)) + anneescolaire = int(scu.AnneeScolaire(REQUEST)) datedebut = str(anneescolaire) + "-08-31" datefin = str(anneescolaire + 1) + "-07-31" nbabs = context.CountAbs(etudid=etudid, debut=datedebut, fin=datefin) @@ -693,7 +697,7 @@ def ListeAbsEtud( En format 'text': texte avec liste d'absences (pour mails). """ absjust_only = int(absjust_only) # si vrai, table absjust seule (export xls ou pdf) - datedebut = "%s-08-31" % AnneeScolaire(REQUEST) + datedebut = "%s-08-31" % scu.AnneeScolaire(REQUEST) etud = context.getEtudInfo(etudid=etudid, filled=True)[0] @@ -714,7 +718,7 @@ def ListeAbsEtud( html_class="table_leftalign", table_id="tab_absnonjust", base_url=base_url_nj, - filename="abs_" + make_filename(etud["nomprenom"]), + filename="abs_" + scu.make_filename(etud["nomprenom"]), caption="Absences non justifiées de %(nomprenom)s" % etud, preferences=context.get_preferences(), ) @@ -725,7 +729,7 @@ def ListeAbsEtud( html_class="table_leftalign", table_id="tab_absjust", base_url=base_url_j, - filename="absjust_" + make_filename(etud["nomprenom"]), + filename="absjust_" + scu.make_filename(etud["nomprenom"]), caption="Absences justifiées de %(nomprenom)s" % etud, preferences=context.get_preferences(), ) @@ -830,7 +834,7 @@ def absences_index_html(context, REQUEST=None): % REQUEST.URL0, formChoixSemestreGroupe(context), "

    ", - context.CalSelectWeek(REQUEST=REQUEST), + cal_select_week(context, REQUEST=REQUEST), """

    Sélectionner le groupe d'étudiants, puis cliquez sur une semaine pour saisir les absences de toute cette semaine.

    """, @@ -843,3 +847,16 @@ saisir les absences de toute cette semaine.

    H.append(context.sco_footer(REQUEST)) return "\n".join(H) + + +def cal_select_week(context, year=None, REQUEST=None): + "display calendar allowing week selection" + if not year: + year = scu.AnneeScolaire(REQUEST) + sems = sco_formsemestre.do_formsemestre_list(context) + if not sems: + js = "" + else: + js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' + C = sco_abs.YearTable(context, int(year), dayattributes=js) + return C diff --git a/sco_entreprises.py b/sco_entreprises.py new file mode 100644 index 00000000..fdbef5e5 --- /dev/null +++ b/sco_entreprises.py @@ -0,0 +1,326 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Fonctions sur les entreprises +""" +# codes anciens déplacés de ZEntreprise +import datetime + +import sco_utils as scu +from notesdb import ScoDocCursor, EditableTable, DateISOtoDMY, DateDMYtoISO + + +def _format_nom(nom): + "formatte nom (filtre en entree db) d'une entreprise" + if not nom: + return nom + nom = nom.decode(scu.SCO_ENCODING) + return (nom[0].upper() + nom[1:]).encode(scu.SCO_ENCODING) + + +class EntreprisesEditor(EditableTable): + def delete(self, cnx, oid): + "delete correspondants and contacts, then self" + # first, delete all correspondants and contacts + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "delete from entreprise_contact where entreprise_id=%(entreprise_id)s", + {"entreprise_id": oid}, + ) + cursor.execute( + "delete from entreprise_correspondant where entreprise_id=%(entreprise_id)s", + {"entreprise_id": oid}, + ) + cnx.commit() + EditableTable.delete(self, cnx, oid) + + def list( + self, + cnx, + args={}, + operator="and", + test="=", + sortkey=None, + sort_on_contact=False, + context=None, + limit="", + offset="", + ): + # list, then sort on date of last contact + R = EditableTable.list( + self, + cnx, + args=args, + operator=operator, + test=test, + sortkey=sortkey, + limit=limit, + offset=offset, + ) + if sort_on_contact: + for r in R: + c = do_entreprise_contact_list( + context, + args={"entreprise_id": r["entreprise_id"]}, + disable_formatting=True, + ) + if c: + r["date"] = max([x["date"] or datetime.date.min for x in c]) + else: + r["date"] = datetime.date.min + # sort + R.sort(lambda r1, r2: cmp(r2["date"], r1["date"])) + for r in R: + r["date"] = DateISOtoDMY(r["date"]) + return R + + def list_by_etud( + self, cnx, args={}, sort_on_contact=False, disable_formatting=False + ): + "cherche rentreprise ayant eu contact avec etudiant" + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom", + args, + ) + _, res = [x[0] for x in cursor.description], cursor.dictfetchall() + R = [] + for r in res: + r["etud_prenom"] = r["etud_prenom"] or "" + d = {} + for key in r: + v = r[key] + # format value + if not disable_formatting and self.output_formators.has_key(key): + v = self.output_formators[key](v) + d[key] = v + R.append(d) + # sort + if sort_on_contact: + R.sort( + lambda r1, r2: cmp( + r2["date"] or datetime.date.min, r1["date"] or datetime.date.min + ) + ) + for r in R: + r["date"] = DateISOtoDMY(r["date"] or datetime.date.min) + return R + + +_entreprisesEditor = EntreprisesEditor( + "entreprises", + "entreprise_id", + ( + "entreprise_id", + "nom", + "adresse", + "ville", + "codepostal", + "pays", + "contact_origine", + "secteur", + "privee", + "localisation", + "qualite_relation", + "plus10salaries", + "note", + "date_creation", + ), + sortkey="nom", + input_formators={"nom": _format_nom}, +) + +# ----------- Correspondants +_entreprise_correspEditor = EditableTable( + "entreprise_correspondant", + "entreprise_corresp_id", + ( + "entreprise_corresp_id", + "entreprise_id", + "civilite", + "nom", + "prenom", + "fonction", + "phone1", + "phone2", + "mobile", + "fax", + "mail1", + "mail2", + "note", + ), + sortkey="nom", +) + + +# ----------- Contacts +_entreprise_contactEditor = EditableTable( + "entreprise_contact", + "entreprise_contact_id", + ( + "entreprise_contact_id", + "date", + "type_contact", + "entreprise_id", + "entreprise_corresp_id", + "etudid", + "description", + "enseignant", + ), + sortkey="date", + output_formators={"date": DateISOtoDMY}, + input_formators={"date": DateDMYtoISO}, +) + + +def do_entreprise_create(context, args): + "entreprise_create" + cnx = context.GetDBConnexion() + r = _entreprisesEditor.create(cnx, args) + return r + + +def do_entreprise_delete(context, oid): + "entreprise_delete" + cnx = context.GetDBConnexion() + _entreprisesEditor.delete(cnx, oid) + + +def do_entreprise_list(context, **kw): + "entreprise_list" + cnx = context.GetDBConnexion() + kw["context"] = context + return _entreprisesEditor.list(cnx, **kw) + + +def do_entreprise_list_by_etud(context, **kw): + "entreprise_list_by_etud" + cnx = context.GetDBConnexion() + return _entreprisesEditor.list_by_etud(cnx, **kw) + + +def do_entreprise_edit(context, *args, **kw): + "entreprise_edit" + cnx = context.GetDBConnexion() + _entreprisesEditor.edit(cnx, *args, **kw) + + +def do_entreprise_correspondant_create(context, args): + "entreprise_correspondant_create" + cnx = context.GetDBConnexion() + r = _entreprise_correspEditor.create(cnx, args) + return r + + +def do_entreprise_correspondant_delete(context, oid): + "entreprise_correspondant_delete" + cnx = context.GetDBConnexion() + _entreprise_correspEditor.delete(cnx, oid) + + +def do_entreprise_correspondant_list(context, **kw): + "entreprise_correspondant_list" + cnx = context.GetDBConnexion() + return _entreprise_correspEditor.list(cnx, **kw) + + +def do_entreprise_correspondant_edit(context, *args, **kw): + "entreprise_correspondant_edit" + cnx = context.GetDBConnexion() + _entreprise_correspEditor.edit(cnx, *args, **kw) + + +def do_entreprise_correspondant_listnames(context, args={}): + "-> liste des noms des correspondants (pour affichage menu)" + C = do_entreprise_correspondant_list(context, args=args) + return [(x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C] + + +def do_entreprise_contact_delete(context, oid): + "entreprise_contact_delete" + cnx = context.GetDBConnexion() + _entreprise_contactEditor.delete(cnx, oid) + + +def do_entreprise_contact_list(context, **kw): + "entreprise_contact_list" + cnx = context.GetDBConnexion() + return _entreprise_contactEditor.list(cnx, **kw) + + +def do_entreprise_contact_edit(context, *args, **kw): + "entreprise_contact_edit" + cnx = context.GetDBConnexion() + _entreprise_contactEditor.edit(cnx, *args, **kw) + + +def do_entreprise_contact_create(context, args): + "entreprise_contact_create" + cnx = context.GetDBConnexion() + r = _entreprise_contactEditor.create(cnx, args) + return r + + +def do_entreprise_check_etudiant(context, etudiant): + """Si etudiant est vide, ou un ETUDID valide, ou un nom unique, + retourne (1, ETUDID). + Sinon, retourne (0, 'message explicatif') + """ + etudiant = etudiant.strip().translate( + None, "'()" + ) # suppress parens and quote from name + if not etudiant: + return 1, None + cnx = context.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select etudid, nom, prenom from identite where upper(nom) ~ upper(%(etudiant)s) or etudid=%(etudiant)s", + {"etudiant": etudiant}, + ) + r = cursor.fetchall() + if len(r) < 1: + return 0, 'Aucun etudiant ne correspond à "%s"' % etudiant + elif len(r) > 10: + return ( + 0, + "%d etudiants correspondent à ce nom (utilisez le code)" % len(r), + ) + elif len(r) > 1: + e = ['
      '] + for x in r: + e.append( + "
    • %s %s (code %s)
    • " + % (scu.strupper(x[1]), x[2] or "", x[0].strip()) + ) + e.append("
    ") + return ( + 0, + "Les étudiants suivants correspondent: préciser le nom complet ou le code\n" + + "\n".join(e), + ) + else: # une seule reponse ! + return 1, r[0][0].strip() \ No newline at end of file diff --git a/sco_groups_view.py b/sco_groups_view.py index 152ceb98..fbc62220 100644 --- a/sco_groups_view.py +++ b/sco_groups_view.py @@ -30,8 +30,16 @@ """ # Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code) +import datetime +import cgi +import urllib +import time +import collections +import operator -from sco_utils import * +import sco_utils as scu +from sco_permissions import ScoEtudInscrit, ScoEtudAddAnnotations, ScoAbsChange +from sco_exceptions import ScoValueError import html_sco_header from gen_tables import GenTable import scolars @@ -441,8 +449,6 @@ def groups_table( # "enter groups_table %s: %s" # % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-")) # ) - authuser = REQUEST.AUTHENTICATED_USER - with_codes = int(with_codes) with_paiement = int(with_paiement) with_archives = int(with_archives) @@ -891,7 +897,7 @@ def form_choix_jour_saisie_hebdo( FA.append(""" - -

    -(attention, vérifier que les groupes sont compatibles, selon votre organisation) -

    - - -

    Créer un nouveau groupe: - - - - -

    -

    - - - - -""" - % ( - etudid, - formsemestre_id, - sem["nomgroupetd"], - sem["nomgroupeta"], - sem["nomgroupetp"], - REQUEST.URL1, - ) - ) - - return header + "\n".join(H) + self.sco_footer(REQUEST) - # --- Gestion des groupes: security.declareProtected(ScoView, "affectGroups") affectGroups = sco_groups_edit.affectGroups diff --git a/config/create_dept.sh b/config/create_dept.sh index 0079cc6a..52775272 100755 --- a/config/create_dept.sh +++ b/config/create_dept.sh @@ -9,6 +9,7 @@ # E. Viennet, Juin 2008 # +set -euo pipefail source config.sh source utils.sh diff --git a/config/delete_dept.sh b/config/delete_dept.sh index 3f2f05f6..d5e2bce2 100755 --- a/config/delete_dept.sh +++ b/config/delete_dept.sh @@ -4,11 +4,11 @@ # ScoDoc: suppression d'un departement # # Ce script supprime la base de donnees ScoDoc d'un departement -# *** le departement doit au prealable avoir �t� supprime via l'interface web ! *** +# *** le departement doit au prealable avoir été supprime via l'interface web ! *** # # Ne fonctionne que pour les configurations "standards" (dbname=xxx) # -# Il doit �tre lanc� par l'utilisateur unix root dans le repertoire .../config +# Il doit être lancé par l'utilisateur unix root dans le repertoire .../config # ^^^^^^^^^^^^^^^^^^^^^ # E. Viennet, Sept 2008 # @@ -17,7 +17,7 @@ source config.sh source utils.sh -check_uid_root $0 +check_uid_root "$0" usage() { echo "$0 [-n DEPT]" echo "(default to interactive mode)" @@ -59,11 +59,16 @@ then scodocctl stop # suppression de la base postgres - db_name=$(cat "$cfg_pathname" | sed '/^dbname=*/!d; s///;q') - echo "suppression de la base postgres $db_name" - su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name" + db_name=$(sed '/^dbname=*/!d; s///;q' < "$cfg_pathname") + if su -c "psql -lt" "$POSTGRES_SUPERUSER" | cut -d \| -f 1 | grep -wq SCORT + then + echo "Suppression de la base postgres $db_name ..." + su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name" + else + echo "la base postgres $db_name n'existe pas." + fi # suppression du fichier de config - /bin/rm -f "$cfg_pathname" || terminate "ne peux supprimer $cfg_pathname" + /bin/rm -f "$cfg_pathname" || terminate "Ne peux supprimer $cfg_pathname" # relance ScoDoc if [ "$interactive" = 1 ] then @@ -76,6 +81,7 @@ then fi exit 0 else - echo 'Erreur: pas de configuration trouvee pour "'"$DEPT"'"' - exit 1 + echo 'Attention: pas de configuration trouvee pour "'"$DEPT"'"' + echo " => ne fait rien." + exit 0 fi diff --git a/htmlutils.py b/htmlutils.py index 70ff837c..f75d9ccf 100644 --- a/htmlutils.py +++ b/htmlutils.py @@ -28,6 +28,8 @@ """Various HTML generation functions """ +import listhistogram + def horizontal_bargraph(value, mark): """html drawing an horizontal bar and a mark @@ -42,9 +44,6 @@ def horizontal_bargraph(value, mark): return tmpl % {"value": int(value), "mark": int(mark)} -import listhistogram - - def histogram_notes(notes): "HTML code drawing histogram" if not notes: @@ -70,3 +69,56 @@ def histogram_notes(notes): ) D.append("") return "\n".join(D) + + +def make_menu(title, items, css_class="", base_url="", alone=False): + """HTML snippet to render a simple drop down menu. + items is a list of dicts: + { 'title' : + 'url' : + 'id' : + 'attr' : "" # optionnal html attributes + 'enabled' : # True by default + 'helpmsg' : + 'submenu' : [ list of sub-items ] + } + """ + + def gen_menu_items(items): + H.append("") + + H = [] + if alone: + H.append('
      ' % css_class) + H.append("""
    • %s""" % title) + gen_menu_items(items) + H.append("
    • ") + if alone: + H.append("
    ") + return "".join(H) diff --git a/notes_table.py b/notes_table.py index 82613e8a..bdb30300 100644 --- a/notes_table.py +++ b/notes_table.py @@ -41,7 +41,8 @@ from sco_parcours_dut import formsemestre_get_etud_capitalisation from sco_parcours_dut import list_formsemestre_utilisateurs_uecap import sco_parcours_dut import sco_formsemestre -from sco_formsemestre_edit import formsemestre_uecoef_list, formsemestre_uecoef_create +from sco_formsemestre import formsemestre_uecoef_list, formsemestre_uecoef_create +import sco_moduleimpl import sco_evaluations import sco_compute_moy from sco_formulas import NoteVector @@ -85,7 +86,9 @@ def get_sem_ues_modimpls(context, formsemestre_id, modimpls=None): (utilisé quand on ne peut pas construire nt et faire nt.get_ues()) """ if modimpls is None: - modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id) + modimpls = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id + ) uedict = {} for modimpl in modimpls: mod = context.do_module_list(args={"module_id": modimpl["module_id"]})[0] diff --git a/sco_abs_views.py b/sco_abs_views.py index a76e225a..aabab46f 100644 --- a/sco_abs_views.py +++ b/sco_abs_views.py @@ -42,6 +42,7 @@ from notes_log import log import sco_groups import sco_find_etud import sco_formsemestre +import sco_moduleimpl import sco_photos import sco_abs @@ -86,7 +87,7 @@ def doSignaleAbsence( J = "NON " M = "" if moduleimpl_id and moduleimpl_id != "NULL": - mod = context.Notes.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + mod = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] formsemestre_id = mod["formsemestre_id"] nt = context.Notes._getNotesCache().get_NotesTable( context.Notes, formsemestre_id diff --git a/sco_bulletins.py b/sco_bulletins.py index 1a8fe6d6..9d2a8139 100644 --- a/sco_bulletins.py +++ b/sco_bulletins.py @@ -28,13 +28,16 @@ """Génération des bulletins de notes """ -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEBase import MIMEBase -from email.Header import Header -from email import Encoders +from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error + MIMEMultipart, +) +from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error +from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error +from email.Header import Header # pylint: disable=no-name-in-module,import-error +from email import Encoders # pylint: disable=no-name-in-module,import-error -import htmlutils, time +import time +import htmlutils from reportlab.lib.colors import Color from sco_utils import * @@ -1146,7 +1149,7 @@ def _formsemestre_bulletinetud_header_html( ] H.append("""
    """) - H.append(sco_formsemestre_status.makeMenu("Autres opérations", menuBul, alone=True)) + H.append(htmlutils.make_menu("Autres opérations", menuBul, alone=True)) H.append("""
    """) H.append( ' %s' diff --git a/sco_compute_moy.py b/sco_compute_moy.py index 108f764f..368506fc 100644 --- a/sco_compute_moy.py +++ b/sco_compute_moy.py @@ -29,14 +29,24 @@ """ import traceback +import pprint +from types import FloatType -from sco_utils import * -from notesdb import * +import sco_utils as scu +from sco_utils import ( + NOTES_ATTENTE, + NOTES_NEUTRALISE, + EVALUATION_NORMALE, + EVALUATION_RATTRAPAGE, +) +from sco_exceptions import ScoException +from notesdb import EditableTable, quote_html from notes_log import log, sendAlarm import sco_formsemestre +import sco_moduleimpl import sco_groups import sco_evaluations -from sco_formulas import * +import sco_formulas import sco_abs @@ -67,7 +77,9 @@ def formsemestre_expressions_use_abscounts(context, formsemestre_id): if expr and expr[0] != "#" and ab in expr: return True # 2- moyennes de modules - for mod in context.Notes.do_moduleimpl_list(formsemestre_id=formsemestre_id): + for mod in sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id + ): if moduleimpl_has_expression(context, mod) and ab in mod["computation_expr"]: return True return False @@ -148,7 +160,7 @@ def compute_user_formula( try: formula = formula.replace("\n", "").replace("\r", "") # log('expression : %s\nvariables=%s\n' % (formula, variables)) # XXX debug - user_moy = eval_user_expression(context, formula, variables) + user_moy = sco_formulas.eval_user_expression(context, formula, variables) # log('user_moy=%s' % user_moy) if user_moy != "NA0" and user_moy != "NA": user_moy = float(user_moy) @@ -188,10 +200,10 @@ def do_moduleimpl_moyennes(context, nt, mod): """ diag_info = {} # message d'erreur formule moduleimpl_id = mod["moduleimpl_id"] - is_malus = mod["module"]["module_type"] == MODULE_MALUS + is_malus = mod["module"]["module_type"] == scu.MODULE_MALUS sem = sco_formsemestre.get_formsemestre(context, mod["formsemestre_id"]) - etudids = context.do_moduleimpl_listeetuds( - moduleimpl_id + etudids = sco_moduleimpl.do_moduleimpl_listeetuds( + context, moduleimpl_id ) # tous, y compris demissions # Inscrits au semestre (pour traiter les demissions): inssem_set = set( @@ -256,7 +268,7 @@ def do_moduleimpl_moyennes(context, nt, mod): ] # R = {} - formula = unescape_html(mod["computation_expr"]) + formula = scu.unescape_html(mod["computation_expr"]) formula_use_abs = "abs" in formula for etudid in insmod_set: # inscrits au semestre et au module @@ -353,12 +365,14 @@ def do_formsemestre_moyennes(context, nt, formsemestre_id): la liste des moduleimpls, la liste des evaluations valides, liste des moduleimpls avec notes en attente. """ - sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) - inscr = context.do_formsemestre_inscription_list( - args={"formsemestre_id": formsemestre_id} + # sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) + # inscr = context.do_formsemestre_inscription_list( + # args={"formsemestre_id": formsemestre_id} + # ) + # etudids = [x["etudid"] for x in inscr] + modimpls = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id ) - etudids = [x["etudid"] for x in inscr] - modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id) # recupere les moyennes des etudiants de tous les modules D = {} valid_evals = [] diff --git a/sco_cost_formation.py b/sco_cost_formation.py index 2334740f..22693867 100644 --- a/sco_cost_formation.py +++ b/sco_cost_formation.py @@ -37,6 +37,7 @@ from gen_tables import GenTable import sco_excel, sco_pdf from sco_pdf import SU import sco_formsemestre +import sco_moduleimpl import sco_formsemestre_status @@ -60,7 +61,9 @@ def formsemestre_table_estim_cost( """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) sco_formsemestre_status.fill_formsemestre(context, sem, REQUEST=REQUEST) - Mlist = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) + Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list( + context, formsemestre_id=formsemestre_id + ) T = [] for M in Mlist: Mod = M["module"] diff --git a/sco_evaluations.py b/sco_evaluations.py index e877b62c..5e5b17e4 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -41,9 +41,11 @@ from gen_tables import GenTable from TrivialFormulator import TrivialFormulator import sco_news import sco_formsemestre +import sco_moduleimpl import sco_groups import sco_abs import sco_evaluations +import sco_saisie_notes # -------------------------------------------------------------------- # @@ -101,7 +103,7 @@ def do_evaluation_delete(context, REQUEST, evaluation_id): context._evaluationEditor.delete(cnx, evaluation_id) # inval cache pour ce semestre - M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] context._inval_cache(formsemestre_id=M["formsemestre_id"]) # > eval delete # news mod = context.do_module_list(args={"module_id": M["module_id"]})[0] @@ -165,7 +167,7 @@ def do_evaluation_etat( last_modif = None # ---- Liste des groupes complets et incomplets E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0] - M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] + M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus formsemestre_id = M["formsemestre_id"] @@ -182,7 +184,9 @@ def do_evaluation_etat( # (pour avoir l'etat et le groupe) et aussi les inscriptions # au module (pour gerer les modules optionnels correctement) insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id) - insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"]) + insmod = sco_moduleimpl.do_moduleimpl_inscription_list( + context, moduleimpl_id=E["moduleimpl_id"] + ) insmodset = set([x["etudid"] for x in insmod]) # retire de insem ceux qui ne sont pas inscrits au module ins = [i for i in insem if i["etudid"] in insmodset] @@ -451,7 +455,9 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None): if not e["jour"]: continue day = e["jour"].strftime("%Y-%m-%d") - mod = context.do_moduleimpl_withmodule_list(moduleimpl_id=e["moduleimpl_id"])[0] + mod = sco_moduleimpl.do_moduleimpl_withmodule_list( + context, moduleimpl_id=e["moduleimpl_id"] + )[0] txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval" if e["heure_debut"]: debut = e["heure_debut"].strftime("%Hh%M") @@ -524,10 +530,10 @@ def evaluation_date_first_completion(context, evaluation_id): # (pour avoir l'etat et le groupe) et aussi les inscriptions # au module (pour gerer les modules optionnels correctement) # E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0] - # M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] + # M = sco_moduleimpl.do_moduleimpl_list(context,moduleimpl_id=E["moduleimpl_id"])[0] # formsemestre_id = M["formsemestre_id"] # insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id) - # insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"]) + # insmod = sco_moduleimpl.do_moduleimpl_inscription_list(context,moduleimpl_id=E["moduleimpl_id"]) # insmodset = set([x["etudid"] for x in insmod]) # retire de insem ceux qui ne sont pas inscrits au module # ins = [i for i in insem if i["etudid"] in insmodset] @@ -566,7 +572,9 @@ def formsemestre_evaluations_delai_correction( evals = nt.get_sem_evaluation_etat_list() T = [] for e in evals: - M = context.do_moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0] + M = sco_moduleimpl.do_moduleimpl_list( + context, moduleimpl_id=e["moduleimpl_id"] + )[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or ( Mod["module_type"] == scu.MODULE_MALUS @@ -732,14 +740,14 @@ def evaluation_describe(context, evaluation_id="", edit_in_place=True, REQUEST=N """ E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0] moduleimpl_id = E["moduleimpl_id"] - M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] formsemestre_id = M["formsemestre_id"] u = context.Users.user_info(M["responsable_id"]) resp = u["prenomnom"] nomcomplet = u["nomcomplet"] - can_edit = context.can_edit_notes( - REQUEST.AUTHENTICATED_USER, moduleimpl_id, allow_ens=False + can_edit = sco_saisie_notes.can_edit_notes( + context, REQUEST.AUTHENTICATED_USER, moduleimpl_id, allow_ens=False ) link = ( @@ -810,7 +818,9 @@ def evaluation_create_form( the_eval = context.do_evaluation_list({"evaluation_id": evaluation_id})[0] moduleimpl_id = the_eval["moduleimpl_id"] # - M = context.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] + M = sco_moduleimpl.do_moduleimpl_withmodule_list( + context, moduleimpl_id=moduleimpl_id + )[0] is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus formsemestre_id = M["formsemestre_id"] min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible diff --git a/sco_formsemestre.py b/sco_formsemestre.py index 55c88ece..6ad764e1 100644 --- a/sco_formsemestre.py +++ b/sco_formsemestre.py @@ -241,6 +241,53 @@ def write_formsemestre_responsables(context, sem): return _write_formsemestre_aux(context, sem, "responsables", "responsable_id") +# ---------------------- Coefs des UE + +_formsemestre_uecoef_editor = EditableTable( + "notes_formsemestre_uecoef", + "formsemestre_uecoef_id", + ("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"), +) + +formsemestre_uecoef_create = _formsemestre_uecoef_editor.create +formsemestre_uecoef_edit = _formsemestre_uecoef_editor.edit +formsemestre_uecoef_list = _formsemestre_uecoef_editor.list +formsemestre_uecoef_delete = _formsemestre_uecoef_editor.delete + + +def do_formsemestre_uecoef_edit_or_create(context, cnx, formsemestre_id, ue_id, coef): + "modify or create the coef" + coefs = formsemestre_uecoef_list( + cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id} + ) + if coefs: + formsemestre_uecoef_edit( + cnx, + args={ + "formsemestre_uecoef_id": coefs[0]["formsemestre_uecoef_id"], + "coefficient": coef, + }, + ) + else: + formsemestre_uecoef_create( + cnx, + args={ + "formsemestre_id": formsemestre_id, + "ue_id": ue_id, + "coefficient": coef, + }, + ) + + +def do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue_id): + "delete coef for this (ue,sem)" + coefs = formsemestre_uecoef_list( + cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id} + ) + if coefs: + formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"]) + + def read_formsemestre_etapes(context, formsemestre_id): """recupere liste des codes etapes associés à ce semestre :returns: liste d'instance de ApoEtapeVDI diff --git a/sco_formsemestre_custommenu.py b/sco_formsemestre_custommenu.py index 661856bf..5365aa5c 100644 --- a/sco_formsemestre_custommenu.py +++ b/sco_formsemestre_custommenu.py @@ -74,7 +74,7 @@ def formsemestre_custommenu_html(context, formsemestre_id, base_url=""): + formsemestre_id, } ) - return sco_formsemestre_status.makeMenu("Liens", menu) + return sco_formsemestre_status.htmlutils.make_menu("Liens", menu) def formsemestre_custommenu_edit(context, formsemestre_id, REQUEST=None): diff --git a/sco_formsemestre_edit.py b/sco_formsemestre_edit.py index b35b3649..7850202e 100644 --- a/sco_formsemestre_edit.py +++ b/sco_formsemestre_edit.py @@ -41,6 +41,7 @@ import sco_codes_parcours import sco_compute_moy import sco_modalites import sco_formsemestre +import sco_moduleimpl from sco_formsemestre import ApoEtapeVDI @@ -155,7 +156,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): # if REQUEST.form.get('tf-submitted',False) and not REQUEST.form.has_key('inscrire_etudslist'): # REQUEST.form['inscrire_etudslist'] = [] # add associated modules to tf-checked - ams = context.do_moduleimpl_list(formsemestre_id=formsemestre_id) + ams = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id + ) sem_module_ids = set([x["module_id"] for x in ams]) initvalues["tf-checked"] = [x["module_id"] for x in ams] for x in ams: @@ -706,7 +709,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): "formsemestre_id": formsemestre_id, "responsable_id": tf[2][module_id], } - mid = context.do_moduleimpl_create(modargs) + mid = sco_moduleimpl.do_moduleimpl_create(context, modargs) return REQUEST.RESPONSE.redirect( "formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé" % formsemestre_id @@ -720,7 +723,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): # nouveaux modules checkedmods = tf[2]["tf-checked"] sco_formsemestre.do_formsemestre_edit(context, tf[2]) - ams = context.do_moduleimpl_list(formsemestre_id=formsemestre_id) + ams = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id + ) existingmods = [x["module_id"] for x in ams] mods_tocreate = [x for x in checkedmods if not x in existingmods] # modules a existants a modifier @@ -735,7 +740,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): "formsemestre_id": formsemestre_id, "responsable_id": tf[2][module_id], } - moduleimpl_id = context.do_moduleimpl_create(modargs) + moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(context, modargs) mod = context.do_module_list({"module_id": module_id})[0] msg += ["création de %s (%s)" % (mod["code"], mod["titre"])] # INSCRIPTIONS DES ETUDIANTS @@ -771,8 +776,8 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): ) msg += diag for module_id in mods_toedit: - moduleimpl_id = context.do_moduleimpl_list( - formsemestre_id=formsemestre_id, module_id=module_id + moduleimpl_id = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id, module_id=module_id )[0]["moduleimpl_id"] modargs = { "moduleimpl_id": moduleimpl_id, @@ -780,7 +785,9 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): "formsemestre_id": formsemestre_id, "responsable_id": tf[2][module_id], } - context.do_moduleimpl_edit(modargs, formsemestre_id=formsemestre_id) + sco_moduleimpl.do_moduleimpl_edit( + context, modargs, formsemestre_id=formsemestre_id + ) mod = context.do_module_list({"module_id": module_id})[0] if msg: @@ -814,8 +821,8 @@ def formsemestre_delete_moduleimpls(context, formsemestre_id, module_ids_to_del) msg = [] for module_id in module_ids_to_del: # get id - moduleimpl_id = context.do_moduleimpl_list( - formsemestre_id=formsemestre_id, module_id=module_id + moduleimpl_id = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id, module_id=module_id )[0]["moduleimpl_id"] mod = context.do_module_list({"module_id": module_id})[0] # Evaluations dans ce module ? @@ -828,7 +835,9 @@ def formsemestre_delete_moduleimpls(context, formsemestre_id, module_ids_to_del) ok = False else: msg += ["suppression de %s (%s)" % (mod["code"], mod["titre"])] - context.do_moduleimpl_delete(moduleimpl_id, formsemestre_id=formsemestre_id) + sco_moduleimpl.do_moduleimpl_delete( + context, moduleimpl_id, formsemestre_id=formsemestre_id + ) return ok, msg @@ -984,17 +993,21 @@ def do_formsemestre_clone( formsemestre_id = context.do_formsemestre_create(args, REQUEST) log("created formsemestre %s" % formsemestre_id) # 2- create moduleimpls - mods_orig = context.do_moduleimpl_list(formsemestre_id=orig_formsemestre_id) + mods_orig = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=orig_formsemestre_id + ) for mod_orig in mods_orig: args = mod_orig.copy() args["formsemestre_id"] = formsemestre_id - mid = context.do_moduleimpl_create(args) + mid = sco_moduleimpl.do_moduleimpl_create(context, args) # copy notes_modules_enseignants - ens = context.do_ens_list(args={"moduleimpl_id": mod_orig["moduleimpl_id"]}) + ens = sco_moduleimpl.do_ens_list( + context, args={"moduleimpl_id": mod_orig["moduleimpl_id"]} + ) for e in ens: args = e.copy() args["moduleimpl_id"] = mid - context.do_ens_create(args) + sco_moduleimpl.do_ens_create(context, args) # optionally, copy evaluations if clone_evaluations: evals = context.do_evaluation_list( @@ -1007,11 +1020,13 @@ def do_formsemestre_clone( evaluation_id = context.do_evaluation_create(REQUEST=REQUEST, **args) # 3- copy uecoefs - objs = formsemestre_uecoef_list(cnx, args={"formsemestre_id": orig_formsemestre_id}) + objs = sco_formsemestre.formsemestre_uecoef_list( + cnx, args={"formsemestre_id": orig_formsemestre_id} + ) for obj in objs: args = obj.copy() args["formsemestre_id"] = formsemestre_id - c = formsemestre_uecoef_create(cnx, args) + c = sco_formsemestre.formsemestre_uecoef_create(cnx, args) # NB: don't copy notes_formsemestre_custommenu (usually specific) @@ -1177,7 +1192,9 @@ def _reassociate_moduleimpls( et met à jour les décisions de jury (validations d'UE). """ # re-associate moduleimpls to new modules: - modimpls = context.do_moduleimpl_list(formsemestre_id=formsemestre_id) + modimpls = sco_moduleimpl.do_moduleimpl_list( + context, formsemestre_id=formsemestre_id + ) for mod in modimpls: mod["module_id"] = modules_old2new[mod["module_id"]] context.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id, cnx=cnx) @@ -1314,8 +1331,8 @@ def do_formsemestre_delete(context, formsemestre_id, REQUEST): ) context.get_evaluations_cache().inval_cache(key=e["evaluation_id"]) - context.do_moduleimpl_delete( - mod["moduleimpl_id"], formsemestre_id=formsemestre_id + sco_moduleimpl.do_moduleimpl_delete( + context, mod["moduleimpl_id"], formsemestre_id=formsemestre_id ) # --- Desinscription des etudiants cursor = cnx.cursor(cursor_factory=ScoDocCursor) @@ -1465,20 +1482,6 @@ def formsemestre_change_publication_bul( ) -# ---------------------- Coefs des UE - -_formsemestre_uecoef_editor = EditableTable( - "notes_formsemestre_uecoef", - "formsemestre_uecoef_id", - ("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"), -) - -formsemestre_uecoef_create = _formsemestre_uecoef_editor.create -formsemestre_uecoef_edit = _formsemestre_uecoef_editor.edit -formsemestre_uecoef_list = _formsemestre_uecoef_editor.list -formsemestre_uecoef_delete = _formsemestre_uecoef_editor.delete - - def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST=None): """Changement manuel des coefficients des UE capitalisées.""" context = context.Notes # si appele d'en haut, eg par exception ScoValueError @@ -1527,7 +1530,7 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST= initvalues = {"formsemestre_id": formsemestre_id} form = [("formsemestre_id", {"input_type": "hidden"})] for ue in ues: - coefs = formsemestre_uecoef_list( + coefs = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} ) if coefs: @@ -1564,7 +1567,7 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST= msg = [] for ue in ues: val = tf[2]["ue_" + ue["ue_id"]] - coefs = formsemestre_uecoef_list( + coefs = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} ) if val == "" or val == "auto": @@ -1594,11 +1597,13 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST= # apply modifications for ue in ue_modified: - do_formsemestre_uecoef_edit_or_create( + sco_formsemestre.do_formsemestre_uecoef_edit_or_create( context, cnx, formsemestre_id, ue["ue_id"], ue["coef"] ) for ue in ue_deleted: - do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue["ue_id"]) + sco_formsemestre.do_formsemestre_uecoef_delete( + context, cnx, formsemestre_id, ue["ue_id"] + ) if ue_modified or ue_deleted: z = ["""

    Modification effectuées

    """] @@ -1630,39 +1635,6 @@ def formsemestre_edit_uecoefs(context, formsemestre_id, err_ue_id=None, REQUEST= ) -def do_formsemestre_uecoef_edit_or_create(context, cnx, formsemestre_id, ue_id, coef): - "modify or create the coef" - coefs = formsemestre_uecoef_list( - cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id} - ) - if coefs: - formsemestre_uecoef_edit( - cnx, - args={ - "formsemestre_uecoef_id": coefs[0]["formsemestre_uecoef_id"], - "coefficient": coef, - }, - ) - else: - formsemestre_uecoef_create( - cnx, - args={ - "formsemestre_id": formsemestre_id, - "ue_id": ue_id, - "coefficient": coef, - }, - ) - - -def do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue_id): - "delete coef for this (ue,sem)" - coefs = formsemestre_uecoef_list( - cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id} - ) - if coefs: - formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"]) - - # ----- identification externe des sessions (pour SOJA et autres logiciels) def get_formsemestre_session_id(context, sem, F, parcours): """Identifiant de session pour ce semestre diff --git a/sco_formsemestre_exterieurs.py b/sco_formsemestre_exterieurs.py index e90e4805..1240d5e1 100644 --- a/sco_formsemestre_exterieurs.py +++ b/sco_formsemestre_exterieurs.py @@ -436,7 +436,7 @@ def _list_ue_with_coef_and_validations(context, sem, etudid): ue_list = context.do_ue_list({"formation_id": sem["formation_id"]}) for ue in ue_list: # add coefficient - uecoef = sco_formsemestre_edit.formsemestre_uecoef_list( + uecoef = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} ) if uecoef: diff --git a/sco_formsemestre_inscriptions.py b/sco_formsemestre_inscriptions.py index 602ad670..44f2d137 100644 --- a/sco_formsemestre_inscriptions.py +++ b/sco_formsemestre_inscriptions.py @@ -37,6 +37,7 @@ from TrivialFormulator import TrivialFormulator, TF # from notes_table import * import sco_find_etud import sco_formsemestre +import sco_moduleimpl import sco_groups @@ -75,10 +76,13 @@ def do_formsemestre_inscription_with_modules( gdone[group_id] = 1 # inscription a tous les modules de ce semestre - modimpls = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) + modimpls = sco_moduleimpl.do_moduleimpl_withmodule_list( + context, formsemestre_id=formsemestre_id + ) for mod in modimpls: if mod["ue"]["type"] != UE_SPORT: - context.do_moduleimpl_inscription_create( + sco_moduleimpl.do_moduleimpl_inscription_create( + context, {"moduleimpl_id": mod["moduleimpl_id"], "etudid": etudid}, REQUEST=REQUEST, formsemestre_id=formsemestre_id, @@ -278,8 +282,10 @@ def formsemestre_inscription_option(context, etudid, formsemestre_id, REQUEST=No ] # Cherche les moduleimpls et les inscriptions - mods = context.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) - inscr = context.do_moduleimpl_inscription_list(etudid=etudid) + mods = sco_moduleimpl.do_moduleimpl_withmodule_list( + context, formsemestre_id=formsemestre_id + ) + inscr = sco_moduleimpl.do_moduleimpl_inscription_list(context, etudid=etudid) # Formulaire modimpls_by_ue_ids = DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ] modimpls_by_ue_names = DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_name ] @@ -502,13 +508,14 @@ def do_moduleimpl_incription_options( # inscriptions for moduleimpl_id in a_inscrire: # verifie que ce module existe bien - mods = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) + mods = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id) if len(mods) != 1: raise ScoValueError( "inscription: invalid moduleimpl_id: %s" % moduleimpl_id ) mod = mods[0] - context.do_moduleimpl_inscription_create( + sco_moduleimpl.do_moduleimpl_inscription_create( + context, {"moduleimpl_id": moduleimpl_id, "etudid": etudid}, REQUEST=REQUEST, formsemestre_id=mod["formsemestre_id"], @@ -516,14 +523,14 @@ def do_moduleimpl_incription_options( # desinscriptions for moduleimpl_id in a_desinscrire: # verifie que ce module existe bien - mods = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) + mods = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id) if len(mods) != 1: raise ScoValueError( "desinscription: invalid moduleimpl_id: %s" % moduleimpl_id ) mod = mods[0] - inscr = context.do_moduleimpl_inscription_list( - moduleimpl_id=moduleimpl_id, etudid=etudid + inscr = sco_moduleimpl.do_moduleimpl_inscription_list( + context, moduleimpl_id=moduleimpl_id, etudid=etudid ) if not inscr: raise ScoValueError( @@ -531,8 +538,8 @@ def do_moduleimpl_incription_options( % (etudid, moduleimpl_id) ) oid = inscr[0]["moduleimpl_inscription_id"] - context.do_moduleimpl_inscription_delete( - oid, formsemestre_id=mod["formsemestre_id"] + sco_moduleimpl.do_moduleimpl_inscription_delete( + context, oid, formsemestre_id=mod["formsemestre_id"] ) if REQUEST: diff --git a/sco_formsemestre_status.py b/sco_formsemestre_status.py index b3132e6a..e71e4624 100644 --- a/sco_formsemestre_status.py +++ b/sco_formsemestre_status.py @@ -31,9 +31,18 @@ # Rewritten from ancient DTML code from mx.DateTime import DateTime as mxDateTime -from notesdb import * from notes_log import log -from sco_utils import * +import sco_utils as scu +from sco_permissions import ( + ScoImplement, + ScoChangeFormation, + ScoEtudInscrit, + ScoView, + ScoEtudChangeAdr, +) +from sco_exceptions import ScoValueError +import VERSION +import htmlutils from sco_formsemestre_custommenu import formsemestre_custommenu_html from gen_tables import GenTable import sco_archives @@ -41,64 +50,12 @@ import sco_groups import sco_evaluations import sco_formsemestre import sco_formsemestre_edit +import sco_moduleimpl import sco_compute_moy import sco_codes_parcours import sco_bulletins -def makeMenu(title, items, css_class="", base_url="", alone=False): - """HTML snippet to render a simple drop down menu. - items is a list of dicts: - { 'title' : - 'url' : - 'id' : - 'attr' : "" # optionnal html attributes - 'enabled' : # True by default - 'helpmsg' : - 'submenu' : [ list of sub-items ] - } - """ - - def gen_menu_items(items): - H.append("") - - H = [] - if alone: - H.append('
      ' % css_class) - H.append("""
    • %s""" % title) - gen_menu_items(items) - H.append("
    • ") - if alone: - H.append("
    ") - return "".join(H) - - # H = [ """") H.append( - 'continuer' + 'continuer' % formsemestre_id ) H.append(context.sco_footer(REQUEST)) @@ -1184,7 +1184,7 @@ def formsemestre_validate_previous_ue(context, formsemestre_id, etudid, REQUEST= ) return REQUEST.RESPONSE.redirect( context.ScoURL() - + "/Notes/formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s&head_message=Validation%%20d'UE%%20enregistree" + + "/Notes/formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s&head_message=Validation%%20d'UE%%20enregistree" % (formsemestre_id, etudid) ) @@ -1289,7 +1289,7 @@ def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None): valid["s"] += " (S%d)" % valid["semestre_id"] valid["ds"] = formsemestre_id H.append( - '
  • %(code)s%(m)s%(s)s, le %(event_date)s effacer
  • ' + '
  • %(code)s%(m)s%(s)s, le %(event_date)s effacer
  • ' % valid ) H.append("") @@ -1311,7 +1311,7 @@ def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST return REQUEST.RESPONSE.redirect( context.NotesURL() - + "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s" + + "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s" % (etudid, formsemestre_id) ) diff --git a/sco_groups.py b/sco_groups.py index aa79d05f..d1dfc481 100644 --- a/sco_groups.py +++ b/sco_groups.py @@ -847,13 +847,13 @@ def editPartitionForm(context, formsemestre_id=None, REQUEST=None): ) if i != 0: H.append( - '%s' + '%s' % (p["partition_id"], arrow_up) ) H.append('') if i < len(partitions) - 2: H.append( - '%s' + '%s' % (p["partition_id"], arrow_down) ) i += 1 @@ -1494,7 +1494,7 @@ def form_group_choice( def make_query_groups(group_ids): if group_ids: - return "&".join(["group_ids%3Alist=" + group_id for group_id in group_ids]) + return "&".join(["group_ids%3Alist=" + group_id for group_id in group_ids]) else: return "" diff --git a/sco_groups_view.py b/sco_groups_view.py index 6336d970..59a9470b 100644 --- a/sco_groups_view.py +++ b/sco_groups_view.py @@ -343,7 +343,7 @@ class DisplayedGroupsInfos: gq = [] for group_id in group_ids: gq.append("group_ids=" + group_id) - self.groups_query_args = "&".join(gq) + self.groups_query_args = "&".join(gq) self.base_url = REQUEST.URL0 + "?" + self.groups_query_args self.group_ids = group_ids self.groups = [] @@ -457,10 +457,10 @@ def groups_table( with_archives = int(with_archives) with_annotations = int(with_annotations) - base_url_np = groups_infos.base_url + "&with_codes=%s" % with_codes + base_url_np = groups_infos.base_url + "&with_codes=%s" % with_codes base_url = ( base_url_np - + "&with_paiement=%s&with_archives=%s&with_annotations=%s" + + "&with_paiement=%s&with_archives=%s&with_annotations=%s" % (with_paiement, with_archives, with_annotations) ) # @@ -668,11 +668,11 @@ def groups_table( [ tab.html(), "", "

    Feuilles

    ", '", ] @@ -853,7 +853,7 @@ def tab_absences_html(context, groups_infos, etat=None, REQUEST=None): group_id = sco_groups.get_default_group(context, groups_infos.formsemestre_id) if authuser.has_permission(ScoEtudInscrit, context): H.append( - '
  • Vérifier codes Apogée (de tous les groupes)
  • ' + '
  • Vérifier codes Apogée (de tous les groupes)
  • ' % (group_id, etat or "") ) # Lien pour ajout fichiers étudiants diff --git a/sco_inscr_passage.py b/sco_inscr_passage.py index e5bdd1c4..d1fd0885 100644 --- a/sco_inscr_passage.py +++ b/sco_inscr_passage.py @@ -552,7 +552,7 @@ def etuds_select_boxes( H.append(")") if base_url and etuds: H.append( - '%s ' + '%s ' % (base_url, src_cat, scu.ICON_XLS) ) H.append("") diff --git a/sco_liste_notes.py b/sco_liste_notes.py index 55cd2e19..fa662fb3 100644 --- a/sco_liste_notes.py +++ b/sco_liste_notes.py @@ -314,7 +314,7 @@ def _make_table_notes( "_code_td_attrs": 'style="padding-left: 1em; padding-right: 2em;"', "etudid": etudid, "nom": scu.strupper(etud["nom"]), - "_nomprenom_target": "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" + "_nomprenom_target": "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % (M["formsemestre_id"], etudid), "_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]), "prenom": scu.strcapitalize(scu.strlower(etud["prenom"])), @@ -420,15 +420,15 @@ def _make_table_notes( columns_ids.append("comment") # titres divers: - gl = "".join(["&group_ids%3Alist=" + g for g in group_ids]) + gl = "".join(["&group_ids%3Alist=" + g for g in group_ids]) if note_sur_20: - gl = "&note_sur_20%3Alist=yes" + gl + gl = "¬e_sur_20%3Alist=yes" + gl if anonymous_listing: - gl = "&anonymous_listing%3Alist=yes" + gl + gl = "&anonymous_listing%3Alist=yes" + gl if hide_groups: - gl = "&hide_groups%3Alist=yes" + gl + gl = "&hide_groups%3Alist=yes" + gl if with_emails: - gl = "&with_emails%3Alist=yes" + gl + gl = "&with_emails%3Alist=yes" + gl if len(evals) == 1: evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"])) hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudids)) @@ -808,7 +808,7 @@ def evaluation_check_absences_html( ) if linkabs: H.append( - 'signaler cette absence' + 'signaler cette absence' % ( etud["etudid"], urllib.quote(E["jour"]), diff --git a/sco_lycee.py b/sco_lycee.py index 0b3fbf93..6e5568c0 100644 --- a/sco_lycee.py +++ b/sco_lycee.py @@ -182,9 +182,9 @@ def formsemestre_etuds_lycees( ) tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id) if only_primo: - tab.base_url += "&only_primo=1" + tab.base_url += "&only_primo=1" if no_grouping: - tab.base_url += "&no_grouping=1" + tab.base_url += "&no_grouping=1" t = tab.make_page(context, format=format, with_html_headers=False, REQUEST=REQUEST) if format != "html": return t diff --git a/sco_moduleimpl_inscriptions.py b/sco_moduleimpl_inscriptions.py index 43327b24..6f68f589 100644 --- a/sco_moduleimpl_inscriptions.py +++ b/sco_moduleimpl_inscriptions.py @@ -367,14 +367,14 @@ def moduleimpl_inscriptions_stats(context, formsemestre_id, REQUEST=None): ) if can_change: H.append( - '' + '' % (etud["etudid"], formsemestre_id, ue["ue_id"]) ) else: H.append("(non réinscrit dans cette UE)") if can_change: H.append( - '' + '' % (etud["etudid"], formsemestre_id, ue["ue_id"]) ) H.append("") diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index ba32596d..52dbcb42 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -122,7 +122,7 @@ def moduleimpl_evaluation_menu(context, evaluation_id, nbnotes=0, REQUEST=None): }, { "title": "Absences ce jour", - "url": "Absences/EtatAbsencesDate?date=%s&group_ids=%s" + "url": "Absences/EtatAbsencesDate?date=%s&group_ids=%s" % (urllib.quote(E["jour"], safe=""), group_id), "enabled": E["jour"], }, @@ -322,7 +322,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No if sem["etat"] == "1": # non verrouillé top_table_links = ( """Créer nouvelle évaluation - Trier par date + Trier par date """ % M ) @@ -384,14 +384,14 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No H.append('') if eval_index != (len(ModEvals) - 1) and caneditevals: H.append( - '%s' + '%s' % (eval["evaluation_id"], arrow_up) ) else: H.append(arrow_none) if (eval_index > 0) and caneditevals: H.append( - '%s' + '%s' % (eval["evaluation_id"], arrow_down) ) else: @@ -546,7 +546,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No if gr_moyenne["gr_nb_notes"] > 0: H.append("%(gr_moy)s" % gr_moyenne) H.append( - """  (%s notes""" + """  (%s notes""" % ( eval["evaluation_id"], gr_moyenne["group_id"], @@ -563,7 +563,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No H.append("""[""") if caneditnotes: H.append( - """incomplet]""" + """incomplet]""" % (eval["evaluation_id"], gr_moyenne["group_id"]) ) else: @@ -572,7 +572,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No H.append("""  """) if caneditnotes: H.append( - """""" + """""" % (eval["evaluation_id"], gr_moyenne["group_id"]) ) H.append("pas de notes") diff --git a/sco_page_etud.py b/sco_page_etud.py index 91d6457a..b0cb60ee 100644 --- a/sco_page_etud.py +++ b/sco_page_etud.py @@ -75,12 +75,12 @@ def _menuScolarite(context, authuser, sem, etudid): if ins["etat"] != "D": dem_title = "Démission" dem_url = ( - "formDem?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args + "formDem?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args ) else: dem_title = "Annuler la démission" dem_url = ( - "doCancelDem?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" + "doCancelDem?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args ) @@ -88,12 +88,12 @@ def _menuScolarite(context, authuser, sem, etudid): if ins["etat"] != sco_codes_parcours.DEF: def_title = "Déclarer défaillance" def_url = ( - "formDef?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args + "formDef?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args ) elif ins["etat"] == sco_codes_parcours.DEF: def_title = "Annuler la défaillance" def_url = ( - "doCancelDef?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" + "doCancelDef?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args ) def_enabled = ( @@ -103,7 +103,7 @@ def _menuScolarite(context, authuser, sem, etudid): ) items = [ # { 'title' : 'Changer de groupe', - # 'url' : 'formChangeGroup?etudid=%s&formsemestre_id=%s' % (etudid,ins['formsemestre_id']), + # 'url' : 'formChangeGroup?etudid=%s&formsemestre_id=%s' % (etudid,ins['formsemestre_id']), # 'enabled' : authuser.has_permission(ScoEtudChangeGroups,context) and not locked, # }, { @@ -113,20 +113,20 @@ def _menuScolarite(context, authuser, sem, etudid): }, { "title": "Validation du semestre (jury)", - "url": "Notes/formsemestre_validation_etud_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" + "url": "Notes/formsemestre_validation_etud_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s" % args, "enabled": authuser.has_permission(ScoEtudInscrit, context) and not locked, }, {"title": def_title, "url": def_url, "enabled": def_enabled}, { "title": "Inscrire à un module optionnel (ou au sport)", - "url": "Notes/formsemestre_inscription_option?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" + "url": "Notes/formsemestre_inscription_option?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" % args, "enabled": authuser.has_permission(ScoEtudInscrit, context) and not locked, }, { "title": "Désinscrire (en cas d'erreur)", - "url": "Notes/formsemestre_desinscription?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" + "url": "Notes/formsemestre_desinscription?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" % args, "enabled": authuser.has_permission(ScoEtudInscrit, context) and not locked, }, @@ -138,7 +138,7 @@ def _menuScolarite(context, authuser, sem, etudid): }, { "title": "Enregistrer un semestre effectué ailleurs", - "url": "Notes/formsemestre_ext_create_form?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" + "url": "Notes/formsemestre_ext_create_form?formsemestre_id=%(formsemestre_id)s&etudid=%(etudid)s" % args, "enabled": authuser.has_permission(ScoImplement, context), }, @@ -290,7 +290,7 @@ def ficheEtud(context, etudid=None, REQUEST=None): else: a[ "dellink" - ] = '%s' % ( + ] = '%s' % ( etudid, a["id"], scu.icontag( @@ -394,7 +394,7 @@ def ficheEtud(context, etudid=None, REQUEST=None): # Inscriptions if info["sems"]: # XXX rcl unused ? à voir rcl = ( - """(récapitulatif parcours)""" + """(récapitulatif parcours)""" % info ) else: diff --git a/sco_placement.py b/sco_placement.py index 9487db08..f1d4382b 100644 --- a/sco_placement.py +++ b/sco_placement.py @@ -205,7 +205,7 @@ def do_placement_selectetuds(context, REQUEST): if columns in ("3", "4", "5", "6", "7", "8"): gs = [("group_ids%3Alist=" + urllib.quote_plus(x)) for x in group_ids] query = ( - "evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&" + "evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&" % ( evaluation_id, placement_method, @@ -215,7 +215,7 @@ def do_placement_selectetuds(context, REQUEST): columns, numbering, ) - + "&".join(gs) + + "&".join(gs) ) return REQUEST.RESPONSE.redirect( context.NotesURL() + "/do_placement?" + query diff --git a/sco_pvjury.py b/sco_pvjury.py index 191247c7..e86a5c00 100644 --- a/sco_pvjury.py +++ b/sco_pvjury.py @@ -867,7 +867,7 @@ def formsemestre_lettres_individuelles( PDFLOCK.release() if not pdfdoc: return REQUEST.RESPONSE.redirect( - "formsemestre_status?formsemestre_id={}&head_message=Aucun%20%C3%A9tudiant%20n%27a%20de%20d%C3%A9cision%20de%20jury".format( + "formsemestre_status?formsemestre_id={}&head_message=Aucun%20%C3%A9tudiant%20n%27a%20de%20d%C3%A9cision%20de%20jury".format( formsemestre_id ) ) diff --git a/sco_recapcomplet.py b/sco_recapcomplet.py index 90e1832f..61ae69e0 100644 --- a/sco_recapcomplet.py +++ b/sco_recapcomplet.py @@ -173,7 +173,7 @@ def formsemestre_recapcomplet( ) else: H.append( - """Saisie des décisions du jury""" + """Saisie des décisions du jury""" % formsemestre_id ) H.append("

    ") @@ -556,9 +556,9 @@ def make_formsemestre_recapcomplet( """ \ No newline at end of file diff --git a/static/mobile/logo192.png b/static/mobile/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/static/mobile/manifest.json b/static/mobile/manifest.json new file mode 100644 index 00000000..6850f28f --- /dev/null +++ b/static/mobile/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "scologo.png", + "type": "image/png", + "sizes": "84x126" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/static/mobile/robots.txt b/static/mobile/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/static/mobile/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/static/mobile/scologo.png b/static/mobile/scologo.png new file mode 100644 index 0000000000000000000000000000000000000000..525f5d34cddab3fc27deacd1e75a1dca703eb5e2 GIT binary patch literal 28219 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:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col-auto,.col-lg,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-auto,.col-md,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md-auto,.col-sm,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;max-width:100%}.row-cols-1>*{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-webkit-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-webkit-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-webkit-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-order:-1;order:-1}.order-last{-webkit-order:13;order:13}.order-0{-webkit-order:0;order:0}.order-1{-webkit-order:1;order:1}.order-2{-webkit-order:2;order:2}.order-3{-webkit-order:3;order:3}.order-4{-webkit-order:4;order:4}.order-5{-webkit-order:5;order:5}.order-6{-webkit-order:6;order:6}.order-7{-webkit-order:7;order:7}.order-8{-webkit-order:8;order:8}.order-9{-webkit-order:9;order:9}.order-10{-webkit-order:10;order:10}.order-11{-webkit-order:11;order:11}.order-12{-webkit-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-webkit-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-webkit-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-webkit-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-order:-1;order:-1}.order-sm-last{-webkit-order:13;order:13}.order-sm-0{-webkit-order:0;order:0}.order-sm-1{-webkit-order:1;order:1}.order-sm-2{-webkit-order:2;order:2}.order-sm-3{-webkit-order:3;order:3}.order-sm-4{-webkit-order:4;order:4}.order-sm-5{-webkit-order:5;order:5}.order-sm-6{-webkit-order:6;order:6}.order-sm-7{-webkit-order:7;order:7}.order-sm-8{-webkit-order:8;order:8}.order-sm-9{-webkit-order:9;order:9}.order-sm-10{-webkit-order:10;order:10}.order-sm-11{-webkit-order:11;order:11}.order-sm-12{-webkit-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-webkit-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-webkit-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-webkit-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-order:-1;order:-1}.order-md-last{-webkit-order:13;order:13}.order-md-0{-webkit-order:0;order:0}.order-md-1{-webkit-order:1;order:1}.order-md-2{-webkit-order:2;order:2}.order-md-3{-webkit-order:3;order:3}.order-md-4{-webkit-order:4;order:4}.order-md-5{-webkit-order:5;order:5}.order-md-6{-webkit-order:6;order:6}.order-md-7{-webkit-order:7;order:7}.order-md-8{-webkit-order:8;order:8}.order-md-9{-webkit-order:9;order:9}.order-md-10{-webkit-order:10;order:10}.order-md-11{-webkit-order:11;order:11}.order-md-12{-webkit-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-webkit-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-webkit-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-webkit-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-order:-1;order:-1}.order-lg-last{-webkit-order:13;order:13}.order-lg-0{-webkit-order:0;order:0}.order-lg-1{-webkit-order:1;order:1}.order-lg-2{-webkit-order:2;order:2}.order-lg-3{-webkit-order:3;order:3}.order-lg-4{-webkit-order:4;order:4}.order-lg-5{-webkit-order:5;order:5}.order-lg-6{-webkit-order:6;order:6}.order-lg-7{-webkit-order:7;order:7}.order-lg-8{-webkit-order:8;order:8}.order-lg-9{-webkit-order:9;order:9}.order-lg-10{-webkit-order:10;order:10}.order-lg-11{-webkit-order:11;order:11}.order-lg-12{-webkit-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-webkit-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-webkit-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-webkit-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-order:-1;order:-1}.order-xl-last{-webkit-order:13;order:13}.order-xl-0{-webkit-order:0;order:0}.order-xl-1{-webkit-order:1;order:1}.order-xl-2{-webkit-order:2;order:2}.order-xl-3{-webkit-order:3;order:3}.order-xl-4{-webkit-order:4;order:4}.order-xl-5{-webkit-order:5;order:5}.order-xl-6{-webkit-order:6;order:6}.order-xl-7{-webkit-order:7;order:7}.order-xl-8{-webkit-order:8;order:8}.order-xl-9{-webkit-order:9;order:9}.order-xl-10{-webkit-order:10;order:10}.order-xl-11{-webkit-order:11;order:11}.order-xl-12{-webkit-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th,.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:hsla(0,0%,100%,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-flex;display:inline-flex;-webkit-align-items:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-flex;display:flex;-webkit-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-items:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{-webkit-justify-content:center;justify-content:center}.form-inline .form-group,.form-inline label{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;margin-bottom:0}.form-inline .form-group{-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-flex-flow:row wrap;flex-flow:row wrap}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-webkit-flex-shrink:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success.focus,.btn-success:focus,.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info.focus,.btn-info:focus,.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light.focus,.btn-light:focus,.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";display:none}.dropleft .dropdown-toggle:before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-flex;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;align-items:flex-start;-webkit-justify-content:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-webkit-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label:after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3),.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label:after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-webkit-flex;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-webkit-inline-flex;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label:before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label:before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label:before{pointer-events:none;background-color:#fff;border:1px solid #adb5bd}.custom-control-label:after,.custom-control-label:before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:""}.custom-control-label:after{background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label:after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{position:relative;width:100%;height:calc(1.5em + .75rem + 2px)}.custom-file-input{z-index:2;margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{left:0;z-index:1;height:calc(1.5em + .75rem + 2px);overflow:hidden;font-weight:400;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label,.custom-file-label:after{position:absolute;top:0;right:0;padding:.375rem .75rem;line-height:1.5;color:#495057}.custom-file-label:after{bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-webkit-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-webkit-flex-basis:0;flex-basis:0;-webkit-flex-grow:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;padding:.5rem 1rem}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:space-between;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-webkit-flex-basis:100%;flex-basis:100%;-webkit-flex-grow:1;flex-grow:1;-webkit-align-items:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-justify-content:flex-start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-webkit-flex!important;display:flex!important;-webkit-flex-basis:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-justify-content:flex-start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-webkit-flex!important;display:flex!important;-webkit-flex-basis:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-justify-content:flex-start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-webkit-flex!important;display:flex!important;-webkit-flex-basis:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-justify-content:flex-start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-webkit-flex!important;display:flex!important;-webkit-flex-basis:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-webkit-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-justify-content:flex-start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-webkit-flex!important;display:flex!important;-webkit-flex-basis:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:hsla(0,0%,100%,.5);border-color:hsla(0,0%,100%,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-webkit-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-bottom:-.75rem;border-bottom:0}.card-header-pills,.card-header-tabs{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-webkit-flex-shrink:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-webkit-flex;display:flex;-webkit-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-webkit-flex:1 0;flex:1 0;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-webkit-flex;display:flex;-webkit-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-flex:1 0;flex:1 0;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;grid-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-flex;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{height:1rem;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress,.progress-bar{display:-webkit-flex;display:flex;overflow:hidden}.progress-bar{-webkit-flex-direction:column;flex-direction:column;-webkit-justify-content:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start}.media-body{-webkit-flex:1 1;flex:1 1}.list-group{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-webkit-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-webkit-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-webkit-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-webkit-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-webkit-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-webkit-flex-basis:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translateY(-50px);transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-webkit-flex;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-webkit-flex-shrink:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-webkit-flex-direction:column;flex-direction:column;-webkit-justify-content:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{position:relative;display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start;-webkit-justify-content:space-between;justify-content:space-between;padding:1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:flex-end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow:before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{top:0;left:0;z-index:1060;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover,.popover .arrow{position:absolute;display:block}.popover .arrow{width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow:after,.popover .arrow:before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{top:0;border-width:0 .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{top:1px;border-width:0 .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner:after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-webkit-flex;display:flex;-webkit-justify-content:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-webkit-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spinner-border{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid;border-right:.25em solid transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-flex!important;display:flex!important}.d-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-flex!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-flex!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-flex!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-flex!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-flex!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-flex!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive:before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9:before{padding-top:42.857143%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{-webkit-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-webkit-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-webkit-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-webkit-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-webkit-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-webkit-flex-grow:0!important;flex-grow:0!important}.flex-grow-1{-webkit-flex-grow:1!important;flex-grow:1!important}.flex-shrink-0{-webkit-flex-shrink:0!important;flex-shrink:0!important}.flex-shrink-1{-webkit-flex-shrink:1!important;flex-shrink:1!important}.justify-content-start{-webkit-justify-content:flex-start!important;justify-content:flex-start!important}.justify-content-end{-webkit-justify-content:flex-end!important;justify-content:flex-end!important}.justify-content-center{-webkit-justify-content:center!important;justify-content:center!important}.justify-content-between{-webkit-justify-content:space-between!important;justify-content:space-between!important}.justify-content-around{-webkit-justify-content:space-around!important;justify-content:space-around!important}.align-items-start{-webkit-align-items:flex-start!important;align-items:flex-start!important}.align-items-end{-webkit-align-items:flex-end!important;align-items:flex-end!important}.align-items-center{-webkit-align-items:center!important;align-items:center!important}.align-items-baseline{-webkit-align-items:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-align-items:stretch!important;align-items:stretch!important}.align-content-start{-webkit-align-content:flex-start!important;align-content:flex-start!important}.align-content-end{-webkit-align-content:flex-end!important;align-content:flex-end!important}.align-content-center{-webkit-align-content:center!important;align-content:center!important}.align-content-between{-webkit-align-content:space-between!important;align-content:space-between!important}.align-content-around{-webkit-align-content:space-around!important;align-content:space-around!important}.align-content-stretch{-webkit-align-content:stretch!important;align-content:stretch!important}.align-self-auto{-webkit-align-self:auto!important;align-self:auto!important}.align-self-start{-webkit-align-self:flex-start!important;align-self:flex-start!important}.align-self-end{-webkit-align-self:flex-end!important;align-self:flex-end!important}.align-self-center{-webkit-align-self:center!important;align-self:center!important}.align-self-baseline{-webkit-align-self:baseline!important;align-self:baseline!important}.align-self-stretch{-webkit-align-self:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-webkit-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-webkit-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-webkit-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-webkit-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-webkit-flex-grow:0!important;flex-grow:0!important}.flex-sm-grow-1{-webkit-flex-grow:1!important;flex-grow:1!important}.flex-sm-shrink-0{-webkit-flex-shrink:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-webkit-flex-shrink:1!important;flex-shrink:1!important}.justify-content-sm-start{-webkit-justify-content:flex-start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-justify-content:flex-end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-justify-content:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-justify-content:space-between!important;justify-content:space-between!important}.justify-content-sm-around{-webkit-justify-content:space-around!important;justify-content:space-around!important}.align-items-sm-start{-webkit-align-items:flex-start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-align-items:flex-end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-align-items:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-align-items:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-align-items:stretch!important;align-items:stretch!important}.align-content-sm-start{-webkit-align-content:flex-start!important;align-content:flex-start!important}.align-content-sm-end{-webkit-align-content:flex-end!important;align-content:flex-end!important}.align-content-sm-center{-webkit-align-content:center!important;align-content:center!important}.align-content-sm-between{-webkit-align-content:space-between!important;align-content:space-between!important}.align-content-sm-around{-webkit-align-content:space-around!important;align-content:space-around!important}.align-content-sm-stretch{-webkit-align-content:stretch!important;align-content:stretch!important}.align-self-sm-auto{-webkit-align-self:auto!important;align-self:auto!important}.align-self-sm-start{-webkit-align-self:flex-start!important;align-self:flex-start!important}.align-self-sm-end{-webkit-align-self:flex-end!important;align-self:flex-end!important}.align-self-sm-center{-webkit-align-self:center!important;align-self:center!important}.align-self-sm-baseline{-webkit-align-self:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-webkit-align-self:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-webkit-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-webkit-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-webkit-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-webkit-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-webkit-flex-grow:0!important;flex-grow:0!important}.flex-md-grow-1{-webkit-flex-grow:1!important;flex-grow:1!important}.flex-md-shrink-0{-webkit-flex-shrink:0!important;flex-shrink:0!important}.flex-md-shrink-1{-webkit-flex-shrink:1!important;flex-shrink:1!important}.justify-content-md-start{-webkit-justify-content:flex-start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-justify-content:flex-end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-justify-content:center!important;justify-content:center!important}.justify-content-md-between{-webkit-justify-content:space-between!important;justify-content:space-between!important}.justify-content-md-around{-webkit-justify-content:space-around!important;justify-content:space-around!important}.align-items-md-start{-webkit-align-items:flex-start!important;align-items:flex-start!important}.align-items-md-end{-webkit-align-items:flex-end!important;align-items:flex-end!important}.align-items-md-center{-webkit-align-items:center!important;align-items:center!important}.align-items-md-baseline{-webkit-align-items:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-align-items:stretch!important;align-items:stretch!important}.align-content-md-start{-webkit-align-content:flex-start!important;align-content:flex-start!important}.align-content-md-end{-webkit-align-content:flex-end!important;align-content:flex-end!important}.align-content-md-center{-webkit-align-content:center!important;align-content:center!important}.align-content-md-between{-webkit-align-content:space-between!important;align-content:space-between!important}.align-content-md-around{-webkit-align-content:space-around!important;align-content:space-around!important}.align-content-md-stretch{-webkit-align-content:stretch!important;align-content:stretch!important}.align-self-md-auto{-webkit-align-self:auto!important;align-self:auto!important}.align-self-md-start{-webkit-align-self:flex-start!important;align-self:flex-start!important}.align-self-md-end{-webkit-align-self:flex-end!important;align-self:flex-end!important}.align-self-md-center{-webkit-align-self:center!important;align-self:center!important}.align-self-md-baseline{-webkit-align-self:baseline!important;align-self:baseline!important}.align-self-md-stretch{-webkit-align-self:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-webkit-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-webkit-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-webkit-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-webkit-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-webkit-flex-grow:0!important;flex-grow:0!important}.flex-lg-grow-1{-webkit-flex-grow:1!important;flex-grow:1!important}.flex-lg-shrink-0{-webkit-flex-shrink:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-webkit-flex-shrink:1!important;flex-shrink:1!important}.justify-content-lg-start{-webkit-justify-content:flex-start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-justify-content:flex-end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-justify-content:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-justify-content:space-between!important;justify-content:space-between!important}.justify-content-lg-around{-webkit-justify-content:space-around!important;justify-content:space-around!important}.align-items-lg-start{-webkit-align-items:flex-start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-align-items:flex-end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-align-items:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-align-items:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-align-items:stretch!important;align-items:stretch!important}.align-content-lg-start{-webkit-align-content:flex-start!important;align-content:flex-start!important}.align-content-lg-end{-webkit-align-content:flex-end!important;align-content:flex-end!important}.align-content-lg-center{-webkit-align-content:center!important;align-content:center!important}.align-content-lg-between{-webkit-align-content:space-between!important;align-content:space-between!important}.align-content-lg-around{-webkit-align-content:space-around!important;align-content:space-around!important}.align-content-lg-stretch{-webkit-align-content:stretch!important;align-content:stretch!important}.align-self-lg-auto{-webkit-align-self:auto!important;align-self:auto!important}.align-self-lg-start{-webkit-align-self:flex-start!important;align-self:flex-start!important}.align-self-lg-end{-webkit-align-self:flex-end!important;align-self:flex-end!important}.align-self-lg-center{-webkit-align-self:center!important;align-self:center!important}.align-self-lg-baseline{-webkit-align-self:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-webkit-align-self:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-webkit-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-webkit-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-webkit-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-webkit-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-webkit-flex-grow:0!important;flex-grow:0!important}.flex-xl-grow-1{-webkit-flex-grow:1!important;flex-grow:1!important}.flex-xl-shrink-0{-webkit-flex-shrink:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-webkit-flex-shrink:1!important;flex-shrink:1!important}.justify-content-xl-start{-webkit-justify-content:flex-start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-justify-content:flex-end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-justify-content:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-justify-content:space-between!important;justify-content:space-between!important}.justify-content-xl-around{-webkit-justify-content:space-around!important;justify-content:space-around!important}.align-items-xl-start{-webkit-align-items:flex-start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-align-items:flex-end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-align-items:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-align-items:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-align-items:stretch!important;align-items:stretch!important}.align-content-xl-start{-webkit-align-content:flex-start!important;align-content:flex-start!important}.align-content-xl-end{-webkit-align-content:flex-end!important;align-content:flex-end!important}.align-content-xl-center{-webkit-align-content:center!important;align-content:center!important}.align-content-xl-between{-webkit-align-content:space-between!important;align-content:space-between!important}.align-content-xl-around{-webkit-align-content:space-around!important;align-content:space-around!important}.align-content-xl-stretch{-webkit-align-content:stretch!important;align-content:stretch!important}.align-self-xl-auto{-webkit-align-self:auto!important;align-self:auto!important}.align-self-xl-start{-webkit-align-self:flex-start!important;align-self:flex-start!important}.align-self-xl-end{-webkit-align-self:flex-end!important;align-self:flex-end!important}.align-self-xl-center{-webkit-align-self:center!important;align-self:center!important}.align-self-xl-baseline{-webkit-align-self:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-webkit-align-self:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{position:fixed;right:0;left:0;z-index:1030}.fixed-bottom{bottom:0}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:transparent}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=2.4c97ca4f.chunk.css.map */ \ No newline at end of file diff --git a/static/mobile/static/css/2.4c97ca4f.chunk.css.map b/static/mobile/static/css/2.4c97ca4f.chunk.css.map new file mode 100644 index 00000000..fed12c88 --- /dev/null +++ b/static/mobile/static/css/2.4c97ca4f.chunk.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack://node_modules/bootstrap/scss/bootstrap.scss","webpack://node_modules/bootstrap/scss/_root.scss","webpack://node_modules/bootstrap/dist/css/dist/css/bootstrap.css","webpack://node_modules/bootstrap/scss/_reboot.scss","webpack://node_modules/bootstrap/scss/vendor/_rfs.scss","webpack://node_modules/bootstrap/dist/css/bootstrap.css","webpack://node_modules/bootstrap/scss/mixins/_hover.scss","webpack://node_modules/bootstrap/scss/_type.scss","webpack://node_modules/bootstrap/scss/mixins/_lists.scss","webpack://node_modules/bootstrap/scss/_images.scss","webpack://node_modules/bootstrap/scss/mixins/_image.scss","webpack://node_modules/bootstrap/scss/mixins/_border-radius.scss","webpack://node_modules/bootstrap/scss/_code.scss","webpack://node_modules/bootstrap/scss/_grid.scss","webpack://node_modules/bootstrap/scss/mixins/_grid.scss","webpack://node_modules/bootstrap/scss/mixins/_breakpoints.scss","webpack://node_modules/bootstrap/scss/mixins/_grid-framework.scss","webpack://node_modules/bootstrap/scss/_tables.scss","webpack://node_modules/bootstrap/scss/mixins/_table-row.scss","webpack://node_modules/bootstrap/scss/_forms.scss","webpack://node_modules/bootstrap/scss/mixins/_transition.scss","webpack://node_modules/bootstrap/scss/mixins/_forms.scss","webpack://node_modules/bootstrap/scss/mixins/_gradients.scss","webpack://node_modules/bootstrap/scss/_buttons.scss","webpack://node_modules/bootstrap/scss/mixins/_buttons.scss","webpack://node_modules/bootstrap/scss/_transitions.scss","webpack://node_modules/bootstrap/scss/_dropdown.scss","webpack://node_modules/bootstrap/scss/mixins/_caret.scss","webpack://node_modules/bootstrap/scss/mixins/_nav-divider.scss","webpack://node_modules/bootstrap/scss/_button-group.scss","webpack://node_modules/bootstrap/scss/_input-group.scss","webpack://node_modules/bootstrap/scss/_custom-forms.scss","webpack://node_modules/bootstrap/scss/_nav.scss","webpack://node_modules/bootstrap/scss/_navbar.scss","webpack://node_modules/bootstrap/scss/_card.scss","webpack://node_modules/bootstrap/scss/_breadcrumb.scss","webpack://node_modules/bootstrap/scss/_pagination.scss","webpack://node_modules/bootstrap/scss/mixins/_pagination.scss","webpack://node_modules/bootstrap/scss/_badge.scss","webpack://node_modules/bootstrap/scss/mixins/_badge.scss","webpack://node_modules/bootstrap/scss/_jumbotron.scss","webpack://node_modules/bootstrap/scss/_alert.scss","webpack://node_modules/bootstrap/scss/mixins/_alert.scss","webpack://node_modules/bootstrap/scss/_progress.scss","webpack://node_modules/bootstrap/scss/_media.scss","webpack://node_modules/bootstrap/scss/_list-group.scss","webpack://node_modules/bootstrap/scss/mixins/_list-group.scss","webpack://node_modules/bootstrap/scss/_close.scss","webpack://node_modules/bootstrap/scss/_toasts.scss","webpack://node_modules/bootstrap/scss/_modal.scss","webpack://node_modules/bootstrap/scss/_tooltip.scss","webpack://node_modules/bootstrap/scss/mixins/_reset-text.scss","webpack://node_modules/bootstrap/scss/_popover.scss","webpack://node_modules/bootstrap/scss/_carousel.scss","webpack://node_modules/bootstrap/scss/mixins/_clearfix.scss","webpack://node_modules/bootstrap/scss/_spinners.scss","webpack://node_modules/bootstrap/scss/utilities/_align.scss","webpack://node_modules/bootstrap/scss/mixins/_background-variant.scss","webpack://node_modules/bootstrap/scss/utilities/_background.scss","webpack://node_modules/bootstrap/scss/utilities/_borders.scss","webpack://node_modules/bootstrap/scss/utilities/_display.scss","webpack://node_modules/bootstrap/scss/utilities/_embed.scss","webpack://node_modules/bootstrap/scss/utilities/_flex.scss","webpack://node_modules/bootstrap/scss/utilities/_float.scss","webpack://node_modules/bootstrap/scss/utilities/_interactions.scss","webpack://node_modules/bootstrap/scss/utilities/_overflow.scss","webpack://node_modules/bootstrap/scss/utilities/_position.scss","webpack://node_modules/bootstrap/scss/utilities/_screenreaders.scss","webpack://node_modules/bootstrap/scss/mixins/_screen-reader.scss","webpack://node_modules/bootstrap/scss/utilities/_shadows.scss","webpack://node_modules/bootstrap/scss/utilities/_sizing.scss","webpack://node_modules/bootstrap/scss/utilities/_spacing.scss","webpack://node_modules/bootstrap/scss/utilities/_stretched-link.scss","webpack://node_modules/bootstrap/scss/utilities/_text.scss","webpack://node_modules/bootstrap/scss/mixins/_text-truncate.scss","webpack://node_modules/bootstrap/scss/mixins/_text-emphasis.scss","webpack://node_modules/bootstrap/scss/mixins/_text-hide.scss","webpack://node_modules/bootstrap/scss/utilities/_visibility.scss","webpack://node_modules/bootstrap/scss/_print.scss"],"names":[],"mappings":"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","file":"2.4c97ca4f.chunk.css","sourcesContent":["/*!\n * Bootstrap v4.6.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"utilities\";\n@import \"print\";\n",":root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","/*!\n * Bootstrap v4.6.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Liberation Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Liberation Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n -webkit-text-decoration-skip-ink: none;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([class]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([class]):hover {\n color: inherit;\n text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n -ms-overflow-style: scrollbar;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n text-align: -webkit-match-parent;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\n[role=\"button\"] {\n cursor: pointer;\n}\n\nselect {\n word-wrap: normal;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton:not(:disabled),\n[type=\"button\"]:not(:disabled),\n[type=\"reset\"]:not(:disabled),\n[type=\"submit\"]:not(:disabled) {\n cursor: pointer;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-wrap: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container,\n.container-fluid,\n.container-sm,\n.container-md,\n.container-lg,\n.container-xl {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container, .container-sm {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container, .container-sm, .container-md {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container, .container-sm, .container-md, .container-lg {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container, .container-sm, .container-md, .container-lg, .container-xl {\n max-width: 1140px;\n }\n}\n\n.row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.row-cols-1 > * {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.row-cols-2 > * {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.row-cols-3 > * {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.row-cols-4 > * {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.row-cols-5 > * {\n -ms-flex: 0 0 20%;\n flex: 0 0 20%;\n max-width: 20%;\n}\n\n.row-cols-6 > * {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n -ms-flex-order: -1;\n order: -1;\n}\n\n.order-last {\n -ms-flex-order: 13;\n order: 13;\n}\n\n.order-0 {\n -ms-flex-order: 0;\n order: 0;\n}\n\n.order-1 {\n -ms-flex-order: 1;\n order: 1;\n}\n\n.order-2 {\n -ms-flex-order: 2;\n order: 2;\n}\n\n.order-3 {\n -ms-flex-order: 3;\n order: 3;\n}\n\n.order-4 {\n -ms-flex-order: 4;\n order: 4;\n}\n\n.order-5 {\n -ms-flex-order: 5;\n order: 5;\n}\n\n.order-6 {\n -ms-flex-order: 6;\n order: 6;\n}\n\n.order-7 {\n -ms-flex-order: 7;\n order: 7;\n}\n\n.order-8 {\n -ms-flex-order: 8;\n order: 8;\n}\n\n.order-9 {\n -ms-flex-order: 9;\n order: 9;\n}\n\n.order-10 {\n -ms-flex-order: 10;\n order: 10;\n}\n\n.order-11 {\n -ms-flex-order: 11;\n order: 11;\n}\n\n.order-12 {\n -ms-flex-order: 12;\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-sm-1 > * {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-sm-2 > * {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-sm-3 > * {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-sm-4 > * {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-sm-5 > * {\n -ms-flex: 0 0 20%;\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-sm-6 > * {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-sm-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-sm-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-sm-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-sm-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-sm-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-sm-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-sm-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-sm-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-sm-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-sm-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-sm-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-sm-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-sm-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-sm-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-md-1 > * {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-md-2 > * {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-md-3 > * {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-md-4 > * {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-md-5 > * {\n -ms-flex: 0 0 20%;\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-md-6 > * {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-md-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-md-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-md-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-md-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-md-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-md-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-md-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-md-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-md-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-md-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-md-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-md-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-md-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-md-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-lg-1 > * {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-lg-2 > * {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-lg-3 > * {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-lg-4 > * {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-lg-5 > * {\n -ms-flex: 0 0 20%;\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-lg-6 > * {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-lg-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-lg-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-lg-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-lg-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-lg-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-lg-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-lg-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-lg-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-lg-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-lg-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-lg-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-lg-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-lg-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-lg-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-xl-1 > * {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-xl-2 > * {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-xl-3 > * {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-xl-4 > * {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-xl-5 > * {\n -ms-flex: 0 0 20%;\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-xl-6 > * {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-xl-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-xl-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-xl-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-xl-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-xl-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-xl-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-xl-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-xl-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-xl-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-xl-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-xl-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-xl-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-xl-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-xl-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n color: #212529;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n color: #212529;\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #343a40;\n border-color: #454d55;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #454d55;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 #495057;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::-webkit-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-moz-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\ninput[type=\"date\"].form-control,\ninput[type=\"time\"].form-control,\ninput[type=\"datetime-local\"].form-control,\ninput[type=\"month\"].form-control {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding: 0.375rem 0;\n margin-bottom: 0;\n font-size: 1rem;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(1.5em + 1rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input[disabled] ~ .form-check-label,\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n -ms-flex-align: center;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.form-row > .col > .valid-tooltip,\n.form-row > [class*=\"col-\"] > .valid-tooltip {\n left: 5px;\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") right 0.75rem center/8px 10px no-repeat, #fff url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.form-row > .col > .invalid-tooltip,\n.form-row > [class*=\"col-\"] > .invalid-tooltip {\n left: 5px;\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") right 0.75rem center/8px 10px no-repeat, #fff url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n text-decoration: none;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #e9ecef;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #adb5bd;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: -ms-inline-flexbox;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: stretch;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n width: 1%;\n min-width: 0;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group:not(.has-validation) > .form-control:not(:last-child),\n.input-group:not(.has-validation) > .custom-select:not(:last-child),\n.input-group:not(.has-validation) > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group.has-validation > .form-control:nth-last-child(n + 3),\n.input-group.has-validation > .custom-select:nth-last-child(n + 3),\n.input-group.has-validation > .custom-file:nth-last-child(n + 3) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: -ms-flexbox;\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn,\n.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text,\n.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn,\n.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n z-index: 1;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n -webkit-print-color-adjust: exact;\n color-adjust: exact;\n}\n\n.custom-control-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n left: 0;\n z-index: -1;\n width: 1rem;\n height: 1.25rem;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background: 50% / 50% 50% no-repeat;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n -webkit-transform: translateX(0.75rem);\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") right 0.75rem center/8px 10px no-repeat;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n display: none;\n}\n\n.custom-select:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 #495057;\n}\n\n.custom-select-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(1.5em + 1rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin: 0;\n overflow: hidden;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input[disabled] ~ .custom-file-label,\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n overflow: hidden;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: calc(1.5em + 0.75rem);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: 1.4rem;\n padding: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: 0;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n -webkit-transition: none;\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -moz-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n -moz-transition: none;\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n -ms-transition: none;\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-link {\n margin-bottom: -1px;\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill > .nav-link,\n.nav-fill .nav-item {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified > .nav-link,\n.nav-justified .nav-item {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar .container,\n.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n -ms-flex-preferred-size: 100%;\n flex-basis: 100%;\n -ms-flex-positive: 1;\n flex-grow: 1;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: 50% / 100% 100% no-repeat;\n}\n\n.navbar-nav-scroll {\n max-height: 75vh;\n overflow-y: auto;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-sm .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-md .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-lg .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-nav-scroll {\n overflow: visible;\n }\n .navbar-expand-xl .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-nav-scroll {\n overflow: visible;\n}\n\n.navbar-expand .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group {\n border-top: inherit;\n border-bottom: inherit;\n}\n\n.card > .list-group:first-child {\n border-top-width: 0;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card > .list-group:last-child {\n border-bottom-width: 0;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card > .card-header + .list-group,\n.card > .list-group + .card-footer {\n border-top: 0;\n}\n\n.card-body {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n min-height: 1px;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n width: 100%;\n}\n\n.card-img,\n.card-img-top {\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img,\n.card-img-bottom {\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n }\n .card-group > .card {\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n -webkit-column-count: 3;\n -moz-column-count: 3;\n column-count: 3;\n -webkit-column-gap: 1.25rem;\n -moz-column-gap: 1.25rem;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion {\n overflow-anchor: none;\n}\n\n.accordion > .card {\n overflow: hidden;\n}\n\n.accordion > .card:not(:last-of-type) {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion > .card:not(:first-of-type) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion > .card > .card-header {\n border-radius: 0;\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n float: left;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: -ms-flexbox;\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 3;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 3;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .badge {\n transition: none;\n }\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\na.badge-primary:focus, a.badge-primary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\na.badge-secondary:focus, a.badge-secondary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\na.badge-success:focus, a.badge-success.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\na.badge-info:focus, a.badge-info.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\na.badge-warning:focus, a.badge-warning.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\na.badge-danger:focus, a.badge-danger.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\na.badge-light:focus, a.badge-light.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\na.badge-dark:focus, a.badge-dark.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: -ms-flexbox;\n display: flex;\n height: 1rem;\n overflow: hidden;\n line-height: 0;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n overflow: hidden;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n -webkit-animation: 1s linear infinite progress-bar-stripes;\n animation: 1s linear infinite progress-bar-stripes;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n -webkit-animation: none;\n animation: none;\n }\n}\n\n.media {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n}\n\n.media-body {\n -ms-flex: 1;\n flex: 1;\n}\n\n.list-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n border-radius: 0.25rem;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n}\n\n.list-group-item:last-child {\n border-bottom-right-radius: inherit;\n border-bottom-left-radius: inherit;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-item + .list-group-item {\n border-top-width: 0;\n}\n\n.list-group-item + .list-group-item.active {\n margin-top: -1px;\n border-top-width: 1px;\n}\n\n.list-group-horizontal {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.list-group-horizontal > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n}\n\n.list-group-horizontal > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n}\n\n.list-group-horizontal > .list-group-item.active {\n margin-top: 0;\n}\n\n.list-group-horizontal > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n}\n\n.list-group-horizontal > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-sm > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-md > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-lg > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-xl > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n.list-group-flush {\n border-radius: 0;\n}\n\n.list-group-flush > .list-group-item {\n border-width: 0 0 1px;\n}\n\n.list-group-flush > .list-group-item:last-child {\n border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n -ms-flex-preferred-size: 350px;\n flex-basis: 350px;\n max-width: 350px;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n opacity: 0;\n border-radius: 0.25rem;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: -webkit-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;\n -webkit-transform: translate(0, -50px);\n transform: translate(0, -50px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n -webkit-transform: none;\n transform: none;\n}\n\n.modal.modal-static .modal-dialog {\n -webkit-transform: scale(1.02);\n transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n display: -ms-flexbox;\n display: flex;\n max-height: calc(100% - 1rem);\n}\n\n.modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 1rem);\n overflow: hidden;\n}\n\n.modal-dialog-scrollable .modal-header,\n.modal-dialog-scrollable .modal-footer {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n}\n\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n min-height: calc(100% - 1rem);\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - 1rem);\n height: -webkit-min-content;\n height: -moz-min-content;\n height: min-content;\n content: \"\";\n}\n\n.modal-dialog-centered.modal-dialog-scrollable {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n height: 100%;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable .modal-content {\n max-height: none;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable::before {\n content: none;\n}\n\n.modal-content {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #dee2e6;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: end;\n justify-content: flex-end;\n padding: 0.75rem;\n border-top: 1px solid #dee2e6;\n border-bottom-right-radius: calc(0.3rem - 1px);\n border-bottom-left-radius: calc(0.3rem - 1px);\n}\n\n.modal-footer > * {\n margin: 0.25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-scrollable {\n max-height: calc(100% - 3.5rem);\n }\n .modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 3.5rem);\n }\n .modal-dialog-centered {\n min-height: calc(100% - 3.5rem);\n }\n .modal-dialog-centered::before {\n height: calc(100vh - 3.5rem);\n height: -webkit-min-content;\n height: -moz-min-content;\n height: min-content;\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Liberation Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Liberation Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top > .arrow, .bs-popover-auto[x-placement^=\"top\"] > .arrow {\n bottom: calc(-0.5rem - 1px);\n}\n\n.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^=\"top\"] > .arrow::before {\n bottom: 0;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^=\"top\"] > .arrow::after {\n bottom: 1px;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right > .arrow, .bs-popover-auto[x-placement^=\"right\"] > .arrow {\n left: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^=\"right\"] > .arrow::before {\n left: 0;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^=\"right\"] > .arrow::after {\n left: 1px;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow {\n top: calc(-0.5rem - 1px);\n}\n\n.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::before {\n top: 0;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::after {\n top: 1px;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left > .arrow, .bs-popover-auto[x-placement^=\"left\"] > .arrow {\n right: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^=\"left\"] > .arrow::before {\n right: 0;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^=\"left\"] > .arrow::after {\n right: 1px;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n -ms-touch-action: pan-y;\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n transition: -webkit-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n -webkit-transform: translateX(100%);\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n -webkit-transform: translateX(-100%);\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n -webkit-transform: none;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: opacity 0s 0.6s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: 50% / 100% 100% no-repeat;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n -ms-flex: 0 1 auto;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@-webkit-keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n -webkit-animation: .75s linear infinite spinner-border;\n animation: .75s linear infinite spinner-border;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@-webkit-keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n -webkit-transform: none;\n transform: none;\n }\n}\n\n@keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n -webkit-transform: none;\n transform: none;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n -webkit-animation: .75s linear infinite spinner-grow;\n animation: .75s linear infinite spinner-grow;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner-border,\n .spinner-grow {\n -webkit-animation-duration: 1.5s;\n animation-duration: 1.5s;\n }\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded-sm {\n border-radius: 0.2rem !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-lg {\n border-radius: 0.3rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n}\n\n.d-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-md-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-print-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n}\n\n.flex-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n}\n\n.justify-content-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n}\n\n.align-items-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n}\n\n.align-items-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n}\n\n.align-items-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n}\n\n.align-items-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n}\n\n.align-content-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n}\n\n.align-content-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n}\n\n.align-content-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n}\n\n.align-content-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n}\n\n.align-content-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n}\n\n.align-self-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n}\n\n.align-self-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n}\n\n.align-self-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n}\n\n.align-self-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n}\n\n.align-self-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-sm-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-sm-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-sm-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-sm-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-sm-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-sm-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-sm-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-sm-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-md-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-md-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-md-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-md-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-md-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-md-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-md-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-md-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-md-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-md-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-md-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-md-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-md-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-md-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-md-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-md-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-lg-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-lg-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-lg-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-lg-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-lg-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-lg-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-lg-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-lg-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-xl-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-xl-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-xl-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-xl-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-xl-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-xl-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-xl-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-xl-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.user-select-all {\n -webkit-user-select: all !important;\n -moz-user-select: all !important;\n user-select: all !important;\n}\n\n.user-select-auto {\n -webkit-user-select: auto !important;\n -moz-user-select: auto !important;\n -ms-user-select: auto !important;\n user-select: auto !important;\n}\n\n.user-select-none {\n -webkit-user-select: none !important;\n -moz-user-select: none !important;\n -ms-user-select: none !important;\n user-select: none !important;\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports ((position: -webkit-sticky) or (position: sticky)) {\n .sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n pointer-events: auto;\n content: \"\";\n background-color: rgba(0, 0, 0, 0);\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-break {\n word-break: break-word !important;\n word-wrap: break-word !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -webkit-tap-highlight-color: rgba($black, 0); // 5\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\n// TODO: remove in v5\n// stylelint-disable-next-line selector-list-comma-newline-after\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use\n// the `inherit` value on things like `` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Future-proof rule: in browsers that support :focus-visible, suppress the focus outline\n// on elements that programmatically receive focus but wouldn't normally show a visible\n// focus outline. In general, this would mean that the outline is only applied if the\n// interaction that led to the element receiving programmatic focus was a keyboard interaction,\n// or the browser has somehow determined that the user is primarily a keyboard user and/or\n// wants focus outlines to always be presented.\n//\n// See https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible\n// and https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

    `-`

    ` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable-next-line selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

    `s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Remove the bottom border in Firefox 39-.\n// 5. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 4\n text-decoration-skip-ink: none; // 5\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n @include font-size(80%); // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n @include font-size(75%);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n\n @include hover() {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n color: inherit;\n text-decoration: none;\n\n @include hover() {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n // Disable auto-hiding scrollbar in IE & legacy Edge to avoid overlap,\n // making it impossible to interact with the content\n -ms-overflow-style: scrollbar;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg {\n // Workaround for the SVG overflow bug in IE10/11 is still required.\n // See https://github.com/twbs/bootstrap/issues/26878\n overflow: hidden;\n vertical-align: middle;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// Set the cursor for non-`\r\n \r\n \r\n {this.result()}\r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\n\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /* getJsonData () {\r\n return require('..\\\\json\\\\sems.json');\r\n } */\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ semestres: res.data });\r\n }));\r\n }\r\n\r\n getData () {\r\n return this.state.semestres\r\n }\r\n\r\n dismissToast() {\r\n this.setState({toast: false})\r\n }\r\n\r\n render() {\r\n return (\r\n

    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\n\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + 'list_depts?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ depts: res.data })\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\n\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n fetch(BASE_URL, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': 'Basic ' + btoa(login + \":\" + pass)\r\n },\r\n })\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\n\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\n\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n onFormSubmit = e => {\r\n e.preventDefault()\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n console.log(formDataObj)\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust')) {\r\n reqstr += \"&estjust=True\"\r\n }\r\n if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n postData(data) {\r\n console.log(data)\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n if (response.status === 200) {\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\n\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n postData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n this.setState({isOpen: false})\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n console.log(response)\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\n\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n abs: [],\r\n isOpen: false,\r\n isDelOpen: false,\r\n isEditOpen: false,\r\n data: {}\r\n }\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({abs: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n \r\n } {this.props.id !== \"\" &&\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'élève
    \r\n }\r\n {this.state.abs.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\n\r\nclass Eleves extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ students: res.data});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des élèves

    \r\n
    \r\n
    \r\n {this.state.students.map((student, index) => {\r\n return (\r\n
    \r\n \r\n {student.nom_disp} {student.prenom}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Eleves","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\n\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n this.getJsonData = this.getJsonData.bind(this);\r\n }\r\n\r\n getJsonData () {\r\n this.setState({bltn: require('..\\\\..\\\\json\\\\bltn.json')}, () => {\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n this.setState({loaded: true})\r\n })\r\n })\r\n\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' +\r\n sem +'&etudid=' + this.props.id +'&format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ bltn: res.data }, () => {\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n );\r\n }\r\n\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n fetch( BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?\" +\r\n \"formsemestre_id=\" + sem + \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\", {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then( res => res.blob() )\r\n .then( blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Eleves from \"./GestionSemestre/Eleves\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\n\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n res.data.map((student, index) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n );\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\n\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /* getJsonData () {\r\n return require('..\\\\json\\\\sems.json');\r\n } */\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ semestres: res.data });\r\n }));\r\n }\r\n\r\n getData () {\r\n return this.state.semestres\r\n }\r\n\r\n dismissToast() {\r\n this.setState({toast: false})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat === \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat !== \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\n\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + 'list_depts?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ depts: res.data })\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\n\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n fetch(BASE_URL, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': 'Basic ' + btoa(login + \":\" + pass)\r\n },\r\n })\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\n\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\n\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n onFormSubmit = e => {\r\n e.preventDefault()\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n console.log(formDataObj)\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust')) {\r\n reqstr += \"&estjust=True\"\r\n }\r\n if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n postData(data) {\r\n console.log(data)\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n if (response.status === 200) {\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\n\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n postData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n this.setState({isOpen: false})\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n console.log(response)\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\n\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n abs: [],\r\n isOpen: false,\r\n isDelOpen: false,\r\n isEditOpen: false,\r\n data: {}\r\n }\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({abs: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n \r\n } {this.props.id !== \"\" &&\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'élève
    \r\n }\r\n {this.state.abs.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\n\r\nclass Eleves extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ students: res.data});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des élèves

    \r\n
    \r\n
    \r\n {this.state.students.map((student, index) => {\r\n return (\r\n
    \r\n \r\n {student.nom_disp} {student.prenom}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Eleves","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\n\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n this.getJsonData = this.getJsonData.bind(this);\r\n }\r\n\r\n getJsonData () {\r\n this.setState({bltn: require('..\\\\..\\\\json\\\\bltn.json')}, () => {\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n this.setState({loaded: true})\r\n })\r\n })\r\n\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' +\r\n sem +'&etudid=' + this.props.id +'&format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ bltn: res.data }, () => {\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n );\r\n }\r\n\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n fetch( BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?\" +\r\n \"formsemestre_id=\" + sem + \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\", {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then( res => res.blob() )\r\n .then( blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Eleves from \"./GestionSemestre/Eleves\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\n\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n res.data.map((student, index) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n );\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\n\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ semestres: res.data });\r\n }));\r\n }\r\n\r\n getData () {\r\n return this.state.semestres\r\n }\r\n\r\n dismissToast() {\r\n this.setState({toast: false})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat === \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat !== \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\n\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + 'list_depts?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ depts: res.data })\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\n\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n fetch(BASE_URL, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': 'Basic ' + btoa(login + \":\" + pass)\r\n },\r\n })\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\n\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport Absences from '../Absences'\r\n\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n postData(data) {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\n\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n postData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\n\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n postData(data) {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\n\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {}\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n getData() {\r\n this.getAbs()\r\n this.getAbsJust()\r\n }\r\n\r\n getAbs() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Recuperation de la liste des absences\r\n this.setState({abs: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n getAbsJust() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Recuperation de la liste des absences\r\n this.setState({absjust: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'élève
    \r\n }\r\n {this.state.abs.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\n\r\nclass Eleves extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des élèves inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des élèves

    \r\n
    \r\n {this.state.students.map((students, index) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Eleves","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\n\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' +\r\n sem +'&etudid=' + this.props.id +'&format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n );\r\n }\r\n\r\n // Recuperation du bulletin au format PDF\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n fetch( BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?\" +\r\n \"formsemestre_id=\" + sem + \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\", {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then( res => res.blob() )\r\n .then( blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Eleves from \"./GestionSemestre/Eleves\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\n\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n // Recuperation de la liste des semestres\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Création d'une liste pour le select\r\n res.data.map((student, index) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n );\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {}\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\n\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ semestres: res.data });\r\n }));\r\n }\r\n\r\n getData () {\r\n return this.state.semestres\r\n }\r\n\r\n dismissToast() {\r\n this.setState({toast: false})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat === \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if(sem.etat !== \"1\")\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\n\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + 'list_depts?format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n this.setState({ depts: res.data })\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\n\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n fetch(BASE_URL, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': 'Basic ' + btoa(login + \":\" + pass)\r\n },\r\n })\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\n\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport Absences from '../Absences'\r\n\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n postData(data) {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\n\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n postData() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\n\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n postData(data) {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", {\r\n method: 'POST',\r\n verify: false,\r\n credentials: 'include',\r\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\r\n body: data\r\n })\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\n\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {}\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n getData() {\r\n this.getAbs()\r\n this.getAbsJust()\r\n }\r\n\r\n getAbs() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Recuperation de la liste des absences\r\n this.setState({abs: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n getAbsJust() {\r\n let dept = window.location.href.split('/')[6]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n fetch(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Recuperation de la liste des absences\r\n this.setState({absjust: res.data})\r\n })\r\n );\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'élève
    \r\n }\r\n {this.state.abs.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs, index) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\n\r\nclass Eleves extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des élèves inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }));\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des élèves

    \r\n
    \r\n {this.state.students.map((students, index) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Eleves","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\n\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n getData() {\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' +\r\n sem +'&etudid=' + this.props.id +'&format=json', {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n response.json().then(data => ({\r\n data: data,\r\n status: response.status\r\n })\r\n ).then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n );\r\n }\r\n\r\n // Recuperation du bulletin au format PDF\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n fetch( BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?\" +\r\n \"formsemestre_id=\" + sem + \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\", {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then( res => res.blob() )\r\n .then( blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod, index) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Eleves from \"./GestionSemestre/Eleves\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\n\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n // Recuperation de la liste des semestres\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n let BASE_URL = window.$api_url\r\n fetch(BASE_URL + dept +\r\n '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {\r\n method: 'GET',\r\n verify: false,\r\n credentials: 'include',\r\n })\r\n .then(response =>\r\n // Traitement des données JSON\r\n response.json().then(data => ({\r\n data: data,\r\n })\r\n ).then(res => {\r\n // Création d'une liste pour le select\r\n res.data.map((student, index) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n );\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0]});\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n (Responsable: {this.state.semestre.responsables})

    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {}\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== \"\") &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })}\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[6]\r\n let sem = window.location.href.split('/')[8]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n resp: [],\r\n loading: true\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.setState({loading: true})\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n // Recuperation des infos de semestre\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});\r\n // Recuperation des noms complets des responsables\r\n res.data[0].responsables.map((resp) =>\r\n getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)\r\n .then(res => {\r\n let joined = this.state.resp.concat(res.data.nomplogin)\r\n this.setState({resp: joined, loading: false})\r\n })\r\n .catch(error => {\r\n this.setState({resp: this.state.resp_l, loading: false})\r\n })\r\n )\r\n })\r\n\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.state.loading === false ?\r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n {this.state.resp.length === 1 ? \"Responsable: (\" : \"Responsables (\"}\r\n {this.state.resp.map((resp, index) => {\r\n if (index !== this.state.resp.length-1) {return (resp + \", \")}\r\n else {return (resp + \")\")}\r\n })}\r\n

    \r\n :\r\n // En cas de chargement\r\n \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Spinner, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {},\r\n // En cours de recuperation de données\r\n loading: false\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data, loading: false}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {this.state.loading === true &&\r\n \r\n }\r\n {(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== \"\" && this.state.loading === false) &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n // this.getData()\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps !== this.props) {\r\n if (this.props.students.length) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.length !== 0 ?\r\n this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })\r\n :\r\n
    \r\n \r\n
    \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown, Spinner} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false,\r\n loading: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true, loading: false})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loading === true && this.state.loaded === false &&\r\n \r\n }\r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n defaulttab: \"Accueil\",\r\n defaultsel: \"\"\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n\r\n if (this.props.location.tab) {\r\n this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,\r\n id: this.props.location.etudid, name: this.state.selectOptions.find(option => option.value === this.state.id)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({students: res.data})\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n resp: [],\r\n loading: true\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.setState({loading: true})\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n // Recuperation des infos de semestre\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});\r\n // Recuperation des noms complets des responsables\r\n res.data[0].responsables.map((resp) =>\r\n getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)\r\n .then(res => {\r\n let joined = this.state.resp.concat(res.data.nomplogin)\r\n this.setState({resp: joined, loading: false})\r\n })\r\n .catch(error => {\r\n this.setState({resp: this.state.resp_l, loading: false})\r\n })\r\n )\r\n })\r\n\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.state.loading === false ?\r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n {this.state.resp.length === 1 ? \"Responsable: (\" : \"Responsables (\"}\r\n {this.state.resp.map((resp, index) => {\r\n if (index !== this.state.resp.length-1) {return (resp + \", \")}\r\n else {return (resp + \")\")}\r\n })}\r\n

    \r\n :\r\n // En cas de chargement\r\n \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Spinner, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {},\r\n // En cours de recuperation de données\r\n loading: false\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data, loading: false}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {this.state.loading === true &&\r\n \r\n }\r\n {(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== \"\" && this.state.loading === false) &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n // this.getData()\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps !== this.props) {\r\n if (this.props.students.length) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.length !== 0 ?\r\n this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })\r\n :\r\n
    \r\n \r\n
    \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown, Spinner} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false,\r\n loading: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true, loading: false})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loading === true && this.state.loaded === false &&\r\n \r\n }\r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n {this.state.bltn.hasOwnProperty('rang') &&\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n {ue.hasOwnProperty('rang') &&\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n {mod.hasOwnProperty('rang') &&\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n defaulttab: \"Accueil\",\r\n defaultsel: \"\"\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n\r\n if (this.props.location.tab) {\r\n this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,\r\n id: this.props.location.etudid, name: this.state.selectOptions.find(option => option.value === this.state.id)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({students: res.data})\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n resp: [],\r\n loading: true\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.setState({loading: true})\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n // Recuperation des infos de semestre\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});\r\n // Recuperation des noms complets des responsables\r\n res.data[0].responsables.map((resp) =>\r\n getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)\r\n .then(res => {\r\n let joined = this.state.resp.concat(res.data.nomplogin)\r\n this.setState({resp: joined, loading: false})\r\n })\r\n .catch(error => {\r\n this.setState({resp: this.state.resp_l, loading: false})\r\n })\r\n )\r\n })\r\n\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.state.loading === false ?\r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n {this.state.resp.length === 1 ? \"Responsable: (\" : \"Responsables (\"}\r\n {this.state.resp.map((resp, index) => {\r\n if (index !== this.state.resp.length-1) {return (resp + \", \")}\r\n else {return (resp + \")\")}\r\n })}\r\n

    \r\n :\r\n // En cas de chargement\r\n \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Spinner, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {},\r\n // En cours de recuperation de données\r\n loading: false\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data, loading: false}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {this.state.loading === true &&\r\n \r\n }\r\n {(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== \"\" && this.state.loading === false) &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n // this.getData()\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps !== this.props) {\r\n if (this.props.students.length) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.length !== 0 ?\r\n this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })\r\n :\r\n
    \r\n \r\n
    \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown, Spinner} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false,\r\n loading: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true, loading: false})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loading === true && this.state.loaded === false &&\r\n \r\n }\r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n defaulttab: \"Accueil\",\r\n defaultsel: \"\"\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n\r\n if (this.props.location.tab) {\r\n this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,\r\n id: this.props.location.etudid, name: this.state.selectOptions.find(option => option.value === this.state.id)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({students: res.data})\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n resp: [],\r\n loading: true\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.setState({loading: true})\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n // Recuperation des infos de semestre\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});\r\n // Recuperation des noms complets des responsables\r\n res.data[0].responsables.map((resp) =>\r\n getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)\r\n .then(res => {\r\n let joined = this.state.resp.concat(res.data.nomplogin)\r\n this.setState({resp: joined, loading: false})\r\n })\r\n .catch(error => {\r\n this.setState({resp: this.state.resp_l, loading: false})\r\n })\r\n )\r\n })\r\n\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.state.loading === false ?\r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n {this.state.resp.length === 1 ? \"Responsable: (\" : \"Responsables (\"}\r\n {this.state.resp.map((resp, index) => {\r\n if (index !== this.state.resp.length-1) {return (resp + \", \")}\r\n else {return (resp + \")\")}\r\n })}\r\n

    \r\n :\r\n // En cas de chargement\r\n \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Spinner, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {},\r\n // En cours de recuperation de données\r\n loading: false\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data, loading: false}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {this.state.loading === true &&\r\n \r\n }\r\n {(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== \"\" && this.state.loading === false) &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\nimport Select from \"react-select\";\r\n\r\n// CONSTANTES DE STYLE SELECT GROUP\r\nconst groupStyles = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n};\r\nconst groupBadgeStyles = {\r\n backgroundColor: '#EBECF0',\r\n borderRadius: '2em',\r\n color: '#172B4D',\r\n display: 'inline-block',\r\n fontSize: 12,\r\n fontWeight: 'normal',\r\n lineHeight: '1',\r\n minWidth: 1,\r\n padding: '0.16666666666667em 0.5em',\r\n textAlign: 'center',\r\n};\r\n\r\nconst formatGroupLabel = data => (\r\n
    \r\n {data.label}\r\n {data.options.length}\r\n
    \r\n);\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n // Gestion du select\r\n selectOptions: [{label: \"Filtre: Aucun\", value: \"Default\"}],\r\n id: \"\",\r\n name: '',\r\n };\r\n }\r\n\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps !== this.props) {\r\n if (this.props.students.length && this.state.students.length === 0) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des groupes du semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/formsemestre_partition_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // eslint-disable-next-line array-callback-return\r\n res.data.map((part) => {\r\n // Ajout de la catégorie\r\n let new_part = {label: part.partition_name, options: []}\r\n // Ajout des groupes\r\n // eslint-disable-next-line array-callback-return\r\n part.group.map((group) => {\r\n new_part.options.push({label: group.group_name, value: group.group_id})\r\n })\r\n // Ajout au state\r\n let joined = this.state.selectOptions.concat(new_part);\r\n this.setState({ selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants dans un groupe depuis l'API\r\n */\r\n getStudents() {\r\n let dept = window.location.href.split('/')[7]\r\n let group = this.state.id\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/groups_view?with_codes=1&format=json&group_ids=' + group)\r\n .then(res => {\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({students: dat});\r\n })\r\n }\r\n\r\n /**\r\n * Gestion des données du Select\r\n */\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label}, () => {\r\n if (this.state.id !== \"Default\") {this.getStudents()}\r\n else {\r\n if (this.props.students.length) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n \r\n {this.state.students.length !== 0 ?\r\n this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })\r\n :\r\n
    \r\n \r\n
    \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown, Spinner} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false,\r\n loading: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true, loading: false})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loading === true && this.state.loaded === false &&\r\n \r\n }\r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n {this.state.bltn.hasOwnProperty('rang') &&\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n {ue.hasOwnProperty('rang') &&\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n {mod.hasOwnProperty('rang') &&\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n defaulttab: \"Accueil\",\r\n defaultsel: \"\"\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n\r\n if (this.props.location.tab) {\r\n this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,\r\n id: this.props.location.etudid, name: this.state.selectOptions.find(option => option.value === this.state.id)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({students: res.data})\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n \r\n
    \r\n \r\n
    \r\n
    \r\n {this.result()}\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default SearchStudent","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport SearchStudent from './SearchStudent'\r\nimport {Accordion, Card, Button} from 'react-bootstrap'\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de choix du semestre */\r\nclass Scolarite extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestres: [],\r\n students: [],\r\n toast: false\r\n };\r\n this.dismissToast = this.dismissToast.bind(this);\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des semestres depuis l'API\r\n */\r\n getData () {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')\r\n .then(res => {\r\n this.setState({ semestres: res.data });\r\n })\r\n }\r\n\r\n dismissToast = () => this.setState({toast: false})\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n

    Scolarité

    \r\n
    \r\n\r\n \r\n \r\n \r\n \r\n Semestres en cours\r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat === \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Semestres passés\r\n \r\n \r\n \r\n \r\n {this.state.semestres.map((sem, index) => {\r\n if (sem.etat !== \"1\") {\r\n return (\r\n
    \r\n \r\n

    {sem.titre} [{sem.modalite}]

    \r\n

    Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]

    \r\n \r\n
    \r\n )\r\n }\r\n })}\r\n
    \r\n
    \r\n
    \r\n \r\n \r\n \r\n Recherche étudiant\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default Scolarite;","import React, {Component} from \"react\";\r\nimport {Link} from \"react-router-dom\";\r\nimport './Style.css'\r\nimport {getJson} from './Request'\r\n\r\n/** Page de choix du département */\r\nclass ChoixDept extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des départements disponibles pour l'utilisateur\r\n depts: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère la liste des départements depuis l'API\r\n */\r\n getData() {\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + 'list_depts?format=json')\r\n .then(res => {\r\n this.setState({ depts: res.data })\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Choix du département

    \r\n
    \r\n
    \r\n {this.state.depts.map((dept, index) => {\r\n return (\r\n
    \r\n \r\n Département {dept}\r\n \r\n
    \r\n )\r\n },)}\r\n
    \r\n
    \r\n
    \r\n );\r\n }\r\n}\r\n\r\nexport default ChoixDept","import React, {Component} from \"react\";\r\nimport { isMobile } from 'react-device-detect';\r\nimport './Style.css'\r\nimport ChoixDept from \"./ChoixDept\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport {getLogin} from \"./Request\";\r\n\r\n/** Page de Login */\r\nclass Login extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n login: \"\",\r\n pass: \"\",\r\n status: 0,\r\n };\r\n this.handleChangeLogin = this.handleChangeLogin.bind(this);\r\n this.handleChangePass = this.handleChangePass.bind(this);\r\n this.checkCredentials = this.checkCredentials.bind(this)\r\n }\r\n\r\n handleChangeLogin(e) {\r\n this.setState({ login: e.target.value });\r\n }\r\n\r\n handleChangePass(e) {\r\n this.setState({ pass: e.target.value });\r\n }\r\n\r\n /**\r\n * Verifie la validité des identifiants depuis l'API\r\n * @param e {event}\r\n */\r\n checkCredentials(e) {\r\n e.preventDefault();\r\n\r\n let login = this.state.login\r\n let pass = this.state.pass\r\n\r\n let BASE_URL = window.$api_url\r\n\r\n getLogin(BASE_URL, login, pass)\r\n .then(res => {\r\n this.setState({ status: res[\"status\"] });\r\n })\r\n .catch(console.log)\r\n }\r\n\r\n\r\n render() {\r\n return (\r\n
    \r\n {!isMobile &&\r\n // TODO: Redirection mobile/desktop\r\n \r\n }\r\n {(this.state.status !== 0 && this.state.status !== 200) &&\r\n
    \r\n
    \r\n

    {\"⚠️\"} Login ou mot de passe incorrect

    \r\n
    \r\n
    \r\n }\r\n {document.cookie === \"\" &&\r\n
    \r\n
    \r\n

    Connexion a ScoDoc

    \r\n
    \r\n \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n }\r\n
    \r\n {document.cookie !== \"\" &&\r\n \r\n }{document.cookie !== \"\" &&\r\n \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Login","import React, {Component} from \"react\";\r\nimport '../Style.css'\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page d'accueil de la gestion du semestre */\r\nclass Accueil extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n semestre: {},\r\n resp: [],\r\n loading: true\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n this.setState({loading: true})\r\n this.getData()\r\n }\r\n\r\n /**\r\n * Recupère les données du semestre selectionné depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n // Recuperation des infos de semestre\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});\r\n // Recuperation des noms complets des responsables\r\n res.data[0].responsables.map((resp) =>\r\n getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)\r\n .then(res => {\r\n let joined = this.state.resp.concat(res.data.nomplogin)\r\n this.setState({resp: joined, loading: false})\r\n })\r\n .catch(error => {\r\n this.setState({resp: this.state.resp_l, loading: false})\r\n })\r\n )\r\n })\r\n\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.state.loading === false ?\r\n

    {this.state.semestre.titre}
    \r\n Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}
    \r\n {this.state.resp.length === 1 ? \"Responsable: (\" : \"Responsables (\"}\r\n {this.state.resp.map((resp, index) => {\r\n if (index !== this.state.resp.length-1) {return (resp + \", \")}\r\n else {return (resp + \")\")}\r\n })}\r\n

    \r\n :\r\n // En cas de chargement\r\n \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Accueil","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de saisie des absences */\r\nclass SaisieAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n form: {},\r\n error: false,\r\n etudid: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\"\r\n\r\n if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== \"\") {\r\n let dateDebut = formDataObj['dateDebut'].split(\"-\")\r\n dateDebut = dateDebut[2] + \"/\" + dateDebut[1] + \"/\" + dateDebut[0]\r\n reqstr += dateDebut\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + dateDebut\r\n }\r\n if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n }\r\n if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&estjust=True&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n } else {\r\n this.setState({error: true})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doSignaleAbsence\", data)\r\n .then(response => {\r\n if (response.status === 200) {\r\n // Fermeture du modal\r\n this.closeModal()\r\n }\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Saisie d'absence\r\n \r\n\r\n \r\n {this.state.error &&\r\n Erreur: La date de début ne doit pas être vide\r\n }\r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n\r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SaisieAbs","import React, {Component} from \"react\";\r\nimport {Button, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de suppression des absences */\r\nclass SupprAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n let data = \"datedebut=\" + this.props.data.date + \"&datefin=\" + this.props.data.date +\r\n \"&demijournee=\" + this.props.data.demijournee + \"&etudid=\" + this.state.etudid\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doAnnuleAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n

    Etes-vous sûr.e de vouloir supprimer cette absence ?

    \r\n
    \r\n \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default SupprAbs","import React, {Component} from \"react\";\r\nimport {Button, Col, Form, Modal} from \"react-bootstrap\";\r\nimport {post} from \"../../Request\";\r\n\r\n/** Module de justification des absences */\r\nclass JustAbs extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n isOpen: false,\r\n etudid: \"\",\r\n date: \"\"\r\n }\r\n }\r\n\r\n openModal = () => this.setState({ isOpen: true });\r\n closeModal = () => this.setState({ isOpen: false });\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.open !== this.props.open) {\r\n this.setState({etudid: this.props.etudid})\r\n if (this.props.open === true) {\r\n this.setState({isOpen: true})\r\n }\r\n // Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)\r\n let date = this.props.data.date.split(\"/\")\r\n date = new Date(date[2] + \"-\" + date[1] + \"-\" + date[0])\r\n date = date.toISOString().substr(0,10);\r\n this.setState({date: date})\r\n }\r\n }\r\n\r\n /**\r\n * Envoie une requête POST a l'API\r\n * @param data {String} - Données à envoyer sous la forme param1=val1¶m2=val2...\r\n */\r\n postData(data) {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n post(BASE_URL + dept + \"/Scolarite/Absences/doJustifAbsence\", data)\r\n // Fermeture du modal\r\n this.setState({isOpen: false})\r\n }\r\n\r\n /**\r\n * Gestion des données du formulaire\r\n * @param e {Event}\r\n */\r\n onFormSubmit = e => {\r\n // Traitement du formulaire\r\n // Empeche le bouton de rediriger ou actualiser la page\r\n e.preventDefault()\r\n // Recuperation des valeurs\r\n const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())\r\n\r\n let reqstr = \"etudid=\" + this.state.etudid + \"&datedebut=\" + this.props.data.date\r\n\r\n if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== \"\") {\r\n let dateFin = formDataObj['dateFin'].split(\"-\")\r\n dateFin = dateFin[2] + \"/\" + dateFin[1] + \"/\" + dateFin[0]\r\n reqstr += \"&datefin=\" + dateFin\r\n } else {\r\n reqstr += \"&datefin=\" + this.props.data.date\r\n } if (formDataObj.hasOwnProperty('duree')) {\r\n reqstr += \"&demijournee=\" + formDataObj['duree']\r\n } else {\r\n reqstr += \"&demijournee=\" + this.props.data.demijournee\r\n } if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== \"\") {\r\n reqstr += \"&description=\" + formDataObj['motif']\r\n }\r\n this.postData(reqstr)\r\n }\r\n\r\n render() {\r\n return (\r\n <>\r\n \r\n \r\n Suppression d'absence\r\n \r\n \r\n
    \r\n \r\n \r\n Date début\r\n \r\n \r\n \r\n Date fin (Optionnel)\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n Motif\r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n
    \r\n \r\n \r\n \r\n
    \r\n \r\n )\r\n }\r\n}\r\n\r\nexport default JustAbs","import React, {Component} from \"react\";\r\nimport {Button, Spinner, Col} from 'react-bootstrap'\r\nimport '../Style.css'\r\nimport SaisieAbs from \"./Absences/SaisieAbs\";\r\nimport SupprAbs from \"./Absences/SupprAbs\";\r\nimport JustAbs from \"./Absences/JustAbs\";\r\nimport {getJson} from \"../Request\";\r\n\r\n/** Page de gestion des absences */\r\nclass Absences extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n // Gestion des fenetres modales\r\n // Ajout d'absences\r\n isOpen: false,\r\n // Suppression\r\n isDelOpen: false,\r\n // Justification\r\n isJustOpen: false,\r\n // Données de la liste des absences\r\n abs: [],\r\n absjust: [],\r\n // Données d'une absence selectionnée\r\n data: {},\r\n // En cours de recuperation de données\r\n loading: false\r\n }\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n /**\r\n * Gère l'ouverture des Modal\r\n * @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]\r\n * @param data {Object} - Objet contenant les données à transmettre\r\n */\r\n openModal(key, data) {\r\n this.setState({[key]: true}, () => setTimeout(() => {\r\n this.setState({[key]: false})\r\n }, 500))\r\n if (data) {this.setState({data: data})}\r\n }\r\n\r\n /**\r\n * Recupère les données d'absences depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let BASE_URL = window.$api_url\r\n if (this.state.id !== \"\") {\r\n // Recuperation des absences non-justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=\" + this.props.id)\r\n .then(res => this.setState({abs: res.data}));\r\n // Recuperation des absences justifiées\r\n getJson(BASE_URL + dept + \"/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=\" + this.props.id)\r\n .then(res => this.setState({absjust: res.data, loading: false}));\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n {this.props.id !== \"\" &&\r\n // Gestion du modal de saisie\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de suppression\r\n \r\n } {this.props.id !== \"\" &&\r\n // Gestion du modal de justification\r\n \r\n }\r\n

    Gestion des absences

    \r\n {this.props.name !== \"\" &&\r\n
    \r\n

    Absences de {this.props.name + \" \"}\r\n \r\n \r\n

    \r\n {this.state.loading === true &&\r\n \r\n }\r\n {(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== \"\" && this.state.loading === false) &&\r\n
    Aucune absence de l'étudiant.e
    \r\n }\r\n {this.state.abs.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n {abs.motif === \"\" &&\r\n \r\n }\r\n \r\n \r\n
    \r\n )\r\n })}\r\n {this.state.absjust.map((abs) => {\r\n return (\r\n
    \r\n \r\n
    {abs.datedmy} | {abs.matin}
    \r\n {abs.motif !== \"\" &&\r\n Motif: {abs.motif}\r\n } {abs.exams !== \"\" &&\r\n Exam a rattraper: {abs.exams}\r\n }\r\n \r\n \r\n \r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Absences","import React, {Component} from \"react\";\r\nimport {LazyLoadImage} from 'react-lazy-load-image-component';\r\nimport '../Style.css'\r\nimport {Link} from \"react-router-dom\";\r\nimport {getJson} from \"../Request\";\r\nimport {Spinner} from \"react-bootstrap\";\r\n\r\n/** Page de présentation des étudiants inscrits au semestre */\r\nclass Etudiants extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n // Liste des étudiants inscrits au semestre\r\n students: [],\r\n };\r\n }\r\n\r\n componentWillMount() {\r\n // this.getData()\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (prevProps !== this.props) {\r\n if (this.props.students.length) {\r\n const dat = this.props.students.map((x,i) => {\r\n return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n // Gestion des données sous forme de tableau a deux colonnes\r\n const dat = res.data.map((x,i) => {\r\n return i % 2 === 0 ? res.data.slice(i, i+2) : null;\r\n }).filter(x => x != null);\r\n this.setState({ students: dat});\r\n })\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n

    Liste des étudiants

    \r\n
    \r\n {this.state.students.length !== 0 ?\r\n this.state.students.map((students) => {\r\n // Creation du tableau de deux colonnes\r\n return (\r\n
    \r\n {students.map((etud, index) => {\r\n return (\r\n
    \r\n \r\n {/* Recuperation de la photo de l'etudiant */}\r\n {' '}
    \r\n {etud.nom_disp} {etud.prenom}\r\n \r\n
    \r\n )\r\n })}\r\n
    \r\n )\r\n })\r\n :\r\n
    \r\n \r\n
    \r\n }\r\n
    \r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Etudiants","import React, {Component} from \"react\";\r\nimport {Table, Button, Dropdown, Spinner} from \"react-bootstrap\"\r\nimport '../Style.css'\r\nimport {get, getJson} from \"../Request\";\r\n\r\n/** Page de présentation des bulletins étudiants */\r\nclass Bulletin extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n bltn: {},\r\n datue: {},\r\n loaded: false,\r\n loading: false\r\n };\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +\r\n this.props.id +'&format=json')\r\n .then(res => {\r\n // Recuperation des données du bulletin\r\n this.setState({ bltn: res.data }, () => {\r\n // Recuperation d'un tableau CodeUE | NomUE\r\n let ls = {}\r\n for (let elm in this.state.bltn.decision_ue) {\r\n elm = this.state.bltn.decision_ue[elm]\r\n ls[elm.acronyme] = elm.titre\r\n }\r\n this.setState({datue: ls}, () => {\r\n // Marquage du bulletin comme \"chargé\"\r\n this.setState({loaded: true, loading: false})\r\n })\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Recupère les données de bulletin en tant que \"blob\" pour un PDF depuis l'API\r\n */\r\n getPdf() {\r\n let BASE_URL = window.$api_url\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n get(BASE_URL + dept + \"/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=\" + sem +\r\n \"&etudid=\" + this.props.id + \"&format=pdf&version=selectedevals\")\r\n .then(res => res.blob())\r\n .then(blob => {\r\n let file = window.URL.createObjectURL(blob);\r\n window.location.assign(file);\r\n });\r\n }\r\n\r\n // Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)\r\n componentDidUpdate(prevProps) {\r\n if (prevProps.id !== this.props.id) {\r\n this.setState({loading: true})\r\n this.getData();\r\n }\r\n }\r\n\r\n // Recuperation des données lors du chargement de la page si un étudiant est selectionné\r\n componentDidMount() {\r\n if (this.props.id !== \"\") {this.getData()}\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n
    \r\n

    Bulletins de notes

    \r\n
    \r\n {this.state.loading === true && this.state.loaded === false &&\r\n \r\n }\r\n {this.state.loaded === true &&\r\n
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {this.state.bltn.ue.map((ue) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {ue.module.map((mod) => {\r\n return (\r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n
    \r\n Note/20
    Moyenne générale\r\n \r\n \r\n {this.state.bltn.note.value}\r\n \r\n\r\n \r\n Min: {this.state.bltn.note.min}\r\n Max: {this.state.bltn.note.max}\r\n {this.state.bltn.hasOwnProperty('rang') &&\r\n Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {ue.acronyme} - {this.state.datue[ue.acronyme]}\r\n \r\n \r\n {ue.note.value}\r\n \r\n\r\n \r\n Min: {ue.note.min}\r\n Max: {ue.note.max}\r\n {ue.hasOwnProperty('rang') &&\r\n Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}\r\n }\r\n \r\n \r\n
    {mod.titre.replace(\"'\", \"'\")}\r\n \r\n \r\n {mod.note.value}\r\n \r\n\r\n \r\n Min: {mod.note.min}\r\n Max: {mod.note.max}\r\n {mod.hasOwnProperty('rang') &&\r\n Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}\r\n }\r\n Coefficient: {mod.coefficient}\r\n \r\n \r\n
    \r\n
    \r\n \r\n
    \r\n
    \r\n }\r\n
    \r\n )\r\n }\r\n}\r\n\r\nexport default Bulletin","import React, {Component} from \"react\";\r\nimport {Tabs, Tab} from \"react-bootstrap\"\r\nimport Accueil from \"./GestionSemestre/Accueil\";\r\nimport Absences from \"./GestionSemestre/Absences\";\r\nimport Etudiants from \"./GestionSemestre/Etudiants\";\r\nimport ScoNavBar from \"./ScoNavBar\";\r\nimport Bulletin from \"./GestionSemestre/Bulletin\";\r\nimport Select from \"react-select\";\r\nimport {getJson} from \"./Request\";\r\n\r\n/** Page de gestion du semestre */\r\nclass GestionSemestre extends Component {\r\n constructor(props){\r\n super(props)\r\n this.state = {\r\n selectOptions: [],\r\n id: \"\",\r\n name: '',\r\n defaulttab: \"Accueil\",\r\n defaultsel: \"\"\r\n }\r\n }\r\n\r\n componentWillMount() {\r\n this.getData()\r\n\r\n if (this.props.location.tab) {\r\n this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,\r\n id: this.props.location.etudid, name: this.state.selectOptions.find(option => option.value === this.state.id)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API\r\n */\r\n getData() {\r\n let dept = window.location.href.split('/')[7]\r\n let sem = window.location.href.split('/')[9]\r\n let BASE_URL = window.$api_url\r\n getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)\r\n .then(res => {\r\n this.setState({students: res.data})\r\n // Création d'une liste pour le select\r\n res.data.map((student) => {\r\n let joined = this.state.selectOptions.concat({label: student.nom_disp + \" \" + student.prenom, value: student.etudid});\r\n this.setState({selectOptions: joined})\r\n })\r\n })\r\n }\r\n\r\n handleSelectChange(e){\r\n this.setState({id:e.value, name:e.label})\r\n }\r\n\r\n render() {\r\n return (\r\n
    \r\n \r\n
    \r\n
    \r\n {/* Selection de l'étudiant pour les sous-composants */}\r\n
    \r\n Choix de l'étudiant\r\n """) + H.append("""
    +

    Gestion des étudiants

    ") else: diff --git a/sco_formsemestre_validation.py b/sco_formsemestre_validation.py index 95313f59..cc9065ec 100644 --- a/sco_formsemestre_validation.py +++ b/sco_formsemestre_validation.py @@ -454,8 +454,8 @@ def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""): H.append("Code %sDevenir" % TitleCur) for ch in choices: H.append( - """""" - % (trclass, ch.rule_id, ch.codechoice) + """""" + % (trclass, ch.rule_id, ch.codechoice,ch.rule_id) ) H.append("%s " % ch.explication) if Se.prev: @@ -640,8 +640,8 @@ def formsemestre_recap_parcours_table( # log('') H.append( - '%s' - % (class_ue, " ".join(explanation_ue), scu.fmt_note(moy_ue)) + '%s' + % (class_ue,ue["acronyme"], " ".join(explanation_ue), scu.fmt_note(moy_ue)) ) if len(ues) < Se.nb_max_ue: H.append('' % (Se.nb_max_ue - len(ues))) @@ -677,7 +677,7 @@ def formsemestre_recap_parcours_table( for ue in ues: ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) H.append( - '%g %g' + '%g%g' % (ue_status["ects_pot"], ue_status["ects_pot_fond"]) ) H.append("") diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index 52dbcb42..1050cd22 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -321,7 +321,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No top_table_links = "" if sem["etat"] == "1": # non verrouillé top_table_links = ( - """Créer nouvelle évaluation + """Créer nouvelle évaluation Trier par date """ % M @@ -360,11 +360,12 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No """Evaluation sans date""" % eval ) - H.append("    %(description)s" % eval) + H.append("    %(description)s" % eval) if eval["evaluation_type"] == EVALUATION_RATTRAPAGE: H.append( """rattrapage""" ) + H.append("") elif eval["evaluation_type"] == EVALUATION_SESSION2: H.append( """session 2""" @@ -453,7 +454,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No """""" % eval ) - H.append(scu.icontag("delete_img", alt="supprimer", title="Supprimer")) + H.append(scu.icontag("delete_img", alt="supprimer", title="Supprimer", id="delete_buttion_%s" %eval["evaluation_id"])) if caneditevals: H.append("""""") elif etat["evalcomplete"]: diff --git a/sco_page_etud.py b/sco_page_etud.py index b0cb60ee..dd589b5f 100644 --- a/sco_page_etud.py +++ b/sco_page_etud.py @@ -430,16 +430,16 @@ def ficheEtud(context, etudid=None, REQUEST=None):
    - + %(groupes_row)s - +
    Situation :%(situation)s
    Situation :%(situation)s
    Né%(ne)s le :%(info_naissance)s
    Né%(ne)s le :%(info_naissance)s
    -
    Adresse : %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s +Adresse :Code étudiant (%s) dupliqué !""" % code_name, + message="""

    Code étudiant (%s) dupliqué !

    """ % code_name, helpmsg="""Le %s %s est déjà utilisé: un seul étudiant peut avoir ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.

    • """ % (code_name, args[code_name]) + "
    • ".join(listh) @@ -372,7 +372,7 @@ def _check_duplicate_code(cnx, args, code_name, context, edit=True, REQUEST=None REQUEST=REQUEST, ) else: - err_page = """

      Code étudiant (%s) dupliqué !

      """ % code_name + err_page = """

      Code étudiant (%s) dupliqué !

      """ % code_name log("*** error: code %s duplique: %s" % (code_name, args[code_name])) raise ScoGenError(err_page)