diff --git a/deployment/localsettings.template.py b/deployment/localsettings.template.py
index 270f4df29..2e469f457 100644
--- a/deployment/localsettings.template.py
+++ b/deployment/localsettings.template.py
@@ -1,3 +1,7 @@
+from fractions import Fraction
+
+from django.utils.safestring import mark_safe
+
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # postgresql', 'mysql', 'sqlite3' or 'oracle'.
@@ -15,3 +19,18 @@
# Make apache work when DEBUG == False
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
+
+### Evaluation progress rewards
+GLOBAL_EVALUATION_PROGRESS_REWARDS: list[tuple[Fraction, str]] = [
+ (Fraction("0"), "0€"),
+ (Fraction("0.25"), "1.000€"),
+ (Fraction("0.6"), "3.000€"),
+ (Fraction("0.7"), "7.000€"),
+ (Fraction("0.9"), "10.000€"),
+]
+GLOBAL_EVALUATION_PROGRESS_EXCLUDED_COURSE_TYPE_IDS: list[int] = []
+GLOBAL_EVALUATION_PROGRESS_EXCLUDED_EVALUATION_IDS: list[int] = []
+GLOBAL_EVALUATION_PROGRESS_INFO_TEXT = {
+ "de": mark_safe("Deine Teilnahme am Evaluationsprojekt wird helfen. Evaluiere also jetzt!"),
+ "en": mark_safe("Your participation in the evaluation helps, so evaluate now!"),
+}
diff --git a/evap/evaluation/migrations/0144_alter_evaluation_state.py b/evap/evaluation/migrations/0144_alter_evaluation_state.py
new file mode 100644
index 000000000..2bfbb3f1a
--- /dev/null
+++ b/evap/evaluation/migrations/0144_alter_evaluation_state.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.0.6 on 2024-06-17 23:00
+
+import django_fsm
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("evaluation", "0143_alter_evaluation_state"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="evaluation",
+ name="state",
+ field=django_fsm.FSMIntegerField(
+ choices=[
+ (10, "new"),
+ (20, "prepared"),
+ (30, "editor approved"),
+ (40, "approved"),
+ (50, "in evaluation"),
+ (60, "evaluated"),
+ (70, "reviewed"),
+ (80, "published"),
+ ],
+ default=10,
+ protected=True,
+ verbose_name="state",
+ ),
+ ),
+ ]
diff --git a/evap/evaluation/models_logging.py b/evap/evaluation/models_logging.py
index d82b6f7bb..bcbaa6c55 100644
--- a/evap/evaluation/models_logging.py
+++ b/evap/evaluation/models_logging.py
@@ -15,6 +15,7 @@
from django.template.defaultfilters import yesno
from django.utils.formats import localize
from django.utils.translation import gettext_lazy as _
+from typing_extensions import assert_never
from evap.evaluation.tools import capitalize_first
@@ -100,18 +101,21 @@ def field_context_data(self):
@property
def message(self):
- if self.action_type == InstanceActionType.CHANGE:
- if self.content_object:
- message = _("The {cls} {obj} was changed.")
- else: # content_object might be deleted
- message = _("A {cls} was changed.")
- elif self.action_type == InstanceActionType.CREATE:
- if self.content_object:
- message = _("The {cls} {obj} was created.")
- else:
- message = _("A {cls} was created.")
- elif self.action_type == InstanceActionType.DELETE:
- message = _("A {cls} was deleted.")
+ match self.action_type:
+ case InstanceActionType.CHANGE:
+ if self.content_object:
+ message = _("The {cls} {obj} was changed.")
+ else: # content_object might be deleted
+ message = _("A {cls} was changed.")
+ case InstanceActionType.CREATE:
+ if self.content_object:
+ message = _("The {cls} {obj} was created.")
+ else:
+ message = _("A {cls} was created.")
+ case InstanceActionType.DELETE:
+ message = _("A {cls} was deleted.")
+ case _:
+ assert_never(self.action_type)
return message.format(
cls=capitalize_first(self.content_type.model_class()._meta.verbose_name),
diff --git a/evap/evaluation/templates/base.html b/evap/evaluation/templates/base.html
index 53f7c11ce..9cc581b22 100644
--- a/evap/evaluation/templates/base.html
+++ b/evap/evaluation/templates/base.html
@@ -148,7 +148,7 @@
const baseOptions = {
createOnBlur: true,
placeholder: "{% translate 'Please select...' %}",
- hidePlaceholder: true,
+ hidePlaceholder: element.hasAttribute("data-tomselect-fullwidth") ? false : true,
minimumInputLength,
render: {
option_create: (data, escape) => `
${ escape(data.input) }
`,
diff --git a/evap/evaluation/templatetags/evaluation_filters.py b/evap/evaluation/templatetags/evaluation_filters.py
index c7b4df28f..915494349 100644
--- a/evap/evaluation/templatetags/evaluation_filters.py
+++ b/evap/evaluation/templatetags/evaluation_filters.py
@@ -108,6 +108,14 @@ def percentage_one_decimal(fraction, population):
return None
+@register.filter
+def percentage_zero_on_error(fraction, population):
+ try:
+ return f"{int(float(fraction) / float(population) * 100):.0f}%"
+ except (ZeroDivisionError, ValueError):
+ return "0%"
+
+
@register.filter
def to_colors(question_result: RatingResult | None):
if question_result is None:
diff --git a/evap/locale/de/LC_MESSAGES/django.po b/evap/locale/de/LC_MESSAGES/django.po
index 7507e3f9a..110eef430 100644
--- a/evap/locale/de/LC_MESSAGES/django.po
+++ b/evap/locale/de/LC_MESSAGES/django.po
@@ -5,8 +5,8 @@ msgid ""
msgstr ""
"Project-Id-Version: EvaP\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-06-17 20:33+0200\n"
-"PO-Revision-Date: 2024-06-17 20:33+0200\n"
+"POT-Creation-Date: 2024-07-06 13:53+0200\n"
+"PO-Revision-Date: 2024-07-06 13:55+0200\n"
"Last-Translator: Johannes Wolf \n"
"Language-Team: Johannes Wolf (janno42)\n"
"Language: de\n"
@@ -336,7 +336,7 @@ msgstr "Entwicklung"
#: evap/evaluation/forms.py:19 evap/staff/templates/staff_user_list.html:45
#: evap/staff/templates/staff_user_merge.html:56 evap/staff/views.py:777
-#: evap/staff/views.py:1507 evap/staff/views.py:2141
+#: evap/staff/views.py:1508 evap/staff/views.py:2142
msgid "Email"
msgstr "E‑Mail"
@@ -357,7 +357,7 @@ msgstr ""
"zur Anmeldung benötigt."
#: evap/evaluation/forms.py:63 evap/rewards/exporters.py:14
-#: evap/rewards/views.py:168
+#: evap/rewards/views.py:173
msgid "Email address"
msgstr "E‑Mail-Adresse"
@@ -1332,7 +1332,7 @@ msgid "email address"
msgstr "E‑Mail-Adresse"
#: evap/evaluation/models.py:1651 evap/staff/templates/staff_user_merge.html:32
-#: evap/staff/views.py:2141
+#: evap/staff/views.py:2142
msgid "Title"
msgstr "Titel"
@@ -1458,31 +1458,31 @@ msgstr ""
msgid "vote timestamp"
msgstr "Zeitstempel der Abstimmung"
-#: evap/evaluation/models_logging.py:67
+#: evap/evaluation/models_logging.py:68
msgid ""
msgstr ""
-#: evap/evaluation/models_logging.py:105
+#: evap/evaluation/models_logging.py:107
#, python-brace-format
msgid "The {cls} {obj} was changed."
msgstr "{cls} {obj} wurde geändert."
-#: evap/evaluation/models_logging.py:107
+#: evap/evaluation/models_logging.py:109
#, python-brace-format
msgid "A {cls} was changed."
msgstr "{cls} wurde geändert."
-#: evap/evaluation/models_logging.py:110
+#: evap/evaluation/models_logging.py:112
#, python-brace-format
msgid "The {cls} {obj} was created."
msgstr "{cls} {obj} wurde erstellt."
-#: evap/evaluation/models_logging.py:112
+#: evap/evaluation/models_logging.py:114
#, python-brace-format
msgid "A {cls} was created."
msgstr "{cls} wurde erstellt."
-#: evap/evaluation/models_logging.py:114
+#: evap/evaluation/models_logging.py:116
#, python-brace-format
msgid "A {cls} was deleted."
msgstr "{cls} wurde gelöscht."
@@ -2619,16 +2619,16 @@ msgid "Redemptions"
msgstr "Einlösungen"
#: evap/rewards/exporters.py:12 evap/staff/templates/staff_user_merge.html:50
-#: evap/staff/views.py:1507 evap/staff/views.py:2141
+#: evap/staff/views.py:1508 evap/staff/views.py:2142
msgid "Last name"
msgstr "Nachname"
#: evap/rewards/exporters.py:13 evap/staff/templates/staff_user_merge.html:38
-#: evap/staff/views.py:1507 evap/staff/views.py:2141
+#: evap/staff/views.py:1508 evap/staff/views.py:2142
msgid "First name"
msgstr "Vorname"
-#: evap/rewards/exporters.py:15 evap/rewards/views.py:168
+#: evap/rewards/exporters.py:15 evap/rewards/views.py:173
msgid "Number of points"
msgstr "Anzahl der Punkte"
@@ -2839,23 +2839,23 @@ msgid ""
"well."
msgstr "Wir freuen uns auch auf dein Feedback in den anderen Evaluierungen."
-#: evap/rewards/views.py:48
+#: evap/rewards/views.py:49
msgid "You successfully redeemed your points."
msgstr "Punkte erfolgreich eingelöst."
-#: evap/rewards/views.py:57
+#: evap/rewards/views.py:59
msgid "You cannot redeem 0 points."
msgstr "Du kannst nicht 0 Punkte einlösen."
-#: evap/rewards/views.py:59
+#: evap/rewards/views.py:61
msgid "You don't have enough reward points."
msgstr "Du hast nicht genügend Belohnungspunkte."
-#: evap/rewards/views.py:61
+#: evap/rewards/views.py:63
msgid "Sorry, the deadline for this event expired already."
msgstr "Sorry, die Frist für diese Veranstaltung ist bereits abgelaufen."
-#: evap/rewards/views.py:65
+#: evap/rewards/views.py:67
msgid ""
"It appears that your browser sent multiple redemption requests. You can see "
"all successful redemptions below."
@@ -2863,19 +2863,19 @@ msgstr ""
"Dein Browser scheint mehrere Anfragen zum Einlösen geschickt zu haben. Du "
"kannst alle erfolgreichen Einlösungen unten sehen."
-#: evap/rewards/views.py:83
+#: evap/rewards/views.py:88
msgid "Reward for"
msgstr "Belohnung für"
-#: evap/rewards/views.py:125
+#: evap/rewards/views.py:130
msgid "Successfully created event."
msgstr "Veranstaltung erfolgreich erstellt."
-#: evap/rewards/views.py:134
+#: evap/rewards/views.py:139
msgid "Successfully updated event."
msgstr "Veranstaltung erfolgreich geändert."
-#: evap/rewards/views.py:154 evap/rewards/views.py:164
+#: evap/rewards/views.py:159 evap/rewards/views.py:169
msgid "RewardPoints"
msgstr "Belohnungspunkte"
@@ -5458,50 +5458,50 @@ msgstr "E-Mails für '%s' wurden erfolgreich versendet."
msgid "{} participants were deleted from evaluation {}"
msgstr "{} Teilnehmende wurden aus der Evaluierung {} entfernt"
-#: evap/staff/views.py:1396
+#: evap/staff/views.py:1397
msgid "{} contributors were deleted from evaluation {}"
msgstr "{} Mitwirkende wurden aus der Evaluierung {} entfernt"
-#: evap/staff/views.py:1507
+#: evap/staff/views.py:1508
msgid "Login key"
msgstr "Anmeldeschlüssel"
-#: evap/staff/views.py:1782 evap/staff/views.py:1884 evap/staff/views.py:1929
+#: evap/staff/views.py:1783 evap/staff/views.py:1885 evap/staff/views.py:1930
msgid "Successfully created questionnaire."
msgstr "Fragebogen erfolgreich erstellt."
-#: evap/staff/views.py:1848
+#: evap/staff/views.py:1849
msgid "Successfully updated questionnaire."
msgstr "Fragebogen erfolgreich geändert."
-#: evap/staff/views.py:1904
+#: evap/staff/views.py:1905
msgid ""
"Questionnaire creation aborted. A new version was already created today."
msgstr ""
"Fragebogen-Erstellung abgebrochen. Es wurde heute bereits eine neue Version "
"angelegt."
-#: evap/staff/views.py:2014
+#: evap/staff/views.py:2015
msgid "Successfully updated the degrees."
msgstr "Studiengänge erfolgreich geändert."
-#: evap/staff/views.py:2029
+#: evap/staff/views.py:2030
msgid "Successfully updated the course types."
msgstr "Veranstaltungstypen erfolgreich geändert."
-#: evap/staff/views.py:2054
+#: evap/staff/views.py:2055
msgid "Successfully merged course types."
msgstr "Veranstaltungstypen erfolgreich zusammengeführt."
-#: evap/staff/views.py:2076
+#: evap/staff/views.py:2077
msgid "Successfully updated text warning answers."
msgstr "Textantwort-Warnungen erfolgreich geändert."
-#: evap/staff/views.py:2155
+#: evap/staff/views.py:2156
msgid "Successfully created user."
msgstr "Account erfolgreich erstellt."
-#: evap/staff/views.py:2210
+#: evap/staff/views.py:2211
#, python-brace-format
msgid ""
"The removal of evaluations has granted the user \"{granting.user_profile."
@@ -5518,19 +5518,19 @@ msgstr[1] ""
"user_profile.email}\" {granting.value} Belohnungspunkte für das aktive "
"Semester vergeben."
-#: evap/staff/views.py:2227
+#: evap/staff/views.py:2228
msgid "Successfully updated user."
msgstr "Account erfolgreich geändert."
-#: evap/staff/views.py:2253
+#: evap/staff/views.py:2254
msgid "Successfully deleted user."
msgstr "Account erfolgreich gelöscht."
-#: evap/staff/views.py:2270
+#: evap/staff/views.py:2271
msgid "Successfully resent evaluation started email."
msgstr "E-Mail über den Evaluierungsbeginn erfolgreich erneut gesendet."
-#: evap/staff/views.py:2299
+#: evap/staff/views.py:2300
msgid ""
"An error happened when processing the file. Make sure the file meets the "
"requirements."
@@ -5538,29 +5538,29 @@ msgstr ""
"Ein Fehler ist bei der Verarbeitung der Datei aufgetreten. Stelle sicher, "
"dass die Datei allen Anforderungen genügt."
-#: evap/staff/views.py:2364
+#: evap/staff/views.py:2365
msgid "Merging the users failed. No data was changed."
msgstr ""
"Das Zusammenführen der Accounts ist fehlgeschlagen. Es wurden keine Daten "
"verändert."
-#: evap/staff/views.py:2366
+#: evap/staff/views.py:2367
msgid "Successfully merged users."
msgstr "Accounts erfolgreich zusammengeführt."
-#: evap/staff/views.py:2388
+#: evap/staff/views.py:2389
msgid "Successfully updated template."
msgstr "Vorlage erfolgreich geändert."
-#: evap/staff/views.py:2433
+#: evap/staff/views.py:2434
msgid "Successfully updated the FAQ sections."
msgstr "FAQ-Abschnitte erfolgreich geändert."
-#: evap/staff/views.py:2451
+#: evap/staff/views.py:2452
msgid "Successfully updated the FAQ questions."
msgstr "FAQ-Fragen erfolgreich geändert."
-#: evap/staff/views.py:2463
+#: evap/staff/views.py:2464
msgid "Successfully updated the infotext entries."
msgstr "Infotexte erfolgreich geändert."
@@ -5568,7 +5568,27 @@ msgstr "Infotexte erfolgreich geändert."
msgid "Warning order"
msgstr "Reihenfolge"
-#: evap/student/templates/student_index.html:23
+#: evap/student/templates/student_global_reward.html:6
+msgid "Fundraising"
+msgstr "Spendenaktion"
+
+#: evap/student/templates/student_global_reward.html:9
+#, python-format
+msgid "Last evaluation: %(time_interval)s ago"
+msgstr "Letzte Evaluierung: Vor %(time_interval)s"
+
+#: evap/student/templates/student_global_reward.html:21
+#, python-format
+msgid "%(votes)s submitted evaluation (%(percent)s)"
+msgid_plural "%(votes)s submitted evaluations (%(percent)s)"
+msgstr[0] "%(votes)s abgesendete Evaluierung (%(percent)s)"
+msgstr[1] "%(votes)s abgesendete Evaluierungen (%(percent)s)"
+
+#: evap/student/templates/student_global_reward.html:42
+msgid "Every vote counts! Read more about our fundraising goal."
+msgstr "Jede Stimme zählt! Erfahre mehr über unser Spendenziel."
+
+#: evap/student/templates/student_index.html:25
msgid "Open and upcoming evaluations"
msgstr "Laufende und bevorstehende Evaluierungen"
@@ -5812,6 +5832,6 @@ msgstr "Textantwort zu dieser Frage hinzufügen"
msgid "After publishing, this text answer can be seen by:"
msgstr "Nach dem Veröffentlichen können diese Textantwort sehen:"
-#: evap/student/views.py:256
+#: evap/student/views.py:325
msgid "Your vote was recorded."
msgstr "Deine Antworten wurden gespeichert."
diff --git a/evap/locale/de/LC_MESSAGES/djangojs.po b/evap/locale/de/LC_MESSAGES/djangojs.po
index e102f4bd8..56e6ceb93 100644
--- a/evap/locale/de/LC_MESSAGES/djangojs.po
+++ b/evap/locale/de/LC_MESSAGES/djangojs.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: EvaP\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-06-17 20:37+0200\n"
+"POT-Creation-Date: 2024-07-06 13:53+0200\n"
"PO-Revision-Date: 2023-12-18 18:41+0100\n"
"Last-Translator: Johannes Wolf \n"
"Language-Team: Johannes Wolf (janno42)\n"
@@ -22,10 +22,10 @@ msgstr ""
msgid "The server is not responding."
msgstr "Der Server reagiert nicht."
-#: evap/static/js/sortable-form.js:23 evap/static/js/sortable_form.js:19
+#: evap/static/js/sortable_form.js:19
msgid "Delete"
msgstr "Löschen"
-#: evap/static/js/sortable-form.js:24 evap/static/js/sortable_form.js:20
+#: evap/static/js/sortable_form.js:20
msgid "add another"
msgstr "Weitere·n hinzufügen"
diff --git a/evap/rewards/views.py b/evap/rewards/views.py
index 724d515c0..f5cbbd261 100644
--- a/evap/rewards/views.py
+++ b/evap/rewards/views.py
@@ -13,6 +13,7 @@
from django.utils.translation import gettext_lazy
from django.views.decorators.http import require_POST
from django.views.generic import CreateView, UpdateView
+from typing_extensions import assert_never
from evap.evaluation.auth import manager_required, reward_user_required
from evap.evaluation.models import Semester, UserProfile
@@ -53,17 +54,21 @@ def redeem_reward_points(request):
OutdatedRedemptionDataError,
) as error:
status_code = 400
- if isinstance(error, NoPointsSelectedError):
- error_string = _("You cannot redeem 0 points.")
- elif isinstance(error, NotEnoughPointsError):
- error_string = _("You don't have enough reward points.")
- elif isinstance(error, RedemptionEventExpiredError):
- error_string = _("Sorry, the deadline for this event expired already.")
- elif isinstance(error, OutdatedRedemptionDataError):
- status_code = 409
- error_string = _(
- "It appears that your browser sent multiple redemption requests. You can see all successful redemptions below."
- )
+ match error:
+ case NoPointsSelectedError():
+ error_string = _("You cannot redeem 0 points.")
+ case NotEnoughPointsError():
+ error_string = _("You don't have enough reward points.")
+ case RedemptionEventExpiredError():
+ error_string = _("Sorry, the deadline for this event expired already.")
+ case OutdatedRedemptionDataError():
+ status_code = 409
+ error_string = _(
+ "It appears that your browser sent multiple redemption requests. You can see all successful redemptions below."
+ )
+ case _:
+ assert_never(type(error))
+
messages.error(request, error_string)
return status_code
return 200
diff --git a/evap/settings.py b/evap/settings.py
index cc5a45134..be20a246f 100644
--- a/evap/settings.py
+++ b/evap/settings.py
@@ -11,6 +11,7 @@
import logging
import os
import sys
+from fractions import Fraction
from typing import Any
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
@@ -352,6 +353,13 @@ class ManifestStaticFilesStorageWithJsReplacement(ManifestStaticFilesStorage):
# Absolute filesystem path to the directory that will hold user-uploaded files.
MEDIA_ROOT = os.path.join(BASE_DIR, "upload")
+### Evaluation progress rewards
+GLOBAL_EVALUATION_PROGRESS_REWARDS: list[tuple[Fraction, str]] = (
+ []
+) # (required_voter_ratio between 0 and 1, reward_text)
+GLOBAL_EVALUATION_PROGRESS_EXCLUDED_COURSE_TYPE_IDS: list[int] = []
+GLOBAL_EVALUATION_PROGRESS_EXCLUDED_EVALUATION_IDS: list[int] = []
+GLOBAL_EVALUATION_PROGRESS_INFO_TEXT: dict[str, str] = {"de": "", "en": ""}
### Slogans
SLOGANS_DE = [
diff --git a/evap/staff/tools.py b/evap/staff/tools.py
index 5bdf8822e..23b10fe42 100644
--- a/evap/staff/tools.py
+++ b/evap/staff/tools.py
@@ -100,8 +100,8 @@ def find_matching_internal_user_for_email(request, email):
return matching_users[0]
-def bulk_update_users(request, user_file_content, test_run):
- # pylint: disable=too-many-branches,too-many-locals
+def bulk_update_users(request, user_file_content, test_run): # noqa: PLR0912
+ # pylint: disable=too-many-locals
# user_file must have one user per line in the format "{username},{email}"
imported_emails = {clean_email(line.decode().split(",")[1]) for line in user_file_content.splitlines()}
diff --git a/evap/staff/views.py b/evap/staff/views.py
index 01c0630f0..7d29d1559 100644
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -1391,7 +1391,8 @@ def helper_delete_users_from_evaluation(evaluation, operation):
deleted_person_count = evaluation.participants.count()
deletion_message = _("{} participants were deleted from evaluation {}")
evaluation.participants.clear()
- elif "contributors" in operation:
+ else:
+ assert "contributors" in operation
deleted_person_count = evaluation.contributions.exclude(contributor=None).count()
deletion_message = _("{} contributors were deleted from evaluation {}")
evaluation.contributions.exclude(contributor=None).delete()
diff --git a/evap/static/scss/_mixins.scss b/evap/static/scss/_mixins.scss
index 16b27e403..f956071b6 100644
--- a/evap/static/scss/_mixins.scss
+++ b/evap/static/scss/_mixins.scss
@@ -1,7 +1,7 @@
@import "../bootstrap/scss/mixins";
@mixin bar-shadow($color) {
- text-shadow: 0 0 4px $color, 0 0 4px $color, 0 0 2px $color;
+ text-shadow: 0 0 2px $color, 0 0 2px $color, 0 0 2px $color, 0 0 2px $color, 0 0 2px $color, 0 0 2px $color;
}
@mixin no-user-select {
diff --git a/evap/static/scss/_utilities.scss b/evap/static/scss/_utilities.scss
index 11a49eed9..17e0ca688 100644
--- a/evap/static/scss/_utilities.scss
+++ b/evap/static/scss/_utilities.scss
@@ -84,4 +84,8 @@ a.no-underline:hover {
.width-percent-#{$i} {
width: $i * 1% !important;
}
+
+ .left-percent-#{$i} {
+ left: $i * 1% !important;
+ }
}
diff --git a/evap/static/scss/components/_card.scss b/evap/static/scss/components/_card.scss
index d63653408..51fb211e1 100644
--- a/evap/static/scss/components/_card.scss
+++ b/evap/static/scss/components/_card.scss
@@ -26,6 +26,14 @@
}
}
+.card-outline-light {
+ border-color: $light-gray;
+
+ > .card-header {
+ background-color: rgba($lighter-gray, 0.5);
+ }
+}
+
.card-noflex {
flex: none;
display: block;
diff --git a/evap/static/scss/components/_progress.scss b/evap/static/scss/components/_progress.scss
index 6eef3ce4a..3cdc7af69 100644
--- a/evap/static/scss/components/_progress.scss
+++ b/evap/static/scss/components/_progress.scss
@@ -54,3 +54,31 @@
.multi-progress-bar .progress-container {
padding-bottom: 3px;
}
+
+.global-rewards-progress-bar {
+ position: relative;
+ display: grid;
+ padding-top: .5rem;
+ padding-inline: 1.5rem;
+
+ .progress-container {
+ display: flex;
+ height: 25px;
+ grid-column-start: 1;
+ grid-row-start: 1;
+ }
+}
+
+.progress-step {
+ transform: translateX(-50%);
+ width: fit-content;
+ grid-column-start: 1;
+ grid-row-start: 2;
+
+ .seperator {
+ width: 1px;
+ height: 8px;
+ border: $dark-gray 1px solid;
+ }
+}
+
diff --git a/evap/static/scss/components/_transitions.scss b/evap/static/scss/components/_transitions.scss
index 0b4bed90b..a77fdb602 100644
--- a/evap/static/scss/components/_transitions.scss
+++ b/evap/static/scss/components/_transitions.scss
@@ -25,3 +25,12 @@
transform: rotate(-90deg);
}
}
+
+
+.collapse-toggle-light {
+ font-weight: normal;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
diff --git a/evap/static/ts/src/infobox.ts b/evap/static/ts/src/infobox.ts
index d2a60057f..159f84429 100644
--- a/evap/static/ts/src/infobox.ts
+++ b/evap/static/ts/src/infobox.ts
@@ -6,6 +6,7 @@ export class InfoboxLogic {
private readonly infobox: HTMLDivElement;
private readonly closeButton: HTMLButtonElement;
private readonly storageKey: string;
+ private timeout?: number;
constructor(infobox_id: string) {
this.infobox = selectOrError("#infobox-" + infobox_id);
@@ -17,6 +18,8 @@ export class InfoboxLogic {
// close the infobox and save state
this.closeButton.addEventListener("click", event => {
this.infobox.classList.add("closing");
+ this.infobox.classList.remove("opening");
+ clearTimeout(this.timeout);
setTimeout(() => {
this.infobox.classList.replace("closing", "closed");
}, OPEN_CLOSE_TIMEOUT);
@@ -28,7 +31,7 @@ export class InfoboxLogic {
this.infobox.addEventListener("click", _ => {
if (this.infobox.className.includes("closed")) {
this.infobox.classList.replace("closed", "opening");
- setTimeout(() => {
+ this.timeout = setTimeout(() => {
this.infobox.classList.remove("opening");
}, OPEN_CLOSE_TIMEOUT);
localStorage[this.storageKey] = "show";
diff --git a/evap/static/ts/tsconfig.compile.json b/evap/static/ts/tsconfig.compile.json
index d44c76f3e..0984ea111 100644
--- a/evap/static/ts/tsconfig.compile.json
+++ b/evap/static/ts/tsconfig.compile.json
@@ -4,7 +4,12 @@
"rootDir": "./src",
"outDir": "../../static/js",
"incremental": true,
- "tsBuildInfoFile": ".tsbuildinfo.json"
+ "tsBuildInfoFile": ".tsbuildinfo.json",
+ "types": [
+ "bootstrap",
+ "jquery",
+ "sortablejs"
+ ]
},
"include": ["src/**/*.ts"]
}
diff --git a/evap/student/forms.py b/evap/student/forms.py
index dccd9f623..114d7e9e9 100644
--- a/evap/student/forms.py
+++ b/evap/student/forms.py
@@ -80,7 +80,8 @@ def __init__(self, *args, contribution, questionnaire, **kwargs):
field = TextAnswerField.from_question(question)
elif question.is_rating_question:
field = RatingAnswerField.from_question(question)
- elif question.is_heading_question:
+ else:
+ assert question.is_heading_question
field = HeadingField.from_question(question)
identifier = answer_field_id(contribution, questionnaire, question)
diff --git a/evap/student/templates/student_global_reward.html b/evap/student/templates/student_global_reward.html
new file mode 100644
index 000000000..91ff92884
--- /dev/null
+++ b/evap/student/templates/student_global_reward.html
@@ -0,0 +1,53 @@
+{% load evaluation_filters %}
+
+{% if global_rewards %}
+
+
+
+
+
+
+
+
+ {% blocktranslate trimmed count votes=global_rewards.vote_count with percent=global_rewards.vote_count|percentage_zero_on_error:global_rewards.participation_count %}
+ {{ votes }} submitted evaluation ({{ percent }})
+ {% plural %}
+ {{ votes }} submitted evaluations ({{ percent }})
+ {% endblocktranslate %}
+
+
+
+ {% for reward in global_rewards.rewards_with_progress %}
+
+
+
+ {{ reward.vote_ratio|percentage:1 }}
+
+
{{ reward.text }}
+
+ {% endfor %}
+
+
+
+
+
+ {{ global_rewards.info_text }}
+
+
+
+
+
+{% endif %}
diff --git a/evap/student/templates/student_index.html b/evap/student/templates/student_index.html
index beef87904..c31426427 100644
--- a/evap/student/templates/student_index.html
+++ b/evap/student/templates/student_index.html
@@ -17,6 +17,8 @@
{% endif %}
+ {% include 'student_global_reward.html' with global_rewards=global_rewards %}
+
{% if unfinished_evaluations %}