mirror of
https://github.com/ae-utbm/sith.git
synced 2026-09-04 19:44:34 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
338ca6cd25 |
@@ -14,6 +14,8 @@
|
|||||||
{%- endblock %}"
|
{%- endblock %}"
|
||||||
>
|
>
|
||||||
<meta property="og:site_name" content="Association des Étudiants de l'UTBM" />
|
<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 %}
|
{% block metatags %}
|
||||||
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
|
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"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"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@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"
|
msgid "Timetable generator"
|
||||||
msgstr "Générateur d'emploi du temps"
|
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
|
#: timetable/templates/timetable/generator.jinja
|
||||||
msgid "Generate"
|
msgid "Generate"
|
||||||
msgstr "Générer"
|
msgstr "Générer"
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ class UeFilterSchema(FilterSchema):
|
|||||||
set[Literal["CS", "TM", "EC", "OM", "QC"]] | None,
|
set[Literal["CS", "TM", "EC", "OM", "QC"]] | None,
|
||||||
FilterLookup("credit_type__in"),
|
FilterLookup("credit_type__in"),
|
||||||
] = None
|
] = None
|
||||||
is_open: bool | None = None
|
|
||||||
language: str = "FR"
|
language: str = "FR"
|
||||||
department: Annotated[set[str] | None, FilterLookup("department__in")] = None
|
department: Annotated[set[str] | None, FilterLookup("department__in")] = None
|
||||||
|
|
||||||
@@ -188,10 +187,3 @@ class UeFilterSchema(FilterSchema):
|
|||||||
return Q()
|
return Q()
|
||||||
value.add("AUTUMN_AND_SPRING")
|
value.add("AUTUMN_AND_SPRING")
|
||||||
return Q(semester__in=value)
|
return Q(semester__in=value)
|
||||||
|
|
||||||
def filter_is_open(self, value: bool | None) -> Q: # noqa: FBT001
|
|
||||||
if value is None:
|
|
||||||
return Q()
|
|
||||||
if not value:
|
|
||||||
return Q(semester="CLOSED")
|
|
||||||
return ~Q(semester="CLOSED")
|
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
getCurrentUrlParams,
|
||||||
|
History,
|
||||||
|
updateQueryString,
|
||||||
|
} from "#core:utils/history.ts";
|
||||||
|
import { ueFetchUeList } from "#openapi";
|
||||||
|
|
||||||
|
const pageDefault = 1;
|
||||||
|
const pageSizeDefault = 100;
|
||||||
|
|
||||||
|
document.addEventListener("alpine:init", () => {
|
||||||
|
Alpine.data("ue_search", () => ({
|
||||||
|
ues: {
|
||||||
|
count: 0,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
page: pageDefault,
|
||||||
|
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||||
|
page_size: pageSizeDefault,
|
||||||
|
search: "",
|
||||||
|
department: [],
|
||||||
|
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||||
|
credit_type: [],
|
||||||
|
semester: [],
|
||||||
|
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||||
|
to_change: [],
|
||||||
|
pushstate: History.Push,
|
||||||
|
|
||||||
|
update: undefined,
|
||||||
|
|
||||||
|
initializeArgs() {
|
||||||
|
const url = getCurrentUrlParams();
|
||||||
|
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.has("semester") ? url.get("semester").split("_AND_") : [];
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.update = Alpine.debounce(async () => {
|
||||||
|
/* Create the whole url before changing everything all at once */
|
||||||
|
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"];
|
||||||
|
const paginationParams = ["page", "page_size"];
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
this.to_change.push({ param: param, value: value });
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("popstate", () => {
|
||||||
|
this.initializeArgs();
|
||||||
|
});
|
||||||
|
this.initializeArgs();
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true;
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
maxPage() {
|
||||||
|
return Math.ceil(this.ues.count / this.page_size);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import { getCurrentUrlParams, updateQueryString } from "#core:utils/history";
|
|
||||||
import { type SimpleUeSchema, ueFetchUeList } from "#openapi";
|
|
||||||
|
|
||||||
const pageDefault = 1;
|
|
||||||
const pageSizeDefault = 100;
|
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
|
||||||
Alpine.data("ue_search", () => ({
|
|
||||||
ues: {
|
|
||||||
count: 0,
|
|
||||||
next: null as string | null,
|
|
||||||
previous: null as string | null,
|
|
||||||
results: [] as SimpleUeSchema[],
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
page: pageDefault,
|
|
||||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
|
||||||
page_size: pageSizeDefault,
|
|
||||||
search: "",
|
|
||||||
hideClosedUes: true,
|
|
||||||
department: [] as string[],
|
|
||||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
|
||||||
credit_type: [] as string[],
|
|
||||||
semester: [] as string[],
|
|
||||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
|
||||||
to_change: [] as { param: string; value: string }[],
|
|
||||||
|
|
||||||
// dummy implementation to make TS happy.
|
|
||||||
// The real function is initialized in init
|
|
||||||
update: () => {
|
|
||||||
console.warn("Update not yet initialized");
|
|
||||||
},
|
|
||||||
|
|
||||||
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.search = url.get("search") || "";
|
|
||||||
this.hideClosedUes = url.get("hideClosed") || true;
|
|
||||||
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.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
await this.fetchData(); /* reload data on form change */
|
|
||||||
this.to_change = [];
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
const searchParams = [
|
|
||||||
"search",
|
|
||||||
"hideClosedUes",
|
|
||||||
"department",
|
|
||||||
"credit_type",
|
|
||||||
"semester",
|
|
||||||
];
|
|
||||||
const paginationParams = ["page", "page_size"];
|
|
||||||
|
|
||||||
for (const param of searchParams) {
|
|
||||||
this.$watch(param, () => {
|
|
||||||
/* Reset pagination on search */
|
|
||||||
this.page = pageDefault;
|
|
||||||
this.page_size = pageSizeDefault;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const param of searchParams.concat(paginationParams)) {
|
|
||||||
this.$watch(param, (value: string) => {
|
|
||||||
this.to_change.push({ param: param, value: value });
|
|
||||||
this.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
|
||||||
is_open: this.hideClosedUes ? true : undefined,
|
|
||||||
department: this.department.length > 0 ? this.department : undefined,
|
|
||||||
search: this.search || undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.data !== undefined) {
|
|
||||||
this.ues = res.data;
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
maxPage() {
|
|
||||||
return Math.ceil(this.ues.count / this.page_size);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
@@ -44,10 +44,6 @@
|
|||||||
x-model.debounce.500ms="search"
|
x-model.debounce.500ms="search"
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
|
||||||
<input type="checkbox" class="switch" x-model="hideClosedUes" id="hide-closed-ues" name="hide-closed-ues">
|
|
||||||
<label for="hide-closed-ues">{% trans %}Hide closed UEs{% endtrans %}</label>
|
|
||||||
</fieldset>
|
|
||||||
<div class="row gap-3x margin-bottom radio-guide">
|
<div class="row gap-3x margin-bottom radio-guide">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% set departments = [
|
{% set departments = [
|
||||||
|
|||||||
@@ -12,15 +12,36 @@
|
|||||||
{% trans %}Timetable generator{% endtrans %}
|
{% trans %}Timetable generator{% endtrans %}
|
||||||
{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div x-data="timetableGenerator">
|
<div x-data="timetableGenerator">
|
||||||
<form @submit.prevent="generate()">
|
<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>
|
<div class="alert alert-red" x-show="!!error" x-cloak>
|
||||||
<span class="alert-main" x-text="error"></span>
|
<span class="alert-main" x-text="error"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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>
|
<textarea id="timetable-input" cols="30" rows="15" x-model="content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" class="btn btn-blue" value="{% trans %}Generate{% endtrans %}">
|
<input type="submit" class="btn btn-blue" value="{% trans %}Generate{% endtrans %}">
|
||||||
|
|||||||
Reference in New Issue
Block a user