From 5b9c93caee50f332f6540c8d56860a4e3c28640f Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Wed, 24 Sep 2025 20:09:44 +0200 Subject: [PATCH 01/13] replace controls in overviews --- .../calendar-desktop-overview.component.html | 73 +++++---- .../calendar-desktop-overview.component.scss | 43 ++++++ ...alendar-desktop-overview.component.spec.ts | 6 +- .../calendar-desktop-overview.component.ts | 142 ++++++++++++++---- .../calendar-mobile-overview.component.html | 42 ++++-- .../calendar-mobile-overview.component.scss | 10 +- .../calendar-mobile-overview.component.ts | 30 +++- .../calendar-event-filter.component.html | 38 +---- .../calendar-event-filter.component.scss | 4 +- .../calendar-event-filter.component.ts | 48 +----- .../calendar/shared/util/calendar-util.ts | 11 -- src/main/webapp/app/primeng-artemis-theme.ts | 20 +-- 12 files changed, 282 insertions(+), 185 deletions(-) diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html index 9559f3004e35..50a3f15c5acc 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html @@ -2,57 +2,66 @@
{{ getMonthDescription() }}
- + + + @if (selectedFilterOptions().length) { +
+ @for (optionWithMetadata of selectedFilterOptions(); track optionWithMetadata.option) { +
+
+ {{ optionWithMetadata.name }} + +
+ } +
+ } +
+
+ @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); @if (courseId && subscriptionToken) {
} - -
- - - -
-
- + + - -
+
- @if (presentation() === 'month') { + @if (selectedPresentation() === 'month') { } @else { diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss index df1ff20392a6..322c1fab1c48 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss @@ -13,6 +13,49 @@ flex: 1; height: 100%; } + + .chips-section { + display: flex; + gap: 0.375rem; + min-width: 6rem; + + .chip { + display: flex; + align-items: center; + gap: 0.25rem; + box-sizing: border-box; + padding: 0.125rem 0.35rem; + border-radius: 0.25rem; + background: var(--body-bg); + font-size: 0.75rem; + + .remove-button { + cursor: pointer; + } + } + + .exam-chip { + background-color: var(--pastel-teal); + } + + .lecture-chip { + background-color: var(--pastel-blue); + } + + .tutorial-chip { + background-color: var(--pastel-purple); + } + + .exercise-chip { + background-color: var(--pastel-cyan); + } + + .legend-circle { + width: 0.75rem; + height: 0.75rem; + border-radius: 0.25rem; + } + } } .calendar-presentation-container { diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts index ae52c920bcce..5d6b0171ee05 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts @@ -78,7 +78,7 @@ describe('CalendarDesktopOverviewComponent', () => { expect(weekButton).toBeTruthy(); expect(monthButton).toBeTruthy(); - expect(component.presentation()).toBe('month'); + expect(component.selectedPresentation()).toBe('month'); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-month-presentation'))).toBeTruthy(); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-week-presentation'))).toBeFalsy(); @@ -102,7 +102,7 @@ describe('CalendarDesktopOverviewComponent', () => { weekButton.click(); fixture.detectChanges(); - expect(component.presentation()).toBe('week'); + expect(component.selectedPresentation()).toBe('week'); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-week-presentation'))).toBeTruthy(); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-month-presentation'))).toBeFalsy(); @@ -159,7 +159,7 @@ describe('CalendarDesktopOverviewComponent', () => { component.firstDayOfCurrentMonth.set(october.startOf('month')); component.firstDayOfCurrentWeek.set(october.startOf('isoWeek')); - component.presentation.set('month'); + component.selectedPresentation.set('month'); fixture.detectChanges(); const heading = () => fixture.debugElement.query(By.css('.h3')).nativeElement.textContent.trim(); diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts index d5fcf4e5baf5..e7e5100882f7 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts @@ -1,77 +1,112 @@ -import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; +import { Component, OnInit, computed, effect, inject, signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; import { NgClass } from '@angular/common'; import { finalize } from 'rxjs/operators'; import dayjs, { Dayjs } from 'dayjs/esm'; import 'dayjs/esm/locale/en'; import 'dayjs/esm/locale/de'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons'; +import { faChevronLeft, faChevronRight, faXmark } from '@fortawesome/free-solid-svg-icons'; import { TranslateService } from '@ngx-translate/core'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { CalendarDesktopMonthPresentationComponent } from 'app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component'; import { CalendarDesktopWeekPresentationComponent } from 'app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { CalendarEventFilterComponent, CalendarEventFilterComponentVariant } from 'app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component'; import { CalendarSubscriptionPopoverComponent } from 'app/core/calendar/shared/calendar-subscription-popover/calendar-subscription-popover.component'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { FormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { ButtonGroupModule } from 'primeng/buttongroup'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; + +type CalendarEventFilterOptionAndMetadata = { option: CalendarEventFilterOption; name: string; colorClassName: string }; @Component({ selector: 'jhi-calendar-desktop-overview', imports: [ CalendarDesktopMonthPresentationComponent, CalendarDesktopWeekPresentationComponent, - CalendarEventFilterComponent, NgClass, FaIconComponent, TranslateDirective, CalendarSubscriptionPopoverComponent, + SelectButtonModule, + FormsModule, + ButtonModule, + ButtonGroupModule, + MultiSelectModule, ], templateUrl: './calendar-desktop-overview.component.html', styleUrl: './calendar-desktop-overview.component.scss', }) -export class CalendarDesktopOverviewComponent implements OnInit, OnDestroy { +export class CalendarDesktopOverviewComponent implements OnInit { + private static readonly FILTER_OPTION_NAME_KEY_MAP: Record = { + exerciseEvents: 'artemisApp.calendar.filterOption.exercises', + lectureEvents: 'artemisApp.calendar.filterOption.lectures', + tutorialEvents: 'artemisApp.calendar.filterOption.tutorials', + examEvents: 'artemisApp.calendar.filterOption.exams', + }; + private static readonly FILTER_OPTION_COLOR_CLASS_MAP: Record = { + exerciseEvents: 'exercise-chip', + lectureEvents: 'lecture-chip', + tutorialEvents: 'tutorial-chip', + examEvents: 'exam-chip', + }; + private calendarService = inject(CalendarService); private translateService = inject(TranslateService); private activatedRoute = inject(ActivatedRoute); - private activatedRouteSubscription?: Subscription; - private currentLocaleSubscription?: Subscription; - private currentLocale = signal(this.translateService.currentLang); + private currentLocale = toSignal(this.translateService.onLangChange.pipe(map((event) => event.lang)), { initialValue: this.translateService.currentLang }); - readonly CalendarEventFilterComponentVariant = CalendarEventFilterComponentVariant; - readonly faChevronRight = faChevronRight; - readonly faChevronLeft = faChevronLeft; + protected readonly faChevronRight = faChevronRight; + protected readonly faChevronLeft = faChevronLeft; + protected readonly faXmark = faXmark; - presentation = signal<'week' | 'month'>('month'); + selectedPresentation = signal<'week' | 'month'>('month'); + presentationOptions = computed<{ label: string; value: 'week' | 'month' }[]>(() => { + this.currentLocale(); + return this.buildPresentationOptions(); + }); + selectedFilterOptions = computed(() => { + this.currentLocale(); + return this.computeSelectedFilterOptions(this.calendarService.includedEventFilterOptions()); + }); + filterOptions = computed(() => { + this.currentLocale(); + return this.buildFilterOptions(); + }); firstDayOfCurrentMonth = signal(dayjs().startOf('month')); firstDayOfCurrentWeek = signal(dayjs().startOf('isoWeek')); - isLoading = signal(false); calendarSubscriptionToken = this.calendarService.subscriptionToken; - currentCourseId = signal(undefined); - - ngOnInit(): void { - this.currentLocaleSubscription = this.translateService.onLangChange.subscribe((event) => { - this.currentLocale.set(event.lang); - }); + isLoading = signal(false); + currentCourseId = toSignal( + this.activatedRoute.parent!.paramMap.pipe( + map((params) => { + const id = params.get('courseId'); + return id ? +id : undefined; + }), + ), + { initialValue: undefined }, + ); - this.activatedRouteSubscription = this.activatedRoute.parent?.paramMap.subscribe((parameterMap) => { - const courseIdParameter = parameterMap.get('courseId'); - if (courseIdParameter) { - this.currentCourseId.set(+courseIdParameter); + constructor() { + effect(() => { + const courseId = this.currentCourseId(); + if (courseId) { this.loadEventsForCurrentMonth(); } }); - - this.calendarService.loadSubscriptionToken().subscribe(); } - ngOnDestroy() { - this.currentLocaleSubscription?.unsubscribe(); - this.activatedRouteSubscription?.unsubscribe(); + ngOnInit(): void { + this.calendarService.loadSubscriptionToken().subscribe(); } goToPrevious(): void { - if (this.presentation() === 'week') { + if (this.selectedPresentation() === 'week') { this.firstDayOfCurrentWeek.update((current) => current.subtract(1, 'week')); const firstDayOfCurrentWeek = this.firstDayOfCurrentWeek(); const firstDayOfCurrentMonth = this.firstDayOfCurrentMonth(); @@ -86,7 +121,7 @@ export class CalendarDesktopOverviewComponent implements OnInit, OnDestroy { } goToNext(): void { - if (this.presentation() === 'week') { + if (this.selectedPresentation() === 'week') { this.firstDayOfCurrentWeek.update((current) => current.add(1, 'week')); const endOfCurrentWeek = this.firstDayOfCurrentWeek().endOf('isoWeek'); const endOfCurrentMonth = this.firstDayOfCurrentMonth().endOf('month'); @@ -108,7 +143,7 @@ export class CalendarDesktopOverviewComponent implements OnInit, OnDestroy { getMonthDescription(): string { const currentLocale = this.currentLocale(); - if (this.presentation() === 'month') { + if (this.selectedPresentation() === 'month') { return this.firstDayOfCurrentMonth().locale(currentLocale).format('MMMM YYYY'); } else { const firstDayOfCurrentWeek = this.firstDayOfCurrentWeek().locale(currentLocale); @@ -121,6 +156,23 @@ export class CalendarDesktopOverviewComponent implements OnInit, OnDestroy { } } + onSelectionOptionsChange(newSelectedOptionsAndMetadata: CalendarEventFilterOptionAndMetadata[]): void { + const options = newSelectedOptionsAndMetadata.map((optionAndMetadata) => optionAndMetadata.option); + this.calendarService.includedEventFilterOptions.set(options); + } + + removeOption(option: CalendarEventFilterOption): void { + this.calendarService.includedEventFilterOptions.update((currentOptions) => currentOptions.filter((otherOption) => otherOption !== option)); + } + + private addMetadataTo(option: CalendarEventFilterOption): CalendarEventFilterOptionAndMetadata { + return { + option: option, + name: this.translateService.instant(CalendarDesktopOverviewComponent.FILTER_OPTION_NAME_KEY_MAP[option]), + colorClassName: CalendarDesktopOverviewComponent.FILTER_OPTION_COLOR_CLASS_MAP[option], + }; + } + private loadEventsForCurrentMonth(): void { const courseId = this.currentCourseId(); if (!courseId) return; @@ -130,4 +182,30 @@ export class CalendarDesktopOverviewComponent implements OnInit, OnDestroy { .pipe(finalize(() => this.isLoading.set(false))) .subscribe(); } + + private buildPresentationOptions() { + return [ + { + label: this.translateService.instant('artemisApp.calendar.weekButtonLabel'), + value: 'week' as const, + }, + { + label: this.translateService.instant('artemisApp.calendar.monthButtonLabel'), + value: 'month' as const, + }, + ]; + } + + private computeSelectedFilterOptions(includedOptions: CalendarEventFilterOption[]): CalendarEventFilterOptionAndMetadata[] { + return includedOptions.map((option) => this.addMetadataTo(option)); + } + + private buildFilterOptions(): CalendarEventFilterOptionAndMetadata[] { + return [ + this.addMetadataTo(CalendarEventFilterOption.LectureEvents), + this.addMetadataTo(CalendarEventFilterOption.ExamEvents), + this.addMetadataTo(CalendarEventFilterOption.ExerciseEvents), + this.addMetadataTo(CalendarEventFilterOption.TutorialEvents), + ]; + } } diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html index e7f96c37f7fd..ff962874ae22 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html @@ -1,24 +1,27 @@
- - @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); @if (courseId && subscriptionToken) {
-
} - +
-
+
@if (selectedDate()) {
{{ firstDateOfCurrentMonth().format('MMMM YYYY') }}
-
- -
+
@for (dayNameKey of weekdayNameKeys; track dayNameKey) { @@ -58,3 +59,24 @@ }
+ + +
+
+ + Lectures +
+
+ + Exercises +
+
+ + Tutorials +
+
+ + Exams +
+
+
diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss index d90f34ae0a08..e3b4bcffc816 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss @@ -14,7 +14,7 @@ border-radius: 0.5rem; line-height: 0; - .today-label { + .button-label { height: 1.25rem; line-height: 1rem; display: flex; @@ -68,7 +68,7 @@ border-top-left-radius: 0.75rem; border-top-right-radius: 0.75rem; - .title-close-button-row { + .title-and-control-row { display: flex; align-items: center; padding: 0.75rem 0.75rem 0.5rem; @@ -115,3 +115,9 @@ opacity: 1; } } + +.popover-content { + display: flex; + flex-direction: column; + gap: 0.5rem; +} diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts index 4dbea2e3bc1c..98d755c7490c 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; +import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core'; import { Subscription } from 'rxjs'; import { finalize } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; @@ -8,10 +8,14 @@ import { CalendarMobileMonthPresentationComponent } from 'app/core/calendar/mobi import { TranslateDirective } from 'app/shared/language/translate.directive'; import { CalendarMobileDayPresentationComponent } from 'app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faChevronLeft, faChevronRight, faXmark } from '@fortawesome/free-solid-svg-icons'; -import { CalendarEventFilterComponent, CalendarEventFilterComponentVariant } from 'app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component'; +import { faChevronLeft, faChevronRight, faFilter, faXmark } from '@fortawesome/free-solid-svg-icons'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarSubscriptionPopoverComponent } from 'app/core/calendar/shared/calendar-subscription-popover/calendar-subscription-popover.component'; +import { PopoverModule } from 'primeng/popover'; +import { CheckboxModule } from 'primeng/checkbox'; +import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; +import { FormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; @Component({ selector: 'jhi-calendar-mobile-overview', @@ -20,8 +24,11 @@ import { CalendarSubscriptionPopoverComponent } from 'app/core/calendar/shared/c CalendarMobileDayPresentationComponent, TranslateDirective, FaIconComponent, - CalendarEventFilterComponent, CalendarSubscriptionPopoverComponent, + PopoverModule, + CheckboxModule, + FormsModule, + ButtonModule, ], templateUrl: './calendar-mobile-overview.component.html', styleUrl: './calendar-mobile-overview.component.scss', @@ -31,10 +38,11 @@ export class CalendarMobileOverviewComponent implements OnInit, OnDestroy { private activatedRoute = inject(ActivatedRoute); private activatedRouteSubscription?: Subscription; - readonly CalendarEventFilterComponentVariant = CalendarEventFilterComponentVariant; + readonly CalendarEventFilterOption = CalendarEventFilterOption; readonly faXmark = faXmark; readonly faChevronRight = faChevronRight; readonly faChevronLeft = faChevronLeft; + readonly faFilter = faFilter; firstDateOfCurrentMonth = signal(dayjs().startOf('month')); selectedDate = signal(undefined); @@ -42,6 +50,10 @@ export class CalendarMobileOverviewComponent implements OnInit, OnDestroy { isLoading = signal(false); calendarSubscriptionToken = this.calendarService.subscriptionToken; currentCourseId = signal(undefined); + lectureFilterOptionSelected = computed(() => this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.LectureEvents)); + exerciseFilterOptionSelected = computed(() => this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.ExerciseEvents)); + tutorialFilterOptionSelected = computed(() => this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.TutorialEvents)); + examFilterOptionSelected = computed(() => this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.ExamEvents)); ngOnInit(): void { this.activatedRouteSubscription = this.activatedRoute.parent?.paramMap.subscribe((parameterMap) => { @@ -55,6 +67,14 @@ export class CalendarMobileOverviewComponent implements OnInit, OnDestroy { this.calendarService.loadSubscriptionToken().subscribe(); } + toggleFilterOption(option: CalendarEventFilterOption) { + if (this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.LectureEvents)) { + this.calendarService.includedEventFilterOptions.update((oldOptions) => oldOptions.filter((otherOption) => otherOption !== option)); + } else { + this.calendarService.includedEventFilterOptions.update((oldOptions) => [...oldOptions, option]); + } + } + ngOnDestroy() { this.activatedRouteSubscription?.unsubscribe(); } diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html index ff8a068bf348..cb4b693ae7bc 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html @@ -1,4 +1,5 @@
+
- - - - diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss index 051c775e7811..94ea978f4846 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss @@ -14,8 +14,8 @@ align-items: center; gap: 0.25rem; box-sizing: border-box; - padding: 0.25rem 0.5rem; - border-radius: 0.75rem; + padding: 0.125rem 0.35rem; + border-radius: 0.25rem; background: var(--body-bg); font-size: 0.75rem; } diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts index 97e5b1c706de..167e9e84fa22 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts @@ -1,63 +1,27 @@ -import { Component, computed, inject, input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { NgClass } from '@angular/common'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faChevronDown, faFilter, faXmark } from '@fortawesome/free-solid-svg-icons'; +import { faChevronDown, faFilter } from '@fortawesome/free-solid-svg-icons'; import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateDirective } from 'app/shared/language/translate.directive'; -import * as utils from 'app/core/calendar/shared/util/calendar-util'; -import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { FormsModule } from '@angular/forms'; export enum CalendarEventFilterComponentVariant { MOBILE, DESKTOP, } -type IncludedOptionAndMetadata = { option: CalendarEventFilterOption; nameKey: string; colorClassName: string }; - -type OptionAndMetadata = { option: CalendarEventFilterOption; nameKey: string; isIncluded: boolean }; - @Component({ selector: 'jhi-calendar-event-filter', - imports: [NgbPopover, FaIconComponent, TranslateDirective, NgClass], + imports: [NgbPopover, FaIconComponent, TranslateDirective, NgClass, FormsModule, MultiSelectModule], templateUrl: './calendar-event-filter.component.html', styleUrl: './calendar-event-filter.component.scss', }) export class CalendarEventFilterComponent { - private calendarService = inject(CalendarService); - variant = input.required(); - includedOptionsAndMetadata = computed(() => { - const includedOptions = this.calendarService.includedEventFilterOptions(); - return includedOptions.map((option) => ({ option: option, nameKey: utils.getFilterOptionNameKey(option), colorClassName: this.getColorClassFor(option) })); - }); - optionsAndMetadata = computed(() => { - const includedOptions = this.calendarService.includedEventFilterOptions(); - return this.calendarService.eventFilterOptions.map((option) => ({ - option: option, - nameKey: utils.getFilterOptionNameKey(option), - isIncluded: includedOptions.includes(option), - })); - }); - readonly CalendarEventFilterComponentVariant = CalendarEventFilterComponentVariant; readonly faChevronDown = faChevronDown; - readonly faXmark = faXmark; - readonly faFilter = faFilter; - - toggleOption(option: CalendarEventFilterOption) { - this.calendarService.toggleEventFilterOption(option); - } - private getColorClassFor(option: CalendarEventFilterOption): string { - if (option === 'examEvents') { - return 'exam-chip'; - } else if (option === 'lectureEvents') { - return 'lecture-chip'; - } else if (option === 'tutorialEvents') { - return 'tutorial-chip'; - } else { - return 'exercise-chip'; - } - } + readonly faFilter = faFilter; } diff --git a/src/main/webapp/app/core/calendar/shared/util/calendar-util.ts b/src/main/webapp/app/core/calendar/shared/util/calendar-util.ts index d296221acd0a..4ee80f9063af 100644 --- a/src/main/webapp/app/core/calendar/shared/util/calendar-util.ts +++ b/src/main/webapp/app/core/calendar/shared/util/calendar-util.ts @@ -106,14 +106,3 @@ export enum CalendarEventFilterOption { TutorialEvents = 'tutorialEvents', ExerciseEvents = 'exerciseEvents', } - -const filterOptionNameKeyMap: Record = { - exerciseEvents: 'artemisApp.calendar.filterOption.exercises', - lectureEvents: 'artemisApp.calendar.filterOption.lectures', - tutorialEvents: 'artemisApp.calendar.filterOption.tutorials', - examEvents: 'artemisApp.calendar.filterOption.exams', -}; - -export function getFilterOptionNameKey(option: CalendarEventFilterOption): string { - return filterOptionNameKeyMap[option]; -} diff --git a/src/main/webapp/app/primeng-artemis-theme.ts b/src/main/webapp/app/primeng-artemis-theme.ts index 9831a47981e9..f60b51ff3f73 100644 --- a/src/main/webapp/app/primeng-artemis-theme.ts +++ b/src/main/webapp/app/primeng-artemis-theme.ts @@ -9,17 +9,17 @@ import Aura from '@primeuix/themes/aura'; export const AuraArtemis = definePreset(Aura, { semantic: { primary: { - 50: '#3e8acc', - 100: '#3e8acc', - 200: '#3e8acc', - 300: '#3e8acc', + 50: '#d8e8f5', + 100: '#c5dcf0', + 200: '#9fc5e6', + 300: '#65a1d6', 400: '#3e8acc', - 500: '#3e8acc', - 600: '#3e8acc', - 700: '#3e8acc', - 800: '#3e8acc', - 900: '#3e8acc', - 950: '#3e8acc', + 500: '#387cb8', + 600: '#326ea3', + 700: '#2b618f', + 800: '#1f4566', + 900: '#13293d', + 950: '#060e14', }, }, }); From 1a4ca7d28a26e616a844db6a85f6721bd29740d7 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Wed, 24 Sep 2025 21:06:27 +0200 Subject: [PATCH 02/13] finish refactoring of mobile overview --- .../calendar-mobile-overview.component.html | 45 ++++++----- .../calendar-mobile-overview.component.scss | 74 ++++++++++--------- .../calendar-mobile-overview.component.ts | 2 +- 3 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html index ff962874ae22..e058b3ded35d 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html @@ -5,22 +5,23 @@
- + @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); @if (courseId && subscriptionToken) {
- +
} - +
{{ firstDateOfCurrentMonth().format('MMMM YYYY') }}
-
+
-
+
@for (dayNameKey of weekdayNameKeys; track dayNameKey) {
} @@ -62,21 +63,25 @@
-
+
- Lectures +
+
-
+
- Exercises +
+
-
+
- Tutorials +
+
-
+
- Exams +
+
diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss index e3b4bcffc816..33a8804777ab 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.scss @@ -8,39 +8,12 @@ align-items: center; padding: 0.5rem 0; - .header-button { - all: unset; - cursor: pointer; - border-radius: 0.5rem; - line-height: 0; - - .button-label { - height: 1.25rem; - line-height: 1rem; - display: flex; - justify-content: center; - align-items: center; - } - - .chevron { - width: 1.25rem; - height: 1.25rem; - display: flex; - justify-content: center; - align-items: center; - } - } - - .secondary-button { - color: var(--secondary); - border: 1px solid var(--secondary); - padding: 0.125rem 0.25rem; - } - - .primary-button { - color: var(--primary); - border: 1px solid var(--primary); - padding: 0.125rem; + .chevron { + width: 1.25rem; + height: 1.25rem; + display: flex; + justify-content: center; + align-items: center; } } @@ -68,7 +41,7 @@ border-top-left-radius: 0.75rem; border-top-right-radius: 0.75rem; - .title-and-control-row { + .presentation-header-top-section { display: flex; align-items: center; padding: 0.75rem 0.75rem 0.5rem; @@ -79,7 +52,7 @@ color: var(--secondary); } - .header-spacer { + .spacer { width: calc(100% / 7); display: flex; justify-content: center; @@ -94,7 +67,7 @@ } } - .day-name-row { + .presentation-header-bottom-section { display: flex; padding: 0 0.75rem 0.75rem; border-bottom: 1px solid var(--border-color); @@ -119,5 +92,34 @@ .popover-content { display: flex; flex-direction: column; + align-items: start; gap: 0.5rem; + + .filter-option-row { + display: flex; + align-items: center; + gap: 0.5rem; + + .filter-option-indicator { + width: 0.75rem; + height: 0.75rem; + border-radius: 0.375rem; + + &.exam { + background-color: var(--pastel-teal); + } + + &.lecture { + background-color: var(--pastel-blue); + } + + &.tutorial { + background-color: var(--pastel-purple); + } + + &.exercise { + background-color: var(--pastel-cyan); + } + } + } } diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts index 98d755c7490c..409ea941190c 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts @@ -68,7 +68,7 @@ export class CalendarMobileOverviewComponent implements OnInit, OnDestroy { } toggleFilterOption(option: CalendarEventFilterOption) { - if (this.calendarService.includedEventFilterOptions().includes(CalendarEventFilterOption.LectureEvents)) { + if (this.calendarService.includedEventFilterOptions().includes(option)) { this.calendarService.includedEventFilterOptions.update((oldOptions) => oldOptions.filter((otherOption) => otherOption !== option)); } else { this.calendarService.includedEventFilterOptions.update((oldOptions) => [...oldOptions, option]); From 2660a92f606357ed1369e9f7e6b9fde1fb386a4d Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Wed, 24 Sep 2025 21:11:10 +0200 Subject: [PATCH 03/13] use icon for filter button --- .../mobile/overview/calendar-mobile-overview.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html index e058b3ded35d..0d56389ef17d 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html @@ -6,6 +6,9 @@
+ @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); @if (courseId && subscriptionToken) { @@ -21,7 +24,6 @@
} - - + @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); @if (courseId && subscriptionToken) {
- +
} diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts index 409ea941190c..699d61e71d0b 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.ts @@ -8,7 +8,7 @@ import { CalendarMobileMonthPresentationComponent } from 'app/core/calendar/mobi import { TranslateDirective } from 'app/shared/language/translate.directive'; import { CalendarMobileDayPresentationComponent } from 'app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faChevronLeft, faChevronRight, faFilter, faXmark } from '@fortawesome/free-solid-svg-icons'; +import { faArrowUpFromBracket, faChevronLeft, faChevronRight, faFilter, faXmark } from '@fortawesome/free-solid-svg-icons'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarSubscriptionPopoverComponent } from 'app/core/calendar/shared/calendar-subscription-popover/calendar-subscription-popover.component'; import { PopoverModule } from 'primeng/popover'; @@ -43,6 +43,7 @@ export class CalendarMobileOverviewComponent implements OnInit, OnDestroy { readonly faChevronRight = faChevronRight; readonly faChevronLeft = faChevronLeft; readonly faFilter = faFilter; + readonly faArrowUpFromBracket = faArrowUpFromBracket; firstDateOfCurrentMonth = signal(dayjs().startOf('month')); selectedDate = signal(undefined); diff --git a/src/main/webapp/i18n/en/calendar.json b/src/main/webapp/i18n/en/calendar.json index 177d50ac96db..7225804eea32 100644 --- a/src/main/webapp/i18n/en/calendar.json +++ b/src/main/webapp/i18n/en/calendar.json @@ -33,7 +33,7 @@ "tokenLoadingError": "Data that is needed to configure calendar subscriptions could not be loaded. Please refresh the page to try again and display the option to configure a subscription.", "subscriptionPopover": { "title": "iCalendar subscription", - "configurationExplanation": "To configure your subscription, please choose a language and select course contents for which you would like to include events:", + "configurationExplanation": "To configure a subscription, please choose a language and select course contents for which you would like to include events:", "copyExplanation": "You can use the following link to subscribe external calendar applications to the Artemis calendar.", "languageOption": { "german": "German", From f39c6bc046e58c3d57eefbe0ef0c8e0727b8dab0 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Fri, 26 Sep 2025 20:13:28 +0200 Subject: [PATCH 05/13] replace event detail popovers --- ...-desktop-month-presentation.component.html | 16 +--- ...ar-desktop-month-presentation.component.ts | 23 +---- ...ar-new-event-detail-popover.component.html | 48 ++++++++++ ...ar-new-event-detail-popover.component.scss | 40 +++++++++ ...new-event-detail-popover.component.spec.ts | 90 +++++++++++++++++++ ...ndar-new-event-detail-popover.component.ts | 46 ++++++++++ ...lendar-event-detail-popover.component.html | 4 +- ...ndar-events-per-day-section.component.html | 16 +--- ...lendar-events-per-day-section.component.ts | 23 +---- 9 files changed, 235 insertions(+), 71 deletions(-) create mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html create mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss create mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts create mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html index 6ea08af6fadd..af0ce671418c 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html @@ -28,13 +28,6 @@ }
- - @let event = selectedEvent(); - @if (event) { - - } - -
{{ event.title }}
+ + diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts index 11cf73cb1ddd..92cf54655e60 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts @@ -1,6 +1,5 @@ import { Component, computed, inject, input, signal } from '@angular/core'; import { NgClass, NgTemplateOutlet } from '@angular/common'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { Dayjs } from 'dayjs/esm'; import { TranslateDirective } from 'app/shared/language/translate.directive'; @@ -8,16 +7,15 @@ import * as utils from 'app/core/calendar/shared/util/calendar-util'; import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component'; +import { CalendarNewEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component'; @Component({ selector: 'jhi-calendar-desktop-month-presentation', - imports: [NgClass, NgTemplateOutlet, NgbPopover, FaIconComponent, TranslateDirective, CalendarDayBadgeComponent, CalendarEventDetailPopoverComponent], + imports: [NgClass, NgTemplateOutlet, FaIconComponent, TranslateDirective, CalendarDayBadgeComponent, CalendarNewEventDetailPopoverComponent], templateUrl: './calendar-desktop-month-presentation.component.html', styleUrls: ['./calendar-desktop-month-presentation.component.scss'], }) export class CalendarDesktopMonthPresentationComponent { - private popover?: NgbPopover; private eventMap = inject(CalendarService).eventMap; firstDayOfCurrentMonth = input.required(); @@ -32,23 +30,6 @@ export class CalendarDesktopMonthPresentationComponent { return this.eventMap().get(key) ?? []; } - openPopover(event: CalendarEvent, popover: NgbPopover) { - if (this.selectedEvent() === event) { - this.closePopover(); - return; - } - this.selectedEvent.set(event); - this.popover?.close(); - this.popover = popover; - popover.open(); - } - - closePopover() { - this.popover?.close(); - this.popover = undefined; - this.selectedEvent.set(undefined); - } - /** * Computes all weeks overlapping with the month of the parameter. * diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html new file mode 100644 index 000000000000..a97c6baddbd6 --- /dev/null +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html @@ -0,0 +1,48 @@ + + @let eventToDisplay = event(); + @if (eventToDisplay) { +
+
+
{{ eventToDisplay.title }}
+ +
+
+
+ +
+
+
+ @if (eventToDisplay.endDate) { +
+
+ +
+
{{ utils.getTimeString(eventToDisplay.startDate) }}-{{ utils.getTimeString(eventToDisplay.endDate!) }}
+
+ } @else { +
+
+ +
+
{{ utils.getTimeString(eventToDisplay.startDate) }}
+
+ } + @if (eventToDisplay.location) { +
+
+ +
+
{{ eventToDisplay.location }}
+
+ } + @if (eventToDisplay.facilitator) { +
+
+ +
+
{{ eventToDisplay.facilitator }}
+
+ } +
+ } +
diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss new file mode 100644 index 000000000000..be968512edf6 --- /dev/null +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss @@ -0,0 +1,40 @@ +.frame { + display: flex; + flex-direction: column; + gap: 0.5rem; + min-width: 12rem; + + .header { + display: flex; + flex-direction: row; + align-items: start; + justify-content: space-between; + gap: 2rem; + font-size: 1rem; + font-weight: 600; + + .close-button { + color: var(--secondary); + } + + .close-button:hover { + color: var(--primary); + cursor: pointer; + } + } + + .info-row { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + color: var(--secondary); + + .icon-frame { + width: 1rem; + display: flex; + justify-content: center; + align-items: center; + } + } +} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts new file mode 100644 index 000000000000..183aaa099451 --- /dev/null +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts @@ -0,0 +1,90 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import dayjs from 'dayjs/esm'; +import { By } from '@angular/platform-browser'; +import { MockDirective, MockPipe } from 'ng-mocks'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; +import { CalendarEventDetailPopoverComponent } from './calendar-event-detail-popover.component'; + +describe('CalendarEventDetailPopoverNewComponent', () => { + let fixture: ComponentFixture; + let component: CalendarEventDetailPopoverComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CalendarEventDetailPopoverComponent, MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], + }).compileComponents(); + + fixture = TestBed.createComponent(CalendarEventDetailPopoverComponent); + component = fixture.componentInstance; + }); + + function setEventInput(event: CalendarEvent) { + fixture.componentRef.setInput('event', event); + fixture.detectChanges(); + } + + it('should render start-and-end-row if endDate is provided', () => { + const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 1', dayjs('2025-07-05T10:00:00'), dayjs('2025-07-05T12:00:00'), 'Room 42', 'Dr. Smith'); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#start-and-end-row'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#start-row'))).toBeFalsy(); + }); + + it('should render only start-row if endDate is missing', () => { + const event = new CalendarEvent(CalendarEventType.Lecture, 'Start: Lecture 1', dayjs('2025-07-05T10:00:00'), undefined, 'Room 42', 'Dr. Smith'); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#start-row'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#start-and-end-row'))).toBeFalsy(); + }); + + it('should render location-row if location is present', () => { + const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 2', dayjs(), dayjs(), 'Main Hall', 'Dr. Jane'); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#location-row'))).toBeTruthy(); + }); + + it('should not render location-row if location is missing', () => { + const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 2', dayjs(), dayjs(), undefined, 'Dr. Jane'); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#location-row'))).toBeFalsy(); + }); + + it('should render facilitator-row if facilitator is present', () => { + const event = new CalendarEvent(CalendarEventType.Tutorial, 'Tutorial 1', dayjs(), dayjs(), 'Lab 1', 'John Doe'); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#facilitator-row'))).toBeTruthy(); + }); + + it('should not render facilitator-row if facilitator is missing', () => { + const event = new CalendarEvent(CalendarEventType.Tutorial, 'Tutorial 2', dayjs(), dayjs(), 'Lab 2', undefined); + + setEventInput(event); + + expect(fixture.debugElement.query(By.css('#facilitator-row'))).toBeFalsy(); + }); + + it('should emit onClosePopover when close button is clicked', () => { + const event = new CalendarEvent(CalendarEventType.Exam, 'Exam 1', dayjs(), dayjs(), 'Auditorium', 'Prof. Exam'); + + setEventInput(event); + + const emitSpy = jest.spyOn(component.onClosePopover, 'emit'); + + const closeButton = fixture.debugElement.query(By.css('.close-button')).nativeElement; + closeButton.click(); + + expect(emitSpy).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts new file mode 100644 index 000000000000..48a1403644fe --- /dev/null +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts @@ -0,0 +1,46 @@ +import { Component, signal, viewChild } from '@angular/core'; +import { CalendarEvent } from 'app/core/calendar/shared/entities/calendar-event.model'; +import { Popover, PopoverModule } from 'primeng/popover'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { faClock, faLocationDot, faUser, faXmark } from '@fortawesome/free-solid-svg-icons'; +import * as utils from 'app/core/calendar/shared/util/calendar-util'; + +@Component({ + selector: 'jhi-calendar-event-detail-popover-new-component', + imports: [PopoverModule, TranslateDirective, FaIconComponent], + templateUrl: './calendar-new-event-detail-popover.component.html', + styleUrl: './calendar-new-event-detail-popover.component.scss', +}) +export class CalendarNewEventDetailPopoverComponent { + readonly utils = utils; + readonly faXmark = faXmark; + readonly faClock = faClock; + readonly faUser = faUser; + readonly faLocationDot = faLocationDot; + + event = signal(undefined); + popover = viewChild('popover'); + isOpen = signal(false); + + open(mouseEvent: MouseEvent, event: CalendarEvent) { + const popover = this.popover(); + if (popover && !this.isOpen()) { + this.event.set(event); + popover.show(mouseEvent, mouseEvent.currentTarget); + } + } + + close() { + this.popover()?.hide(); + } + + onHide() { + this.isOpen.set(false); + this.event.set(undefined); + } + + onShow() { + this.isOpen.set(true); + } +} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html index 9ed5f4359b06..e9cd034e6147 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html @@ -5,7 +5,7 @@
- +
@@ -35,7 +35,7 @@ @if (event().facilitator) {
- +
{{ event().facilitator }}
diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.html b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.html index ecbc050a6443..fda2d586d701 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.html +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.html @@ -27,13 +27,7 @@ left: position.left + '%', width: position.width + '%', }" - [ngbPopover]="eventPopover" - [container]="'body'" - triggers="manual" - [autoClose]="false" - [placement]="['top', 'bottom', 'right', 'left']" - (click)="openPopover(event, popover)" - #popover="ngbPopover" + (click)="eventDetailPopover.open($event, event)" [attr.data-testid]="event.title" > {{ event.title }} @@ -42,10 +36,4 @@
}
- - - @let event = selectedEvent(); - @if (event) { - - } - + diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts index 40283bedb60b..2f4933ed43cc 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts @@ -5,14 +5,13 @@ import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entit import { Dayjs } from 'dayjs/esm'; import { CalendarEventAndPosition, PositionInfo } from 'app/core/calendar/shared/entities/calendar-event-and-position.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component'; +import { CalendarNewEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component'; type Day = { date: Dayjs; eventsAndPositions: CalendarEventAndPosition[]; id: string }; @Component({ selector: 'jhi-calendar-events-per-day-section', - imports: [NgClass, NgStyle, CalendarEventDetailPopoverComponent, NgbPopover], + imports: [NgClass, NgStyle, CalendarNewEventDetailPopoverComponent], templateUrl: './calendar-events-per-day-section.component.html', styleUrl: './calendar-events-per-day-section.component.scss', }) @@ -22,7 +21,6 @@ export class CalendarEventsPerDaySectionComponent { private eventService = inject(CalendarService); private dateToEventAndPositionMap = computed(() => this.computeDateToEventAndPositionMap(this.eventService.eventMap(), this.dates())); - private popover?: NgbPopover; readonly CalendarEventType = CalendarEventType; @@ -45,23 +43,6 @@ export class CalendarEventsPerDaySectionComponent { }); } - openPopover(event: CalendarEvent, popover: NgbPopover) { - if (this.selectedEvent() === event) { - this.closePopover(); - return; - } - this.selectedEvent.set(event); - this.popover?.close(); - this.popover = popover; - popover.open(); - } - - closePopover() { - this.popover?.close(); - this.popover = undefined; - this.selectedEvent.set(undefined); - } - private getEventsAndPositions(date: Dayjs): CalendarEventAndPosition[] { return this.dateToEventAndPositionMap().get(date.format('YYYY-MM-DD')) ?? []; } From b2f14135b5e95adee0b92d7d375723adabc13e79 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Sat, 27 Sep 2025 11:21:31 +0200 Subject: [PATCH 06/13] clean up --- ...ar-desktop-month-presentation.component.ts | 4 +- .../calendar-desktop-overview.component.ts | 2 - ...endar-event-detail-popover.component.html} | 0 ...lendar-event-detail-popover.component.scss | 0 ...ar-event-detail-popover.component.spec.ts} | 0 ...alendar-event-detail-popover.component.ts} | 6 +- ...ar-new-event-detail-popover.component.scss | 40 ----- ...lendar-event-detail-popover.component.html | 43 ----- ...dar-event-detail-popover.component.spec.ts | 90 ---------- ...calendar-event-detail-popover.component.ts | 27 --- .../calendar-event-filter.component.html | 18 -- .../calendar-event-filter.component.scss | 75 --------- .../calendar-event-filter.component.spec.ts | 159 ------------------ .../calendar-event-filter.component.ts | 27 --- ...lendar-events-per-day-section.component.ts | 4 +- .../manage/update/course-update.component.ts | 3 +- 16 files changed, 8 insertions(+), 490 deletions(-) rename src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/{calendar-new-event-detail-popover.component.html => calendar-event-detail-popover.component.html} (100%) rename src/main/webapp/app/core/calendar/shared/{calendar-event-detail-popover => calendar-event-detail-popover-new-component}/calendar-event-detail-popover.component.scss (100%) rename src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/{calendar-new-event-detail-popover.component.spec.ts => calendar-event-detail-popover.component.spec.ts} (100%) rename src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/{calendar-new-event-detail-popover.component.ts => calendar-event-detail-popover.component.ts} (87%) delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.spec.ts delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.ts delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.spec.ts delete mode 100644 src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts index 92cf54655e60..ddcdbc7da96b 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts @@ -7,11 +7,11 @@ import * as utils from 'app/core/calendar/shared/util/calendar-util'; import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; -import { CalendarNewEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component'; @Component({ selector: 'jhi-calendar-desktop-month-presentation', - imports: [NgClass, NgTemplateOutlet, FaIconComponent, TranslateDirective, CalendarDayBadgeComponent, CalendarNewEventDetailPopoverComponent], + imports: [NgClass, NgTemplateOutlet, FaIconComponent, TranslateDirective, CalendarDayBadgeComponent, CalendarEventDetailPopoverComponent], templateUrl: './calendar-desktop-month-presentation.component.html', styleUrls: ['./calendar-desktop-month-presentation.component.scss'], }) diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts index e7e5100882f7..13fa5b8a39e5 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit, computed, effect, inject, signal } from '@angular/co import { toSignal } from '@angular/core/rxjs-interop'; import { map } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; -import { NgClass } from '@angular/common'; import { finalize } from 'rxjs/operators'; import dayjs, { Dayjs } from 'dayjs/esm'; import 'dayjs/esm/locale/en'; @@ -29,7 +28,6 @@ type CalendarEventFilterOptionAndMetadata = { option: CalendarEventFilterOption; imports: [ CalendarDesktopMonthPresentationComponent, CalendarDesktopWeekPresentationComponent, - NgClass, FaIconComponent, TranslateDirective, CalendarSubscriptionPopoverComponent, diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.html similarity index 100% rename from src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.html rename to src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.html diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.scss similarity index 100% rename from src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.scss rename to src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.scss diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.spec.ts similarity index 100% rename from src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.spec.ts rename to src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.spec.ts diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.ts similarity index 87% rename from src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts rename to src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.ts index 48a1403644fe..7f2a81147819 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component.ts @@ -9,10 +9,10 @@ import * as utils from 'app/core/calendar/shared/util/calendar-util'; @Component({ selector: 'jhi-calendar-event-detail-popover-new-component', imports: [PopoverModule, TranslateDirective, FaIconComponent], - templateUrl: './calendar-new-event-detail-popover.component.html', - styleUrl: './calendar-new-event-detail-popover.component.scss', + templateUrl: './calendar-event-detail-popover.component.html', + styleUrl: './calendar-event-detail-popover.component.scss', }) -export class CalendarNewEventDetailPopoverComponent { +export class CalendarEventDetailPopoverComponent { readonly utils = utils; readonly faXmark = faXmark; readonly faClock = faClock; diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss deleted file mode 100644 index be968512edf6..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component.scss +++ /dev/null @@ -1,40 +0,0 @@ -.frame { - display: flex; - flex-direction: column; - gap: 0.5rem; - min-width: 12rem; - - .header { - display: flex; - flex-direction: row; - align-items: start; - justify-content: space-between; - gap: 2rem; - font-size: 1rem; - font-weight: 600; - - .close-button { - color: var(--secondary); - } - - .close-button:hover { - color: var(--primary); - cursor: pointer; - } - } - - .info-row { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.5rem; - color: var(--secondary); - - .icon-frame { - width: 1rem; - display: flex; - justify-content: center; - align-items: center; - } - } -} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html deleted file mode 100644 index e9cd034e6147..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
{{ event().title }}
- -
-
-
- -
-
-
- @if (event().endDate) { -
-
- -
-
{{ utils.getTimeString(event().startDate) }}-{{ utils.getTimeString(event().endDate!) }}
-
- } @else { -
-
- -
-
{{ utils.getTimeString(event().startDate) }}
-
- } - @if (event().location) { -
-
- -
-
{{ event().location }}
-
- } - @if (event().facilitator) { -
-
- -
-
{{ event().facilitator }}
-
- } -
diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.spec.ts deleted file mode 100644 index df7a175ace0f..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import dayjs from 'dayjs/esm'; -import { By } from '@angular/platform-browser'; -import { MockDirective, MockPipe } from 'ng-mocks'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; -import { CalendarEventDetailPopoverComponent } from './calendar-event-detail-popover.component'; - -describe('CalendarEventDetailPopoverComponent', () => { - let fixture: ComponentFixture; - let component: CalendarEventDetailPopoverComponent; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [CalendarEventDetailPopoverComponent, MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], - }).compileComponents(); - - fixture = TestBed.createComponent(CalendarEventDetailPopoverComponent); - component = fixture.componentInstance; - }); - - function setEventInput(event: CalendarEvent) { - fixture.componentRef.setInput('event', event); - fixture.detectChanges(); - } - - it('should render start-and-end-row if endDate is provided', () => { - const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 1', dayjs('2025-07-05T10:00:00'), dayjs('2025-07-05T12:00:00'), 'Room 42', 'Dr. Smith'); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#start-and-end-row'))).toBeTruthy(); - expect(fixture.debugElement.query(By.css('#start-row'))).toBeFalsy(); - }); - - it('should render only start-row if endDate is missing', () => { - const event = new CalendarEvent(CalendarEventType.Lecture, 'Start: Lecture 1', dayjs('2025-07-05T10:00:00'), undefined, 'Room 42', 'Dr. Smith'); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#start-row'))).toBeTruthy(); - expect(fixture.debugElement.query(By.css('#start-and-end-row'))).toBeFalsy(); - }); - - it('should render location-row if location is present', () => { - const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 2', dayjs(), dayjs(), 'Main Hall', 'Dr. Jane'); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#location-row'))).toBeTruthy(); - }); - - it('should not render location-row if location is missing', () => { - const event = new CalendarEvent(CalendarEventType.Lecture, 'Lecture 2', dayjs(), dayjs(), undefined, 'Dr. Jane'); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#location-row'))).toBeFalsy(); - }); - - it('should render facilitator-row if facilitator is present', () => { - const event = new CalendarEvent(CalendarEventType.Tutorial, 'Tutorial 1', dayjs(), dayjs(), 'Lab 1', 'John Doe'); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#facilitator-row'))).toBeTruthy(); - }); - - it('should not render facilitator-row if facilitator is missing', () => { - const event = new CalendarEvent(CalendarEventType.Tutorial, 'Tutorial 2', dayjs(), dayjs(), 'Lab 2', undefined); - - setEventInput(event); - - expect(fixture.debugElement.query(By.css('#facilitator-row'))).toBeFalsy(); - }); - - it('should emit onClosePopover when close button is clicked', () => { - const event = new CalendarEvent(CalendarEventType.Exam, 'Exam 1', dayjs(), dayjs(), 'Auditorium', 'Prof. Exam'); - - setEventInput(event); - - const emitSpy = jest.spyOn(component.onClosePopover, 'emit'); - - const closeButton = fixture.debugElement.query(By.css('.close-button')).nativeElement; - closeButton.click(); - - expect(emitSpy).toHaveBeenCalledOnce(); - }); -}); diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.ts deleted file mode 100644 index b0ac37a68f22..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, input, output } from '@angular/core'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faClock, faLocationDot, faUser, faXmark } from '@fortawesome/free-solid-svg-icons'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import * as utils from 'app/core/calendar/shared/util/calendar-util'; -import { CalendarEvent } from 'app/core/calendar/shared/entities/calendar-event.model'; - -@Component({ - selector: 'jhi-calendar-event-detail-popover', - imports: [FaIconComponent, TranslateDirective], - templateUrl: './calendar-event-detail-popover.component.html', - styleUrl: './calendar-event-detail-popover.component.scss', -}) -export class CalendarEventDetailPopoverComponent { - event = input.required(); - onClosePopover = output(); - - readonly utils = utils; - readonly faXmark = faXmark; - readonly faClock = faClock; - readonly faUser = faUser; - readonly faLocationDot = faLocationDot; - - closePopover() { - this.onClosePopover.emit(); - } -} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html deleted file mode 100644 index cb4b693ae7bc..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss deleted file mode 100644 index 94ea978f4846..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.scss +++ /dev/null @@ -1,75 +0,0 @@ -.frame { - position: relative; - display: flex; - align-items: center; - gap: 0.5rem; - - .chips-section { - display: flex; - gap: 0.375rem; - min-width: 6rem; - - .chip { - display: flex; - align-items: center; - gap: 0.25rem; - box-sizing: border-box; - padding: 0.125rem 0.35rem; - border-radius: 0.25rem; - background: var(--body-bg); - font-size: 0.75rem; - } - - .exam-chip { - background-color: var(--pastel-teal); - } - - .lecture-chip { - background-color: var(--pastel-blue); - } - - .tutorial-chip { - background-color: var(--pastel-purple); - } - - .exercise-chip { - background-color: var(--pastel-cyan); - } - - .legend-circle { - width: 0.75rem; - height: 0.75rem; - border-radius: 0.25rem; - } - } -} - -.filter-button { - all: unset; - cursor: pointer; - color: var(--secondary); - - .filter-icon { - font-size: 0.75rem; - } -} - -.remove-button { - cursor: pointer; -} - -.dropdown { - display: flex; - flex-direction: column; - - .dropdown-item { - padding: 0.375rem 0.5rem; - cursor: pointer; - user-select: none; - } -} - -input[type='checkbox'] { - margin-right: 0.5rem; - margin-left: 0 !important; -} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.spec.ts deleted file mode 100644 index e798a8877243..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.spec.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { signal } from '@angular/core'; -import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; -import { By } from '@angular/platform-browser'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { MockDirective } from 'ng-mocks'; -import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; -import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { CalendarEventFilterComponent, CalendarEventFilterComponentVariant } from './calendar-event-filter.component'; - -describe('CalendarEventFilterComponent', () => { - let fixture: ComponentFixture; - let mockService: jest.Mocked; - - const eventFilterOptions: CalendarEventFilterOption[] = [ - CalendarEventFilterOption.LectureEvents, - CalendarEventFilterOption.ExerciseEvents, - CalendarEventFilterOption.TutorialEvents, - CalendarEventFilterOption.ExamEvents, - ]; - const includedEventFilterOptions = signal([CalendarEventFilterOption.LectureEvents, CalendarEventFilterOption.ExamEvents]); - - beforeEach(async () => { - mockService = { - toggleEventFilterOption: jest.fn(), - eventFilterOptions: eventFilterOptions, - includedEventFilterOptions: includedEventFilterOptions, - } as unknown as jest.Mocked; - - await TestBed.configureTestingModule({ - imports: [CalendarEventFilterComponent, NgbPopoverModule], - declarations: [MockDirective(TranslateDirective)], - providers: [{ provide: CalendarService, useValue: mockService }], - }).compileComponents(); - - fixture = TestBed.createComponent(CalendarEventFilterComponent); - }); - - describe('desktop variant', () => { - beforeEach(() => { - fixture.componentRef.setInput('variant', CalendarEventFilterComponentVariant.DESKTOP); - fixture.detectChanges(); - }); - - it('should display chips only for included options', () => { - const chips = fixture.debugElement.queryAll(By.css('.chip')); - expect(chips).toHaveLength(2); - - const examChip = fixture.debugElement.query(By.css('[data-testid="chip-examEvents"]')); - const lectureChip = fixture.debugElement.query(By.css('[data-testid="chip-lectureEvents"]')); - - expect(examChip).toBeTruthy(); - expect(lectureChip).toBeTruthy(); - }); - - it('should call toggleOption when chip remove icon is clicked', () => { - const chipRemoveButtons = fixture.debugElement.queryAll(By.css('.remove-button')); - chipRemoveButtons[0].nativeElement.click(); - fixture.detectChanges(); - - expect(mockService.toggleEventFilterOption).toHaveBeenCalledWith(CalendarEventFilterOption.LectureEvents); - }); - - it('should show all filter options in the popover', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const checkboxes = fixture.debugElement.queryAll(By.css('.dropdown-item')); - expect(checkboxes).toHaveLength(4); - - const checkboxIds = checkboxes.map((checkbox) => checkbox.query(By.css('input')).attributes['id']); - expect(checkboxIds).toEqual(['filter-lectureEvents', 'filter-exerciseEvents', 'filter-tutorialEvents', 'filter-examEvents']); - }); - - it('should check the box for included options and uncheck for excluded', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const checkboxes = fixture.debugElement.queryAll(By.css('.dropdown-item')); - - checkboxes.forEach((item) => { - const input = item.query(By.css('input')).nativeElement; - const option = input.id.replace('filter-', '') as CalendarEventFilterOption; - - if (includedEventFilterOptions().includes(option)) { - expect(input.checked).toBeTrue(); - } else { - expect(input.checked).toBeFalse(); - } - }); - }); - - it('should call toggleOption when a checkbox is changed', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const tutorialCheckbox = fixture.debugElement.queryAll(By.css('input')).find((input) => input.attributes['id'] === 'filter-tutorialEvents'); - - tutorialCheckbox!.triggerEventHandler('change', {}); - fixture.detectChanges(); - - expect(mockService.toggleEventFilterOption).toHaveBeenCalledWith('tutorialEvents'); - }); - }); - - describe('mobile variant', () => { - beforeEach(() => { - fixture.componentRef.setInput('variant', CalendarEventFilterComponentVariant.MOBILE); - fixture.detectChanges(); - }); - - it('should show all filter options in the popover', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const checkboxes = fixture.debugElement.queryAll(By.css('.dropdown-item')); - expect(checkboxes).toHaveLength(4); - - const checkboxIds = checkboxes.map((checkbox) => checkbox.query(By.css('input')).attributes['id']); - expect(checkboxIds).toEqual(['filter-lectureEvents', 'filter-exerciseEvents', 'filter-tutorialEvents', 'filter-examEvents']); - }); - - it('should check the box for included options and uncheck for excluded', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const checkboxes = fixture.debugElement.queryAll(By.css('.dropdown-item')); - - checkboxes.forEach((item) => { - const input = item.query(By.css('input')).nativeElement; - const option = input.id.replace('filter-', '') as CalendarEventFilterOption; - - if (includedEventFilterOptions().includes(option)) { - expect(input.checked).toBeTrue(); - } else { - expect(input.checked).toBeFalse(); - } - }); - }); - - it('should call toggleOption when a checkbox is changed', () => { - const filterButton = fixture.debugElement.query(By.css('button')).nativeElement; - filterButton.click(); - fixture.detectChanges(); - - const tutorialCheckbox = fixture.debugElement.queryAll(By.css('input')).find((input) => input.attributes['id'] === 'filter-tutorialEvents'); - - tutorialCheckbox!.triggerEventHandler('change', {}); - fixture.detectChanges(); - - expect(mockService.toggleEventFilterOption).toHaveBeenCalledWith('tutorialEvents'); - }); - }); -}); diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts deleted file mode 100644 index 167e9e84fa22..000000000000 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, input } from '@angular/core'; -import { NgClass } from '@angular/common'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { faChevronDown, faFilter } from '@fortawesome/free-solid-svg-icons'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { MultiSelectModule } from 'primeng/multiselect'; -import { FormsModule } from '@angular/forms'; - -export enum CalendarEventFilterComponentVariant { - MOBILE, - DESKTOP, -} - -@Component({ - selector: 'jhi-calendar-event-filter', - imports: [NgbPopover, FaIconComponent, TranslateDirective, NgClass, FormsModule, MultiSelectModule], - templateUrl: './calendar-event-filter.component.html', - styleUrl: './calendar-event-filter.component.scss', -}) -export class CalendarEventFilterComponent { - variant = input.required(); - - readonly faChevronDown = faChevronDown; - - readonly faFilter = faFilter; -} diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts index 2f4933ed43cc..de5a14225b20 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts @@ -5,13 +5,13 @@ import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entit import { Dayjs } from 'dayjs/esm'; import { CalendarEventAndPosition, PositionInfo } from 'app/core/calendar/shared/entities/calendar-event-and-position.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { CalendarNewEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-new-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component'; type Day = { date: Dayjs; eventsAndPositions: CalendarEventAndPosition[]; id: string }; @Component({ selector: 'jhi-calendar-events-per-day-section', - imports: [NgClass, NgStyle, CalendarNewEventDetailPopoverComponent], + imports: [NgClass, NgStyle, CalendarEventDetailPopoverComponent], templateUrl: './calendar-events-per-day-section.component.html', styleUrl: './calendar-events-per-day-section.component.scss', }) diff --git a/src/main/webapp/app/core/course/manage/update/course-update.component.ts b/src/main/webapp/app/core/course/manage/update/course-update.component.ts index a2c3f986b29e..0952bc6cd51c 100644 --- a/src/main/webapp/app/core/course/manage/update/course-update.component.ts +++ b/src/main/webapp/app/core/course/manage/update/course-update.component.ts @@ -33,7 +33,7 @@ import { scrollToTopOfPage } from 'app/shared/util/utils'; import { CourseStorageService } from 'app/core/course/manage/services/course-storage.service'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { KeyValuePipe, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common'; +import { KeyValuePipe, NgStyle, NgTemplateOutlet } from '@angular/common'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { HelpIconComponent } from 'app/shared/components/help-icon/help-icon.component'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; @@ -57,7 +57,6 @@ const DEFAULT_CUSTOM_GROUP_NAME = 'artemis-dev'; TranslateDirective, NgStyle, ColorSelectorComponent, - NgClass, NgbTooltip, FormDateTimePickerComponent, HelpIconComponent, From 8e1e650eb17047292573e6d21731d40dda229fa3 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Mon, 29 Sep 2025 12:22:18 +0200 Subject: [PATCH 07/13] pin primeuix/styled dependency --- package-lock.json | 56 +++++++++++++++++++++++------------------------ package.json | 3 ++- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index e820d59ccb92..faa570be5099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2973,9 +2973,9 @@ } }, "node_modules/@cacheable/memory/node_modules/keyv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.2.tgz", - "integrity": "sha512-TXcFHbmm/z7MGd1u9ASiCSfTS+ei6Z8B3a5JHzx3oPa/o7QzWVtPRpc4KGER5RR469IC+/nfg4U5YLIuDUua2g==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", "dev": true, "license": "MIT", "dependencies": { @@ -6774,9 +6774,9 @@ } }, "node_modules/@primeuix/styled": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.3.tgz", - "integrity": "sha512-qAzefzODuYZRaEh2b2uTJCtaT+qUMAt1WPCZhYgkbLUVQ8qt7hFL5BjTsqqCaiBWZCZtDkGmKhP19SRIL6hAlQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.2.tgz", + "integrity": "sha512-tIJ6byZezTYZ9YUICNSidQHOIQOQL3zeUgjwiX0JnBTK3+WCvy4DyCBcrJ94RtiX0WGFZSYNvaGaFkTo4jU8FQ==", "license": "MIT", "dependencies": { "@primeuix/utils": "^0.6.1" @@ -6786,9 +6786,9 @@ } }, "node_modules/@primeuix/styles": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.4.tgz", - "integrity": "sha512-G9tRvbC+YTzrkp/SjOdQ8k4pbnYiHopK0KQnq0eDSdNk99m+sM57eh1CuXVlXW/+1NRC42kB0g0XNLtYa67/ww==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.5.tgz", + "integrity": "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q==", "license": "MIT", "dependencies": { "@primeuix/styled": "^0.7.3" @@ -9030,9 +9030,9 @@ } }, "node_modules/ansis": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", - "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", "dev": true, "license": "ISC", "engines": { @@ -9378,9 +9378,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz", - "integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", + "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -9723,9 +9723,9 @@ } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.2.tgz", - "integrity": "sha512-TXcFHbmm/z7MGd1u9ASiCSfTS+ei6Z8B3a5JHzx3oPa/o7QzWVtPRpc4KGER5RR469IC+/nfg4U5YLIuDUua2g==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", "dev": true, "license": "MIT", "dependencies": { @@ -11167,9 +11167,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.224", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz", - "integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==", + "version": "1.5.227", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz", + "integrity": "sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA==", "license": "ISC" }, "node_modules/emittery": { @@ -15668,9 +15668,9 @@ } }, "node_modules/memfs": { - "version": "4.46.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.46.1.tgz", - "integrity": "sha512-2wjHDg7IjP+ufAqqqSxjiNePFDrvWviA79ajUwG9lkHhk3HzZOLBzzoUG8cx9vCagj6VvBQD7oXuLuFz5LaAOQ==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.47.0.tgz", + "integrity": "sha512-Xey8IZA57tfotV/TN4d6BmccQuhFP+CqRiI7TTNdipZdZBzF2WnzUcH//Cudw6X4zJiUbo/LTuU/HPA/iC/pNg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -19936,9 +19936,9 @@ } }, "node_modules/stylelint-config-recommended-scss": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-16.0.1.tgz", - "integrity": "sha512-wfpU6kmTUwPEHMACYdpt5wLM/aS44+sqE8yk82LkOkA7yVpAuTZDwd3m9762d0mRnNCn0JMUx4XfDVDmbb8hTA==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-16.0.2.tgz", + "integrity": "sha512-aUTHhPPWCvFyWaxtckJlCPaXTDFsp4pKO8evXNCsW9OwsaUWyMd6jvcUhSmfGWPrTddvzNqK4rS/UuSLcbVGdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -19951,7 +19951,7 @@ }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^16.23.1" + "stylelint": "^16.24.0" }, "peerDependenciesMeta": { "postcss": { diff --git a/package.json b/package.json index 4c1e1720d339..d52ed73df16c 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,8 @@ "webpack-dev-server": "5.2.2", "word-wrap": "1.2.5", "ws": "8.18.3", - "yargs-parser": "22.0.0" + "yargs-parser": "22.0.0", + "@primeuix/styled": "0.7.2" }, "devDependencies": { "@angular-builders/jest": "20.0.0", From 25c58100f392e13b9b93dcf7b2b574b1e95b8352 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Wed, 1 Oct 2025 22:59:40 +0200 Subject: [PATCH 08/13] remove merge artifact --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 370f73aab789..0b923533bf03 100644 --- a/package.json +++ b/package.json @@ -99,8 +99,7 @@ "webpack-dev-server": "5.2.2", "word-wrap": "1.2.5", "ws": "8.18.3", - "yargs-parser": "22.0.0", - "@primeuix/styled": "0.7.2" + "yargs-parser": "22.0.0" }, "devDependencies": { "@angular-builders/jest": "20.0.0", From a9d8a3a7ec196537cfedfe482e977ca9cf9f0c55 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Thu, 2 Oct 2025 01:24:41 +0200 Subject: [PATCH 09/13] fix client tests --- ...-desktop-month-presentation.component.html | 2 +- ...sktop-month-presentation.component.spec.ts | 69 +++++++------------ ...ar-desktop-month-presentation.component.ts | 11 ++- .../calendar-desktop-overview.component.html | 1 + ...alendar-desktop-overview.component.spec.ts | 27 ++++---- ...r-desktop-week-presentation.component.html | 4 +- ...esktop-week-presentation.component.spec.ts | 12 ---- ...dar-desktop-week-presentation.component.ts | 3 +- ...dar-mobile-day-presentation.component.html | 4 +- ...-mobile-day-presentation.component.spec.ts | 16 ----- ...endar-mobile-day-presentation.component.ts | 3 +- .../calendar-mobile-overview.component.html | 4 +- ...calendar-mobile-overview.component.spec.ts | 27 ++++---- ...lendar-event-detail-popover.component.html | 0 ...lendar-event-detail-popover.component.scss | 0 ...dar-event-detail-popover.component.spec.ts | 69 ++++++++++--------- ...calendar-event-detail-popover.component.ts | 2 +- ...ndar-events-per-day-section.component.html | 2 +- ...r-events-per-day-section.component.spec.ts | 65 +++++------------ ...lendar-events-per-day-section.component.ts | 12 +--- 20 files changed, 123 insertions(+), 210 deletions(-) rename src/main/webapp/app/core/calendar/shared/{calendar-event-detail-popover-new-component => calendar-event-detail-popover-component}/calendar-event-detail-popover.component.html (100%) rename src/main/webapp/app/core/calendar/shared/{calendar-event-detail-popover-new-component => calendar-event-detail-popover-component}/calendar-event-detail-popover.component.scss (100%) rename src/main/webapp/app/core/calendar/shared/{calendar-event-detail-popover-new-component => calendar-event-detail-popover-component}/calendar-event-detail-popover.component.spec.ts (64%) rename src/main/webapp/app/core/calendar/shared/{calendar-event-detail-popover-new-component => calendar-event-detail-popover-component}/calendar-event-detail-popover.component.ts (95%) diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html index af0ce671418c..075f4af432e9 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.html @@ -45,4 +45,4 @@
- + diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.spec.ts index c336c21f8514..2bf93a338fe7 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.spec.ts @@ -9,7 +9,8 @@ import { CalendarService } from 'app/core/calendar/shared/service/calendar.servi import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; import { CalendarDesktopMonthPresentationComponent } from './calendar-desktop-month-presentation.component'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; describe('CalendarDesktopMonthPresentationComponent', () => { let fixture: ComponentFixture; @@ -61,6 +62,7 @@ describe('CalendarDesktopMonthPresentationComponent', () => { provide: CalendarService, useFactory: () => new MockCalendarService(mockMap), }, + provideNoopAnimations(), ], }).compileComponents(); @@ -105,66 +107,47 @@ describe('CalendarDesktopMonthPresentationComponent', () => { expect(lecture3EventCell).toBeFalsy(); }); - it('should open popover', () => { - const eventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); - expect(eventCell).toBeTruthy(); + it('should open popover', async () => { + const popoverDebugElement = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); + const popoverComponent = popoverDebugElement.componentInstance as CalendarEventDetailPopoverComponent; + const eventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); eventCell.nativeElement.click(); fixture.detectChanges(); + await fixture.whenStable(); - expect(component.selectedEvent()?.id).toBe(events[0].id); - - const popoverContent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(popoverContent).toBeTruthy(); + expect(popoverComponent.isOpen()).toBeTrue(); }); - it('should close popover only when close button used', () => { + it('should close popover only when close button used', async () => { + const popoverDebugElement = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); + const popoverComponent = popoverDebugElement.componentInstance as CalendarEventDetailPopoverComponent; + const closeSpy = jest.spyOn(popoverComponent, 'close'); + const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); expect(examEventCell).toBeTruthy(); - examEventCell.nativeElement.click(); fixture.detectChanges(); - - let popoverComponent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(component.selectedEvent()?.id).toBe(events[0].id); - expect(popoverComponent).toBeTruthy(); + await fixture.whenStable(); + expect(popoverComponent.isOpen()).toBeTrue(); const emptyDayCell = fixture.debugElement.queryAll(By.css('.day-cell')).find((cell) => cell.queryAll(By.css('.event-cell')).length === 0); expect(emptyDayCell).toBeTruthy(); - emptyDayCell!.nativeElement.click(); fixture.detectChanges(); + await fixture.whenStable(); + expect(popoverComponent.isOpen()).toBeFalse(); - expect(component.selectedEvent()?.id).toBe(events[0].id); - popoverComponent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(popoverComponent).toBeTruthy(); - - const closeButton = popoverComponent.query(By.css('.close-button')); - expect(closeButton).toBeTruthy(); - - closeButton.nativeElement.click(); - fixture.detectChanges(); - - expect(component.selectedEvent()).toBeUndefined(); - popoverComponent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(popoverComponent).toBeFalsy(); - }); - - it('should replace popover when other event selected', () => { - const firstEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); - firstEventCell.nativeElement.click(); + examEventCell.nativeElement.click(); fixture.detectChanges(); + await fixture.whenStable(); + expect(popoverComponent.isOpen()).toBeTrue(); - let popoverComponent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(component.selectedEvent()?.id).toBe(events[0].id); - expect(popoverComponent).toBeTruthy(); - - const secondEventCell = fixture.debugElement.query(By.css('[data-testid="Object Design"]')); - secondEventCell.nativeElement.click(); + const closeButton = document.querySelector('.close-button') as HTMLElement; + expect(closeButton).toBeTruthy(); + closeButton.click(); fixture.detectChanges(); - - popoverComponent = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); - expect(popoverComponent).toBeTruthy(); - expect(component.selectedEvent()?.id).toBe(events[1].id); + await fixture.whenStable(); + expect(closeSpy).toHaveBeenCalledOnce(); }); }); diff --git a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts index ddcdbc7da96b..118d9f664caf 100644 --- a/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, input, signal } from '@angular/core'; +import { Component, computed, inject, input } from '@angular/core'; import { NgClass, NgTemplateOutlet } from '@angular/common'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { Dayjs } from 'dayjs/esm'; @@ -7,7 +7,7 @@ import * as utils from 'app/core/calendar/shared/util/calendar-util'; import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component'; @Component({ selector: 'jhi-calendar-desktop-month-presentation', @@ -18,12 +18,11 @@ import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/ca export class CalendarDesktopMonthPresentationComponent { private eventMap = inject(CalendarService).eventMap; - firstDayOfCurrentMonth = input.required(); - selectedEvent = signal(undefined); - readonly utils = utils; readonly CalendarEventType = CalendarEventType; - readonly weeks = computed(() => this.computeWeeksFrom(this.firstDayOfCurrentMonth())); + + firstDayOfCurrentMonth = input.required(); + weeks = computed(() => this.computeWeeksFrom(this.firstDayOfCurrentMonth())); getEventsOf(day: Dayjs): CalendarEvent[] { const key = day.format('YYYY-MM-DD'); diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html index 50a3f15c5acc..8a641c97bb1b 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html @@ -32,6 +32,7 @@ optionLabel="label" optionValue="value" size="small" + id="presentation-select-button" /> @let courseId = currentCourseId(); @let subscriptionToken = calendarSubscriptionToken(); diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts index 5d6b0171ee05..bd017f747963 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts @@ -12,13 +12,12 @@ import { CalendarEvent } from 'app/core/calendar/shared/entities/calendar-event. import { CalendarDesktopWeekPresentationComponent } from 'app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component'; import { CalendarDesktopMonthPresentationComponent } from 'app/core/calendar/desktop/month-presentation/calendar-desktop-month-presentation.component'; import { CalendarSubscriptionPopoverComponent } from 'app/core/calendar/shared/calendar-subscription-popover/calendar-subscription-popover.component'; -import { CalendarEventFilterComponent } from 'app/core/calendar/shared/calendar-event-filter/calendar-event-filter.component'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; import { CalendarDesktopOverviewComponent } from './calendar-desktop-overview.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MockTranslateService } from 'test/helpers/mocks/service/mock-translate.service'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; describe('CalendarDesktopOverviewComponent', () => { let component: CalendarDesktopOverviewComponent; @@ -29,6 +28,12 @@ describe('CalendarDesktopOverviewComponent', () => { loadEventsForCurrentMonth: jest.fn().mockReturnValue(of([])), subscriptionToken: signal('testToken'), loadSubscriptionToken: jest.fn().mockReturnValue(of([])), + includedEventFilterOptions: signal([ + CalendarEventFilterOption.LectureEvents, + CalendarEventFilterOption.ExerciseEvents, + CalendarEventFilterOption.TutorialEvents, + CalendarEventFilterOption.ExamEvents, + ]), }; const activatedRouteMock = { @@ -40,12 +45,7 @@ describe('CalendarDesktopOverviewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [CalendarDesktopOverviewComponent, CalendarDesktopWeekPresentationComponent, CalendarDesktopMonthPresentationComponent, FaIconComponent], - declarations: [ - MockComponent(CalendarEventDetailPopoverComponent), - MockComponent(CalendarDayBadgeComponent), - MockPipe(ArtemisTranslatePipe), - MockComponent(CalendarEventFilterComponent), - ], + declarations: [MockComponent(CalendarDayBadgeComponent), MockPipe(ArtemisTranslatePipe)], providers: [ { provide: CalendarService, useValue: calendarServiceMock }, { provide: ActivatedRoute, useValue: activatedRouteMock }, @@ -71,12 +71,10 @@ describe('CalendarDesktopOverviewComponent', () => { const previousButton = fixture.debugElement.query(By.css('#previous-button')).nativeElement; const nextButton = fixture.debugElement.query(By.css('#next-button')).nativeElement; - const weekButton = fixture.debugElement.query(By.css('#week-button')).nativeElement; - const monthButton = fixture.debugElement.query(By.css('#month-button')).nativeElement; + const selectButton = fixture.debugElement.queryAll(By.css('#presentation-select-button')); expect(previousButton).toBeTruthy(); expect(nextButton).toBeTruthy(); - expect(weekButton).toBeTruthy(); - expect(monthButton).toBeTruthy(); + expect(selectButton).toBeTruthy(); expect(component.selectedPresentation()).toBe('month'); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-month-presentation'))).toBeTruthy(); @@ -100,7 +98,7 @@ describe('CalendarDesktopOverviewComponent', () => { expect(firstDayOfCurrentMonth.isSame(expectedFirstDayOfCurrentMonth, 'day')).toBeTrue(); expect(firstDayOfCurrentWeek.isSame(expectedFirstDayOfCurrentWeek, 'day')).toBeTrue(); - weekButton.click(); + component.selectedPresentation.set('week'); fixture.detectChanges(); expect(component.selectedPresentation()).toBe('week'); expect(fixture.debugElement.query(By.css('jhi-calendar-desktop-week-presentation'))).toBeTruthy(); @@ -176,8 +174,7 @@ describe('CalendarDesktopOverviewComponent', () => { fixture.detectChanges(); expect(heading()).toBe('October 2025'); - const weekButton = fixture.debugElement.query(By.css('#week-button')).nativeElement; - weekButton.click(); + component.selectedPresentation.set('week'); fixture.detectChanges(); expect(heading()).toContain('September | October 2025'); diff --git a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.html b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.html index 4ef7f72fa34d..30eca754b058 100644 --- a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.html +++ b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.html @@ -10,7 +10,7 @@ } -
- +
+
diff --git a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts index aa6f1510499c..0b0b93c41958 100644 --- a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts @@ -36,16 +36,4 @@ describe('CalendarDesktopWeekPresentationComponent', () => { const dayInfoElements = fixture.debugElement.queryAll(By.css('.day-info')); expect(dayInfoElements).toHaveLength(7); }); - - it('should apply "no-scroll" class only when isEventSelected is true', () => { - const scrollContainer = fixture.debugElement.query(By.css('.scroll-container')); - expect(scrollContainer).toBeTruthy(); - - expect(component.isEventSelected()).toBeFalse(); - expect(scrollContainer.nativeElement.classList).not.toContain('no-scroll'); - - component.isEventSelected.set(true); - fixture.detectChanges(); - expect(scrollContainer.nativeElement.classList).toContain('no-scroll'); - }); }); diff --git a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.ts b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.ts index 23979a96a15d..458d742c30c9 100644 --- a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, computed, input, signal, viewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, computed, input, viewChild } from '@angular/core'; import { Dayjs } from 'dayjs/esm'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import * as utils from 'app/core/calendar/shared/util/calendar-util'; @@ -16,7 +16,6 @@ export class CalendarDesktopWeekPresentationComponent implements AfterViewInit { private static readonly INITIAL_SCROLL_POSITION = CalendarDesktopWeekPresentationComponent.INITIAL_SCROLL_HOURS_AFTER_MIDNIGHT * CalendarEventsPerDaySectionComponent.HOUR_HEIGHT_IN_PIXEL; firstDayOfCurrentWeek = input.required(); - isEventSelected = signal(false); scrollContainer = viewChild('scrollContainer'); weekDays = computed(() => this.computeWeekDaysFrom(this.firstDayOfCurrentWeek())); diff --git a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.html b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.html index 004ddee42946..28211fd4d338 100644 --- a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.html +++ b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.html @@ -4,7 +4,7 @@ } -
- +
+
diff --git a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.spec.ts b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.spec.ts index 75ec38bf45bb..e2e7a9c1ac12 100644 --- a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.spec.ts +++ b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.spec.ts @@ -45,20 +45,4 @@ describe('CalendarMobileDayPresentation', () => { expect(nonSelectedDayBadge.isSelectedDay()).toBeFalse(); } }); - - it('should apply no-scroll class when isEventSelected is true', () => { - component.isEventSelected.set(true); - fixture.detectChanges(); - - const scrollContainerElement = fixture.nativeElement.querySelector('.scroll-container'); - expect(scrollContainerElement.classList).toContain('no-scroll'); - }); - - it('should update isEventSelected signal when child emits true', () => { - const eventsSectionComponent = ngMocks.findInstance(CalendarEventsPerDaySectionComponent); - eventsSectionComponent.isEventSelected.emit(true); - fixture.detectChanges(); - - expect(component.isEventSelected()).toBeTrue(); - }); }); diff --git a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.ts b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.ts index 851104555294..7365462442a5 100644 --- a/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.ts +++ b/src/main/webapp/app/core/calendar/mobile/day-presentation/calendar-mobile-day-presentation.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, computed, input, signal, viewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, computed, input, viewChild } from '@angular/core'; import { CalendarDayBadgeComponent } from 'app/core/calendar/shared/calendar-day-badge/calendar-day-badge.component'; import { Dayjs } from 'dayjs/esm'; import * as utils from 'app/core/calendar/shared/util/calendar-util'; @@ -20,7 +20,6 @@ export class CalendarMobileDayPresentationComponent implements AfterViewInit { private scrollContainer = viewChild('scrollContainer'); selectedDate = input.required(); - isEventSelected = signal(false); weekDays = computed(() => { const selectedDate = this.selectedDate(); const dates = utils.getDatesInWeekOf(selectedDate); diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html index 7b852dad59b4..e4e750ffb457 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html @@ -1,6 +1,6 @@
-
} -
} - + diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts index fdde3ef1fe1f..3b339a25d326 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts @@ -7,8 +7,9 @@ import { TranslateDirective } from 'app/shared/language/translate.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover/calendar-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component'; import { CalendarEventsPerDaySectionComponent } from 'app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; describe('CalendarEventsPerDaySectionComponent', () => { let component: CalendarEventsPerDaySectionComponent; @@ -46,6 +47,7 @@ describe('CalendarEventsPerDaySectionComponent', () => { provide: CalendarService, useFactory: () => new MockCalendarService(mockMap), }, + provideNoopAnimations(), ], }).compileComponents(); @@ -114,69 +116,36 @@ describe('CalendarEventsPerDaySectionComponent', () => { const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); expect(examEventCell).toBeTruthy(); + const popoverDebugElement = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); + const popoverComponent = popoverDebugElement.componentInstance as CalendarEventDetailPopoverComponent; + examEventCell.nativeElement.click(); fixture.detectChanges(); - const popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeTruthy(); + expect(popoverComponent.isOpen).toBeTruthy(); }); - it('should close popover only when close button used', () => { + it('should close popover', () => { + const popoverDebugElement = fixture.debugElement.query(By.directive(CalendarEventDetailPopoverComponent)); + const popoverComponent = popoverDebugElement.componentInstance as CalendarEventDetailPopoverComponent; + const closeSpy = jest.spyOn(popoverComponent, 'close'); + const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); examEventCell.nativeElement.click(); fixture.detectChanges(); - let popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeTruthy(); - const infoColumn = fixture.debugElement.query(By.css('.info-column')); infoColumn.nativeElement.click(); fixture.detectChanges(); - - popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeTruthy(); - - const closeButton = popover!.querySelector('.close-button') as HTMLElement; - expect(closeButton).toBeTruthy(); - - closeButton.click(); - fixture.detectChanges(); - - popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeNull(); - }); - - it('should replace popover when other event selected', () => { - const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); - const lectureEventCell = fixture.debugElement.query(By.css('[data-testid="Object Design"]')); - - examEventCell.nativeElement.click(); - fixture.detectChanges(); - - let popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeTruthy(); - expect(component.selectedEvent()?.id).toBe(events[0].id); - - lectureEventCell.nativeElement.click(); - fixture.detectChanges(); - - popover = document.querySelector('jhi-calendar-event-detail-popover'); - expect(popover).toBeTruthy(); - expect(component.selectedEvent()?.id).toBe(events[1].id); - }); - - it('should emit isEventSelected correctly when popover opens/closes', () => { - const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); - expect(examEventCell).toBeTruthy(); - - const emitSpy = jest.spyOn(component.isEventSelected, 'emit'); + expect(popoverComponent.isOpen()).toBeFalse(); examEventCell.nativeElement.click(); fixture.detectChanges(); - expect(emitSpy).toHaveBeenCalledWith(true); - examEventCell.nativeElement.click(); + const closeButton = document.querySelector('.close-button') as HTMLElement; + expect(closeButton).toBeTruthy(); + closeButton.click(); fixture.detectChanges(); - expect(emitSpy).toHaveBeenCalledWith(false); + expect(closeSpy).toHaveBeenCalledOnce(); }); }); diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts index f44ccbf6d476..6d39b9b048e0 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.ts @@ -1,11 +1,11 @@ -import { Component, computed, effect, inject, input, output, signal } from '@angular/core'; +import { Component, computed, inject, input } from '@angular/core'; import { NgClass, NgStyle } from '@angular/common'; import * as utils from 'app/core/calendar/shared/util/calendar-util'; import { CalendarEvent, CalendarEventType } from 'app/core/calendar/shared/entities/calendar-event.model'; import { Dayjs } from 'dayjs/esm'; import { CalendarEventAndPosition, PositionInfo } from 'app/core/calendar/shared/entities/calendar-event-and-position.model'; import { CalendarService } from 'app/core/calendar/shared/service/calendar.service'; -import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-new-component/calendar-event-detail-popover.component'; +import { CalendarEventDetailPopoverComponent } from 'app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component'; type Day = { date: Dayjs; eventsAndPositions: CalendarEventAndPosition[]; id: string }; @@ -39,8 +39,6 @@ export class CalendarEventsPerDaySectionComponent { readonly CalendarEventType = CalendarEventType; dates = input.required(); - isEventSelected = output(); - selectedEvent = signal(undefined); hoursOfDay = utils.getHoursOfDay(); zeroToTwentyFour = utils.range(24); days = computed(() => { @@ -51,12 +49,6 @@ export class CalendarEventsPerDaySectionComponent { })); }); - constructor() { - effect(() => { - this.isEventSelected.emit(this.selectedEvent() !== undefined); - }); - } - private getEventsAndPositions(date: Dayjs): CalendarEventAndPosition[] { return this.dateToEventAndPositionMap().get(date.format('YYYY-MM-DD')) ?? []; } From 613628a86aadf14090a0de41c20410b376d39c7b Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Thu, 2 Oct 2025 01:30:44 +0200 Subject: [PATCH 10/13] fix client test compilation error --- .../calendar-desktop-week-presentation.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts index 0b0b93c41958..9857cb003435 100644 --- a/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/week-presentation/calendar-desktop-week-presentation.component.spec.ts @@ -9,7 +9,6 @@ import { TranslateDirective } from 'app/shared/language/translate.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; describe('CalendarDesktopWeekPresentationComponent', () => { - let component: CalendarDesktopWeekPresentationComponent; let fixture: ComponentFixture; const startOfMonday = dayjs('2025-05-05'); @@ -26,7 +25,6 @@ describe('CalendarDesktopWeekPresentationComponent', () => { }).compileComponents(); fixture = TestBed.createComponent(CalendarDesktopWeekPresentationComponent); - component = fixture.componentInstance; fixture.componentRef.setInput('firstDayOfCurrentWeek', startOfMonday); fixture.detectChanges(); From 691c8b69e9ca75a48db3f1aa9196eefd85aa17c3 Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Thu, 2 Oct 2025 01:45:05 +0200 Subject: [PATCH 11/13] adjust coverage --- .../module-coverage-client/check-client-module-coverage.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs b/supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs index 4e332462135e..81e53e92771a 100644 --- a/supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs +++ b/supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs @@ -48,9 +48,9 @@ const moduleThresholds = { lines: 92.80, }, core: { - statements: 89.80, + statements: 89.75, branches: 72.40, - functions: 81.80, + functions: 81.59, lines: 89.70, }, exam: { From 870a9d698e8778daf33f60e5623db512bd15ce9c Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Thu, 2 Oct 2025 16:54:28 +0200 Subject: [PATCH 12/13] apply Coderabbit' and Enver's change requests --- .../overview/calendar-desktop-overview.component.html | 6 ++++-- .../overview/calendar-desktop-overview.component.scss | 2 +- .../calendar-desktop-overview.component.spec.ts | 2 +- .../overview/calendar-desktop-overview.component.ts | 10 +++++++++- .../calendar-event-detail-popover.component.html | 4 +++- .../calendar-event-detail-popover.component.scss | 1 + .../calendar-events-per-day-section.component.spec.ts | 6 +++--- src/main/webapp/i18n/de/calendar.json | 2 +- src/main/webapp/i18n/en/calendar.json | 2 +- 9 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html index 8a641c97bb1b..02675d0a5cc6 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.html @@ -7,7 +7,7 @@ [ngModel]="selectedFilterOptions()" (ngModelChange)="onSelectionOptionsChange($event)" optionLabel="name" - placeholder="Select Events" + [placeholder]="filterComponentPlaceholder()" [showHeader]="false" > @@ -17,7 +17,9 @@
{{ optionWithMetadata.name }} - +
} diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss index 322c1fab1c48..112248e5858c 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.scss @@ -30,7 +30,7 @@ font-size: 0.75rem; .remove-button { - cursor: pointer; + all: unset; } } diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts index bd017f747963..63cfcf7a3576 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.spec.ts @@ -71,7 +71,7 @@ describe('CalendarDesktopOverviewComponent', () => { const previousButton = fixture.debugElement.query(By.css('#previous-button')).nativeElement; const nextButton = fixture.debugElement.query(By.css('#next-button')).nativeElement; - const selectButton = fixture.debugElement.queryAll(By.css('#presentation-select-button')); + const selectButton = fixture.debugElement.query(By.css('#presentation-select-button')); expect(previousButton).toBeTruthy(); expect(nextButton).toBeTruthy(); expect(selectButton).toBeTruthy(); diff --git a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts index 13fa5b8a39e5..f71b19203693 100644 --- a/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts +++ b/src/main/webapp/app/core/calendar/desktop/overview/calendar-desktop-overview.component.ts @@ -21,7 +21,11 @@ import { ButtonGroupModule } from 'primeng/buttongroup'; import { MultiSelectModule } from 'primeng/multiselect'; import { CalendarEventFilterOption } from 'app/core/calendar/shared/util/calendar-util'; -type CalendarEventFilterOptionAndMetadata = { option: CalendarEventFilterOption; name: string; colorClassName: string }; +interface CalendarEventFilterOptionAndMetadata { + option: CalendarEventFilterOption; + name: string; + colorClassName: string; +} @Component({ selector: 'jhi-calendar-desktop-overview', @@ -68,6 +72,10 @@ export class CalendarDesktopOverviewComponent implements OnInit { this.currentLocale(); return this.buildPresentationOptions(); }); + filterComponentPlaceholder = computed(() => { + this.currentLocale(); + return this.translateService.instant('artemisApp.calendar.filterComponentPlaceholder'); + }); selectedFilterOptions = computed(() => { this.currentLocale(); return this.computeSelectedFilterOptions(this.calendarService.includedEventFilterOptions()); diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.html b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.html index a97c6baddbd6..fb23414b88ac 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.html +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.html @@ -4,7 +4,9 @@
{{ eventToDisplay.title }}
- +
diff --git a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.scss b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.scss index be968512edf6..4e872bb2f76e 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.scss +++ b/src/main/webapp/app/core/calendar/shared/calendar-event-detail-popover-component/calendar-event-detail-popover.component.scss @@ -14,6 +14,7 @@ font-weight: 600; .close-button { + all: unset; color: var(--secondary); } diff --git a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts index 3b339a25d326..026977a69e5b 100644 --- a/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts +++ b/src/main/webapp/app/core/calendar/shared/calendar-events-per-day-section/calendar-events-per-day-section.component.spec.ts @@ -112,7 +112,7 @@ describe('CalendarEventsPerDaySectionComponent', () => { expect(textExerciseStyle.left).toBeCloseTo(67.17, 1); }); - it('should open popover', () => { + it('should open popover', async () => { const examEventCell = fixture.debugElement.query(By.css('[data-testid="Exam"]')); expect(examEventCell).toBeTruthy(); @@ -121,8 +121,8 @@ describe('CalendarEventsPerDaySectionComponent', () => { examEventCell.nativeElement.click(); fixture.detectChanges(); - - expect(popoverComponent.isOpen).toBeTruthy(); + await fixture.whenStable(); + expect(popoverComponent.isOpen()).toBeTrue(); }); it('should close popover', () => { diff --git a/src/main/webapp/i18n/de/calendar.json b/src/main/webapp/i18n/de/calendar.json index 29ada08631cb..f061d833aac1 100644 --- a/src/main/webapp/i18n/de/calendar.json +++ b/src/main/webapp/i18n/de/calendar.json @@ -24,7 +24,7 @@ "tutorials": "Tutorien", "exams": "Klausuren" }, - "filterButtonLabel": "Filter", + "filterComponentPlaceholder": "Wähle Event-Typen", "subscribeButtonLabel": "Abonnieren", "todayButtonLabel": "Heute", "weekButtonLabel": "Woche", diff --git a/src/main/webapp/i18n/en/calendar.json b/src/main/webapp/i18n/en/calendar.json index 7225804eea32..a928210deff6 100644 --- a/src/main/webapp/i18n/en/calendar.json +++ b/src/main/webapp/i18n/en/calendar.json @@ -24,7 +24,7 @@ "tutorials": "Tutorials", "exams": "Exams" }, - "filterButtonLabel": "Filter", + "filterComponentPlaceholder": "Choose Event-Types", "subscribeButtonLabel": "Subscribe", "todayButtonLabel": "Today", "weekButtonLabel": "Week", From 7974b1f4de82f87475bd042f1926bdd60d2206bc Mon Sep 17 00:00:00 2001 From: marlonnienaber Date: Thu, 2 Oct 2025 17:06:20 +0200 Subject: [PATCH 13/13] fix wrong class name --- .../mobile/overview/calendar-mobile-overview.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html index e4e750ffb457..239abdfa3147 100644 --- a/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html +++ b/src/main/webapp/app/core/calendar/mobile/overview/calendar-mobile-overview.component.html @@ -28,7 +28,7 @@
-
+
@if (selectedDate()) {