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 0000000..1c2c97a
Binary files /dev/null and b/favicon.ico differ
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
+
+
+
+
+
+
+
+
\ 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