diff --git a/app/util/datetime/DateTimeHandlerImpl.java b/app/util/datetime/DateTimeHandlerImpl.java index 4c85b7239..406d8d9ee 100644 --- a/app/util/datetime/DateTimeHandlerImpl.java +++ b/app/util/datetime/DateTimeHandlerImpl.java @@ -32,7 +32,6 @@ import org.joda.time.DateTimeZone; import org.joda.time.Interval; import org.joda.time.LocalDate; -import org.joda.time.LocalTime; import util.config.ConfigReader; public class DateTimeHandlerImpl implements DateTimeHandler { @@ -115,13 +114,25 @@ public List getExceptionEvents( DateTime end = new DateTime(ewh.getEndDate()).plusMillis(ewh.getEndDateTimezoneOffset()); Interval exception = new Interval(start, end); Interval wholeDay = date.toInterval(); + // exception covers this day fully if (exception.contains(wholeDay) || exception.equals(wholeDay)) { exceptions.clear(); exceptions.add(wholeDay); - break; - } - if (exception.overlaps(wholeDay)) { - exceptions.add(new Interval(exception.getStart(), exception.getEnd())); + } else if (exception.overlaps(wholeDay)) { + // exception starts this day but ends on a later day + if (start.toLocalDate().equals(date) && end.toLocalDate().isAfter(date)) { + exceptions.add(new Interval(exception.getStart(), wholeDay.getEnd())); + } + // exception ends this day but starts on an earlier day + else if (start.toLocalDate().isBefore(date) && end.toLocalDate().equals(date)) { + exceptions.add(new Interval(wholeDay.getStart(), exception.getEnd())); + } + // exception starts and ends this day + else { + exceptions.add( + new Interval(exception.getStart().withDate(date), exception.getEnd().withDate(date)) + ); + } } } } @@ -275,17 +286,9 @@ public List getWorkingHoursForDate(LocalDate date, ExamRoom room) List unifiedIntervals = mergeSlots( Stream.concat(workingHours.stream().map(OpeningHours::getHours), extensionEvents.stream()).toList() ); - int tzOffset; - if (workingHours.isEmpty()) { - LocalTime lt = LocalTime.now().withHourOfDay(java.time.LocalTime.NOON.getHour()); - tzOffset = DateTimeZone.forID(room.getLocalTimezone()).getOffset(date.toDateTime(lt)); - } else { - tzOffset = workingHours.getFirst().getTimezoneOffset(); - } + int offset = DateTimeZone.forID(room.getLocalTimezone()).getOffset(DateTime.now().withDayOfYear(1)); workingHours.clear(); - workingHours.addAll( - unifiedIntervals.stream().map(interval -> new OpeningHours(interval, tzOffset)).toList() - ); + workingHours.addAll(unifiedIntervals.stream().map(interval -> new OpeningHours(interval, offset)).toList()); } if (!restrictionEvents.isEmpty()) { for (OpeningHours hours : workingHours) { diff --git a/ui/src/app/exam/editor/sections/section-question.component.html b/ui/src/app/exam/editor/sections/section-question.component.html index b96e31208..60dc44838 100644 --- a/ui/src/app/exam/editor/sections/section-question.component.html +++ b/ui/src/app/exam/editor/sections/section-question.component.html @@ -1,44 +1,55 @@
- #{{ sectionQuestion.question.id }} - - @if ( - sectionQuestion.evaluationType === 'Points' || - sectionQuestion.question.type === 'ClozeTestQuestion' || - sectionQuestion.question.type === 'MultipleChoiceQuestion' - ) { - 0 / {{ sectionQuestion.maxScore }} {{ 'i18n_unit_points' | translate }} - } - @if (sectionQuestion.evaluationType === 'Selection') { - - {{ 'i18n_evaluation_select' | translate }} - - } - @if (sectionQuestion.question.type === 'WeightedMultipleChoiceQuestion') { - 0 / {{ calculateWeightedMaxPoints() }} {{ 'i18n_unit_points' | translate }} - } - @if (sectionQuestion.question.type === 'ClaimChoiceQuestion') { - - {{ getMinimumOptionScore() }} / {{ getCorrectClaimChoiceOptionScore() }} - {{ 'i18n_unit_points' | translate }} - - } - @if (sectionQuestion.question.attachment?.id || sectionQuestion.question.attachment?.externalId) { - - - - - - } + + + + + #{{ sectionQuestion.question.id }} + + @if ( + sectionQuestion.evaluationType === 'Points' || + sectionQuestion.question.type === 'ClozeTestQuestion' || + sectionQuestion.question.type === 'MultipleChoiceQuestion' + ) { + 0 / {{ sectionQuestion.maxScore }} {{ 'i18n_unit_points' | translate }} + } + @if (sectionQuestion.evaluationType === 'Selection') { + + {{ 'i18n_evaluation_select' | translate }} + + } + @if (sectionQuestion.question.type === 'WeightedMultipleChoiceQuestion') { + 0 / {{ calculateWeightedMaxPoints() }} {{ 'i18n_unit_points' | translate }} + } + @if (sectionQuestion.question.type === 'ClaimChoiceQuestion') { + + {{ getMinimumOptionScore() }} / {{ getCorrectClaimChoiceOptionScore() }} + {{ 'i18n_unit_points' | translate }} + + } + @if (sectionQuestion.question.attachment?.id || sectionQuestion.question.attachment?.externalId) { + + + + + + } + -
+
@@ -49,89 +60,99 @@
-
- -
-
+
+
+ +
+
+
-
-
-
- @if (sectionQuestion.answerInstructions || sectionQuestion.options.length > 0) { - - } +
+
+ @if (sectionQuestion.answerInstructions || sectionQuestion.options.length > 0) { + + } +
-
-
-
- @if (sectionQuestion.answerInstructions && sectionQuestion.answerInstructions.length > 0) { -
-
- - {{ sectionQuestion.answerInstructions }} +
+
+ @if (sectionQuestion.answerInstructions && sectionQuestion.answerInstructions.length > 0) { +
+
+ + {{ sectionQuestion.answerInstructions }} +
-
- } - @switch (sectionQuestion.question.type) { - @case ('MultipleChoiceQuestion') { - @for (option of sectionQuestion.options; track option) { -
-
- @if (option.option.correctOption) { - - } @else { - - } - {{ option.option.option }} + } + @switch (sectionQuestion.question.type) { + @case ('MultipleChoiceQuestion') { + @for (option of sectionQuestion.options; track option) { +
+
+ @if (option.option.correctOption) { + + } @else { + + } + {{ option.option.option }} +
-
+ } } - } - @case ('WeightedMultipleChoiceQuestion') { - @for (option of sectionQuestion.options; track option) { -
-
- @if (option.score >= 0) { - - } - @if (option.score < 0) { - - } - {{ option.option.option }} - {{ option.score }} {{ 'i18n_unit_points' | translate }} + @case ('WeightedMultipleChoiceQuestion') { + @for (option of sectionQuestion.options; track option) { +
+
+ @if (option.score >= 0) { + + } + @if (option.score < 0) { + + } + {{ option.option.option }} + {{ option.score }} {{ 'i18n_unit_points' | translate }} +
-
+ } } - } - @case ('ClaimChoiceQuestion') { - @for (option of sectionQuestion.options | orderBy: 'option.id'; track option) { -
-
- @if (determineClaimOptionType(option) === 'CorrectOption') { - - } - @if (determineClaimOptionType(option) === 'IncorrectOption') { - - } - @if (determineClaimOptionType(option) === 'SkipOption') { - - } - {{ option.option.option }} - {{ option.score }} {{ 'i18n_unit_points' | translate }} + @case ('ClaimChoiceQuestion') { + @for (option of sectionQuestion.options | orderBy: 'option.id'; track option) { +
+
+ @if (determineClaimOptionType(option) === 'CorrectOption') { + + } + @if (determineClaimOptionType(option) === 'IncorrectOption') { + + } + @if (determineClaimOptionType(option) === 'SkipOption') { + + } + {{ option.option.option }} + {{ option.score }} {{ 'i18n_unit_points' | translate }} +
-
+ } } } - } +
diff --git a/ui/src/app/exam/editor/sections/section-question.component.ts b/ui/src/app/exam/editor/sections/section-question.component.ts index 34b57acdf..e64b791fa 100644 --- a/ui/src/app/exam/editor/sections/section-question.component.ts +++ b/ui/src/app/exam/editor/sections/section-question.component.ts @@ -13,6 +13,7 @@ * See the Licence for the specific language governing permissions and limitations under the Licence. */ +import { CdkDragHandle } from '@angular/cdk/drag-drop'; import { HttpClient } from '@angular/common/http'; import { Component, EventEmitter, Input, Output } from '@angular/core'; import { @@ -44,6 +45,7 @@ import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; templateUrl: './section-question.component.html', standalone: true, imports: [ + CdkDragHandle, NgbPopover, NgbDropdown, NgbDropdownToggle, diff --git a/ui/src/app/exam/editor/sections/section.component.html b/ui/src/app/exam/editor/sections/section.component.html index a56d1edc6..60a55124e 100644 --- a/ui/src/app/exam/editor/sections/section.component.html +++ b/ui/src/app/exam/editor/sections/section.component.html @@ -1,242 +1,256 @@
- {{ 'i18n_section_title' | translate }} {{ index }} - - - - -
- -
- - -
-
-
-
-
- -
-
-
- - - - + + + {{ 'i18n_section_title' | translate }} {{ index }} - - - - - +
+ +
+ + +
+
+
+
+
+
+
+
+
+ + + + + + + + + - @if (!collaborative && canBeOptional) { -
+ @if (!collaborative && canBeOptional) { +
+ + +
+ } +
-
- } -
- - + @if (section.lotteryOn) { +
+
+ +
+
+ +
+
+ } + +
+
+ + @if (getSectionTotalScore() > 0) { +
+
+ {{ 'i18n_section_max_score' | translate }}: {{ getSectionTotalScore() }} +
+
+ } + @if (getAmountOfSelectionEvaluatedQuestions() > 0) { +
+
+ {{ 'i18n_evaluation_select' | translate }} - @if (!questionPointsMatch()) { - {{ 'i18n_error_lottery_points_not_match' | translate }} - }
- @if (section.lotteryOn) { -
-
- -
-
- -
-
- } - -
-
+
+ {{ getAmountOfSelectionEvaluatedQuestions() }} +
+
+ } -@if (getSectionTotalScore() > 0) { -
+
- {{ 'i18n_section_max_score' | translate }}: {{ getSectionTotalScore() }} -
-
-} -@if (getAmountOfSelectionEvaluatedQuestions() > 0) { -
-
- {{ 'i18n_evaluation_select' | translate }} - - - -
-
- {{ getAmountOfSelectionEvaluatedQuestions() }} +
-} -
-
- - -
-
-
- @for ( - sectionQuestion of section.sectionQuestions | orderBy: 'sequenceNumber'; - track sectionQuestion.sequenceNumber - ) { -
-
-
- - -
+
+
+ @if (!collaborative) { + } -
-
-
-
-
- @if (!collaborative) { - - } - +
diff --git a/ui/src/app/exam/editor/sections/section.component.scss b/ui/src/app/exam/editor/sections/section.component.scss deleted file mode 100644 index 5a0321101..000000000 --- a/ui/src/app/exam/editor/sections/section.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.section-question { - box-sizing: border-box; - cursor: move; -} diff --git a/ui/src/app/exam/editor/sections/section.component.ts b/ui/src/app/exam/editor/sections/section.component.ts index 457263c39..796f47618 100644 --- a/ui/src/app/exam/editor/sections/section.component.ts +++ b/ui/src/app/exam/editor/sections/section.component.ts @@ -66,7 +66,7 @@ import { SectionQuestionComponent } from './section-question.component'; TranslateModule, OrderByPipe, ], - styleUrls: ['./section.component.scss', './sections.shared.scss'], + styleUrls: ['./sections.shared.scss'], }) export class SectionComponent { @Input() section!: ExamSection; diff --git a/ui/src/app/exam/editor/sections/sections.component.html b/ui/src/app/exam/editor/sections/sections.component.html index 8b22f46d2..6f2706f91 100644 --- a/ui/src/app/exam/editor/sections/sections.component.html +++ b/ui/src/app/exam/editor/sections/sections.component.html @@ -19,10 +19,10 @@
@for (section of exam.examSections | orderBy: 'sequenceNumber'; track section; let i = $index) { -
+
#{{ i }} {{ section.name }}
-
+
-
diff --git a/ui/src/app/exam/editor/sections/sections.component.scss b/ui/src/app/exam/editor/sections/sections.component.scss index a50b707af..c6e1eff57 100644 --- a/ui/src/app/exam/editor/sections/sections.component.scss +++ b/ui/src/app/exam/editor/sections/sections.component.scss @@ -1,7 +1,9 @@ -.section-handle { - border: dashed 1px transparent; - cursor: move; -} -.section-handle:hover { - border-color: #006674; -} +.section-box { + padding: 10px; + box-sizing: border-box; + border: solid 1px #ccc; + color: rgba(0, 0, 0, 0.87); + position: relative; + + } + \ No newline at end of file diff --git a/ui/src/app/exam/editor/sections/sections.shared.scss b/ui/src/app/exam/editor/sections/sections.shared.scss index 070d54a2d..d27854727 100644 --- a/ui/src/app/exam/editor/sections/sections.shared.scss +++ b/ui/src/app/exam/editor/sections/sections.shared.scss @@ -11,8 +11,8 @@ .dragdrop-placeholder { background: #ccc; - border: dotted 3px #999; - min-height: 120px; + border: solid 3px #999; + min-height: 200px; transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } diff --git a/ui/src/app/navigation/navigation.component.html b/ui/src/app/navigation/navigation.component.html index 623017138..b8d0ded76 100644 --- a/ui/src/app/navigation/navigation.component.html +++ b/ui/src/app/navigation/navigation.component.html @@ -15,9 +15,10 @@ class="mobile-menu" (click)="openMenu()" [hidden]="!user" - [attr.aria-open]="mobileMenuOpen" + [attr.aria-expanded]="mobileMenuOpen" + [attr.aria-label]="'i18n_open_main_menu' | translate" > - {{ 'i18n_mobile_menu' | translate }} +
diff --git a/ui/src/app/review/assessment/sections/section.component.ts b/ui/src/app/review/assessment/sections/section.component.ts index 501f3986b..fd63b7dee 100644 --- a/ui/src/app/review/assessment/sections/section.component.ts +++ b/ui/src/app/review/assessment/sections/section.component.ts @@ -60,9 +60,11 @@ export class ExamSectionComponent implements OnInit, AfterViewInit { this.essayQuestionAmounts = this.Question.getEssayQuestionAmountsBySection(this.section); }; + // getReviewProgress gathers the questions that have been reviewed by calculating essay answers that have been evaluated plus the rest of the questions. + // Since the essay questions are the only ones that need to be evaluated and the rest of the questions are evaluated automatically. getReviewProgress = () => { return this.section.sectionQuestions.filter((q: ExamSectionQuestion) => { - return q.forcedScore || q.essayAnswer?.evaluatedScore; + return q.question.type !== 'EssayQuestion' || q.essayAnswer?.evaluatedScore; }).length; }; diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 4da654a94..5ff4c08d7 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -1112,7 +1112,7 @@ "i18n_date_too_far_in_future": "Selected date is later than latest date allowed:", "i18n_enrolment_assessment_not_received": "Re-enrolment is not possible because your previous examination has not returned from exam visit university", "i18n_not_allowed_to_modify_reservation": "Your current reservation is active so you can't edit it", - "i18n_mobile_menu": "Mobile menu", + "i18n_open_main_menu": "Open main menu", "i18n_exam_logo": "Exam logo", "i18n_show_hide_clock": "Show/hide time", "i18n_select_time_in_future": "Time must be in future", @@ -1178,5 +1178,6 @@ "i18n_unanswered_questions_remain": "There is still unanswered questions in this section", "i18n_unsaved_changes": "Unsaved changes", "i18n_type_to_start_search": "Type to start search", - "i18n_datepicker": "Datepicker" + "i18n_datepicker": "Datepicker", + "i18n_move_question": "Change the order of the exam sections by dragging" } diff --git a/ui/src/assets/i18n/fi.json b/ui/src/assets/i18n/fi.json index 13b3e0095..ffccf2d50 100644 --- a/ui/src/assets/i18n/fi.json +++ b/ui/src/assets/i18n/fi.json @@ -1112,7 +1112,7 @@ "i18n_date_too_far_in_future": "Valittu ajankohta on myöhemmin kuin kaukaisin tällä hetkellä sallittu:", "i18n_enrolment_assessment_not_received": "Uudelleen ilmoittautuminen ei sallittu, edellinen tenttivierailusuoritus ei ole vielä saapunut", "i18n_not_allowed_to_modify_reservation": "Varaus on käynnissä ja sitä ei voi muuttaa", - "i18n_mobile_menu": "Mobiili-menu", + "i18n_open_main_menu": "Avaa päävalikko", "i18n_exam_logo": "Exam-logo", "i18n_show_hide_clock": "Näytä/piilota aika", "i18n_select_time_in_future": "Valitse aika tulevaisuudesta", @@ -1178,5 +1178,6 @@ "i18n_unanswered_questions_remain": "Aihealueessa on vastaamattomia kysymyksiä", "i18n_unsaved_changes": "Tallentamattomia muutoksia", "i18n_type_to_start_search": "Kirjoita aloittaaksesi haku", - "i18n_datepicker": "Päivämäärävalitsin" + "i18n_datepicker": "Päivämäärävalitsin", + "i18n_move_question": "Vaihda kysymyssten järjestystä raahaamalla tästä" } diff --git a/ui/src/assets/i18n/sv.json b/ui/src/assets/i18n/sv.json index cdadcf650..3aa032ef3 100644 --- a/ui/src/assets/i18n/sv.json +++ b/ui/src/assets/i18n/sv.json @@ -1112,7 +1112,7 @@ "i18n_date_too_far_in_future": "Det valda datumet är senare än sista möjliga tidpunkt:", "i18n_enrolment_assessment_not_received": "Det är inte tillåtet att anmäla sig på nytt, eftersom den föregående tentamensprestationen inte ännu har kommit från den högskola där du skrev tentamen. ", "i18n_not_allowed_to_modify_reservation": "Din bokning pågår och kan därför inte ändras", - "i18n_mobile_menu": "Mobilmeny", + "i18n_open_main_menu": "Öppna huvudmenyn", "i18n_exam_logo": "Examlogo", "i18n_show_hide_clock": "Visa/dölj tiden", "i18n_select_time_in_future": "Välj tidpunkt i framtiden", @@ -1178,5 +1178,6 @@ "i18n_unanswered_questions_remain": "Det finns obesvarade frågor i sektionen", "i18n_unsaved_changes": "Osparade ändringar", "i18n_type_to_start_search": "Skriv för att börja söka", - "i18n_datepicker": "Välj datum" + "i18n_datepicker": "Välj datum", + "i18n_move_question": "Ändra ordningsföljden på frågorna genom att dra" }