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 @@
+
+
+
+
+
+ @if (tutorialGroup.teachingAssistantImageUrl) {
+
+ } @else {
+
{{
+ tutorInitials
+ }}
+ }
+
+
+
+ :
+ {{ tutorialGroup.teachingAssistantName }}
+
+
+ @if (tutorialGroup.channel && isMessagingEnabled) {
+
+ }
+
+
+
+
+
+
+
+
+ :
+ {{ (tutorialGroup.averageAttendance && Math.round(tutorialGroup.averageAttendance)) ?? '-' }}
+
+
+
+
+ :
+ {{ tutorialGroup.capacity }}
+
+
+
+ :
+ {{ !tutorialGroup.numberOfRegisteredUsers || tutorialGroup.numberOfRegisteredUsers === 0 ? '-' : tutorialGroup.numberOfRegisteredUsers }}
+
+
+
+
:
+
+
+ @if (utilization) {
+
+
= 60 && utilization < 90, 'bg-danger': utilization >= 90 }"
+ [ngStyle]="{ width: 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)],
})