Compare commits

..
Author SHA1 Message Date
imperosol 338ca6cd25 add og tags to timetable generator 2026-09-04 20:01:15 +02:00
4 changed files with 85 additions and 40 deletions
+2
View File
@@ -14,6 +14,8 @@
{%- endblock %}"
>
<meta property="og:site_name" content="Association des Étudiants de l'UTBM" />
<meta property="og:locale" content="fr_FR" />
<meta property="og:locale:alternate" content="en_GB" />
{% block metatags %}
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
<meta property="og:type" content="website" />
+9 -1
View File
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-04 11:24+0200\n"
"POT-Creation-Date: 2026-09-04 19:39+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -5832,6 +5832,14 @@ msgstr "les groupes pouvant créer des cotisations"
msgid "Timetable generator"
msgstr "Générateur d'emploi du temps"
#: timetable/templates/timetable/generator.jinja
msgid "Get a nice schedule from the raw text in your student file"
msgstr "Générez un joli emploi du temps à partir du texte brut de votre dossier étudiant"
#: timetable/templates/timetable/generator.jinja
msgid "Paste your schedule (without the header)"
msgstr "Colle ton emploi du temps (sans l'entête)"
#: timetable/templates/timetable/generator.jinja
msgid "Generate"
msgstr "Générer"
@@ -1,5 +1,9 @@
import { getCurrentUrlParams, updateQueryString } from "#core:utils/history";
import { type SimpleUeSchema, ueFetchUeList } from "#openapi";
import {
getCurrentUrlParams,
History,
updateQueryString,
} from "#core:utils/history.ts";
import { ueFetchUeList } from "#openapi";
const pageDefault = 1;
const pageSizeDefault = 100;
@@ -8,42 +12,38 @@ document.addEventListener("alpine:init", () => {
Alpine.data("ue_search", () => ({
ues: {
count: 0,
next: null as string | null,
previous: null as string | null,
results: [] as SimpleUeSchema[],
next: null,
previous: null,
results: [],
},
loading: false,
page: pageDefault,
// biome-ignore lint/style/useNamingConvention: api is in snake_case
page_size: pageSizeDefault,
search: "",
department: [] as string[],
department: [],
// biome-ignore lint/style/useNamingConvention: api is in snake_case
credit_type: [] as string[],
semester: [] as string[],
credit_type: [],
semester: [],
// biome-ignore lint/style/useNamingConvention: api is in snake_case
to_change: [] as { param: string; value: string }[],
to_change: [],
pushstate: History.Push,
// dummy implementation to make TS happy.
// The real function is initialized in init
update: () => {
console.warn("Update not yet initialized");
},
update: undefined,
initializeArgs() {
const url = getCurrentUrlParams();
this.page = Number.parseInt(url.get("page") || pageDefault.toString(), 10);
this.page_size = Number.parseInt(
url.get("page_size") || pageSizeDefault.toString(),
10,
);
this.pushstate = History.Replace;
this.page = Number.parseInt(url.get("page"), 10) || pageDefault;
this.page_size = Number.parseInt(url.get("page_size"), 10) || pageSizeDefault;
this.search = url.get("search") || "";
this.department = url.getAll("department");
this.credit_type = url.getAll("credit_type");
/* The semester is easier to use on the backend as an enum (spring/autumn/both/none)
and easier to use on the frontend as an array ([spring, autumn]).
Thus there is some conversion involved when both communicate together */
this.semester = url.get("semester")?.split("_AND_") || [];
this.semester = url.has("semester") ? url.get("semester").split("_AND_") : [];
this.update();
},
@@ -51,11 +51,15 @@ document.addEventListener("alpine:init", () => {
async init() {
this.update = Alpine.debounce(async () => {
/* Create the whole url before changing everything all at once */
for (const val of this.to_change) {
updateQueryString(val.param, val.value);
const first = this.to_change.shift();
let url = updateQueryString(first.param, first.value, History.None);
for (const value of this.to_change) {
url = updateQueryString(value.param, value.value, History.None, url);
}
updateQueryString(first.param, first.value, this.pushstate, url);
await this.fetchData(); /* reload data on form change */
this.to_change = [];
this.pushstate = History.Push;
}, 50);
const searchParams = ["search", "department", "credit_type", "semester"];
@@ -63,37 +67,47 @@ document.addEventListener("alpine:init", () => {
for (const param of searchParams) {
this.$watch(param, () => {
if (this.pushstate !== History.Push) {
/* This means that we are doing a mass param edit */
return;
}
/* Reset pagination on search */
this.page = pageDefault;
this.page_size = pageSizeDefault;
});
}
for (const param of searchParams.concat(paginationParams)) {
this.$watch(param, (value: string) => {
this.$watch(param, (value) => {
this.to_change.push({ param: param, value: value });
this.update();
});
}
window.addEventListener("popstate", () => {
this.initializeArgs();
});
this.initializeArgs();
},
async fetchData() {
this.loading = true;
const res = await ueFetchUeList({
query: {
// biome-ignore lint/style/useNamingConvention: api is in snake_case
page_size: this.page_size,
// biome-ignore lint/style/useNamingConvention: api is in snake_case
credit_type: this.credit_type.length > 0 ? this.credit_type : undefined,
semester: this.semester.length > 0 ? this.semester : undefined,
department: this.department.length > 0 ? this.department : undefined,
search: this.search || undefined,
},
});
if (res.data !== undefined) {
this.ues = res.data;
const args = {
// biome-ignore lint/style/useNamingConvention: api is in snake_case
page_size: this.page_size,
};
for (const [param, value] of new URL(
window.location.href,
).searchParams.entries()) {
// Deal with array type params
if (["credit_type", "department", "semester"].includes(param)) {
if (args[param] === undefined) {
args[param] = [];
}
args[param].push(value);
} else {
args[param] = value;
}
}
this.ues = (await ueFetchUeList({ query: args })).data;
this.loading = false;
},
+23 -2
View File
@@ -12,15 +12,36 @@
{% trans %}Timetable generator{% endtrans %}
{% endblock %}
{% block description -%}
{% trans trimmed %}
Get a nice schedule from the raw text in your student file
{% endtrans %}
{%- endblock %}
{% block metatags %}
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Générateur d'emploi du temps" />
<meta property="og:image" content="{{ request.build_absolute_uri(static("core/img/logo_no_text.png")) }}" />
<meta
property="og:description"
content="Générateur d'emploi du temps de l'UTBM.
Copiez le texte de l'emploi du temps de votre dossier étudiant
et récupérez une image plus jolie."
/>
{% endblock %}
{% block content %}
<div x-data="timetableGenerator">
<form @submit.prevent="generate()">
<h1>Générateur d'emploi du temps</h1>
<h1>{% trans %}Timetable generator{% endtrans %}</h1>
<div class="alert alert-red" x-show="!!error" x-cloak>
<span class="alert-main" x-text="error"></span>
</div>
<div class="form-group">
<label for="timetable-input">Colle ton emploi du temps (sans l'entête)</label>
<label for="timetable-input">
{% trans %}Paste your schedule (without the header){% endtrans %}
</label>
<textarea id="timetable-input" cols="30" rows="15" x-model="content"></textarea>
</div>
<input type="submit" class="btn btn-blue" value="{% trans %}Generate{% endtrans %}">