diff --git a/evap/contributor/templates/contributor_evaluation_form.html b/evap/contributor/templates/contributor_evaluation_form.html index ecd2e22c51..9fb2b90d48 100644 --- a/evap/contributor/templates/contributor_evaluation_form.html +++ b/evap/contributor/templates/contributor_evaluation_form.html @@ -90,9 +90,18 @@
{% trans 'Evaluation data' %}
{% if editable %} - {# webtest does not allow submission with value "approve" if no such button exists #} - - + + + {% trans 'Approve evaluation' %} + {% trans 'Approve evaluation' %} + + {% blocktrans %} + Do you want to approve this evaluation? This will allow the evaluation team to proceed with the preparation, but you won't be able to make any further changes. + {% endblocktrans %} + + + + {% endif %} {% if edit %}{% trans 'Cancel' %}{% else %}{% trans 'Back' %}{% endif %} @@ -121,22 +130,6 @@ {% block modals %} {{ block.super }} - {% trans 'Approve evaluation' as title %} - {% blocktrans asvar question%}Do you want to approve this evaluation? This will allow the evaluation team to proceed with the preparation, but you won't be able to make any further changes.{% endblocktrans %} - {% trans 'Approve evaluation' as action_text %} - {% include 'confirmation_modal.html' with modal_id='approveEvaluationModal' title=title question=question action_text=action_text btn_type='primary' %} - {% blocktrans asvar title with evaluation_name=evaluation.full_name %}Request account creation for {{ evaluation_name }}{% endblocktrans %} {% trans 'Please tell us which new account we should create. We need the name and email for all new accounts.' as teaser %} diff --git a/evap/contributor/templates/contributor_index.html b/evap/contributor/templates/contributor_index.html index e61132d2cc..d49eef391c 100644 --- a/evap/contributor/templates/contributor_index.html +++ b/evap/contributor/templates/contributor_index.html @@ -157,12 +157,22 @@ {% if not evaluation|has_nonresponsible_editor %} - - - + + {% trans 'Delegate preparation' %} + {% trans 'Delegate preparation' %} + + {% blocktrans with evaluation_name=evaluation.full_name %} + Do you really want to delegate the preparation of the evaluation {{ evaluation_name }}? + {% endblocktrans %} +
+ {% include 'bootstrap_form.html' with form=delegate_selection_form wide=True %} +
+
+ + + + +
{% endif %} {% elif evaluation.state == evaluation.State.EDITOR_APPROVED or evaluation.state == evaluation.State.APPROVED %} + + {% endif %} {% endfor %} {% endblock %} - -{% block modals %} - {{ block.super }} - - {% with modal_id='delegateSelectionModal' %} - - - - {% endwith %} -{% endblock %} diff --git a/evap/evaluation/templates/base.html b/evap/evaluation/templates/base.html index cdb785d8e1..4c2d8346c3 100644 --- a/evap/evaluation/templates/base.html +++ b/evap/evaluation/templates/base.html @@ -28,6 +28,8 @@ + {% include "custom_elements.html" %} + {% block modals %} {% if user.is_authenticated %} {% trans 'Feedback' as title %} diff --git a/evap/evaluation/templates/confirmation_modal_template.html b/evap/evaluation/templates/confirmation_modal_template.html new file mode 100644 index 0000000000..bf13e90727 --- /dev/null +++ b/evap/evaluation/templates/confirmation_modal_template.html @@ -0,0 +1,33 @@ +{% load static %} + + diff --git a/evap/evaluation/templates/custom_elements.html b/evap/evaluation/templates/custom_elements.html new file mode 100644 index 0000000000..f2093a71a7 --- /dev/null +++ b/evap/evaluation/templates/custom_elements.html @@ -0,0 +1,9 @@ +{% load static %} + +{% include "confirmation_modal_template.html" %} + + diff --git a/evap/grades/templates/grades_course_view.html b/evap/grades/templates/grades_course_view.html index 7ab7b60fcb..bb2f36c43b 100644 --- a/evap/grades/templates/grades_course_view.html +++ b/evap/grades/templates/grades_course_view.html @@ -1,4 +1,5 @@ {% extends 'grades_course_base.html' %} +{% load static %} {% block content %} {{ block.super }} @@ -30,13 +31,45 @@

{{ course.name }} ({{ semester.name }})

{% if user.is_grade_publisher %} - + + {% trans 'Delete grade document' %} + {% trans 'Delete grade document' %} + + {% blocktrans with description=grade_document.description %} + Do you really want to delete the grade document {{ description }}? + {% endblocktrans %} + + + + {% endif %} {% endfor %} + + {% else %} @@ -49,23 +82,3 @@

{{ course.name }} ({{ semester.name }})

{% trans 'Upload new final grades' %} {% endif %} {% endblock %} - -{% block modals %} - {{ block.super }} - {% trans 'Delete grade document' as title %} - {% trans 'Do you really want to delete the grade document ?' as question %} - {% trans 'Delete grade document' as action_text %} - {% include 'confirmation_modal.html' with modal_id='deleteGradedocumentModal' title=title question=question action_text=action_text btn_type='danger' %} - -{% endblock %} diff --git a/evap/grades/templates/grades_semester_view.html b/evap/grades/templates/grades_semester_view.html index ae9d586115..9df312be6f 100644 --- a/evap/grades/templates/grades_semester_view.html +++ b/evap/grades/templates/grades_semester_view.html @@ -64,17 +64,38 @@

{% if num_final_grades > 0 %} {{ num_final_grades }} {% elif course.gets_no_grade_documents %} - - - + + {% trans 'Will final grades be uploaded?' %} + {% trans 'Confirm' %} + + {% blocktrans with course_name=course.name %} + Please confirm that a grade document for the course {{ course_name }} will be uploaded later on. + {% endblocktrans %} + + + + + + {% endif %} {% if not course.gets_no_grade_documents %} {% if num_final_grades == 0 %} - + + {% trans 'Have final grades been submitted?' %} + {% trans 'Confirm' %} + + {% blocktrans with course_name=course.name %} + Please confirm that the final grades for the course {{ course_name }} have been submitted but will not be uploaded. + {% endblocktrans %} + + + + +
{% endblock %} -{% block modals %} - {{ block.super }} - {% trans 'Have final grades been submitted?' as title %} - {% trans 'Please confirm that the final grades for the course have been submitted but will not be uploaded.' as question %} - {% trans 'Confirm' as action_text %} - {% include 'confirmation_modal.html' with modal_id='confirmNouploadModal' title=title question=question action_text=action_text btn_type='primary' %} - - {% trans 'Will final grades be uploaded?' as title %} - {% trans 'Please confirm that a grade document for the course will be uploaded later on.' as question %} - {% trans 'Confirm' as action_text %} - {% include 'confirmation_modal.html' with modal_id='confirmLateruploadModal' title=title question=question action_text=action_text btn_type='primary' %} - -{% endblock %} - {% block additional_javascript %} + + + {% endif %} {% if request.user.is_manager %}
@@ -72,8 +116,25 @@

{% trans 'Reward points active' %}
- - +
+ {% csrf_token %} + + + {% trans 'Activate reward points' %} + {% trans 'Activate reward points' %} + + {% blocktrans with semester_name=semester.name %} + Do you want to activate the reward points for the semester {{ semester_name }}? + The activation will allow participants to receive reward points when voting and will also grant all eligible points for participants who have already voted so far. + The process will take a while. + {% endblocktrans %} + + + + + + +

@@ -464,26 +525,6 @@

}).catch(error => {window.alert("{% trans 'The server is not responding.' %}");}); } - {% trans 'Delete semester' as title %} - {% blocktrans asvar question %}Do you really want to delete the semester ? All courses and evaluations will be deleted as well as all results. If you are sure, enter the name of the semester below.{% endblocktrans %} - {% trans 'Delete semester' as action_text %} - {% include 'confirmation_text_modal.html' with modal_id='deleteSemesterModal' title=title question=question action_text=action_text btn_type='danger' %} - {% trans 'Archive participations' as title %} {% blocktrans asvar question %}Do you really want to archive all participations in the semester ? Further changes to the evaluations won't be possible and you can't undo this action.{% endblocktrans %} {% trans 'Archive participations' as action_text %} @@ -564,15 +605,6 @@

}).catch(error => {window.alert("{% trans 'The server is not responding.' %}");}); }; - {% trans 'Activate reward points' as title %} - {% blocktrans asvar question %}Do you want to activate the reward points for the semester ? The activation will allow participants to receive reward points when voting and will also grant all eligible points for participants who have already voted so far. The process will take a while.{% endblocktrans %} - {% trans 'Activate reward points' as action_text %} - {% include 'confirmation_modal.html' with modal_id='activateRewardPointsModal' title=title question=question action_text=action_text btn_type='primary' %} - {% endblock %} {% block additional_javascript %} diff --git a/evap/staff/templates/staff_user_import.html b/evap/staff/templates/staff_user_import.html index b316052145..6593fd1351 100644 --- a/evap/staff/templates/staff_user_import.html +++ b/evap/staff/templates/staff_user_import.html @@ -34,33 +34,24 @@

{% trans 'Import users' %}

{% else %} + + + {% trans 'Import Users' %} + {% trans 'Import Users' %} + + {% blocktrans %} + Do you really want to import the users from the Excel file? + {% endblocktrans %} + + + + {% endif %} {% endblock %} -{% block modals %} -{{ block.super }} - {% trans 'Import Users' as title %} - {% blocktrans asvar question %}Do you really want to import the users from the Excel file?{% endblocktrans %} - {% trans 'Import Users' as action_text %} - {% include 'confirmation_modal.html' with modal_id='importUserModal' title=title question=question action_text=action_text btn_type='primary' %} - - -{% endblock %} - {% block additional_javascript %} {% endblock %} diff --git a/evap/staff/views.py b/evap/staff/views.py index d6f143a7b6..6a90c46146 100644 --- a/evap/staff/views.py +++ b/evap/staff/views.py @@ -620,7 +620,8 @@ def semester_delete(request): Contribution.objects.filter(evaluation__course__semester=semester).delete() Evaluation.objects.filter(course__semester=semester).delete() Course.objects.filter(semester=semester).delete() - semester.delete() + # TODO: revert + # semester.delete() return redirect("staff:index") diff --git a/evap/static/scss/_components.scss b/evap/static/scss/_components.scss index bc06ea8d47..f30a3f7eca 100644 --- a/evap/static/scss/_components.scss +++ b/evap/static/scss/_components.scss @@ -17,5 +17,6 @@ @import "components/modal"; @import "components/tooltip"; +@import "components/confirmation-modal"; @import "components/distribution-bar"; @import "components/quick-review"; diff --git a/evap/static/scss/components/_confirmation-modal.scss b/evap/static/scss/components/_confirmation-modal.scss new file mode 100644 index 0000000000..593828e135 --- /dev/null +++ b/evap/static/scss/components/_confirmation-modal.scss @@ -0,0 +1,77 @@ +confirmation-modal:not(:defined) > :not([slot="submit-group"]) { + // Without this, the elements that are slotted into the dialog element show up as if they are just normal child + // elements and disappear once the custom element registration is done. To avoid a short flicker of these elements, + // we hide them until the constructor of the custom element has run. + display: none; +} + +dialog.evap-modal-dialog { + padding: 0; + border: 1px solid $dark-gray; + border-radius: 0.5rem; + z-index: 1050; + inset-block-start: -70vh; + + font-weight: initial; + line-height: initial; + + // https://youtu.be/4prVdA7_6u0 + &[open] { + animation: modal-enter 300ms forwards; + } + + &[closing] { + display: block; + pointer-events: none; + animation: modal-exit 300ms forwards; + } + + @keyframes modal-enter { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0%); + opacity: 1; + } + } + + @keyframes modal-exit { + from { + transform: translateY(0%); + opacity: 1; + } + to { + transform: translateY(-100%); + opacity: 0; + } + } + + &::backdrop { + background-color: black; + opacity: 50%; + } + + .evap-modal-container { + > * { + padding: 1rem; + } + + > :not(:first-child) { + border-top: 1px solid $light-gray; + } + + header { + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .button-area { + display: flex; + flex-flow: wrap; + justify-content: center; + } + } +} diff --git a/evap/static/ts/src/confirmation-modal.ts b/evap/static/ts/src/confirmation-modal.ts new file mode 100644 index 0000000000..29c2d0d6e7 --- /dev/null +++ b/evap/static/ts/src/confirmation-modal.ts @@ -0,0 +1,62 @@ +import { selectOrError } from "./utils.js"; + +export class ConfirmationModal extends HTMLElement { + static formAssociated = true; + + readonly dialog: HTMLDialogElement; + readonly dialogForm: HTMLFormElement; + readonly type: string; + readonly internals: ElementInternals; + + constructor() { + super(); + + const template = selectOrError("#confirmation-modal-template").content; + const shadowRoot = this.attachShadow({ mode: "open" }); + shadowRoot.appendChild(template.cloneNode(true)); + + this.type = this.getAttribute("type") ?? "button"; + this.internals = this.attachInternals(); + + this.dialog = selectOrError("dialog", shadowRoot); + this.dialogForm = selectOrError("form[method=dialog]", this.dialog); + + const confirmButton = selectOrError("[data-event-type=confirm]", this.dialog); + const confirmButtonExtraClass = this.getAttribute("confirm-button-class") ?? "btn-primary"; + confirmButton.className += " " + confirmButtonExtraClass; + + selectOrError("[slot=show-button]", this).addEventListener("click", () => this.dialog.showModal()); + this.dialogForm.addEventListener("submit", this.onDialogFormSubmit); + } + + onDialogFormSubmit = (event: SubmitEvent) => { + event.preventDefault(); + + this.closeDialogSlowly(); + + if (event.submitter?.dataset?.eventType === "confirm") { + this.dispatchEvent(new CustomEvent("confirmed", { detail: new FormData(this.dialogForm) })); + + if (this.type === "submit") { + // Unfortunately, `this` cannot act as the submitter of the form. Instead, we make our `value` attribute + // visible to the form until submission is finished (the `submit` handlers of the form might cancel the + // submission again, which is why we hide reset the visible value again afterwards). + this.internals.setFormValue(this.getAttribute("value")); + this.internals.form?.requestSubmit(); + this.internals.setFormValue(null); + } + } + }; + + closeDialogSlowly = () => { + this.dialog.addEventListener( + "animationend", + () => { + this.dialog.removeAttribute("closing"); + this.dialog.close(); + }, + { once: true } + ); + this.dialog.setAttribute("closing", ""); + }; +} diff --git a/evap/static/ts/src/utils.ts b/evap/static/ts/src/utils.ts index ecaaae5917..de9434e3b5 100644 --- a/evap/static/ts/src/utils.ts +++ b/evap/static/ts/src/utils.ts @@ -1,4 +1,4 @@ -export const selectOrError = (selector: string, root: Element | Document = document): T => { +export const selectOrError = (selector: string, root: ParentNode = document): T => { const elem = root.querySelector(selector); assert(elem, `Element with selector ${selector} not found`); return elem;