Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add students list for study courses, study classes & courses #712 #713 #750

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"schematics": {
"@schematics/angular": {
"component": {
"changeDetection": "OnPush"
}
}
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
</td>
<td class="subject">
@if (isTeacher$ | async) {
<a [href]="buildLink(entry.eventId)" target="_parent">
@let link = buildLink(entry.eventId);
<a
[routerLink]="link.link"
[queryParams]="link.params"
target="_parent"
>
{{ entry.subject
}}<span class="subject-study-class"
>, {{ entry.studyClass }}</span
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AsyncPipe, DatePipe } from "@angular/common";
import { Component, Inject, Input } from "@angular/core";
import { Component, Input } from "@angular/core";
import { RouterLink } from "@angular/router";
import { TranslateModule } from "@ngx-translate/core";
import { SETTINGS, Settings } from "src/app/settings";
import { getEventsStudentsLink } from "src/app/events/utils/events-students";
import { convertLink } from "src/app/shared/utils/url";
import { DashboardService } from "../../services/dashboard.service";
import { DashboardTimetableEntry } from "../../utils/dashboard-timetable-entry";

Expand All @@ -10,7 +12,7 @@ import { DashboardTimetableEntry } from "../../utils/dashboard-timetable-entry";
templateUrl: "./dashboard-timetable-table.component.html",
styleUrls: ["./dashboard-timetable-table.component.scss"],
standalone: true,
imports: [AsyncPipe, DatePipe, TranslateModule],
imports: [AsyncPipe, DatePipe, TranslateModule, RouterLink],
})
export class DashboardTimetableTableComponent {
@Input()
Expand All @@ -19,15 +21,9 @@ export class DashboardTimetableTableComponent {
isStudent$ = this.dashboardService.hasStudentRole$;
isTeacher$ = this.dashboardService.hasLessonTeacherRole$;

constructor(
private dashboardService: DashboardService,
@Inject(SETTINGS) private settings: Settings,
) {}
constructor(private dashboardService: DashboardService) {}

buildLink(eventId: number): string {
return this.settings.eventlist["eventdetail"].replace(
":id",
String(eventId),
);
buildLink(eventId: number) {
return convertLink(getEventsStudentsLink(eventId, "/dashboard"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ describe("DashboardTimetableComponent", () => {

let link = rows[0].querySelector("td.subject a");
expect(link?.textContent?.trim()).toBe("Mathematik, 9a");
expect(link?.getAttribute("href")).toBe("link-to-event-detail-module/10");
expect(link?.getAttribute("href")).toBe(
"/events/students/10?returnlink=%252Fdashboard",
);

expect(rows[0].querySelector("td.study-class")?.textContent).toContain(
"9a",
Expand All @@ -189,7 +191,9 @@ describe("DashboardTimetableComponent", () => {

link = rows[1].querySelector("td.subject a");
expect(link?.textContent?.trim()).toBe("Zeichnen, 8c");
expect(link?.getAttribute("href")).toBe("link-to-event-detail-module/20");
expect(link?.getAttribute("href")).toBe(
"/events/students/20?returnlink=%252Fdashboard",
);

expect(rows[1].querySelector("td.study-class")?.textContent).toContain(
"8c",
Expand Down Expand Up @@ -374,5 +378,6 @@ function buildLessonStudyClass(
EventRef: { Id: entry.EventId, HRef: "" },
LessonRef: { Id: entry.Id, HRef: "" },
StudyClassNumber: studyClassNumber,
StudentRef: { Id: 10, HRef: "" },
};
}
4 changes: 4 additions & 0 deletions src/app/dashboard/utils/dashboard-timetable-entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,25 @@ describe("Dashboard timetable entry utilities", () => {
EventRef: { Id: 9742, HRef: "" },
LessonRef: { Id: 1290, HRef: "" },
StudyClassNumber: "27a",
StudentRef: { Id: 10, HRef: "" },
},
{
EventRef: { Id: 9742, HRef: "" },
LessonRef: { Id: 1290, HRef: "" },
StudyClassNumber: "27b",
StudentRef: { Id: 10, HRef: "" },
},
{
EventRef: { Id: 9741, HRef: "" },
LessonRef: { Id: 1290, HRef: "" },
StudyClassNumber: "27c",
StudentRef: { Id: 10, HRef: "" },
},
{
EventRef: { Id: 9742, HRef: "" },
LessonRef: { Id: 1291, HRef: "" },
StudyClassNumber: "27d",
StudentRef: { Id: 10, HRef: "" },
},
];
const result = createStudyClassesMap(lessonStudyClasses);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
<div class="designation">
<a [href]="event.detailLink">{{ event.designation }}</a>
<a [routerLink]="link().link" [queryParams]="link().params">{{
event().designation
}}</a>
</div>
<div class="date">
@if (event.dateFrom && event.dateTo) {
@if (event().dateFrom && event().dateTo) {
<span
>{{ event.dateFrom | date: "dd.MM.yyyy" }}–<wbr />{{
event.dateTo | date: "dd.MM.yyyy"
>{{ event().dateFrom | date: "dd.MM.yyyy" }}–<wbr />{{
event().dateTo | date: "dd.MM.yyyy"
}}</span
>
}
</div>
<div class="registrations">
{{ event.studentCount }}
{{ event().studentCount }}
<span class="registrations-label">{{
(event.studentCount === 1 ? "events.registration" : "events.registrations")
| translate
(event().studentCount === 1
? "events.registration"
: "events.registrations"
) | translate
}}</span>
</div>
@if (withRatings && event.evaluationText) {
@if (withRatings() && event().evaluationText) {
<div class="rating">
@if (!event.evaluationLink) {
<a class="d-flex" [routerLink]="[event.id, 'tests']">
@if (!event().evaluationLink) {
<a class="d-flex" [routerLink]="[event().id, 'tests']">
<i class="material-icons">arrow_right_alt</i>
<span class="ps-1">{{ event.evaluationText }}</span>
<span class="ps-1">{{ event().evaluationText }}</span>
</a>
}
@if (event.evaluationLink) {
<a class="d-flex" [href]="event.evaluationLink">
@if (event().evaluationLink) {
<a class="d-flex" [routerLink]="event().evaluationLink">
<i class="material-icons">arrow_right_alt</i>
<span class="ps-1">{{ event.evaluationText }} </span>
<span class="ps-1">{{ event().evaluationText }} </span>
</a>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { buildTestModuleMetadata } from "src/spec-helpers";
import { EventsListEntryComponent } from "./events-list-entry.component";

describe("EventsListEntryComponent", () => {
let component: EventsListEntryComponent;
let fixture: ComponentFixture<EventsListEntryComponent>;
let element: HTMLElement;

Expand All @@ -16,23 +15,23 @@ describe("EventsListEntryComponent", () => {
).compileComponents();

fixture = TestBed.createComponent(EventsListEntryComponent);
component = fixture.componentInstance;
element = fixture.debugElement.nativeElement;

component.event = buildEventEntry(1);
component.event.evaluationText = "Lorem ipsum";
const event = buildEventEntry(1);
event.evaluationText = "Lorem ipsum";
fixture.componentRef.setInput("event", event);
});

it("renders entry without ratings column", () => {
component.withRatings = false;
fixture.componentRef.setInput("withRatings", false);

fixture.detectChanges();
expect(element.querySelector(".rating")).toBeNull();
expect(element.textContent).not.toContain("Lorem ipsum");
});

it("renders entry with ratings column", () => {
component.withRatings = true;
fixture.componentRef.setInput("withRatings", true);

fixture.detectChanges();
expect(element.querySelector(".rating")).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { DatePipe } from "@angular/common";
import { Component, Input } from "@angular/core";
import { Component, computed, input } from "@angular/core";
import { RouterLink } from "@angular/router";
import { TranslateModule } from "@ngx-translate/core";
import { convertLink } from "src/app/shared/utils/url";
import { EventEntry } from "../../services/events-state.service";

@Component({
Expand All @@ -12,6 +13,8 @@ import { EventEntry } from "../../services/events-state.service";
styleUrl: "./events-list-entry.component.scss",
})
export class EventsListEntryComponent {
@Input() event: EventEntry;
@Input() withRatings = true;
event = input.required<EventEntry>();
withRatings = input<boolean>(true);

link = computed(() => convertLink(this.event().detailLink));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<bkd-avatar
[studentId]="entry().id"
[link]="link()"
[linkParams]="linkParams()"
class="avatar large"
></bkd-avatar>
<a
class="name"
[title]="name()"
[routerLink]="link()"
[queryParams]="linkParams()"
>{{ name() }}</a
>
<div class="study-class">
{{ multipleStudyClasses() ? entry().studyClass : "" }}
</div>
<div class="company" [title]="entry().company">{{ entry().company }}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@import "../../../../bootstrap-variables";

:host {
display: grid;
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content 1fr;
grid-template-areas:
"avatar name"
"avatar study-class"
"avatar company";
padding: 2 * $spacer $spacer;
}

.avatar {
grid-area: avatar;
margin-right: 1.5 * $spacer;
}

.name {
grid-area: name;
}

.study-class {
grid-area: study-class;
font-size: $font-size-sm;
}

.company {
grid-area: company;
align-content: end;
padding-bottom: 1.5 * $spacer;
}

.name,
.company {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { buildTestModuleMetadata } from "src/spec-helpers";
import { StudentEntry } from "../../services/events-students-state.service";
import { EventsStudentsCourseEntryComponent } from "./events-students-course-entry.component";

describe("EventsStudentsCourseEntryComponent", () => {
let fixture: ComponentFixture<EventsStudentsCourseEntryComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule(
buildTestModuleMetadata({
imports: [EventsStudentsCourseEntryComponent],
}),
).compileComponents();

fixture = TestBed.createComponent(EventsStudentsCourseEntryComponent);
element = fixture.debugElement.nativeElement;

fixture.componentRef.setInput("entry", {
id: 1,
firstName: "Jane",
lastName: "Doe",
email: "jane.doe@example.com",
studyClass: "26a",
company: "Coop Genossenschaft",
} satisfies StudentEntry);
fixture.componentRef.setInput("returnLink", "/events/current");
fixture.detectChanges();
});

it("renders firstname/lastname with link to dossier including returnlink", () => {
const link = element.querySelector<HTMLAnchorElement>("a.name");
expect(link?.textContent).toContain("Jane Doe");
expect(link?.href).toContain(
"student/1/absences?returnparams=returnlink%3D%252Fevents%252Fcurrent",
);
});

describe("study class", () => {
it("does not render if course has only one class", () => {
fixture.componentRef.setInput("multipleStudyClasses", false);
fixture.detectChanges();
expect(element.textContent).not.toContain("26a");
});

it("render if course has only one class", () => {
fixture.componentRef.setInput("multipleStudyClasses", true);
fixture.detectChanges();
expect(element.textContent).toContain("26a");
});
});

it("renders company", () => {
expect(element.textContent).toContain("Coop Genossenschaft");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
} from "@angular/core";
import { Params, RouterLink } from "@angular/router";
import { AvatarComponent } from "src/app/shared/components/avatar/avatar.component";
import { StudentEntry } from "../../services/events-students-state.service";

@Component({
selector: "bkd-events-students-course-entry",
standalone: true,
imports: [RouterLink, AvatarComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "./events-students-course-entry.component.html",
styleUrl: "./events-students-course-entry.component.scss",
})
export class EventsStudentsCourseEntryComponent {
entry = input.required<StudentEntry>();
multipleStudyClasses = input(false);
returnLink = input<Option<string>>(null);

name = computed<string>(
() => `${this.entry().firstName} ${this.entry().lastName}`,
);

link = computed<RouterLink["routerLink"]>(() => [
"student",
this.entry().id,
"absences",
]);
linkParams = computed<Params>(() => {
const returnlink = this.returnLink();
return returnlink
? {
returnparams: new URLSearchParams({ returnlink }).toString(),
}
: {};
});
}
Loading
Loading