diff --git a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.html b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.html index ced9dd74a469..26607ec88597 100644 --- a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.html +++ b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.html @@ -5,10 +5,144 @@

{{ tutorialGroup.title }}


} - @if (tutorialDetailSections) { - - } -
+
+
+ +
+
+ @if (tutorialGroup.teachingAssistantImageUrl) { + + } @else { + {{ + tutorInitials + }} + } +
+ +
+ : + {{ tutorialGroup.teachingAssistantName }} +
+ + @if (tutorialGroup.channel && isMessagingEnabled) { +
+ : + @if (tutorialGroup.channel.isMember) { + + {{ tutorialGroup.channel.name }} + + } @else { + {{ tutorialGroup.channel.name }} + } +
+ } +
+
+
+ +
+ +
+
+ : + {{ (tutorialGroup.averageAttendance && Math.round(tutorialGroup.averageAttendance)) ?? '-' }} + +
+ +
+ : + {{ tutorialGroup.capacity }} +
+ +
+ : + {{ !tutorialGroup.numberOfRegisteredUsers || tutorialGroup.numberOfRegisteredUsers === 0 ? '-' : tutorialGroup.numberOfRegisteredUsers }} +
+ +
+ : + + + @if (utilization) { +
+
+ {{ utilization }}% +
+
+ } @else { +
+
-
+
+ } +
+
+
+
+ +
+ +
+
+ : + {{ tutorialGroup.language }} +
+ +
+ : + {{ tutorialGroup.campus }} +
+ +
+ : + {{ tutorialTimeslotString }} +
+ +
+ : + + {{ tutorialGroup.tutorialGroupSchedule?.location }} +
+ +
+ @if (tutorialGroup.isOnline) { + + + } @else { + + + } +
+
+
+
+ + @if (formattedAdditionalInformation) { +
+
+
+
:
+ +
+
+
+
+ } +
+
@if (sessions && sessions.length) { diff --git a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.scss b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.scss index c25d9ed8613e..0a2501554636 100644 --- a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.scss +++ b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.scss @@ -1,3 +1,5 @@ +$tutor-image-size: 4.5rem; + .tutorial-group-detail { .scrollbar { position: relative; @@ -8,4 +10,25 @@ .table-wrapper-scroll-y { display: block; } + + .markdown-preview { + margin-bottom: -1rem; + } +} + +.tutorial-group-detail-tutor-image { + width: $tutor-image-size; + height: $tutor-image-size; + object-fit: cover; +} + +.tutorial-group-detail-tutor-default-image { + width: $tutor-image-size; + height: $tutor-image-size; + font-size: 2rem; + display: inline-flex; + align-items: center; + justify-content: center; + background-color: var(--gray-400); + color: var(--white); } diff --git a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.ts b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.ts index 65f3b52a53e4..2d2624ac55a4 100644 --- a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.ts +++ b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component.ts @@ -5,10 +5,8 @@ import { SafeHtml } from '@angular/platform-browser'; import { ArtemisMarkdownService } from 'app/shared/markdown.service'; import { getDayTranslationKey } from '../weekdays'; import { TutorialGroupSession, TutorialGroupSessionStatus } from 'app/entities/tutorial-group/tutorial-group-session.model'; -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; -import { DetailOverviewSection, DetailType } from 'app/detail-overview-list/detail-overview-list.component'; import { TranslateService } from '@ngx-translate/core'; -import { Detail } from 'app/detail-overview-list/detail.model'; +import { faCircle, faCircleInfo, faCircleXmark, faPercent, faQuestionCircle, faUserCheck } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; import { SortService } from 'app/shared/service/sort.service'; import { getInitialsFromString } from 'app/utils/text.utils'; @@ -32,12 +30,23 @@ export class TutorialGroupDetailComponent implements OnChanges { course: Course; formattedAdditionalInformation?: SafeHtml; - faQuestionCircle = faQuestionCircle; readonly Math = Math; sessions: TutorialGroupSession[] = []; - tutorialDetailSections: DetailOverviewSection[]; + tutorInitials: string; + tutorialTimeslotString: string | undefined; + tutorDefaultProfilePictureHue: string; + isMessagingEnabled: boolean; + utilization: number | undefined; + + // Icons + readonly faUserCheck = faUserCheck; + readonly faPercent = faPercent; + readonly faCircleInfo = faCircleInfo; + readonly faQuestionCircle = faQuestionCircle; + readonly faCircle = faCircle; + readonly faCircleXmark = faCircleXmark; constructor( private artemisMarkdownService: ArtemisMarkdownService, @@ -60,7 +69,7 @@ export class TutorialGroupDetailComponent implements OnChanges { this.changeDetectorRef.detectChanges(); } } - this.getTutorialDetailSections(); + this.getTutorialDetail(); } getTutorialTimeSlotString(): string | undefined { @@ -79,80 +88,18 @@ export class TutorialGroupDetailComponent implements OnChanges { return `${day} ${start}-${end}, ${repetition}`; } - getTutorialDetailSections() { + getTutorialDetail() { const tutorialGroup = this.tutorialGroup; - const tutorialDetails: Detail[] = [ - { type: DetailType.Link, title: 'artemisApp.entities.tutorialGroup.course', data: { text: tutorialGroup.courseTitle, routerLink: ['../..'] } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.title', data: { text: tutorialGroup.title } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.teachingAssistant', data: { text: tutorialGroup.teachingAssistantName } }, - tutorialGroup.teachingAssistantImageUrl - ? { - type: DetailType.Image, - title: 'artemisApp.entities.tutorialGroup.profilePicture', - data: { imageUrl: tutorialGroup.teachingAssistantImageUrl, altText: 'Profile picture of ' + tutorialGroup.teachingAssistantName }, - } - : { - type: DetailType.DefaultProfilePicture, - title: 'artemisApp.entities.tutorialGroup.profilePicture', - data: { - color: getBackgroundColorHue(tutorialGroup.teachingAssistantId ? tutorialGroup.teachingAssistantId.toString() : 'default'), - initials: getInitialsFromString(tutorialGroup.teachingAssistantName ?? 'NA'), - }, - }, - { - type: DetailType.Text, - title: 'artemisApp.entities.tutorialGroup.utilization', - titleHelpText: 'artemisApp.entities.tutorialGroup.utilizationHelpDetail', - data: { text: tutorialGroup.averageAttendance && tutorialGroup.capacity && Math.round((tutorialGroup.averageAttendance / tutorialGroup.capacity) * 100) }, - }, - { - type: DetailType.Text, - title: 'artemisApp.entities.tutorialGroup.averageAttendanceDetail', - titleHelpText: 'artemisApp.entities.tutorialGroup.averageAttendanceHelpDetail', - data: { text: tutorialGroup.averageAttendance && Math.round(tutorialGroup.averageAttendance) }, - }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.capacity', data: { text: tutorialGroup.capacity } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.registrations', data: { text: tutorialGroup.numberOfRegisteredUsers } }, - { type: DetailType.Boolean, title: 'artemisApp.entities.tutorialGroup.isOnline', data: { boolean: tutorialGroup.isOnline } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.language', data: { text: tutorialGroup.language } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.campus', data: { text: tutorialGroup.campus } }, - { type: DetailType.Markdown, title: 'artemisApp.entities.tutorialGroup.additionalInformation', data: { innerHtml: this.formattedAdditionalInformation } }, - { type: DetailType.Text, title: 'artemisApp.entities.tutorialGroup.schedule', data: { text: this.getTutorialTimeSlotString() } }, - ]; - - // inserting optional details in reversed order, so no index calculation is needed - if (tutorialGroup.isOnline) { - tutorialDetails.splice(12, 0, { - type: DetailType.Text, - title: 'artemisApp.forms.scheduleForm.locationInput.labelOnline', - data: { text: tutorialGroup.tutorialGroupSchedule?.location }, - }); - } else { - tutorialDetails.splice(12, 0, { - type: DetailType.Text, - title: 'artemisApp.forms.scheduleForm.locationInput.labelOffline', - data: { text: tutorialGroup.tutorialGroupSchedule?.location }, - }); - } - if (tutorialGroup.channel && isMessagingEnabled(this.course)) { - tutorialDetails.splice(2, 0, { - type: DetailType.Link, - title: 'artemisApp.entities.tutorialGroup.channel', - data: { - text: tutorialGroup.channel.name, - routerLink: tutorialGroup.channel.isMember ? ['/courses', this.course.id!, 'communication'] : undefined, - queryParams: { conversationId: tutorialGroup.channel.id }, - }, - }); + this.tutorDefaultProfilePictureHue = getBackgroundColorHue(tutorialGroup.teachingAssistantId ? tutorialGroup.teachingAssistantId.toString() : 'default'); + this.tutorInitials = getInitialsFromString(tutorialGroup.teachingAssistantName ?? 'NA'); + this.isMessagingEnabled = isMessagingEnabled(this.course); + if (tutorialGroup.averageAttendance && tutorialGroup.capacity) { + this.utilization = Math.round((tutorialGroup.averageAttendance / tutorialGroup.capacity) * 100); + } else { + this.utilization = undefined; } - - this.tutorialDetailSections = [ - { - headline: 'artemisApp.pages.courseTutorialGroupDetail.sections.general', - details: tutorialDetails, - }, - ]; + this.tutorialTimeslotString = this.getTutorialTimeSlotString(); } recalculateAttendanceDetails() { @@ -166,6 +113,6 @@ export class TutorialGroupDetailComponent implements OnChanges { this.tutorialGroup.averageAttendance = Math.round(averageAttendance); } - this.getTutorialDetailSections(); + this.getTutorialDetail(); } } diff --git a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-groups-shared.module.ts b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-groups-shared.module.ts index 6dc2bf654604..072ea574195e 100644 --- a/src/main/webapp/app/course/tutorial-groups/shared/tutorial-groups-shared.module.ts +++ b/src/main/webapp/app/course/tutorial-groups/shared/tutorial-groups-shared.module.ts @@ -13,9 +13,10 @@ import { TutorialGroupUtilizationIndicatorComponent } from './tutorial-group-uti import { RemoveSecondsPipe } from 'app/course/tutorial-groups/shared/remove-seconds.pipe'; import { MeetingPatternPipe } from 'app/course/tutorial-groups/shared/meeting-pattern.pipe'; import { DetailModule } from 'app/detail-overview-list/detail.module'; +import { IconCardComponent } from 'app/shared/icon-card/icon-card.component'; @NgModule({ - imports: [ArtemisSharedModule, RouterModule, ArtemisSidePanelModule, VerticalProgressBarModule, DetailModule], + imports: [ArtemisSharedModule, RouterModule, ArtemisSidePanelModule, VerticalProgressBarModule, DetailModule, IconCardComponent], declarations: [ TutorialGroupsTableComponent, TutorialGroupDetailComponent, diff --git a/src/main/webapp/app/shared/icon-card/icon-card.component.html b/src/main/webapp/app/shared/icon-card/icon-card.component.html new file mode 100644 index 000000000000..11aabaa71cdc --- /dev/null +++ b/src/main/webapp/app/shared/icon-card/icon-card.component.html @@ -0,0 +1,12 @@ +
+
+
+ + +
+ +
+ + +
+
diff --git a/src/main/webapp/app/shared/icon-card/icon-card.component.scss b/src/main/webapp/app/shared/icon-card/icon-card.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/main/webapp/app/shared/icon-card/icon-card.component.ts b/src/main/webapp/app/shared/icon-card/icon-card.component.ts new file mode 100644 index 000000000000..dc0c603000a2 --- /dev/null +++ b/src/main/webapp/app/shared/icon-card/icon-card.component.ts @@ -0,0 +1,18 @@ +import { Component, input } from '@angular/core'; +import { faCircleInfo } from '@fortawesome/free-solid-svg-icons'; +import { IconDefinition } from '@fortawesome/fontawesome-common-types'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisSharedPipesModule } from 'app/shared/pipes/shared-pipes.module'; + +@Component({ + selector: 'jhi-icon-card', + templateUrl: './icon-card.component.html', + styleUrl: './icon-card.component.scss', + standalone: true, + imports: [ArtemisSharedCommonModule, ArtemisSharedPipesModule], +}) +export class IconCardComponent { + headerIcon = input(faCircleInfo); + + headline = input('Title'); +} diff --git a/src/main/webapp/i18n/de/tutorialGroups.json b/src/main/webapp/i18n/de/tutorialGroups.json index 021d7a6de739..6ba6bf79b1ef 100644 --- a/src/main/webapp/i18n/de/tutorialGroups.json +++ b/src/main/webapp/i18n/de/tutorialGroups.json @@ -55,7 +55,10 @@ "averageAttendanceDetail": "Durchschnittliche Anwesenheit", "averageAttendanceHelpDetail": "Durchschnittliche Anwesenheit in den letzten drei Sitzungen. Falls keine Anwesenheit eingetragen wurde, wird die entsprechende Sitzung ignoriert und die Berechnung mit zwei, bzw. einer Sitzung durchgeführt.", "profilePicture": "Tutor:in Profilbild", - "profilePictureAlt": "Profilbild von {{ user }}" + "profilePictureAlt": "Profilbild von {{ user }}", + "name": "Name", + "information": "Information", + "isNotOnline": "Nicht Online" }, "tutorialGroupSchedule": { "dayOfWeek": "Wochentag", diff --git a/src/main/webapp/i18n/en/tutorialGroups.json b/src/main/webapp/i18n/en/tutorialGroups.json index 7792eb659f6d..cc6b966db947 100644 --- a/src/main/webapp/i18n/en/tutorialGroups.json +++ b/src/main/webapp/i18n/en/tutorialGroups.json @@ -55,7 +55,10 @@ "averageAttendanceDetail": "Average Attendance", "averageAttendanceHelpDetail": "Average attendance in the last three sessions. If no attendance is entered, the corresponding session is ignored and the calculation is performed with two or one session.", "profilePicture": "Tutor Profile Picture", - "profilePictureAlt": "Profile picture of {{ user }}" + "profilePictureAlt": "Profile picture of {{ user }}", + "name": "Name", + "information": "Information", + "isNotOnline": "Not Online" }, "tutorialGroupSchedule": { "dayOfWeek": "Day of Week", diff --git a/src/test/javascript/spec/component/shared/icon-card/icon-card.component.spec.ts b/src/test/javascript/spec/component/shared/icon-card/icon-card.component.spec.ts new file mode 100644 index 000000000000..d96d5417b943 --- /dev/null +++ b/src/test/javascript/spec/component/shared/icon-card/icon-card.component.spec.ts @@ -0,0 +1,40 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockDirective } from 'ng-mocks'; +import { IconCardComponent } from 'app/shared/icon-card/icon-card.component'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { faCircleInfo, faCoffee } from '@fortawesome/free-solid-svg-icons'; + +describe('IconCardComponent', () => { + let component: IconCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [], + declarations: [IconCardComponent, MockDirective(TranslateDirective)], + }).compileComponents(); + + fixture = TestBed.createComponent(IconCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).not.toBeNull(); + }); + + it('should display the default headerIcon and headline', () => { + expect(component.headerIcon()).toBe(faCircleInfo); + expect(component.headline()).toBe('Title'); + }); + + it('should display custom headerIcon and headline when inputs are set', () => { + fixture.componentRef.setInput('headerIcon', faCoffee); + fixture.componentRef.setInput('headline', 'Test'); + + fixture.detectChanges(); + + expect(component.headerIcon()).toBe(faCoffee); + expect(component.headline()).toBe('Test'); + }); +}); diff --git a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts index 02d89da561e4..1290e595fbd3 100644 --- a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts +++ b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts @@ -1,7 +1,7 @@ import { TutorialGroupDetailComponent } from 'app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; +import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { ArtemisMarkdownService } from 'app/shared/markdown.service'; import { generateExampleTutorialGroup } from '../helpers/tutorialGroupExampleModels'; import { ChangeDetectorRef, Component, Input, ViewChild } from '@angular/core'; @@ -11,7 +11,6 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module'; import { TutorialGroupUtilizationIndicatorComponent } from 'app/course/tutorial-groups/shared/tutorial-group-utilization-indicator/tutorial-group-utilization-indicator.component'; import { RemoveSecondsPipe } from 'app/course/tutorial-groups/shared/remove-seconds.pipe'; -import { DetailOverviewListComponent } from 'app/detail-overview-list/detail-overview-list.component'; import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { provideHttpClientTesting } from '@angular/common/http/testing'; @@ -21,6 +20,8 @@ import dayjs from 'dayjs/esm'; import { TutorialGroupSessionStatus } from 'app/entities/tutorial-group/tutorial-group-session.model'; import { provideHttpClient } from '@angular/common/http'; import { RouterModule } from '@angular/router'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { IconCardComponent } from 'app/shared/icon-card/icon-card.component'; @Component({ selector: 'jhi-mock-header', template: '
' }) class MockHeaderComponent { @@ -60,13 +61,14 @@ describe('TutorialGroupDetailWrapperTest', () => { imports: [NgbTooltipMocksModule, RouterModule.forRoot([])], declarations: [ TutorialGroupDetailComponent, - DetailOverviewListComponent, MockWrapperComponent, MockHeaderComponent, + MockComponent(IconCardComponent), MockPipe(ArtemisTranslatePipe), MockPipe(RemoveSecondsPipe), MockComponent(FaIconComponent), MockComponent(TutorialGroupUtilizationIndicatorComponent), + MockDirective(TranslateDirective), ], providers: [ provideHttpClient(), @@ -78,7 +80,6 @@ describe('TutorialGroupDetailWrapperTest', () => { { provide: LocalStorageService, useClass: MockLocalStorageService }, ], }) - .overrideTemplate(DetailOverviewListComponent, '') .compileComponents() .then(() => { fixture = TestBed.createComponent(MockWrapperComponent); @@ -114,8 +115,10 @@ describe('TutorialGroupDetailComponent', () => { TutorialGroupDetailComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FaIconComponent), + MockComponent(IconCardComponent), MockComponent(TutorialGroupUtilizationIndicatorComponent), MockPipe(RemoveSecondsPipe), + MockDirective(TranslateDirective), ], providers: [MockProvider(ArtemisMarkdownService), { provide: TranslateService, useClass: MockTranslateService }, MockProvider(ChangeDetectorRef)], })