-
{{ 'name' | thingTranslate : item }}
+
{{ 'event.name' | thingTranslate : item }}
diff --git a/frontend/app/src/app/modules/profile/page/my-courses.component.ts b/frontend/app/src/app/modules/profile/page/my-courses.component.ts
new file mode 100644
index 00000000..4aec8d72
--- /dev/null
+++ b/frontend/app/src/app/modules/profile/page/my-courses.component.ts
@@ -0,0 +1,70 @@
+import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
+import {mergeMap, ReplaySubject} from 'rxjs';
+import {map} from 'rxjs/operators';
+import {SCDateSeries, SCISO8601Date} from '@openstapps/core';
+import moment from 'moment/moment';
+import {ScheduleProvider} from '../../calendar/schedule.provider';
+
+interface MyCoursesTodayInterface {
+ startTime: string;
+ endTime: string;
+ course: SCDateSeries;
+}
+
+type MyCoursesGroup = [SCISO8601Date, MyCoursesTodayInterface[]][];
+
+/**
+ * Groups date series into a list of events happening in the next days
+ * @param dateSeries the date series to group
+ * @param visibleDays the number of days ahead to group
+ */
+function groupDays(dateSeries: SCDateSeries[], visibleDays: number): MyCoursesGroup {
+ const courses: [SCISO8601Date, MyCoursesTodayInterface[]][] = [];
+ const dates = Array.from({length: visibleDays}, (_, i) => moment().startOf('day').add(i, 'days'));
+
+ for (const day of dates) {
+ const dayCourses: MyCoursesTodayInterface[] = [];
+ for (const course of dateSeries) {
+ for (const date of course.dates) {
+ if (moment(date).isSame(day, 'day')) {
+ dayCourses.push({
+ startTime: moment(date).toISOString(),
+ endTime: moment(date).add(course.duration).toISOString(),
+ course,
+ });
+ }
+ }
+ }
+ courses.push([day.toISOString(), dayCourses]);
+ }
+
+ return courses;
+}
+
+@Component({
+ selector: 'my-courses',
+ templateUrl: 'my-courses.html',
+ styleUrls: ['my-courses.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class MyCoursesComponent {
+ /**
+ * The number of days from today to display
+ */
+ @Input({required: true}) set visibleDays(value: number) {
+ this.visibleDays$.next(value);
+ }
+
+ readonly visibleDays$ = new ReplaySubject();
+
+ myCourses = this.visibleDays$.pipe(
+ mergeMap(visibleDays =>
+ this.scheduleProvider.uuids$.pipe(
+ mergeMap(uuids => this.scheduleProvider.getDateSeries(uuids)),
+ map(dateSeries => groupDays(dateSeries.dates, visibleDays)),
+ ),
+ ),
+ );
+
+ constructor(private scheduleProvider: ScheduleProvider) {}
+}
diff --git a/frontend/app/src/app/modules/profile/page/my-courses.html b/frontend/app/src/app/modules/profile/page/my-courses.html
new file mode 100644
index 00000000..1766b631
--- /dev/null
+++ b/frontend/app/src/app/modules/profile/page/my-courses.html
@@ -0,0 +1,35 @@
+
+
+
+
+ {{ myCoursesDay[0] | amDateFormat: 'dddd, ll' }} - {{ ('profile.courses.' + (myCoursesDay[1].length
+ === 0 ? 'NO' : myCoursesDay[1].length === 1 ? 'ONE' : 'MANY' ) + '_EVENT') | translate: {count:
+ myCoursesDay[1].length} }}
+
+
+
+
+ {{ 'profile.courses.no_courses' | translate }}
+
+
+
+ {{myCourse.startTime | amDateFormat: 'LT'}} - {{myCourse.endTime | amDateFormat:
+ 'LT'}}
+
+
+
+
+
+
diff --git a/frontend/app/src/app/modules/profile/page/my-courses.scss b/frontend/app/src/app/modules/profile/page/my-courses.scss
new file mode 100644
index 00000000..9e8c38f0
--- /dev/null
+++ b/frontend/app/src/app/modules/profile/page/my-courses.scss
@@ -0,0 +1,9 @@
+ion-accordion-group {
+ overflow: hidden;
+ border-radius: var(--border-radius-default);
+}
+
+ion-item-divider {
+ --background: transparent;
+ --color: inherit;
+}
diff --git a/frontend/app/src/app/modules/profile/page/profile-page.component.ts b/frontend/app/src/app/modules/profile/page/profile-page.component.ts
index 5bbe4068..d08a9983 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page.component.ts
+++ b/frontend/app/src/app/modules/profile/page/profile-page.component.ts
@@ -12,41 +12,20 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, OnInit} from '@angular/core';
-import {firstValueFrom, Observable, of, Subscription} from 'rxjs';
+import {Component} from '@angular/core';
import {AuthHelperService} from '../../auth/auth-helper.service';
-import {SCAuthorizationProviderType, SCDateSeries, SCUserConfiguration} from '@openstapps/core';
+import {SCAuthorizationProviderType, SCUserConfiguration} from '@openstapps/core';
import {ActivatedRoute} from '@angular/router';
import {ScheduleProvider} from '../../calendar/schedule.provider';
-import moment from 'moment';
-import {SCIcon} from '../../../util/ion-icon/icon';
import {profilePageSections} from '../../../../config/profile-page-sections';
import {filter, map} from 'rxjs/operators';
-const CourseCard = {
- collapsed: SCIcon`expand_more`,
- expanded: SCIcon`expand_less`,
-};
-
-interface MyCoursesTodayInterface {
- startTime: string;
- endTime: string;
- course: SCDateSeries;
-}
-
@Component({
selector: 'app-home',
templateUrl: 'profile-page.html',
styleUrls: ['profile-page.scss'],
})
-export class ProfilePageComponent implements OnInit {
- data: {
- [key in SCAuthorizationProviderType]: {loggedIn$: Observable};
- } = {
- default: {loggedIn$: of(false)},
- paia: {loggedIn$: of(false)},
- };
-
+export class ProfilePageComponent {
user$ = this.authHelper.getProvider('default').user$.pipe(
filter(user => user !== undefined),
map(userInfo => {
@@ -58,59 +37,18 @@ export class ProfilePageComponent implements OnInit {
logins: SCAuthorizationProviderType[] = [];
- originPath: string | null;
-
userInfo?: SCUserConfiguration;
- courseCardEnum = CourseCard;
-
- courseCardState = CourseCard.expanded;
-
- todayDate = moment().startOf('day').add(0, 'day').format(); // moment().startOf('day').format(); '2022-05-03T00:00:00+02:00'
-
- myCoursesToday: MyCoursesTodayInterface[] = [];
-
- subscriptions: Subscription[] = [];
-
constructor(
- private authHelper: AuthHelperService,
- private route: ActivatedRoute,
- protected readonly scheduleProvider: ScheduleProvider,
+ readonly authHelper: AuthHelperService,
+ readonly activatedRoute: ActivatedRoute,
+ readonly scheduleProvider: ScheduleProvider,
) {}
- ngOnInit() {
- this.data.default.loggedIn$ = this.authHelper.getProvider('default').isAuthenticated$;
- this.data.paia.loggedIn$ = this.authHelper.getProvider('paia').isAuthenticated$;
-
- this.subscriptions.push(
- this.route.queryParamMap.subscribe(queryParameters => {
- this.originPath = queryParameters.get('origin_path');
- }),
- );
-
- this.getMyCourses();
- }
-
- async getMyCourses() {
- const result = await firstValueFrom(this.scheduleProvider.uuids$);
- const courses = await this.scheduleProvider.getDateSeries(result);
-
- for (const course of courses.dates) {
- for (const date of course.dates) {
- if (moment(date).startOf('day').format() === this.todayDate) {
- this.myCoursesToday[this.myCoursesToday.length] = {
- startTime: moment(date).format('LT'),
- endTime: moment(date).add(course.duration).format('LT'),
- course,
- };
- }
- }
- }
- }
-
async signIn(providerType: SCAuthorizationProviderType) {
- await this.handleOriginPath();
- this.authHelper.getProvider(providerType).signIn();
+ const originPath = this.activatedRoute.snapshot.queryParamMap.get('origin_path');
+ await (originPath ? this.authHelper.setOriginPath(originPath) : this.authHelper.deleteOriginPath());
+ await this.authHelper.getProvider(providerType).signIn();
}
async signOut(providerType: SCAuthorizationProviderType) {
@@ -118,25 +56,6 @@ export class ProfilePageComponent implements OnInit {
this.userInfo = undefined;
}
- toggleCourseCardState() {
- if (this.courseCardState === CourseCard.expanded) {
- const card: HTMLElement | null = document.querySelector('.course-card');
- const height = card?.scrollHeight;
- if (card && height) {
- card.style.setProperty('--max-height', height + 'px');
- }
- }
-
- this.courseCardState =
- this.courseCardState === CourseCard.expanded ? CourseCard.collapsed : CourseCard.expanded;
- }
-
- private async handleOriginPath() {
- this.originPath
- ? await this.authHelper.setOriginPath(this.originPath)
- : await this.authHelper.deleteOriginPath();
- }
-
ionViewWillEnter() {
this.authHelper
.getProvider('default')
diff --git a/frontend/app/src/app/modules/profile/page/profile-page.html b/frontend/app/src/app/modules/profile/page/profile-page.html
index d40321c6..2e03e686 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page.html
+++ b/frontend/app/src/app/modules/profile/page/profile-page.html
@@ -23,76 +23,53 @@
-
-
-
-
-
- {{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
-
-
-
-
-
-
-
-
-
- {{ userInfo?.name }}
-
- {{ 'profile.userInfo.studentId' | translate | uppercase }}
- {{ userInfo?.studentId }}
-
-
- {{ 'profile.userInfo.username' | translate | uppercase }}
- {{ userInfo?.id }}
-
-
- {{ 'profile.userInfo.email' | translate | uppercase }}
- {{ userInfo?.email }}
-
-
+
+
+
+
+ {{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
+
+
+
+
+
+
+
+
+
+ {{ userInfo?.name }}
+
+ {{ 'profile.userInfo.studentId' | translate | uppercase }}
+ {{ userInfo?.studentId }}
+
+
+ {{ 'profile.userInfo.username' | translate | uppercase }}
+ {{ userInfo?.id }}
+
+
+ {{ 'profile.userInfo.email' | translate | uppercase }}
+ {{ userInfo?.email }}
+
+
+
+
+
+ {{ 'profile.userInfo.logInPrompt' | translate }}
-
-
- {{ 'profile.userInfo.logInPrompt' | translate }}
-
-
-
-
-
-
-
+
+
+
+
+
-
- {{ 'profile.titleCourses' | translate | uppercase }}
-
-
- {{ 'profile.courses.today' | translate | uppercase }}
-
-
-
-
- {{ 'profile.courses.no_courses' | translate }}
-
-
-
-
{{ myCourse?.startTime }} - {{ myCourse?.endTime }}
-
{{ myCourse?.course.event?.originalCategory }}
-
{{ myCourse.course?.event?.name }}
-
- {{ myCourse.course?.inPlace.name }}
-
-
-
-
-
-
+
+
+
diff --git a/frontend/app/src/app/modules/profile/page/profile-page.scss b/frontend/app/src/app/modules/profile/page/profile-page.scss
index 8a250e45..4945293c 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page.scss
+++ b/frontend/app/src/app/modules/profile/page/profile-page.scss
@@ -12,216 +12,106 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-:host {
- section {
- margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
- padding: var(--spacing-md);
+// TODO: clean up this mess
+.user-card {
+ position: relative;
- &:last-of-type {
- margin-bottom: 0;
- }
- }
+ max-width: 400px;
+ margin: var(--spacing-xl);
- .section-headline {
- margin-bottom: var(--spacing-md);
- }
+ border-radius: var(--border-radius-default);
+ box-shadow: var(--shadow-profile-card);
- .user-card-wrapper {
- margin-bottom: 0;
+ ion-card-header {
+ --background: var(--ion-color-tertiary);
- .user-card {
- position: relative;
-
- max-width: 400px;
- margin: 0;
-
- border-radius: var(--border-radius-default);
- box-shadow: var(--shadow-profile-card);
-
- ion-card-header {
- --background: var(--ion-color-tertiary);
-
- display: flex;
- align-items: center;
- padding-top: var(--spacing-sm);
- padding-bottom: var(--spacing-sm);
-
- ion-img {
- display: block;
- height: 36px;
- margin-right: auto;
- object-position: left 50%;
- }
-
- span {
- padding-top: 3px;
-
- font-size: var(--font-size-lg);
- font-weight: var(--font-weight-bold);
- line-height: 1;
- color: var(--ion-color-light);
- }
- }
-
- ion-card-content {
- min-height: 15vh;
-
- .profile-card-img {
- position: absolute;
-
- width: 50%;
- height: 100%;
- margin-left: calc(var(--spacing-md) * -4);
-
- opacity: 0.13;
- object-position: left bottom;
- }
-
- .main-info {
- display: grid;
- grid-template-areas:
- 'fullName fullName'
- 'matriculationNumber userName'
- 'email email';
-
- ion-label {
- display: block;
- font-size: var(--font-size-sm);
- font-weight: var(--font-weight-bold);
- color: var(--ion-color-medium-shade);
- }
-
- ion-text {
- display: block;
- font-size: var(--font-size-md);
- font-weight: var(--font-weight-bold);
- color: var(--ion-color-text);
- }
-
- .full-name {
- display: block;
- grid-area: fullName;
-
- margin-bottom: var(--spacing-sm);
-
- font-size: var(--font-size-lg);
- font-weight: var(--font-weight-bold);
- }
-
- .matriculation-number {
- grid-area: matriculationNumber;
- margin-bottom: var(--spacing-sm);
- }
-
- .user-name {
- grid-area: userName;
- margin-bottom: var(--spacing-sm);
- }
-
- .email {
- grid-area: email;
- }
- }
-
- .log-in-prompt {
- margin: auto 0;
- font-size: var(--font-size-lg);
- font-weight: var(--font-weight-semi-bold);
- color: var(--ion-color-text);
- }
- }
- }
- }
-
- ion-thumbnail {
+ display: flex;
align-items: center;
+ padding-top: var(--spacing-sm);
+ padding-bottom: var(--spacing-sm);
- width: 80%;
- height: 80%;
- margin: 0;
- padding: 10px;
-
- background: var(--placeholder-gray);
- border-radius: var(--border-radius-default);
-
- ion-icon {
+ ion-img {
display: block;
- width: 100%;
- height: 100%;
- color: white;
+ height: 36px;
+ margin-right: auto;
+ object-position: left 50%;
+ }
+
+ span {
+ padding-top: 3px;
+
+ font-size: var(--font-size-lg);
+ font-weight: var(--font-weight-bold);
+ line-height: 1;
+ color: var(--ion-color-light);
}
}
- ion-row.main-info {
- margin-bottom: 2px;
- font-weight: bold;
- }
+ ion-card-content {
+ min-height: 15vh;
- .courses {
- .courses-card {
- max-width: 800px;
- margin: 0;
+ .profile-card-img {
+ position: absolute;
- background-color: unset;
- border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
- box-shadow: none;
+ width: 50%;
+ height: 100%;
+ margin-left: calc(var(--spacing-md) * -4);
- ion-card-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
+ opacity: 0.13;
+ object-position: left bottom;
+ }
- background-color: var(--ion-item-background);
- border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
+ .main-info {
+ display: grid;
+ grid-template-areas:
+ 'fullName fullName'
+ 'matriculationNumber userName'
+ 'email email';
- span {
- font-size: var(--font-size-lg);
- font-weight: var(--font-weight-bold);
- color: var(--ion-item-background-color-contrast);
- }
-
- ion-icon {
- cursor: pointer;
- color: var(--ion-color-light);
- }
+ ion-label {
+ display: block;
+ font-size: var(--font-size-sm);
+ font-weight: var(--font-weight-bold);
+ color: var(--ion-color-medium-shade);
}
- ion-card-content {
- overflow: hidden;
-
- max-height: 0;
- margin: 0;
- padding: 0;
-
- background-color: var(--ion-item-background);
- border-radius: var(--border-radius-default);
-
- transition: max-height 250ms ease-in-out, padding 250ms ease-in-out, margin 250ms ease-in-out;
-
- &.show-card {
- display: block;
-
- height: 100%;
- max-height: var(--max-height);
- margin: var(--spacing-xxl);
- padding: var(--spacing-md);
- }
-
- div {
- font-size: var(--font-size-md);
- font-weight: var(--font-weight-black);
- color: var(--ion-item-background-color-contrast);
- text-align: center;
-
- &.no-course {
- padding: var(--spacing-xxl) var(--spacing-lg);
- }
-
- &.last {
- margin-bottom: var(--spacing-xl);
- }
- }
+ ion-text {
+ display: block;
+ font-size: var(--font-size-md);
+ font-weight: var(--font-weight-bold);
+ color: var(--ion-color-text);
}
+
+ .full-name {
+ display: block;
+ grid-area: fullName;
+
+ margin-bottom: var(--spacing-sm);
+
+ font-size: var(--font-size-lg);
+ font-weight: var(--font-weight-bold);
+ }
+
+ .matriculation-number {
+ grid-area: matriculationNumber;
+ margin-bottom: var(--spacing-sm);
+ }
+
+ .user-name {
+ grid-area: userName;
+ margin-bottom: var(--spacing-sm);
+ }
+
+ .email {
+ grid-area: email;
+ }
+ }
+
+ .log-in-prompt {
+ margin: auto 0;
+ font-size: var(--font-size-lg);
+ font-weight: var(--font-weight-semi-bold);
+ color: var(--ion-color-text);
}
}
}
diff --git a/frontend/app/src/app/modules/profile/profile.module.ts b/frontend/app/src/app/modules/profile/profile.module.ts
index fc09fc83..36d274af 100644
--- a/frontend/app/src/app/modules/profile/profile.module.ts
+++ b/frontend/app/src/app/modules/profile/profile.module.ts
@@ -12,7 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
@@ -25,6 +24,9 @@ import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {ProfilePageSectionComponent} from './page/profile-page-section.component';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
+import {DataModule} from '../data/data.module';
+import {MyCoursesComponent} from './page/my-courses.component';
+import {MomentModule} from 'ngx-moment';
const routes: Routes = [
{
@@ -34,7 +36,7 @@ const routes: Routes = [
];
@NgModule({
- declarations: [ProfilePageComponent, ProfilePageSectionComponent],
+ declarations: [MyCoursesComponent, ProfilePageComponent, ProfilePageSectionComponent],
imports: [
CommonModule,
FormsModule,
@@ -45,6 +47,8 @@ const routes: Routes = [
SwiperModule,
UtilModule,
ThingTranslateModule,
+ DataModule,
+ MomentModule,
],
})
export class ProfilePageModule {}
diff --git a/frontend/app/src/assets/i18n/de.json b/frontend/app/src/assets/i18n/de.json
index 60831ff6..50bf50f4 100644
--- a/frontend/app/src/assets/i18n/de.json
+++ b/frontend/app/src/assets/i18n/de.json
@@ -501,8 +501,10 @@
"logInPrompt": "Bitte loggen Sie sich ein, um Ihre Nutzerdaten sehen zu können."
},
"courses": {
- "today": "Heute",
- "no_courses": "Heute stehen keine Termine mehr an."
+ "no_courses": "Heute stehen keine Termine mehr an.",
+ "NO_EVENT": "Keine Termine",
+ "ONE_EVENT": "Ein Termin",
+ "MANY_EVENT": "{{count}} Termine"
}
},
"settings": {
diff --git a/frontend/app/src/assets/i18n/en.json b/frontend/app/src/assets/i18n/en.json
index 0d765a26..21b4f93a 100644
--- a/frontend/app/src/assets/i18n/en.json
+++ b/frontend/app/src/assets/i18n/en.json
@@ -501,8 +501,10 @@
"logInPrompt": "Please log in to view your user data."
},
"courses": {
- "today": "Today",
- "no_courses": "There are no more appointments scheduled today."
+ "no_courses": "There are no more appointments scheduled today.",
+ "NO_EVENT": "no events",
+ "ONE_EVENT": "one event",
+ "MANY_EVENT": "{{count}} events"
}
},
"settings": {
diff --git a/frontend/app/src/assets/icons.min.woff2 b/frontend/app/src/assets/icons.min.woff2
index 3b9e8368..3cc1beeb 100644
Binary files a/frontend/app/src/assets/icons.min.woff2 and b/frontend/app/src/assets/icons.min.woff2 differ