From 15d3087fe4e106c4e384f38f73394f59ecf5dfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Wed, 5 Aug 2026 07:23:19 +0200 Subject: [PATCH] Initial commit --- .gitignore | 185 +++++++++++++++++++++++ favicon.ico | Bin 0 -> 54334 bytes static/builder.js | 347 +++++++++++++++++++++++++++++++++++++++++++ static/style.css | 139 +++++++++++++++++ templates/edt.typ | 112 ++++++++++++++ templates/index.html | 244 ++++++++++++++++++++++++++++++ wsgi.py | 242 ++++++++++++++++++++++++++++++ 7 files changed, 1269 insertions(+) create mode 100644 .gitignore create mode 100644 favicon.ico create mode 100644 static/builder.js create mode 100644 static/style.css create mode 100644 templates/edt.typ create mode 100644 templates/index.html create mode 100644 wsgi.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf6f679 --- /dev/null +++ b/.gitignore @@ -0,0 +1,185 @@ +# ============================================================================== +# Created by https://gitignores.com/ +# COMPREHENSIVE FRAMEWORK TEMPLATE for Flask +# Website: https://flask.palletsprojects.com/ +# Repository: https://github.com/pallets/flask +# ============================================================================== + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# TEMPLATE OVERVIEW & USAGE NOTES +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# • TEMPLATE TYPE: COMPREHENSIVE FRAMEWORK TEMPLATE +# • PURPOSE: Complete Flask Python web framework patterns for web applications and development artifacts +# • DESIGN PHILOSOPHY: Self-contained with all Flask and Python-specific patterns +# • COMBINATION GUIDANCE: Use standalone; optionally add IDE/OS templates +# • SECURITY CONSIDERATIONS: Includes security patterns for .env files and credentials +# • BEST PRACTICES: Review patterns before use, test with git check-ignore, customize for your project +# • OFFICIAL SOURCES: Flask documentation and community best practices + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# SECURITY & SENSITIVE DATA PROTECTION (ALWAYS FIRST!) +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# CRITICAL: Protect sensitive data from accidental commits to version control + +*.crt +*.key +*.keystore +*.pem +*.secret +*.token +*_keys +*_secrets +*_tokens +.env +.env.* +.env.*.local +.env.local +id_dsa +id_rsa + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# BUILD ARTIFACTS & DISTRIBUTION +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• + +*.egg +*.egg-info/ +*.so +*.tar.gz +*.whl +*.zip + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# DEPENDENCY MANAGEMENT & PACKAGE CACHE +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• + +.cache/ +.eggs/ +.pybuilder/ +.python-eggs/ +.venv +__pypackages__/ +build/ +develop-eggs/ +dist/ +docs/_build/ +eggs/ +env.bak/ +ENV/ +parts/ +pip-wheel-metadata/ +profile_default/ +sdist/ +share/python-wheels/ +venv.bak/ +venv/ +wheels/ + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# DEVELOPMENT & RUNTIME ARTIFACTS +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• + +*.log +*.log.* +.dmypy.json +.mypy_cache/ +.nox/ +.pyre/ +.pytest_cache/ +.pytype/ +.tox/ +dmypy.json +pip-delete-this-directory.txt +pip-log.txt + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# FRAMEWORK-SPECIFIC PATTERNS +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• + +*.cer +*.csr +*.der +*.jks +*.manifest +*.nupkg +*.o +*.obj +*.p12 +*.p7b +*.p7c +*.pfx +*.pid +*.pid.lock +*.py[co] +*.pyc +*.pyo +*.spec +*.tgz +*.truststore +.Python +.flask-debug +.python-version +.flaskenv +.installed.cfg +cython_debug/ +downloads/ +instance/ +ipython_config.py +lib/ +lib64/ +MANIFEST +out/ +pdm.toml +.webassets-cache +var/ + +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +# TESTING & QUALITY ASSURANCE +# •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• + +.coverage +.coverage.* +.coverage.xml +.coverage_html/ +.hypothesis/ +.nyc_output/ +htmlcov/ +junit.xml +nosetests.xml +test-output/ +test-reports/ +test-results/ +testng-results.xml + +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# TEMPLATE CUSTOMIZATION & BEST PRACTICES +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# 1. REVIEW: Examine all patterns before use +# 2. CUSTOMIZE: Adapt to your project's specific structure +# 3. TEST: Use `git check-ignore` to verify patterns +# 4. SECURE: Always protect sensitive data and credentials +# 5. UPDATE: Review periodically as technology evolves + +# RECOMMENDED USAGE PATTERNS: +# ============================================================================== +# +# BASIC USAGE (STANDALONE): +# ------------------------- +# cp frameworks/flask.gitignore .gitignore +# +# WITH IDE SUPPORT: +# ---------------- +# cat frameworks/flask.gitignore \ +# ides/intellij.gitignore | sort -u > .gitignore +# +# CROSS-PLATFORM DEVELOPMENT: +# --------------------------- +# cat frameworks/flask.gitignore \ +# os/linux.gitignore \ +# os/macos.gitignore \ +# os/windows.gitignore | sort -u > .gitignore +# +# IMPORTANT NOTES: +# ============================================================================== +# 1. This template is self-contained and includes security patterns +# 2. No need to add common/security.gitignore separately +# 3. Test with `git status --ignored` to ensure proper coverage diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1c2c97a8ed428ee7fbb4f0a152fa12e182077a3a GIT binary patch literal 54334 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 literal 0 HcmV?d00001 diff --git a/static/builder.js b/static/builder.js new file mode 100644 index 0000000..5d392b6 --- /dev/null +++ b/static/builder.js @@ -0,0 +1,347 @@ +const dayMap = { + "Lundi": 0, + "Mardi": 1, + "Mercredi": 2, + "Jeudi": 3, + "Vendredi": 4, + "Samedi": 5, + "Dimanche": 6 +}; + + +// ====================================================== +// Cours ajoutés manuellement +// ====================================================== + +function addCourse(course = {}) { + const tbody = document.getElementById("courses"); + + const row = document.createElement("tr"); + + row.innerHTML = ` + + + + + + + + + + + + + + + + + + + `; + + tbody.appendChild(row); + + + row.querySelector(".day").value = course.day ?? 0; + row.querySelector(".start").value = course.start ?? ""; + row.querySelector(".end").value = course.end ?? ""; + row.querySelector(".name").value = course.name ?? ""; + row.querySelector(".prof").value = course.prof ?? ""; + row.querySelector(".room").value = course.room ?? ""; + row.querySelector(".color").value = + course.color ?? "#80b3ff"; + + + row.querySelector(".remove").onclick = () => { + row.remove(); + }; +} + + +// ====================================================== +// Gestion des UE fondamentales +// ====================================================== + +const fonda = + [...document.querySelectorAll( + "#courses-base .ue-selector" + )]; + + +fonda.forEach(cb => { + + cb.addEventListener("change", () => { + + const selected = + fonda.filter(x => x.checked); + + /* + if (selected.length > 2) { + cb.checked = false; + alert("Choisissez seulement 2 UE parmi les 3."); + } + */ + + + updateTD(); + }); + +}); + + + +function updateTD() { + + document + .querySelectorAll( + "#courses-base input[type=radio]" + ) + .forEach(radio => { + + const ue = + radio.dataset.ue; + + + const selected = + document.querySelector( + `.ue-selector[data-ue="${ue}"]` + )?.checked; + + + radio.disabled = !selected; + + + if (!selected) + radio.checked = false; + }); +} + + +// ====================================================== +// Conversion d'une ligne HTML en cours JSON +// ====================================================== + +function rowToCourse(row) { + return { + + day: + Number(row.dataset.day), + + start: + row.dataset.start, + + end: + row.dataset.end, + + name: + row.dataset.name ?? "", + + prof: + row.dataset.prof ?? "", + + room: + row.dataset.room ?? "", + + color: + row.dataset.color ?? "#ffffff" + }; +} + + + +// ====================================================== +// Extraction des cours fixes +// ====================================================== + +function extractFixedCourses(container) { + + const courses = []; + + + container + .querySelectorAll("tr") + .forEach(row => { + //console.log(row); + + const checkbox = + document.getElementById(row.getAttribute("checkbox-id")) + + enabled = checkbox.checked; + + if (!enabled) + return; + + const course = + rowToCourse(row); + console.log(course); + + + if (course) + courses.push(course); + + }); + + + return courses; +} + + +// ====================================================== +// Submit PDF +// ====================================================== + +document + .getElementById("courseForm") + .addEventListener( + "submit", + async e => { + + e.preventDefault(); + + + let cours = []; + + + cours.push( + ...extractFixedCourses( + document.getElementById( + "courses-base" + ) + ) + ); + + + cours.push( + ...extractFixedCourses( + document.getElementById( + "courses-autre" + ) + ) + ); + + + + // Cours ajoutés manuellement + + document + .querySelectorAll( + "#courses tr" + ) + .forEach(row => { + + + if ( + !row.querySelector( + ".select-row" + ).checked + ) + return; + + + cours.push({ + + day: + Number( + row.querySelector( + ".day" + ).value + ), + + start: + row.querySelector( + ".start" + ).value, + + end: + row.querySelector( + ".end" + ).value, + + name: + row.querySelector( + ".name" + ).value, + + prof: + row.querySelector( + ".prof" + ).value, + + room: + row.querySelector( + ".room" + ).value, + + color: + row.querySelector( + ".color" + ).value + }); + + }); + + + + const response = + await fetch( + "/edt.pdf", + { + method: "POST", + headers: { + "Content-Type": + "application/json" + }, + body: + JSON.stringify({ + cours + }) + } + ); + + + const blob = + await response.blob(); + + + window.open( + URL.createObjectURL(blob) + ); + + } + ); + + + +// ====================================================== +// Select all cours ajoutés +// ====================================================== + +const selectAll = + document.getElementById( + "select-all" + ); + + +if (selectAll) { + + selectAll.onchange = () => { + + document + .querySelectorAll( + ".select-row" + ) + .forEach(cb => { + cb.checked = + selectAll.checked; + }); + + }; +} \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..61d03e1 --- /dev/null +++ b/static/style.css @@ -0,0 +1,139 @@ +* { + box-sizing: border-box; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + } + + body { + background: #f5f6fa; + padding: 30px; + color: #222; + } + + form { + background: white; + padding: 25px; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); + max-width: 1200px; + margin: auto; + } + + table { + width: 100%; + border-collapse: collapse; + overflow: hidden; + border-radius: 8px; + } + + thead { + background: #333; + color: white; + } + + tbody tbody:nth-child(even) td { + background-color: #000; + } + + th { + padding: 12px; + text-align: center; + font-weight: 600; + } + + td { + padding: 8px; + text-align: center; + border-bottom: 1px solid #ddd; + } + + tbody tr:hover { + background: #f1f5ff; + } + + input, + select { + width: 100%; + padding: 6px 8px; + border: 1px solid #ccc; + border-radius: 6px; + background: white; + } + + input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + } + + input[type="color"] { + height: 35px; + padding: 2px; + cursor: pointer; + } + + input:focus, + select:focus { + outline: none; + border-color: #4a90e2; + box-shadow: 0 0 0 2px rgba(74,144,226,0.2); + } + + + button { + margin-top: 15px; + padding: 10px 18px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 15px; + transition: 0.15s; + } + + button:hover { + transform: translateY(-1px); + opacity: 0.9; + } + + + button[type="submit"] { + background: #2563eb; + color: white; + } + + button[type="button"] { + background: #e5e7eb; + color: #111; + } + + + .remove { + background: #333 !important; + color: white; + padding: 6px 10px; + font-size: 13px; + margin: 0; + } + + + #courses tr { + transition: background 0.2s; + } + + + /* Better mobile display */ + @media (max-width: 900px) { + + body { + padding: 10px; + } + + form { + padding: 15px; + } + + table { + display: block; + overflow-x: auto; + white-space: nowrap; + } + } \ No newline at end of file diff --git a/templates/edt.typ b/templates/edt.typ new file mode 100644 index 0000000..02b4b11 --- /dev/null +++ b/templates/edt.typ @@ -0,0 +1,112 @@ +#import "@preview/cetz:0.5.1" + +#let time(h,m) = datetime(hour:h, minute: m, second: 0) + +#let parse_time(s) = { + let (h, m) = s.match(regex("(\d{2}):(\d{2})")).captures.map(int) + time(h,m) +} + +#let gety(s) = { + let dt = parse_time(s) + 11-(dt - time(8,0)).hours() +} + +#let sans_font = "Fira Sans" + + +#let titre = "EDT 2026/2027 -- M1 S1" + +#let data = json(bytes(sys.inputs.data)) + +#let cours = data.cours +/* +Un exemple : + +#let cours = ( + (day: 0, start: "08:15", end: "10:15", name:"Algèbre (CM)", prof:"Jean-Benoît Bost", room: "OA1", color: "#ff80b3"), + (day: 0, start: "10:30", end: "13:00", name:"Algèbre (TD)", prof:"x", room: "OA1", color: "#ff80b3"), + (day: 3, start: "14:00", end: "16:00", name:"Algèbre (CM)", prof:"Jean-Benoît Bost", room: "OA1", color: "#ff80b3"), + (day: 3, start: "16:15", end: "18:45", name:"Algèbre (TD)", prof:"x", room: "OA1", color: "#ff80b3"), + + (day: 0, start: "14:00", end: "16:00", name:"Analyse (CM)", prof:"Blanche Buet", room: "OA1", color: "#80ffcc"), + (day: 0, start: "16:15", end: "18:45", name:"Analyse (TD)", prof:"x", room: "OA1", color: "#80ffcc"), + (day: 2, start: "08:15", end: "10:15", name:"Analyse (CM)", prof:"Blanche Buet", room: "OA1", color: "#80ffcc"), + (day: 2, start: "10:30", end: "13:00", name:"Analyse (TD)", prof:"x", room: "OA1", color: "#80ffcc"), + + (day: 1, start: "14:00", end: "16:00", name:"Cryptographie (CM)", prof:"Stéphane Fischler", room: "OA1", color: "#e6b3ff"), + (day: 4, start: "10:30", end: "12:30", name:"Cryptographie (TP)", prof:"Chimène Fischler", room: "OA1", color: "#e6b3ff") +) +*/ + +#set page( + paper: "a4", + flipped: true, + footer: [ + #set align(right) + #set text(font: sans_font, size: 9pt) + $copyright$ EDT Valentin Moguérou 2026 + ], + margin: ( + x: 1.5cm, + top: 1cm, + bottom: 1cm, + ) +) +#set par(justify: true) +#set text( + font: "New Computer Modern", + size: 11pt, +) + +#set align(center) + +#let jours = ("Lundi","Mardi","Mercredi","Jeudi","Vendredi") + +#align(center, text(titre, size: 15pt, weight: "bold")) + +#cetz.canvas( + length: 1.5cm, +{ + import cetz.draw: * + + for heure in range(8,20) { + content((-.5,18.85-heure), + text([#heure:00], font: sans_font, size: 9pt)) + + line((-0.5,19-heure),(0,19-heure), stroke:1pt) + + + if calc.even(heure) { line((0,19-heure),(15,19-heure), stroke:0.5pt+gray) } + + } + line((0,11),(15,11), stroke:1pt) // 8h + line((0,0),(15,0), stroke:1pt) // 19h + + + + + for c in cours { + content( + (3*c.day, gety(c.start)+.05), + (3*(c.day+1), gety(c.end)-.05), + frame: "rect", + fill: rgb(c.color), + stroke: 0.5pt, + align(center+horizon)[ + *#c.name* \ + #c.prof + + #set text(size: 9pt, font: sans_font) + #if c.room != "" [*#c.room*, ] + #(c.start)--#c.end + ] + ) + } + + for jour in range(5) { + content((3*jour+1.5,11.3), text([#jours.at(jour)], font: sans_font, size: 11pt)) + line((3*jour,11.3),(3*jour,0), stroke:1pt) + } + line((15,11.5),(15,0), stroke:1pt) +}) \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..db26745 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,244 @@ + + + + + EDT Generator + + + + + +

#m1edt

+ +
+ +

Cours fondamentaux : choisir 2 parmi 3

+ + + + + + + + + + + + + + + + + +{% set jours = ["Lundi","Mardi","Mercredi","Jeudi","Vendredi"] %} + +{% for ue in cours_base %} + +{% set rowspan = ue.cm|length + 2 * (ue.td|length) %} + +{# ---------------- CM ---------------- #} + +{% for cm in ue.cm %} + + + + {% if loop.first %} + + + + {% endif %} + + + + + + {% if loop.first %} + + + {% endif %} + + + + + +{% endfor %} + + +{# ---------------- TD ---------------- #} + +{% for td in ue.td %} + +{% for slot in td.slots %} + + + + {% if loop.first %} + + {% endif %} + + + + + + {% if loop.first %} + + + {% endif %} + + + + + +{% endfor %} +{% endfor %} + +{% endfor %} + +
UESélectionJourDébutFinAtomeEnseignantSalle
{{ ue.name }} + + {{ jours[cm.day] }}{{ cm.start }}{{ cm.end }}CM{{ cm.prof }}{{ cm.room }}
+ + {{ jours[slot.day] }}{{ slot.start }}{{ slot.end }}TD G{{ td.group }}{{ td.prof }}{{ slot.room }}
+ + + +

Autres cours

+ + + + + + + + + + + + + + + + + + +{% set jours = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"] %} + +{% for ue in cours_autres %} + +{% set rowspan = ue.atoms|length %} + +{% for atom in ue.atoms %} + + + + {% if loop.first %} + + + + + {% endif %} + + + + + + + + + + +{% endfor %} +{% endfor %} + + +
UESélectionJourDébutFinAtomeEnseignantSalle
{{ ue.name }} + + {{ jours[atom.day] }}{{ atom.start }}{{ atom.end }}{{ atom.label }}{{ atom.prof }}{{ atom.room }}
+ +

Ajouter des cours

+ + + + + + + + + + + + + + + + + + + + + +
+ +JourDébutFinNomEnseignantSalleCouleur
+ + + + +
+ + + + + +
+ + + + + + \ No newline at end of file diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..fa0e6b7 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,242 @@ +from flask import Flask +from flask import render_template, send_file, request, redirect, url_for +import typst +import json +from io import BytesIO +import random + +from pprint import pprint + +app = Flask(__name__) + +@app.route("/") +def index(): + cours_base = [ + # ALGEBRE + { + "ue": "algebre", + "name": "Algèbre", + "color": "#ff80b3", + "cm": [ + { + "prof": "Jean-Benoît Bost", + "day": 0, "start": "08:15", "end": "10:15", "room": "Amphi Yoccoz", + }, + { + "prof": "Jean-Benoît Bost", + "day": 3, "start": "14:00", "end": "16:00", "room": "Amphi Yoccoz", + }, + ], + "td": [ + { + "group": 1, + "prof": "Kévin Destagnol", + "slots": [ + {"day": 0, "start": "10:30", "end": "13:00", "room": "0A2"}, + {"day": 3, "start": "16:15", "end": "18:45", "room": "0A2"}, + ], + }, + { + "group": 2, + "prof": "Pierre Lorenzon", + "slots": [ + {"day": 0, "start": "10:30", "end": "13:00", "room": "0A7"}, + {"day": 3, "start": "16:15", "end": "18:45", "room": "0E1"}, + ], + }, + ], + }, + # ANALYSE + { + "ue": "analyse", + "name": "Analyse", + "color": "#80ffcc", + "cm": [ + { + "prof": "Blanche Buet", + "day": 0, "start": "14:00", "end": "16:00", "room": "OA2", + }, + { + "prof": "Blanche Buet", + "day": 2, "start": "08:15", "end": "10:15", "room": "OA1", + }, + ], + "td": [ + { + "group": 1, + "prof": "Frédéric Valet", + "slots": [ + {"day": 0, "start": "16:15", "end": "18:45", "room": "0A2"}, + {"day": 2, "start": "10:30", "end": "13:00", "room": "0A1"}, + ], + }, + { + "group": 2, + "prof": "Sebastian Bechtel", + "slots": [ + {"day": 0, "start": "16:15", "end": "18:45", "room": "0A7"}, + {"day": 2, "start": "10:30", "end": "13:00", "room": "0D1"}, + ], + }, + ], + }, + # PROBAS + { + "ue": "probas", + "name": "Probabilités", + "color": "#ffcc99", + "cm": [ + { + "prof": "Pierre-Loïc Méliot", + "day": 1, "start": "08:15", "end": "10:15", "room": "Amphi Yoccoz", + }, + { + "prof": "Pierre-Loïc Méliot", + "day": 3, "start": "08:15", "end": "10:15", "room": "Amphi Yoccoz", + }, + ], + "td": [ + { + "group": 1, + "prof": "Sophie Lemaire", + "slots": [ + {"day": 1, "start": "10:30", "end": "13:00", "room": "0D1"}, + {"day": 3, "start": "10:30", "end": "13:00", "room": "0E1"}, + ], + }, + { + "group": 2, + "prof": "A. Legrand", + "slots": [ + {"day": 1, "start": "10:30", "end": "13:00", "room": "Amphi Yoccoz"}, + {"day": 3, "start": "10:30", "end": "13:00", "room": "0A2"}, + ], + }, + ], + }, + ] + + + cours_autres = [ + { + "ue": "crypto", + "name": "Cryptographie", + "color": "#e6b3ff", + "atoms": [ + { + "label": "CM", + "prof": "Stéphane Fischler", + "day": 1, + "start": "14:00", + "end": "16:00", + "room": "0A2", + }, + { + "label": "TP", + "prof": "Chimène Fischler", + "day": 4, + "start": "10:30", + "end": "12:30", + "room": "0E10", + }, + ], + }, + { + "ue": "geo_alea", + "name": "Géométrie aléatoire", + "color": "#ccb3ff", + "atoms": [ + { + "label": "CM", + "prof": "N. Curien", + "day": 1, + "start": "16:15", + "end": "18:15", + "room": "1A7", + }, + { + "label": "TP", + "prof": "A. Legrand", + "day": 4, + "start": "08:15", + "end": "10:15", + "room": "0D10", + }, + ], + }, + { + "ue": "edp_num", + "name": "EDP numériques", + "color": "#ccebff", + "atoms": [ + { + "label": "CM", + "prof": "B. Maury", + "day": 1, + "start": "16:15", + "end": "18:15", + "room": "0A5", + }, + { + "label": "TP", + "prof": "B. Graille", + "day": 4, + "start": "08:15", + "end": "10:15", + "room": "0E10", + }, + ], + }, + { + "ue": "topalg", + "name": "Topologie algébrisée", + "color": "#a366ff", + "atoms": [ + { + "label": "CI", + "prof": "Patrick Massot", + "day": 2, + "start": "14:00", + "end": "18:15", + "room": "0A1", + }, + ], + }, + ] + + return render_template('index.html', cours_base=cours_base, cours_autres=cours_autres) + + +@app.route("/favicon.ico") +def favicon(): + return send_file("favicon.ico") + +@app.route('/edt.pdf', methods=["GET", "POST"]) +def generate_pdf(): + if request.method != "POST": + data = { + "cours": [ + {"day": 0, "start": "08:15", "end": "10:xx", "name": "Algèbre (CM)", "prof": "Jean-Benoît Bost", "room": "OA1", "color": "#ff80b3"}, + {"day": 0, "start": "10:30", "end": "13:00", "name": "Algèbre (TD)", "prof": "x", "room": "OA1", "color": "#ff80b3"}, + {"day": 3, "start": "14:00", "end": "16:00", "name": "Algèbre (CM)", "prof": "Jean-Benoît Bost", "room": "OA1", "color": "#ff80b3"}, + {"day": 3, "start": "16:15", "end": "18:45", "name": "Algèbre (TD)", "prof": "x", "room": "OA1", "color": "#ff80b3"}, + + {"day": 0, "start": "14:00", "end": "16:00", "name": "Analyse (CM)", "prof": "Blanche Buet", "room": "OA1", "color": "#80ffcc"}, + {"day": 0, "start": "16:15", "end": "18:45", "name": "Analyse (TD)", "prof": "x", "room": "OA1", "color": "#80ffcc"}, + {"day": 2, "start": "08:15", "end": "10:15", "name": "Analyse (CM)", "prof": "Blanche Buet", "room": "OA1", "color": "#80ffcc"}, + {"day": 2, "start": "10:30", "end": "13:00", "name": "Analyse (TD)", "prof": "x", "room": "OA1", "color": "#80ffcc"}, + + {"day": 1, "start": "14:00", "end": "16:00", "name": "Cryptographie (CM)", "prof": "Stéphane Fischler","room": "OA1", "color": "#e6b3ff"}, + {"day": 4, "start": "10:30", "end": "12:30", "name": "Cryptographie (TP)", "prof": "Chimène Fischler", "room": "OA1", "color": "#e6b3ff"}, + ] + } + print(data) + else: + data = json.loads(request.data) + pprint(data) + + sys_inputs = {"data": json.dumps(data)} + + pdf_file = BytesIO(typst.compile(input="templates/edt.typ", sys_inputs=sys_inputs)) + + return send_file(pdf_file, download_name="edt.pdf") \ No newline at end of file