diff --git a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.html b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.html index de1ed9749..b766fb144 100644 --- a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.html +++ b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.html @@ -6,10 +6,10 @@ > {{ name() }}{{ entry().name }}
{{ multipleStudyClasses() ? entry().studyClass : "" }} diff --git a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.spec.ts b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.spec.ts index 4f4cd27d0..fcb9c88e8 100644 --- a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.spec.ts +++ b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.spec.ts @@ -19,8 +19,7 @@ describe("EventsStudentsCourseEntryComponent", () => { fixture.componentRef.setInput("entry", { id: 1, - firstName: "Jane", - lastName: "Doe", + name: "Doe Jane", email: "jane.doe@example.com", studyClass: "26a", company: "Coop Genossenschaft", @@ -31,9 +30,9 @@ describe("EventsStudentsCourseEntryComponent", () => { it("renders firstname/lastname with link to dossier including returnlink", () => { const link = element.querySelector("a.name"); - expect(link?.textContent).toContain("Jane Doe"); + expect(link?.textContent).toContain("Doe Jane"); expect(link?.href).toContain( - "student/1/absences?returnparams=returnlink%3D%252Fevents%252Fcurrent", + "student/1/addresses?returnparams=returnlink%3D%252Fevents%252Fcurrent", ); }); diff --git a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.ts b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.ts index fe20835ac..33f1c592d 100644 --- a/src/app/events/components/events-students-course-entry/events-students-course-entry.component.ts +++ b/src/app/events/components/events-students-course-entry/events-students-course-entry.component.ts @@ -21,14 +21,10 @@ export class EventsStudentsCourseEntryComponent { multipleStudyClasses = input(false); returnLink = input>(null); - name = computed( - () => `${this.entry().firstName} ${this.entry().lastName}`, - ); - link = computed(() => [ "student", this.entry().id, - "absences", + "addresses", ]); linkParams = computed(() => { const returnlink = this.returnLink(); diff --git a/src/app/events/components/events-students-course-list/events-students-course-list.component.scss b/src/app/events/components/events-students-course-list/events-students-course-list.component.scss index d8f79109d..96ca632f4 100644 --- a/src/app/events/components/events-students-course-list/events-students-course-list.component.scss +++ b/src/app/events/components/events-students-course-list/events-students-course-list.component.scss @@ -7,7 +7,7 @@ section.list { width: calc(100% + 1px); // Hide right border - margin-top: 2 * $spacer; + margin-top: 1 * $spacer; display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); } diff --git a/src/app/events/components/events-students-header/events-students-header.component.html b/src/app/events/components/events-students-header/events-students-header.component.html index b76d431be..f07a445f5 100644 --- a/src/app/events/components/events-students-header/events-students-header.component.html +++ b/src/app/events/components/events-students-header/events-students-header.component.html @@ -15,7 +15,7 @@

{{ title() }}

diff --git a/src/app/events/components/events-students-header/events-students-header.component.scss b/src/app/events/components/events-students-header/events-students-header.component.scss index 09b29563a..376dc1884 100644 --- a/src/app/events/components/events-students-header/events-students-header.component.scss +++ b/src/app/events/components/events-students-header/events-students-header.component.scss @@ -1,5 +1,9 @@ @import "../../../../bootstrap-variables"; +h1 { + margin-bottom: 0; +} + .search { margin-top: $spacer; display: flex; diff --git a/src/app/events/components/events-students-list/events-students-list.component.spec.ts b/src/app/events/components/events-students-list/events-students-list.component.spec.ts index 127a39e4d..88a24a20b 100644 --- a/src/app/events/components/events-students-list/events-students-list.component.spec.ts +++ b/src/app/events/components/events-students-list/events-students-list.component.spec.ts @@ -38,32 +38,28 @@ describe("EventsStudentsListComponent", () => { const entries: ReadonlyArray = [ { id: 10, - firstName: "Paul", - lastName: "McCartney", + name: "McCartney Paul", email: "paul.mccartney@example.com", studyClass: "26a", company: "Apple Records – Abbey Road Studios", }, { id: 20, - firstName: "John", - lastName: "Lennon", + name: "Lennon John", email: "john.lennon@example.com", studyClass: "26a", company: undefined, }, { id: 30, - firstName: "George", - lastName: "Harrison", + name: "Harrison George", email: "george.harrison@example.com", studyClass: "26c", company: undefined, }, { id: 40, - firstName: "Ringo", - lastName: "Starr", + name: "Starr Ringo", email: "ringo.starr@example.com", studyClass: "26c", company: undefined, @@ -104,10 +100,10 @@ describe("EventsStudentsListComponent", () => { element.querySelector("bkd-events-students-course-list"), ).not.toBeNull(); expect(element.querySelector("h1")?.textContent).toContain("English-S3"); - expect(element.textContent).toContain("Paul McCartney"); - expect(element.textContent).toContain("John Lennon"); - expect(element.textContent).toContain("George Harrison"); - expect(element.textContent).toContain("Ringo Starr"); + expect(element.textContent).toContain("McCartney Paul"); + expect(element.textContent).toContain("Lennon John"); + expect(element.textContent).toContain("Harrison George"); + expect(element.textContent).toContain("Starr Ringo"); }); it("renders placeholder if no entries are available", () => { @@ -129,10 +125,10 @@ describe("EventsStudentsListComponent", () => { element.querySelector("bkd-events-students-study-course-list"), ).not.toBeNull(); expect(element.querySelector("h1")?.textContent).toContain("English-S3"); - expect(element.textContent).toContain("Paul McCartney"); - expect(element.textContent).toContain("John Lennon"); - expect(element.textContent).toContain("George Harrison"); - expect(element.textContent).toContain("Ringo Starr"); + expect(element.textContent).toContain("McCartney Paul"); + expect(element.textContent).toContain("Lennon John"); + expect(element.textContent).toContain("Harrison George"); + expect(element.textContent).toContain("Starr Ringo"); }); it("renders placeholder if no entries are available", () => { diff --git a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.html b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.html index 4ff77ce80..c4ee42b43 100644 --- a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.html +++ b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.html @@ -1,4 +1,4 @@ {{ - name() + entry().name }}
{{ entry().status }}
diff --git a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.spec.ts b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.spec.ts index 50c6e882b..2148c697c 100644 --- a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.spec.ts +++ b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.spec.ts @@ -17,8 +17,7 @@ describe("EventsStudentsStudyCourseEntryComponent", () => { element = fixture.debugElement.nativeElement; fixture.componentRef.setInput("entry", { id: 1, - firstName: "Jane", - lastName: "Doe", + name: "Doe Jane", email: "jane.doe@example.com", studyClasses: ["26a", "26c"], company: "Coop Genossenschaft", @@ -33,7 +32,7 @@ describe("EventsStudentsStudyCourseEntryComponent", () => { it("renders firstname/lastname with link to dossier including returnlink", () => { const link = element.querySelector("a.name"); - expect(link?.textContent).toBe("Jane Doe"); + expect(link?.textContent).toBe("Doe Jane"); expect(link?.href).toContain( "student/1/absences?returnparams=returnlink%3D%252Fevents%252Fcurrent", ); diff --git a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.ts b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.ts index 897b40d00..c0fcedf69 100644 --- a/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.ts +++ b/src/app/events/components/events-students-study-course-entry/events-students-study-course-entry.component.ts @@ -19,9 +19,6 @@ export class EventsStudentsStudyCourseEntryComponent { entry = input.required(); returnLink = input>(null); - name = computed( - () => `${this.entry().firstName} ${this.entry().lastName}`, - ); link = computed(() => [ "student", this.entry().id, diff --git a/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.scss b/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.scss index 269978012..8c9fd0bbe 100644 --- a/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.scss +++ b/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.scss @@ -4,14 +4,9 @@ cursor: pointer; display: flex; align-items: center; - border-top: 1px solid $border-color; border-bottom: 2px solid $border-color; } -section.list { - margin-top: 2 * $spacer; -} - .name { padding: $spacer; } diff --git a/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.ts b/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.ts index f206f60d0..38d5d5d67 100644 --- a/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.ts +++ b/src/app/events/components/events-students-study-course-list/events-students-study-course-list.component.ts @@ -43,6 +43,6 @@ export class EventsStudentsStudyCourseListComponent { getSortDirectionCharacter( sortCriteria: SortCriteria, ): string { - return sortCriteria.ascending ? "↓" : "↑"; + return sortCriteria.ascending ? "↑" : "↓"; } } diff --git a/src/app/events/services/events-students-state.service.spec.ts b/src/app/events/services/events-students-state.service.spec.ts index a11b923eb..73ef30666 100644 --- a/src/app/events/services/events-students-state.service.spec.ts +++ b/src/app/events/services/events-students-state.service.spec.ts @@ -253,32 +253,28 @@ describe("EventsStudentsStateService", () => { const expected = [ { id: 10, - firstName: "Paul", - lastName: "McCartney", + name: "McCartney Paul", email: "paul.mccartney@example.com", studyClass: "26a", company: "Apple Records – Abbey Road Studios", }, { id: 20, - firstName: "John", - lastName: "Lennon", + name: "Lennon John", email: "john.lennon@example.com", studyClass: "26a", company: undefined, }, { id: 30, - firstName: "George", - lastName: "Harrison", + name: "Harrison George", email: "george.harrison@example.com", studyClass: "26c", company: undefined, }, { id: 40, - firstName: "Ringo", - lastName: "Starr", + name: "Starr Ringo", email: "ringo.starr@example.com", studyClass: "26c", company: undefined, @@ -287,10 +283,10 @@ describe("EventsStudentsStateService", () => { expect(service.entries()).toEqual(expected); expect(service.sortedEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); expect(service.filteredEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); }); @@ -298,67 +294,75 @@ describe("EventsStudentsStateService", () => { service.searchTerm.set("pau"); TestBed.flushEffects(); - const entries = service - .sortedEntries() - .map(({ firstName }) => firstName); - expect(entries).toEqual(["George", "John", "Paul", "Ringo"]); + const entries = service.sortedEntries().map(({ name }) => name); + expect(entries).toEqual([ + "Harrison George", + "Lennon John", + "McCartney Paul", + "Starr Ringo", + ]); - const filtered = service - .filteredEntries() - .map(({ firstName }) => firstName); - expect(filtered).toEqual(["Paul"]); + const filtered = service.filteredEntries().map(({ name }) => name); + expect(filtered).toEqual(["McCartney Paul"]); }); it("returns filtered entries for matching lastname", () => { service.searchTerm.set("ar"); TestBed.flushEffects(); - const entries = service - .sortedEntries() - .map(({ firstName }) => firstName); - expect(entries).toEqual(["George", "John", "Paul", "Ringo"]); - - const filtered = service - .filteredEntries() - .map(({ firstName }) => firstName); - expect(filtered).toEqual(["George", "Paul", "Ringo"]); + const entries = service.sortedEntries().map(({ name }) => name); + expect(entries).toEqual([ + "Harrison George", + "Lennon John", + "McCartney Paul", + "Starr Ringo", + ]); + + const filtered = service.filteredEntries().map(({ name }) => name); + expect(filtered).toEqual([ + "Harrison George", + "McCartney Paul", + "Starr Ringo", + ]); }); it("returns filtered entries for matching company", () => { service.searchTerm.set("abbey"); TestBed.flushEffects(); - const entries = service - .sortedEntries() - .map(({ firstName }) => firstName); - expect(entries).toEqual(["George", "John", "Paul", "Ringo"]); + const entries = service.sortedEntries().map(({ name }) => name); + expect(entries).toEqual([ + "Harrison George", + "Lennon John", + "McCartney Paul", + "Starr Ringo", + ]); - const filtered = service - .filteredEntries() - .map(({ firstName }) => firstName); - expect(filtered).toEqual(["Paul"]); + const filtered = service.filteredEntries().map(({ name }) => name); + expect(filtered).toEqual(["McCartney Paul"]); }); it("returns filtered entries for matching class", () => { service.searchTerm.set("26c"); TestBed.flushEffects(); - const entries = service - .sortedEntries() - .map(({ firstName }) => firstName); - expect(entries).toEqual(["George", "John", "Paul", "Ringo"]); + const entries = service.sortedEntries().map(({ name }) => name); + expect(entries).toEqual([ + "Harrison George", + "Lennon John", + "McCartney Paul", + "Starr Ringo", + ]); - const filtered = service - .filteredEntries() - .map(({ firstName }) => firstName); - expect(filtered).toEqual(["George", "Ringo"]); + const filtered = service.filteredEntries().map(({ name }) => name); + expect(filtered).toEqual(["Harrison George", "Starr Ringo"]); }); }); describe("mailtoLink", () => { it("is the mailto: link value with all email addresses comma-separated", () => { expect(service.mailtoLink()).toBe( - "mailto:paul.mccartney@example.com,john.lennon@example.com,george.harrison@example.com,ringo.starr@example.com", + "mailto:paul.mccartney@example.com;john.lennon@example.com;george.harrison@example.com;ringo.starr@example.com", ); }); }); @@ -400,40 +404,40 @@ describe("EventsStudentsStateService", () => { const expected = [ { id: 10, - firstName: "Paul", - lastName: "McCartney", + name: "McCartney Paul", email: "paul.mccartney@example.com", status: "Angemeldet", + company: "Apple Records – Abbey Road Studios", }, { id: 20, - firstName: "John", - lastName: "Lennon", + name: "Lennon John", email: "john.lennon@example.com", status: "Aufgenommen", + company: undefined, }, { id: 30, - firstName: "George", - lastName: "Harrison", + name: "Harrison George", email: "george.harrison@example.com", status: "Aufgenommen", + company: undefined, }, { id: 40, - firstName: "Ringo", - lastName: "Starr", + name: "Starr Ringo", email: "ringo.starr@example.com", status: "Aufgenommen", + company: undefined, }, ]; expect(service.entries()).toEqual(expected); expect(service.sortedEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); expect(service.filteredEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); }); }); @@ -441,7 +445,7 @@ describe("EventsStudentsStateService", () => { describe("mailtoLink", () => { it("is the mailto: link value with all email addresses comma-separated", () => { expect(service.mailtoLink()).toBe( - "mailto:paul.mccartney@example.com,john.lennon@example.com,george.harrison@example.com,ringo.starr@example.com", + "mailto:paul.mccartney@example.com;john.lennon@example.com;george.harrison@example.com;ringo.starr@example.com", ); }); }); @@ -483,29 +487,25 @@ describe("EventsStudentsStateService", () => { const expected = [ { id: 10, - firstName: "Paul", - lastName: "McCartney", + name: "McCartney Paul", email: "paul.mccartney@example.com", status: "Angemeldet", }, { id: 20, - firstName: "John", - lastName: "Lennon", + name: "Lennon John", email: "john.lennon@example.com", status: "Aufgenommen", }, { id: 30, - firstName: "George", - lastName: "Harrison", + name: "Harrison George", email: "george.harrison@example.com", status: "Aufgenommen", }, { id: 40, - firstName: "Ringo", - lastName: "Starr", + name: "Starr Ringo", email: "ringo.starr@example.com", status: "Aufgenommen", }, @@ -513,24 +513,30 @@ describe("EventsStudentsStateService", () => { expect(service.entries()).toEqual(expected); expect(service.sortedEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); expect(service.filteredEntries()).toEqual( - expected.sort((a, b) => a.firstName.localeCompare(b.firstName)), + expected.sort((a, b) => a.name.localeCompare(b.name)), ); }); it("changes the sort direction", () => { - expect( - service.sortedEntries().map(({ firstName }) => firstName), - ).toEqual(["George", "John", "Paul", "Ringo"]); + expect(service.sortedEntries().map(({ name }) => name)).toEqual([ + "Harrison George", + "Lennon John", + "McCartney Paul", + "Starr Ringo", + ]); service.toggleSort(); TestBed.flushEffects(); - expect( - service.sortedEntries().map(({ firstName }) => firstName), - ).toEqual(["Ringo", "Paul", "John", "George"]); + expect(service.sortedEntries().map(({ name }) => name)).toEqual([ + "Starr Ringo", + "McCartney Paul", + "Lennon John", + "Harrison George", + ]); }); }); diff --git a/src/app/events/services/events-students-state.service.ts b/src/app/events/services/events-students-state.service.ts index 0c9ca7035..a1c22eb06 100644 --- a/src/app/events/services/events-students-state.service.ts +++ b/src/app/events/services/events-students-state.service.ts @@ -38,8 +38,7 @@ export type StudentEntries = { export type StudentEntry = { id: number; - firstName: string; - lastName: string; + name: string; email?: string; status?: string; studyClass?: string; @@ -82,7 +81,7 @@ export class EventsStudentsStateService { filteredEntries = computed(() => searchEntries( this.sortedEntries(), - ["firstName", "lastName", "status", "company", "studyClass"], + ["name", "status", "company", "studyClass"], this.searchTerm(), ), ); @@ -145,21 +144,8 @@ export class EventsStudentsStateService { } private loadStudyCourseStudents(eventId: number): Observable { - return this.loadSubscriptionStudents(eventId); - } - - private loadStudyClassStudents(eventId: number): Observable { - return this.loadSubscriptionStudents(eventId, { - "filter.IsOkay": "=1", - }); - } - - private loadSubscriptionStudents( - eventId: number, - additionalParams?: Dict, - ): Observable { return this.subscriptionsService - .getSubscriptionsByCourse(eventId, additionalParams) + .getSubscriptionsByCourse(eventId) .pipe( switchMap((subscriptions) => this.personsService @@ -179,6 +165,35 @@ export class EventsStudentsStateService { ); } + private loadStudyClassStudents(eventId: number): Observable { + return this.subscriptionsService + .getSubscriptionsByCourse(eventId, { + "filter.IsOkay": "=1", + }) + .pipe( + switchMap((subscriptions) => { + const studentIds = subscriptions + .map(({ PersonId }) => PersonId) + .filter(notNull); + return combineLatest([ + this.personsService.getSummaries(studentIds), + this.apprenticeshipContractsService.getCompaniesForStudents( + studentIds, + ), + ]).pipe( + map(([persons, contracts]) => { + const students = convertPersonsToStudentEntries( + eventId, + persons, + subscriptions, + ); + return decorateCourseStudentsWithCompanies(students, contracts); + }), + ); + }), + ); + } + private loadCourseStudents(eventId: number): Observable { return this.coursesService.getExpandedCourseWithParticipants(eventId).pipe( map(convertCourseToStudentEntries), @@ -219,7 +234,7 @@ export class EventsStudentsStateService { } const emails = entries.map((entry) => entry.email).filter(Boolean); - return emails.length > 0 ? `mailto:${emails.join(",")}` : null; + return emails.length > 0 ? `mailto:${emails.join(";")}` : null; } private loadReports( @@ -249,10 +264,8 @@ function getStudentEntryComparator( sortCriteria: SortCriteria, ): (a: StudentEntry, b: StudentEntry) => number { return (a, b) => { - const fullNameA = `${a.firstName} ${a.lastName}`; - const fullNameB = `${b.firstName} ${b.lastName}`; return sortCriteria.ascending - ? fullNameA.localeCompare(fullNameB) - : fullNameB.localeCompare(fullNameA); + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name); }; } diff --git a/src/app/events/utils/events-students.ts b/src/app/events/utils/events-students.ts index 857cc9936..271e00c78 100644 --- a/src/app/events/utils/events-students.ts +++ b/src/app/events/utils/events-students.ts @@ -24,8 +24,7 @@ export function convertCourseToStudentEntries(course: Course): StudentEntries { (student) => ({ id: student.Id, - firstName: student.FirstName, - lastName: student.LastName, + name: `${student.LastName} ${student.FirstName}`, email: student.DisplayEmail ?? undefined, }) satisfies StudentEntry, ) ?? []; @@ -71,8 +70,7 @@ export function convertPersonsToStudentEntries( (person) => ({ id: person.Id, - firstName: person.FirstName, - lastName: person.LastName, + name: `${person.LastName} ${person.FirstName}`, email: person.DisplayEmail ?? undefined, status: subscriptions.find((s) => s.PersonId === person.Id)?.Status, }) satisfies StudentEntry, diff --git a/src/app/presence-control/components/presence-control-list/presence-control-list.component.scss b/src/app/presence-control/components/presence-control-list/presence-control-list.component.scss index f526b99c0..34a9a1b8b 100644 --- a/src/app/presence-control/components/presence-control-list/presence-control-list.component.scss +++ b/src/app/presence-control/components/presence-control-list/presence-control-list.component.scss @@ -10,18 +10,14 @@ bkd-presence-control-entry { * Grid view mode (default layout is list view mode) */ .entries.view-mode-grid { - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: repeat( + auto-fill, + minmax($bkd-presence-control-entry-min-width, 1fr) + ); > * { - width: 100%; - - @for $i from 1 through 6 { - @media (min-width: $bkd-presence-control-entry-min-width * $i) { - width: percentage(math.div(1, $i)); - border-right: 1px solid $border-color; - } - } + border-right: 1px solid $border-color; } } diff --git a/src/assets/locales/de-CH.json b/src/assets/locales/de-CH.json index cd491f109..cea453154 100644 --- a/src/assets/locales/de-CH.json +++ b/src/assets/locales/de-CH.json @@ -258,7 +258,7 @@ "registration": "{{count}} Anmeldung", "registrations": "{{count}} Anmeldungen", "search": "Suchen", - "search-by-name": "Name suchen...", + "search-placeholder": "Suchen...", "list": { "header": { "name": "Name" diff --git a/src/assets/locales/fr-CH.json b/src/assets/locales/fr-CH.json index 5c2b07e88..0878cdade 100644 --- a/src/assets/locales/fr-CH.json +++ b/src/assets/locales/fr-CH.json @@ -258,7 +258,7 @@ "registration": "{{count}} inscription", "registrations": "{{count}} inscriptions", "search": "Rechercher", - "search-by-name": "Rechercher un nom...", + "search-placeholder": "Rechercher...", "list": { "header": { "name": "Nom"