diff --git a/com/static/com/css/news-list.scss b/com/static/com/css/news-list.scss
index cc423ccc..cd2fa4d0 100644
--- a/com/static/com/css/news-list.scss
+++ b/com/static/com/css/news-list.scss
@@ -81,9 +81,8 @@
}
#links_content {
- overflow: auto;
box-shadow: $shadow-color 1px 1px 1px;
- height: 20em;
+ padding: .5rem;
h4 {
margin-left: 5px;
diff --git a/com/templates/com/news_list.jinja b/com/templates/com/news_list.jinja
index 92e4dd71..c606e8f7 100644
--- a/com/templates/com/news_list.jinja
+++ b/com/templates/com/news_list.jinja
@@ -1,13 +1,11 @@
{% extends "core/base.jinja" %}
{% from "com/macros.jinja" import news_moderation_alert %}
-{% block title %}
- {% trans %}News{% endtrans %}
-{% endblock %}
+{% block title %}AE UTBM{% endblock %}
{% block additional_css %}
-
+
{# Atom feed discovery, not really css but also goes there #}
@@ -213,6 +211,10 @@
{% trans %}Matmatronch{% endtrans %}
+
+
+ {% trans %}Room reservation{% endtrans %}
+
{% trans %}Elections{% endtrans %}
diff --git a/com/static/com/components/ics-calendar.scss b/core/static/core/components/calendar.scss
similarity index 71%
rename from com/static/com/components/ics-calendar.scss
rename to core/static/core/components/calendar.scss
index 1c0a15bd..8bc9759a 100644
--- a/com/static/com/components/ics-calendar.scss
+++ b/core/static/core/components/calendar.scss
@@ -16,14 +16,74 @@
--event-details-padding: 20px;
--event-details-border: 1px solid #EEEEEE;
--event-details-border-radius: 4px;
- --event-details-box-shadow: 0px 6px 20px 4px rgb(0 0 0 / 16%);
+ --event-details-box-shadow: 0 6px 20px 4px rgb(0 0 0 / 16%);
--event-details-max-width: 600px;
}
-ics-calendar {
+ics-calendar,
+room-scheduler {
border: none;
box-shadow: none;
+ a.fc-col-header-cell-cushion,
+ a.fc-col-header-cell-cushion:hover {
+ color: black;
+ }
+
+ a.fc-daygrid-day-number,
+ a.fc-daygrid-day-number:hover {
+ color: rgb(34, 34, 34);
+ }
+
+ td {
+ overflow: visible; // Show events on multiple days
+ }
+
+ td, th {
+ text-align: unset;
+ }
+
+ //Reset from style.scss
+ table {
+ box-shadow: none;
+ border-radius: 0;
+ -moz-border-radius: 0;
+ margin: 0;
+ }
+
+ // Reset from style.scss
+ thead {
+ background-color: white;
+ color: black;
+ }
+
+ // Reset from style.scss
+ tbody > tr {
+ &:nth-child(even):not(.highlight) {
+ background: white;
+ }
+ }
+
+ .fc .fc-toolbar.fc-footer-toolbar {
+ margin-bottom: 0.5em;
+ }
+
+ button.text-copy,
+ button.text-copy:focus,
+ button.text-copy:hover {
+ background-color: #67AE6E !important;
+ transition: 500ms ease-in;
+ }
+
+ button.text-copied,
+ button.text-copied:focus,
+ button.text-copied:hover {
+ transition: 500ms ease-out;
+ }
+
+}
+
+ics-calendar {
#event-details {
z-index: 10;
max-width: 1151px;
@@ -60,82 +120,60 @@ ics-calendar {
align-items: start;
flex-direction: row;
background-color: var(--event-details-background-color);
- margin-top: 0px;
+ margin-top: 0;
margin-bottom: 4px;
}
}
+}
- a.fc-col-header-cell-cushion,
- a.fc-col-header-cell-cushion:hover {
- color: black;
+// Reset from style.scss
+thead {
+ background-color: white;
+ color: black;
+}
+
+// Reset from style.scss
+tbody > tr {
+ &:nth-child(even):not(.highlight) {
+ background: white;
}
+}
- a.fc-daygrid-day-number,
- a.fc-daygrid-day-number:hover {
- color: rgb(34, 34, 34);
- }
+.fc .fc-toolbar.fc-footer-toolbar {
+ margin-bottom: 0.5em;
+}
- td {
- overflow: visible; // Show events on multiple days
- }
+button.text-copy,
+button.text-copy:focus,
+button.text-copy:hover {
+ background-color: #67AE6E !important;
+ transition: 500ms ease-in;
+}
- //Reset from style.scss
- table {
- box-shadow: none;
- border-radius: 0px;
- -moz-border-radius: 0px;
- margin: 0px;
- }
+button.text-copied,
+button.text-copied:focus,
+button.text-copied:hover {
+ transition: 500ms ease-out;
+}
- // Reset from style.scss
- thead {
- background-color: white;
- color: black;
- }
+.fc .fc-getCalendarLink-button {
+ margin-right: 0.5rem;
+}
- // Reset from style.scss
- tbody>tr {
- &:nth-child(even):not(.highlight) {
- background: white;
- }
- }
-
- .fc .fc-toolbar.fc-footer-toolbar {
- margin-bottom: 0.5em;
- }
-
- button.text-copy,
- button.text-copy:focus,
- button.text-copy:hover {
- background-color: #67AE6E !important;
- transition: 500ms ease-in;
- }
-
- button.text-copied,
- button.text-copied:focus,
- button.text-copied:hover {
- transition: 500ms ease-out;
- }
-
- .fc .fc-getCalendarLink-button {
- margin-right: 0.5rem;
- }
-
- .fc .fc-helpButton-button {
- border-radius: 70%;
- padding-left: 0.5rem;
- padding-right: 0.5rem;
- background-color: rgba(0, 0, 0, 0.8);
- transition: 100ms ease-out;
- width: 30px;
- height: 30px;
- font-size: 11px;
- }
+.fc .fc-helpButton-button {
+ border-radius: 70%;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ background-color: rgba(0, 0, 0, 0.8);
+ transition: 100ms ease-out;
+ width: 30px;
+ height: 30px;
+ font-size: 11px;
+}
- .fc .fc-helpButton-button:hover {
- background-color: rgba(20, 20, 20, 0.6);
- }
+.fc .fc-helpButton-button:hover {
+ background-color: rgba(20, 20, 20, 0.6);
}
.tooltip.calendar-copy-tooltip {
diff --git a/counter/static/bundled/counter/types.d.ts b/counter/static/bundled/counter/types.d.ts
index 4a22a916..60770c24 100644
--- a/counter/static/bundled/counter/types.d.ts
+++ b/counter/static/bundled/counter/types.d.ts
@@ -1,4 +1,4 @@
-type ErrorMessage = string;
+declare type ErrorMessage = string;
export interface InitialFormData {
/* Used to refill the form when the backend raises an error */
diff --git a/package-lock.json b/package-lock.json
index 5ab0eeb9..96cf66d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,10 +13,14 @@
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
"@floating-ui/dom": "^1.6.13",
"@fortawesome/fontawesome-free": "^6.6.0",
- "@fullcalendar/core": "^6.1.15",
- "@fullcalendar/daygrid": "^6.1.15",
- "@fullcalendar/icalendar": "^6.1.15",
- "@fullcalendar/list": "^6.1.15",
+ "@fullcalendar/core": "^6.1.17",
+ "@fullcalendar/daygrid": "^6.1.17",
+ "@fullcalendar/icalendar": "^6.1.17",
+ "@fullcalendar/interaction": "^6.1.17",
+ "@fullcalendar/list": "^6.1.17",
+ "@fullcalendar/resource": "^6.1.17",
+ "@fullcalendar/resource-timeline": "^6.1.17",
+ "@hey-api/client-fetch": "^0.8.2",
"@sentry/browser": "^9.29.0",
"@zip.js/zip.js": "^2.7.52",
"3d-force-graph": "^1.73.4",
@@ -2224,6 +2228,15 @@
"ical.js": "^1.4.0"
}
},
+ "node_modules/@fullcalendar/interaction": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.17.tgz",
+ "integrity": "sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17"
+ }
+ },
"node_modules/@fullcalendar/list": {
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz",
@@ -2233,6 +2246,77 @@
"@fullcalendar/core": "~6.1.17"
}
},
+ "node_modules/@fullcalendar/premium-common": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/premium-common/-/premium-common-6.1.17.tgz",
+ "integrity": "sha512-zoN7fMwGMcP6Xu+2YudRAGfdwD2J+V+A/xAieXgYDSZT+5ekCsjZiwb2rmvthjt+HVnuZcqs6sGp7rnJ8Ie/mA==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17"
+ }
+ },
+ "node_modules/@fullcalendar/resource": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/resource/-/resource-6.1.17.tgz",
+ "integrity": "sha512-hWnbOWlroIN5Wt4NJmHAJh/F7ge2cV6S0PdGSmLFoZJZJA0hJX9GeYRzyz4MlUoj7f4dGzBlesy2RdC+t5FEMw==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@fullcalendar/premium-common": "~6.1.17"
+ },
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17"
+ }
+ },
+ "node_modules/@fullcalendar/resource-timeline": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/resource-timeline/-/resource-timeline-6.1.17.tgz",
+ "integrity": "sha512-QMrtc1mLs4c6DtlBNmWICef8Lr4CmzE47uWS/rcJBd9K2kBzvusTp7AQQ1qn3RX5UnjNHqT8pkKO/wE4yspJQw==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@fullcalendar/premium-common": "~6.1.17",
+ "@fullcalendar/scrollgrid": "~6.1.17",
+ "@fullcalendar/timeline": "~6.1.17"
+ },
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17",
+ "@fullcalendar/resource": "~6.1.17"
+ }
+ },
+ "node_modules/@fullcalendar/scrollgrid": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/scrollgrid/-/scrollgrid-6.1.17.tgz",
+ "integrity": "sha512-lzphEKwxWMS4xQVEuimzZjKFLijlSn49ExvzkYZls0VLDwOa3BYHcRlDJBjQ0LP6kauz9aatg3MfRIde/LAazA==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@fullcalendar/premium-common": "~6.1.17"
+ },
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17"
+ }
+ },
+ "node_modules/@fullcalendar/timeline": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/timeline/-/timeline-6.1.17.tgz",
+ "integrity": "sha512-UhL2OOph/S0cEKs3lzbXjS2gTxmQwaNug2XFjdljvO/ERj10v7OBXj/zvJrPyhjvWR/CSgjNgBaUpngkCu4JtQ==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@fullcalendar/premium-common": "~6.1.17",
+ "@fullcalendar/scrollgrid": "~6.1.17"
+ },
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.17"
+ }
+ },
+ "node_modules/@hey-api/client-fetch": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.8.4.tgz",
+ "integrity": "sha512-SWtUjVEFIUdiJGR2NiuF0njsSrSdTe7WHWkp3BLH3DEl2bRhiflOnBo29NSDdrY90hjtTQiTQkBxUgGOF29Xzg==",
+ "deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/hey-api"
+ }
+ },
"node_modules/@hey-api/json-schema-ref-parser": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.6.tgz",
@@ -2726,75 +2810,75 @@
]
},
"node_modules/@sentry-internal/browser-utils": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.29.0.tgz",
- "integrity": "sha512-Wp6UJCDVV2KVK+TG8GwdLZyDy4GtUYDmVhGMpHKPS3G/Qgpf36cY/XHwChwaHZ5P9Bk1sjS9Ok698J59S8L2nw==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.33.0.tgz",
+ "integrity": "sha512-DT9J0jIamavygIvW6rapgFb4L+7VoATPfEaV0UnXfGNXpSq18x7+vj1CyGMc//GBqqgb9SCHxJHOSkfuDYX7ZA==",
"license": "MIT",
"dependencies": {
- "@sentry/core": "9.29.0"
+ "@sentry/core": "9.33.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.29.0.tgz",
- "integrity": "sha512-ADvetGrtr+RfYcQKrQxah4fHs/xDJ/VjbStVMSuaNllzwWPYNkWIGFE6YjQ7wZszj0DQIu5/H+B6lZKsFYk4xw==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.33.0.tgz",
+ "integrity": "sha512-NQ3Q3d1xvtagI2cYZnI6C1i6hmMkUxIXUMjfO5JFTYpWGNIkzhIaoaY0HFqbiZ94FWwWdfodlQlj6r8Y+M0bnw==",
"license": "MIT",
"dependencies": {
- "@sentry/core": "9.29.0"
+ "@sentry/core": "9.33.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.29.0.tgz",
- "integrity": "sha512-we/1JPRje8sNowQCyogOV1OYWuDOP/3XmDi48XoFG2HB0XMl2HfL5LI8AvgAvC/5nrqVAAo4ktbjoVLm1fb7rg==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.33.0.tgz",
+ "integrity": "sha512-xDFrN19hDkP6+yS4ARYBruI0RinGYD8FPm7JC0BaIMP5yNWAJ80LTT0Jq9Dh1hQfDwUX34dpHy/9Aa7qv+2bRQ==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/browser-utils": "9.29.0",
- "@sentry/core": "9.29.0"
+ "@sentry-internal/browser-utils": "9.33.0",
+ "@sentry/core": "9.33.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.29.0.tgz",
- "integrity": "sha512-TrQYhSAVPhyenvu0fNkon7BznFibu1mzS5bCudxhgOWajZluUVrXcbp8Q3WZ3R+AogrcgA3Vy6aumP/+fMKdwg==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.33.0.tgz",
+ "integrity": "sha512-lFO5DYJ32K/mui5Ck7PbqcD7wzRxTyRKiy49gCGAp7x/mhLg5utf5vWPtegiUoCiiMB22rj+n2z0geZwiGKH4A==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/replay": "9.29.0",
- "@sentry/core": "9.29.0"
+ "@sentry-internal/replay": "9.33.0",
+ "@sentry/core": "9.33.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.29.0.tgz",
- "integrity": "sha512-+GFX/yb+rh6V1fSgTYM6ttAgledl2aUR3T3Rg86HNuegbdX8ym6lOtUOIZ0j9jPK015HR47KIPyIZVZZJ7Rj9g==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.33.0.tgz",
+ "integrity": "sha512-emlZlpE62lcpxMEzvrQzecnh0WeS36XLQlFLEUhGaYVOw7TBl5JPIoSB4mxPrzIn4GpW++3JrtKRpDAHQn/c4Q==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/browser-utils": "9.29.0",
- "@sentry-internal/feedback": "9.29.0",
- "@sentry-internal/replay": "9.29.0",
- "@sentry-internal/replay-canvas": "9.29.0",
- "@sentry/core": "9.29.0"
+ "@sentry-internal/browser-utils": "9.33.0",
+ "@sentry-internal/feedback": "9.33.0",
+ "@sentry-internal/replay": "9.33.0",
+ "@sentry-internal/replay-canvas": "9.33.0",
+ "@sentry/core": "9.33.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz",
- "integrity": "sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==",
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.33.0.tgz",
+ "integrity": "sha512-0mtJAU+x10+q5aV/txyeuPjJ0TmObcD701R0tY0s71yJJOltqqMrmgNpqyuMI/VOASuzTZesiMYdbG6xb3zeSw==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -5803,9 +5887,9 @@
}
},
"node_modules/vite-plugin-static-copy": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.0.2.tgz",
- "integrity": "sha512-/seLvhUg44s1oU9RhjTZZy/0NPbfNctozdysKcvPovxxXZdI5l19mGq6Ri3IaTf1Dy/qChS4BSR7ayxeu8o9aQ==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.0.tgz",
+ "integrity": "sha512-ONFBaYoN1qIiCxMCfeHI96lqLza7ujx/QClIXp4kEULUbyH2qLgYoaL8JHhk3FWjSB4TpzoaN3iMCyCFldyXzw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5819,7 +5903,7 @@
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0"
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/vite-plugin-static-copy/node_modules/chokidar": {
diff --git a/package.json b/package.json
index 3abaca2b..27e0ef4c 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"#core:*": "./core/static/bundled/*",
"#pedagogy:*": "./pedagogy/static/bundled/*",
"#counter:*": "./counter/static/bundled/*",
- "#com:*": "./com/static/bundled/*"
+ "#com:*": "./com/static/bundled/*",
+ "#reservation:*": "./reservation/static/bundled/*"
},
"devDependencies": {
"@babel/core": "^7.25.2",
@@ -43,10 +44,14 @@
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
"@floating-ui/dom": "^1.6.13",
"@fortawesome/fontawesome-free": "^6.6.0",
- "@fullcalendar/core": "^6.1.15",
- "@fullcalendar/daygrid": "^6.1.15",
- "@fullcalendar/icalendar": "^6.1.15",
- "@fullcalendar/list": "^6.1.15",
+ "@fullcalendar/core": "^6.1.17",
+ "@fullcalendar/daygrid": "^6.1.17",
+ "@fullcalendar/icalendar": "^6.1.17",
+ "@fullcalendar/interaction": "^6.1.17",
+ "@fullcalendar/list": "^6.1.17",
+ "@fullcalendar/resource": "^6.1.17",
+ "@fullcalendar/resource-timeline": "^6.1.17",
+ "@hey-api/client-fetch": "^0.8.2",
"@sentry/browser": "^9.29.0",
"@zip.js/zip.js": "^2.7.52",
"3d-force-graph": "^1.73.4",
diff --git a/reservation/static/bundled/reservation/components/room-scheduler-index.ts b/reservation/static/bundled/reservation/components/room-scheduler-index.ts
new file mode 100644
index 00000000..0b60f263
--- /dev/null
+++ b/reservation/static/bundled/reservation/components/room-scheduler-index.ts
@@ -0,0 +1,120 @@
+import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
+import {
+ Calendar,
+ type EventDropArg,
+ type EventSourceFuncArg,
+} from "@fullcalendar/core";
+import enLocale from "@fullcalendar/core/locales/en-gb";
+import frLocale from "@fullcalendar/core/locales/fr";
+
+import {
+ type ReservationslotFetchSlotsData,
+ type SlotSchema,
+ reservableroomFetchRooms,
+ reservationslotFetchSlots,
+ reservationslotUpdateSlot,
+} from "#openapi";
+
+import { paginated } from "#core:utils/api";
+import interactionPlugin from "@fullcalendar/interaction";
+import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
+
+@registerComponent("room-scheduler")
+export class RoomScheduler extends inheritHtmlElement("div") {
+ static observedAttributes = ["locale", "can_edit_slot", "can_create_slot"];
+ private scheduler: Calendar;
+ private locale = "en";
+ private canEditSlot = false;
+ private canBookSlot = false;
+
+ attributeChangedCallback(name: string, _oldValue?: string, newValue?: string) {
+ if (name === "locale") {
+ this.locale = newValue;
+ }
+ if (name === "can_edit_slot") {
+ this.canEditSlot = newValue.toLowerCase() === "true";
+ }
+ if (name === "can_create_slot") {
+ this.canBookSlot = newValue.toLowerCase() === "true";
+ }
+ }
+
+ /**
+ * Fetch the events displayed in the timeline.
+ * cf https://fullcalendar.io/docs/events-function
+ */
+ async fetchEvents(fetchInfo: EventSourceFuncArg) {
+ const res: SlotSchema[] = await paginated(reservationslotFetchSlots, {
+ query: { after: fetchInfo.startStr, before: fetchInfo.endStr },
+ } as ReservationslotFetchSlotsData);
+ return res.map((i) =>
+ Object.assign(i, {
+ title: `${i.author.first_name} ${i.author.last_name}`,
+ resourceId: i.room,
+ editable: new Date(i.start) > new Date(),
+ }),
+ );
+ }
+
+ /**
+ * Fetch the resources which events are associated with.
+ * cf https://fullcalendar.io/docs/resources-function
+ */
+ async fetchResources() {
+ const res = await reservableroomFetchRooms();
+ return res.data.map((i) => Object.assign(i, { title: i.name, group: i.location }));
+ }
+
+ /**
+ * Send a request to the API to change
+ * the start and the duration of a reservation slot
+ */
+ async changeReservation(args: EventDropArg) {
+ const duration = new Date(args.event.end.getTime() - args.event.start.getTime());
+ const response = await reservationslotUpdateSlot({
+ // biome-ignore lint/style/useNamingConvention: api is snake_case
+ path: { slot_id: Number.parseInt(args.event.id) },
+ query: {
+ start: args.event.startStr,
+ duration: `PT${duration.getUTCHours()}H${duration.getUTCMinutes()}M${duration.getUTCSeconds()}S`,
+ },
+ });
+ if (response.response.ok) {
+ this.scheduler.refetchEvents();
+ }
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.scheduler = new Calendar(this.node, {
+ schedulerLicenseKey: "GPL-My-Project-Is-Open-Source",
+ initialView: "resourceTimelineDay",
+ headerToolbar: {
+ left: "prev,next today",
+ center: "title",
+ right: "resourceTimelineDay,resourceTimelineWeek",
+ },
+ plugins: [resourceTimelinePlugin, interactionPlugin],
+ locales: [frLocale, enLocale],
+ height: "auto",
+ locale: this.locale,
+ resourceGroupField: "group",
+ resourceAreaHeaderContent: gettext("Rooms"),
+ editable: this.canEditSlot,
+ snapDuration: "00:15",
+ eventConstraint: { start: new Date() }, // forbid edition of past events
+ eventOverlap: false,
+ eventResourceEditable: false,
+ refetchResourcesOnNavigate: true,
+ resourceAreaWidth: "20%",
+ resources: this.fetchResources,
+ events: this.fetchEvents,
+ selectOverlap: false,
+ selectable: this.canBookSlot,
+ selectConstraint: { start: new Date() },
+ nowIndicator: true,
+ eventDrop: this.changeReservation,
+ });
+ this.scheduler.render();
+ }
+}
diff --git a/reservation/templates/reservation/schedule.jinja b/reservation/templates/reservation/schedule.jinja
new file mode 100644
index 00000000..a29f3d70
--- /dev/null
+++ b/reservation/templates/reservation/schedule.jinja
@@ -0,0 +1,21 @@
+{% extends "core/base.jinja" %}
+
+{% block additional_js %}
+
+
+{% endblock %}
+
+{% block additional_css %}
+
+
+{% endblock %}
+
+
+{% block content %}
+ {% trans %}Room reservation{% endtrans %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/reservation/urls.py b/reservation/urls.py
index cb2f0564..a9912582 100644
--- a/reservation/urls.py
+++ b/reservation/urls.py
@@ -1,12 +1,14 @@
from django.urls import path
from reservation.views import (
+ ReservationScheduleView,
RoomCreateView,
RoomDeleteView,
RoomUpdateView,
)
urlpatterns = [
+ path("", ReservationScheduleView.as_view(), name="main"),
path("room/create/", RoomCreateView.as_view(), name="room_create"),
path("room//edit", RoomUpdateView.as_view(), name="room_edit"),
path("room//delete", RoomDeleteView.as_view(), name="room_delete"),
diff --git a/reservation/views.py b/reservation/views.py
index 47c1697b..8e346875 100644
--- a/reservation/views.py
+++ b/reservation/views.py
@@ -8,10 +8,13 @@ from django.views.generic import CreateView, DeleteView, TemplateView, UpdateVie
from club.models import Club
from core.auth.mixins import CanEditMixin
-from core.views import UseFragmentsMixin
-from core.views.mixins import FragmentMixin
-from reservation.forms import ReservationForm, RoomCreateForm, RoomUpdateForm
-from reservation.models import ReservationSlot, Room
+from reservation.forms import RoomCreateForm, RoomUpdateForm
+from reservation.models import Room
+
+
+class ReservationScheduleView(PermissionRequiredMixin, TemplateView):
+ template_name = "reservation/schedule.jinja"
+ permission_required = "reservation.view_room"
class RoomCreateView(SuccessMessageMixin, PermissionRequiredMixin, CreateView):
diff --git a/tsconfig.json b/tsconfig.json
index 1f47a39e..c7a8df97 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,7 +17,8 @@
"#core:*": ["./core/static/bundled/*"],
"#pedagogy:*": ["./pedagogy/static/bundled/*"],
"#counter:*": ["./counter/static/bundled/*"],
- "#com:*": ["./com/static/bundled/*"]
+ "#com:*": ["./com/static/bundled/*"],
+ "#reservation:*": ["./reservation/static/bundled/*"]
}
}
}