From 2ea66184902ca59270d83e459967a8c07caded82 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:03:26 +0300 Subject: [PATCH 001/113] all: smoother november challenge (fixes #7741)(fixes #7748) (#7751) Co-authored-by: Axel Lo <54468493+RheuX@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +- src/app/community/community.component.ts | 60 ++++++-- .../courses-step-view.component.ts | 8 + src/app/dashboard/dashboard.component.html | 3 - src/app/dashboard/dashboard.component.ts | 8 - src/app/exams/exams-view.component.ts | 21 ++- src/app/home/home.component.html | 4 +- src/app/home/home.component.ts | 15 +- .../notifications.component.html | 6 +- .../notifications/notifications.component.ts | 17 ++- src/app/shared/database/pouch-auth.service.ts | 11 +- .../dialogs-announcement.component.html | 92 ++++++----- .../dialogs-announcement.component.scss | 32 +++- .../dialogs/dialogs-announcement.component.ts | 143 ++++++++++++------ .../dialogs/dialogs-chat-share.component.ts | 16 +- .../shared/dialogs/planet-dialogs.module.ts | 8 +- .../shared/user-challenge-status.service.ts | 41 +++++ 17 files changed, 353 insertions(+), 138 deletions(-) create mode 100644 src/app/shared/user-challenge-status.service.ts diff --git a/package.json b/package.json index 091f07f3e8..d4f944db42 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.50", + "version": "0.15.51", "myplanet": { - "latest": "v0.20.97", - "min": "v0.19.97" + "latest": "v0.20.99", + "min": "v0.19.99" }, "scripts": { "ng": "ng", diff --git a/src/app/community/community.component.ts b/src/app/community/community.component.ts index 836222c18a..82d186ac52 100644 --- a/src/app/community/community.component.ts +++ b/src/app/community/community.component.ts @@ -8,7 +8,6 @@ import { DialogsLoadingService } from '../shared/dialogs/dialogs-loading.service import { MatDialog } from '@angular/material/dialog'; import { CommunityLinkDialogComponent } from './community-link-dialog.component'; import { TeamsService } from '../teams/teams.service'; -import { DialogsAnnouncementComponent } from '../shared/dialogs/dialogs-announcement.component'; import { DialogsPromptComponent } from '../shared/dialogs/dialogs-prompt.component'; import { CouchService } from '../shared/couchdb.service'; import { PlanetMessageService } from '../shared/planet-message.service'; @@ -20,6 +19,13 @@ import { CustomValidators } from '../validators/custom-validators'; import { environment } from '../../environments/environment'; import { planetAndParentId } from '../manager-dashboard/reports/reports.utils'; import { DeviceInfoService, DeviceType } from '../shared/device-info.service'; +import { + DialogsAnnouncementComponent, + DialogsAnnouncementSuccessComponent, + includedCodes, + challengePeriod +} from '../shared/dialogs/dialogs-announcement.component'; +import { UserChallengeStatusService } from '../shared/user-challenge-status.service'; @Component({ selector: 'planet-community', @@ -49,7 +55,6 @@ export class CommunityComponent implements OnInit, OnDestroy { resizeCalendar: any = false; deviceType: DeviceType; deviceTypes = DeviceType; - challengeActive: boolean; isLoading: boolean; constructor( @@ -64,7 +69,8 @@ export class CommunityComponent implements OnInit, OnDestroy { private planetMessageService: PlanetMessageService, private userService: UserService, private usersService: UsersService, - private deviceInfoService: DeviceInfoService + private userStatusService: UserChallengeStatusService, + private deviceInfoService: DeviceInfoService, ) { this.deviceType = this.deviceInfoService.getDeviceType(); } @@ -101,22 +107,31 @@ export class CommunityComponent implements OnInit, OnDestroy { } communityChallenge() { - const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'embakasi', 'uriur' ]; - this.challengeActive = includedCodes.includes(this.configuration.code) && - ((new Date() > new Date(2024, 9, 31)) && (new Date() < new Date(2024, 11, 1))); - const popupShown = localStorage.getItem('announcementPopupShown'); + const challengeActive = includedCodes.includes(this.configuration.code) && challengePeriod; - if (this.challengeActive && !popupShown) { - this.openAnnouncementDialog(); - localStorage.setItem('announcementPopupShown', 'true'); + if (challengeActive) { + const dialogRef = this.dialog.open(DialogsAnnouncementComponent, { + width: '50vw', + maxHeight: '100vh' + }); + dialogRef.afterClosed().subscribe(() => { + if (!this.userStatusService.getCompleteChallenge()) { + this.sendChallengeNotification(this.user).subscribe(); + } + }); } } - openAnnouncementDialog() { - this.dialog.open(DialogsAnnouncementComponent, { - width: '50vw', - maxHeight: '100vh' - }); + sendChallengeNotification(user) { + const data = { + 'user': user._id, + 'message': `El reto está en`, + 'type': 'challenges', + 'priority': 1, + 'status': 'unread', + 'time': this.couchService.datePlaceholder + }; + return this.couchService.updateDocument('notifications', data); } getCommunityData() { @@ -193,7 +208,20 @@ export class CommunityComponent implements OnInit, OnDestroy { return this.couchService.updateDocument('notifications/_bulk_docs', { docs }); }), finalize(() => this.dialogsLoadingService.stop()) - ).subscribe(() => this.dialogsFormService.closeDialogsForm()); + ).subscribe(() => { + this.dialogsFormService.closeDialogsForm(); + if ( + this.userStatusService.getStatus('joinedCourse') && + this.userStatusService.getStatus('surveyComplete') && + !this.userStatusService.getStatus('hasPost') + ) { + this.dialog.open(DialogsAnnouncementSuccessComponent, { + width: '50vw', + maxHeight: '100vh' + }); + this.userStatusService.updateStatus('hasPost', true); + } + }); } sendNotifications(user, currentUser) { diff --git a/src/app/courses/step-view-courses/courses-step-view.component.ts b/src/app/courses/step-view-courses/courses-step-view.component.ts index 7ea1ff1026..eb4b692921 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.ts +++ b/src/app/courses/step-view-courses/courses-step-view.component.ts @@ -11,6 +11,7 @@ import { ResourcesService } from '../../resources/resources.service'; import { DialogsSubmissionsComponent } from '../../shared/dialogs/dialogs-submissions.component'; import { StateService } from '../../shared/state.service'; import { ChatService } from '../../shared/chat.service'; +import { DialogsAnnouncementComponent, includedCodes, challengeCourseId, challengePeriod } from '../../shared/dialogs/dialogs-announcement.component'; @Component({ templateUrl: './courses-step-view.component.html', @@ -141,6 +142,13 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { backToCourseDetail() { this.router.navigate([ '../../' ], { relativeTo: this.route }); + // Challenge option only + if (includedCodes.includes(this.stateService.configuration.code) && challengePeriod && this.courseId === challengeCourseId) { + this.dialog.open(DialogsAnnouncementComponent, { + width: '50vw', + maxHeight: '100vh' + }); + } } setResourceUrl(resourceUrl: string) { diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index b385727c58..292b296d2b 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -21,9 +21,6 @@ <h1 class="mat-title"> </mat-icon> </span> </div> - <div class="cursor-pointer" (click)="openChallengeView()" matTooltip="challenge"> - <mat-icon>stars</mat-icon> - </div> </div> </mat-card> <planet-dashboard-tile diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index 7f19748be9..e6ccedd30d 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -15,7 +15,6 @@ import { CoursesService } from '../courses/courses.service'; import { CoursesViewDetailDialogComponent } from '../courses/view-courses/courses-view-detail.component'; import { foundations, foundationIcons } from '../courses/constants'; import { CertificationsService } from '../manager-dashboard/certifications/certifications.service'; -import { DialogsAnnouncementComponent } from '../shared/dialogs/dialogs-announcement.component'; @Component({ templateUrl: './dashboard.component.html', @@ -212,11 +211,4 @@ export class DashboardComponent implements OnInit, OnDestroy { this.badgeGroups = [ ...foundations, 'none' ].filter(group => this.badgesCourses[group] && this.badgesCourses[group].length); } - openChallengeView() { - this.dialog.open(DialogsAnnouncementComponent, { - width: '50vw', - maxHeight: '100vh' - }); - } - } diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 54e3a67e75..244c0df035 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -10,6 +10,9 @@ import { FormControl, AbstractControl } from '@angular/forms'; import { CustomValidators } from '../validators/custom-validators'; import { Exam, ExamQuestion } from './exams.model'; import { PlanetMessageService } from '../shared/planet-message.service'; +import { DialogsAnnouncementComponent, includedCodes, challengeCourseId, challengePeriod } from '../shared/dialogs/dialogs-announcement.component'; +import { StateService } from '../shared/state.service'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'planet-exams-view', @@ -45,6 +48,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { unansweredQuestions: number[]; isComplete = false; comment: string; + courseId: string; constructor( private router: Router, @@ -53,7 +57,9 @@ export class ExamsViewComponent implements OnInit, OnDestroy { private submissionsService: SubmissionsService, private userService: UserService, private couchService: CouchService, - private planetMessageService: PlanetMessageService + private planetMessageService: PlanetMessageService, + private dialog: MatDialog, + private stateService: StateService, ) { } ngOnInit() { @@ -77,6 +83,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { return; } this.setExam(params); + this.courseId = params.get('id'); }); } @@ -133,6 +140,18 @@ export class ExamsViewComponent implements OnInit, OnDestroy { this.spinnerOn = false; } else { this.routeToNext(nextClicked ? this.questionNum : nextQuestion, previousStatus); + // Challenge option only + if ( + isFinish && + includedCodes.includes(this.stateService.configuration.code) && + challengePeriod && + this.courseId === challengeCourseId + ) { + this.dialog.open(DialogsAnnouncementComponent, { + width: '50vw', + maxHeight: '100vh' + }); + } } }); } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 5cdc488552..8e1f338f34 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -72,12 +72,12 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> <mat-menu #notificationMenu="matMenu" [overlapTrigger]="false" class="notification-menu"> <div class="notification-items"> <a mat-menu-item (click)="readAllNotification()" i18n>Mark all as Read</a> - <a [routerLink]="!notification.link ? '/notifications' : notification.link !== '/' ? [ notification.link, notification.linkParams || {} ] : '/'" + <a [routerLink]="notification.type !== 'challenges' ? (!notification.link ? '/notifications' : notification.link !== '/' ? [notification.link, notification.linkParams || {}] : '/') : null" class="menu-item-notification" [ngClass]="{'primary-text-color':notification.status==='unread'}" mat-menu-item *ngFor="let notification of notifications" - (click)="readNotification(notification)"> + (click)="notification.type === 'challenges' ? openAnnouncementDialog(notification) : readNotification(notification)"> <p [innerHTML]="notification.message"><p> <p *ngIf="notification.time > 0" class="small">{{notification.time | date: 'mediumDate'}}</p> <mat-divider></mat-divider> diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 0930ac29e9..967025d4d8 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -12,6 +12,8 @@ import { PouchAuthService } from '../shared/database/pouch-auth.service'; import { StateService } from '../shared/state.service'; import { DeviceInfoService } from '../shared/device-info.service'; import { NotificationsService } from '../notifications/notifications.service'; +import { DialogsAnnouncementComponent, includedCodes, challengePeriod } from '../shared/dialogs/dialogs-announcement.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ templateUrl: './home.component.html', @@ -60,13 +62,14 @@ export class HomeComponent implements OnInit, DoCheck, AfterViewChecked, OnDestr private onDestroy$ = new Subject<void>(); constructor( + private dialog: MatDialog, private couchService: CouchService, private router: Router, private userService: UserService, private pouchAuthService: PouchAuthService, private stateService: StateService, private deviceInfoService: DeviceInfoService, - private notificationsService: NotificationsService + private notificationsService: NotificationsService, ) { this.userService.userChange$.pipe(takeUntil(this.onDestroy$)) .subscribe(() => { @@ -229,4 +232,14 @@ export class HomeComponent implements OnInit, DoCheck, AfterViewChecked, OnDestr } } + openAnnouncementDialog(notification) { + this.readNotification(notification); + const challengeActive = includedCodes.includes(this.configuration.code) && challengePeriod; + if (challengeActive) { + this.dialog.open(DialogsAnnouncementComponent, { + width: '50vw', + maxHeight: '100vh' + }); + } + } } diff --git a/src/app/notifications/notifications.component.html b/src/app/notifications/notifications.component.html index 8e7cb1b3f5..14635947b7 100644 --- a/src/app/notifications/notifications.component.html +++ b/src/app/notifications/notifications.component.html @@ -23,8 +23,10 @@ </a> </ng-container> <ng-template #noLink> - <span [innerHTML]="element.message"></span> - <span *ngIf="element.time > 0" class="mat-caption margin-lr-8">{{element.time | date: 'medium'}}</span> + <a (click)="element.type === 'challenges' && openAnnouncementDialog()"> + <span [innerHTML]="element.message"></span> + <span *ngIf="element.time > 0" class="mat-caption margin-lr-8">{{element.time | date: 'medium'}}</span> + </a> </ng-template> </p> </mat-cell> diff --git a/src/app/notifications/notifications.component.ts b/src/app/notifications/notifications.component.ts index 41420256fd..0dec83f4c4 100644 --- a/src/app/notifications/notifications.component.ts +++ b/src/app/notifications/notifications.component.ts @@ -8,7 +8,10 @@ import { Subject } from 'rxjs'; import { MatPaginator } from '@angular/material/paginator'; import { MatTableDataSource } from '@angular/material/table'; +import { MatDialog } from '@angular/material/dialog'; import { NotificationsService } from './notifications.service'; +import { DialogsAnnouncementComponent, includedCodes, challengePeriod } from '../shared/dialogs/dialogs-announcement.component'; +import { StateService } from '../shared/state.service'; @Component({ templateUrl: './notifications.component.html', @@ -25,9 +28,11 @@ export class NotificationsComponent implements OnInit, AfterViewInit { anyUnread = true; constructor( + private dialog: MatDialog, + private stateService: StateService, + private notificationsService: NotificationsService, private couchService: CouchService, private userService: UserService, - private notificationsService: NotificationsService ) { this.userService.notificationStateChange$.pipe(takeUntil(this.onDestroy$)).subscribe(() => { this.getNotifications(); @@ -88,4 +93,14 @@ export class NotificationsComponent implements OnInit, AfterViewInit { readAllNotification() { this.notificationsService.setNotificationsAsRead(this.notifications.data); } + + openAnnouncementDialog() { + const challengeActive = includedCodes.includes(this.stateService.configuration.code) && challengePeriod; + if (challengeActive) { + this.dialog.open(DialogsAnnouncementComponent, { + width: '50vw', + maxHeight: '100vh' + }); + } + } } diff --git a/src/app/shared/database/pouch-auth.service.ts b/src/app/shared/database/pouch-auth.service.ts index 363357ca5e..f1f5dddd96 100644 --- a/src/app/shared/database/pouch-auth.service.ts +++ b/src/app/shared/database/pouch-auth.service.ts @@ -1,8 +1,9 @@ import { Injectable } from '@angular/core'; import { from, throwError, Observable, forkJoin } from 'rxjs'; -import { catchError, switchMap, tap } from 'rxjs/operators'; +import { catchError, switchMap } from 'rxjs/operators'; import { PouchService } from './pouch.service'; import { CouchService } from '../couchdb.service'; +import { UserChallengeStatusService } from '../user-challenge-status.service'; interface SessionInfo { userCtx: { @@ -18,7 +19,8 @@ export class PouchAuthService { constructor( private pouchService: PouchService, - private couchService: CouchService + private couchService: CouchService, + private userStatusService: UserChallengeStatusService ) { this.authDB = this.pouchService.getAuthDB(); } @@ -32,10 +34,6 @@ export class PouchAuthService { login(username, password) { this.pouchService.configureDBs(); return from(this.authDB.logIn(username, password)).pipe( - tap(() => { - // Reset the popup flag on successful login - localStorage.removeItem('announcementPopupShown'); - }), catchError(this.handleError) ); } @@ -48,6 +46,7 @@ export class PouchAuthService { } logout() { + this.userStatusService.resetStatus(); return from(this.authDB.logOut()).pipe( switchMap(() => forkJoin(this.pouchService.deconfigureDBs())), catchError(this.handleError) diff --git a/src/app/shared/dialogs/dialogs-announcement.component.html b/src/app/shared/dialogs/dialogs-announcement.component.html index 7cb42a2d72..4998d47191 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.html +++ b/src/app/shared/dialogs/dialogs-announcement.component.html @@ -5,48 +5,64 @@ class="announcement-banner" /> - <div class="steps-container"> - <div class="step"> - <mat-icon color="primary"> - {{ userStatus.joinedCourse ? 'check_circle' : 'radio_button_unchecked' }} - </mat-icon> - <span>Únete al curso <a href="/courses/view/{{ courseId }}">Reto de noviembre</a>.</span> - <a *ngIf="!userStatus.joinedCourse" mat-button mat-raised-button color="primary" type="button" (click)="joinCourse()"> - Únete - </a> - </div> - - <div class="step"> - <mat-icon color="primary"> - {{ userStatus.surveyComplete ? 'check_circle' : 'radio_button_unchecked' }} - </mat-icon> - <span>¡Encuesta finalizada!</span> - <a *ngIf="userStatus.joinedCourse && !userStatus.surveyComplete" mat-button mat-raised-button color="primary" type="button" (click)="doSurvey()"> - Encuesta - </a> + <div class="thermometer-container"> + <div class="thermometer"> + <div class="thermometer-bar" [style.width.%]="getGoalPercentage()"> + <div + class="thermometer-label" + [ngClass]="{ outside: getGoalPercentage() < 8 }" + > + {{ "$" + getTotalMoneyEarned() }} + </div> + </div> </div> + </div> + <hr> - <div class="step"> - <mat-icon color="primary"> - {{ userStatus.hasPost ? 'check_circle' : 'radio_button_unchecked' }} - </mat-icon> - <span>Comparte tu opinión en Nuestras Voces.</span> - <a *ngIf="userStatus.joinedCourse && userStatus.surveyComplete && !userStatus.hasPost" mat-button mat-raised-button color="primary" type="button" (click)="postVoice()"> - Voces - </a> - </div> + <div *ngIf="isLoading; else content"> + <mat-spinner></mat-spinner> </div> - <div class="thermometer-container"> - <div class="thermometer"> - <div class="thermometer-bar" [style.width.%]="getGoalPercentage()"> - <div class="thermometer-label" [ngClass]="{'outside': getGoalPercentage() < 8}"> - {{ getGoalPercentage() | number:'1.0-2' }}% ({{ '$'+ groupSummary?.length }}) + <ng-template #content> + <div class="steps-container"> + <div class="step"> + <mat-icon color="primary"> + {{ getStatus('joinedCourse') ? 'check_circle' : 'radio_button_unchecked' }} + </mat-icon> + <span>Únete al curso Reto noviembre.</span> + <a *ngIf="!getStatus('joinedCourse')" mat-button mat-raised-button color="primary" type="button" (click)="joinCourse()"> + Unirse + </a> + </div> + + <div class="step"> + <mat-icon color="primary"> + {{ getStatus('surveyComplete') ? 'check_circle' : 'radio_button_unchecked' }} + </mat-icon> + <span>¡Encuesta finalizada!</span> + <a *ngIf="getStatus('joinedCourse') && !getStatus('surveyComplete')" mat-button mat-raised-button color="primary" type="button" (click)="doSurvey()"> + Encuesta + </a> + </div> + + <div class="step"> + <mat-icon color="primary"> + {{ getStatus('hasPost') ? 'check_circle' : 'radio_button_unchecked' }} + </mat-icon> + <span>Comparte tu opinión en Nuestras Voces. <br><span>{{getStatus('amountEarned')}} de 5 Voces diarias</span> </span> + <a *ngIf="getStatus('joinedCourse') && getStatus('surveyComplete') && !getStatus('hasPost'); else dailyVoices" mat-button mat-raised-button color="primary" type="button" (click)="chatNShare()"> + Chatea y comparte + </a> + <ng-template #dailyVoices> + <div class="daily-voces-dots"> + <div + *ngFor="let dot of [0, 1, 2, 3, 4]; let i = index" + class="dot" + [ngClass]="{ completed: i < getStatus('amountEarned') }" + ></div> + </div> + </ng-template> </div> </div> - </div> - <div class="thermometer-goal"> - <span>$0</span> - <span>$500</span> - </div> + </ng-template> </div> diff --git a/src/app/shared/dialogs/dialogs-announcement.component.scss b/src/app/shared/dialogs/dialogs-announcement.component.scss index aec859b3d2..f3d53594ca 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.scss +++ b/src/app/shared/dialogs/dialogs-announcement.component.scss @@ -42,9 +42,9 @@ .thermometer { width: 100%; - height: 30px; + height: 40px; background-color: #e0e0e0; - border-radius: 15px; + border-radius: 20px; overflow: hidden; position: relative; margin-bottom: 10px; @@ -60,8 +60,8 @@ } .thermometer-label { - color: white; - font-size: 12px; + color: black; + font-size: 1.5rem; padding: 2px; border-radius: 3px; } @@ -71,9 +71,25 @@ left: 0.5rem; } -.thermometer-goal { +.success-msg { + font-size: 1.5rem; + font-weight: bold; + margin-top: 20px; +} + +.daily-voces-dots { display: flex; - justify-content: space-between; - width: 100%; - font-size: 14px; + gap: 8px; + margin-top: 8px; +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + background-color: lightgray; +} + +.dot.completed { + background-color: $primary; } diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts index f1b141548c..9f45a636d3 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.ts +++ b/src/app/shared/dialogs/dialogs-announcement.component.ts @@ -11,8 +11,28 @@ import { NewsService } from '../../news/news.service'; import { StateService } from '../state.service'; import { SubmissionsService } from '../../submissions/submissions.service'; import { UserService } from '../user.service'; +import { UserChallengeStatusService } from '../user-challenge-status.service'; import { planetAndParentId } from '../../manager-dashboard/reports/reports.utils'; +export const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'ollonde', 'okuro', 'uriur', 'mutugi', 'vi' ]; +export const challengeCourseId = '9517e3b45a5bb63e69bb8f269216974d'; +export const challengePeriod = (new Date() > new Date(2024, 9, 31)) && (new Date() < new Date(2024, 11, 1)); + +@Component({ + template: ` + <div class="announcement-container"> + <img + src="https://res.cloudinary.com/mutugiii/image/upload/v1730395098/challenge_horizontal_new_tnco4v.jpg" + alt="Issues Challenge" + class="announcement-banner" + /> + <p class="success-msg">¡Felicidades reto completado!</p> + </div> + `, + styleUrls: [ './dialogs-announcement.component.scss' ] +}) +export class DialogsAnnouncementSuccessComponent { } + @Component({ templateUrl: './dialogs-announcement.component.html', styleUrls: [ './dialogs-announcement.component.scss' ] @@ -23,17 +43,13 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { currentUserName = this.userService.get().name; configuration = this.stateService.configuration; teamId = planetAndParentId(this.stateService.configuration); - submissionsSet = new Set(); + submissions = []; groupSummary = []; enrolledMembers: any; - courseId = '9517e3b45a5bb63e69bb8f269216974d'; + courseId = challengeCourseId; startDate = new Date(2024, 9, 31); endDate = new Date(2024, 11, 1); - userStatus = { - joinedCourse: false, - surveyComplete: false, - hasPost: false, - }; + isLoading = true; constructor( public dialogRef: MatDialogRef<DialogsAnnouncementComponent>, @@ -43,15 +59,16 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { private newsService: NewsService, private stateService: StateService, private submissionsService: SubmissionsService, - private userService: UserService + private userService: UserService, + private userStatusService: UserChallengeStatusService ) {} ngOnInit() { - const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'embakasi', 'uriur' ]; - if (includedCodes.includes(this.configuration.code)) { this.configuration = this.stateService.configuration; this.initializeData(); + } else { + this.isLoading = false; } } @@ -82,21 +99,52 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { joinCourse() { const courseTitle = this.coursesService.getCourseNameFromId(this.courseId); this.coursesService.courseResignAdmission(this.courseId, 'admission', courseTitle).subscribe((res) => { - this.router.navigate([ '/courses/view', this.courseId ]); + this.router.navigate([ `/courses/view/${this.courseId}/step/1` ]); }, (error) => ((error))); this.dialogRef.close(); } doSurvey() { - this.router.navigate([ `/courses/view/${this.courseId}/step/3` ]); + this.router.navigate([ `/courses/view/${this.courseId}/step/3/exam`, { + id: this.courseId, + stepNum: 3, + questionNum: 1, + type: 'survey', + preview: 'false', + examId: '83fe016d8a983de6f7112e761c014545' + } ]); this.dialogRef.close(); } - postVoice() { - this.router.navigate([ '/' ]); + chatNShare() { + this.router.navigate([ '/chat' ]); this.dialogRef.close(); } + hasCompletedSurvey(userName: string) { + return this.submissions.some(submission => submission.name === userName && submission.status === 'complete'); + } + + hasSubmittedVoice(news: any[], userName: string) { + const uniqueDays = new Set<string>(); + + news.forEach(post => { + if ( + post.doc.user.name === userName && + post.doc.time > this.startDate && + post.doc.time < this.endDate && + !post.doc.replyTo + ) { + uniqueDays.add(new Date(post.doc.time).toDateString()); + } + }); + return Math.min(uniqueDays.size, 5); + } + + hasEnrolledCourse(member) { + return member.courseIds.includes(this.courseId); + } + fetchEnrolled() { this.couchService.findAll('shelf', { selector: { courseIds: { $elemMatch: { $eq: this.courseId } } }, @@ -111,24 +159,6 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { }); } - hasCompletedSurvey(userName: string) { - return this.submissionsSet.has(userName); - } - - hasSubmittedVoice(news: any[], userName: string) { - return news.some(post => { - return ( - post.doc.user.name === userName && - post.doc.time > this.startDate && - post.doc.time < this.endDate - ); - }); - } - - hasEnrolledCourse(member) { - return member.courseIds.includes(this.courseId); - } - fetchCourseAndNews() { this.newsService.newsUpdated$.pipe( takeUntil(this.onDestroy$) @@ -147,33 +177,58 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { this.submissionsService.getSubmissions(findDocuments({ type: 'survey' })) .subscribe((submissions: any[]) => { const filteredSubmissions = submissions.filter(submission => submission.parentId.includes(this.courseId)); - this.submissionsSet = new Set(filteredSubmissions.map(submission => submission.user.name)); + this.submissions = filteredSubmissions.map(submission => ({ + name: submission.user.name, + status: submission.status, + time: submission.lastUpdateTime + })); + // Group Summary this.enrolledMembers.forEach((member) => { - const hasCompletedSurvey = this.hasCompletedSurvey(member.name); - const hasPosted = this.hasSubmittedVoice(news, member.name); const hasJoinedCourse = this.hasEnrolledCourse(member); - - if (hasCompletedSurvey && hasPosted && hasJoinedCourse) { - this.groupSummary.push(member); + const hasCompletedSurvey = this.hasCompletedSurvey(member.name); + const hasPosted = this.hasSubmittedVoice(news, member.name) > 0; + const userAmount = this.hasSubmittedVoice(news, member.name); + + if (hasCompletedSurvey && hasPosted && hasJoinedCourse && userAmount > 0) { + if (!this.groupSummary.some(m => m.name === member.name)) { + this.groupSummary.push({ + ...member, + amountEarned: userAmount + }); + } } }); // Individual stats - this.userStatus.surveyComplete = this.hasCompletedSurvey(this.currentUserName); - this.userStatus.hasPost = this.hasSubmittedVoice(news, this.currentUserName); + this.userStatusService.updateStatus('surveyComplete', this.hasCompletedSurvey(this.currentUserName)); + this.userStatusService.updateStatus('hasPost', this.hasSubmittedVoice(news, this.currentUserName) > 0); + this.userStatusService.updateStatus('amountEarned', this.hasSubmittedVoice(news, this.currentUserName)); this.enrolledMembers.some(member => { if (member.name === this.currentUserName) { - this.userStatus.joinedCourse = this.hasEnrolledCourse(member); + this.userStatusService.updateStatus('joinedCourse', this.hasEnrolledCourse(member)); } }); - }); - }); + this.isLoading = false; + }, () => this.isLoading = false); + }, () => this.isLoading = false); + } + + getTotalMoneyEarned(): number { + return this.groupSummary.reduce((total, member) => { + const amount = Number(member.amountEarned); + return total + (isNaN(amount) ? 0 : amount); + }, 0); } getGoalPercentage(): number { const goal = 500; - return (this.groupSummary?.length / goal) * 100; + const totalMoneyEarned = this.getTotalMoneyEarned(); + return (totalMoneyEarned / goal) * 100; + } + + getStatus(key: string) { + return this.userStatusService.getStatus(key); } } diff --git a/src/app/shared/dialogs/dialogs-chat-share.component.ts b/src/app/shared/dialogs/dialogs-chat-share.component.ts index d6599a1955..a968fb1b39 100644 --- a/src/app/shared/dialogs/dialogs-chat-share.component.ts +++ b/src/app/shared/dialogs/dialogs-chat-share.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit, ViewChild } from '@angular/core'; import { MatStepper } from '@angular/material/stepper'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog'; import { forkJoin } from 'rxjs'; import { switchMap, map } from 'rxjs/operators'; @@ -10,6 +10,8 @@ import { CouchService } from '../../shared/couchdb.service'; import { NewsService } from '../../news/news.service'; import { TeamsService } from '../../teams/teams.service'; import { UserService } from '../../shared/user.service'; +import { UserChallengeStatusService } from '../user-challenge-status.service'; +import { DialogsAnnouncementSuccessComponent } from '../../shared/dialogs/dialogs-announcement.component'; @Component({ templateUrl: './dialogs-chat-share.component.html', @@ -44,6 +46,8 @@ export class DialogsChatShareComponent implements OnInit { private newsService: NewsService, private teamsService: TeamsService, private userService: UserService, + private dialog: MatDialog, + private userStatusService: UserChallengeStatusService, ) { this.conversation = data || this.conversation; } @@ -139,6 +143,16 @@ export class DialogsChatShareComponent implements OnInit { } this.conversation.chat = true; this.newsService.shareNews(this.conversation, null, $localize`Chat has been successfully shared to community`).subscribe(() => {}); + if ( + this.userStatusService.getStatus('joinedCourse') && + this.userStatusService.getStatus('surveyComplete') && + !this.userStatusService.getStatus('hasPost') + ) { + this.dialog.open(DialogsAnnouncementSuccessComponent, { + width: '50vw', + maxHeight: '100vh' + }); + } } } diff --git a/src/app/shared/dialogs/planet-dialogs.module.ts b/src/app/shared/dialogs/planet-dialogs.module.ts index 0a7f384566..c0f9a4f63b 100644 --- a/src/app/shared/dialogs/planet-dialogs.module.ts +++ b/src/app/shared/dialogs/planet-dialogs.module.ts @@ -16,7 +16,7 @@ import { SharedComponentsModule } from '../shared-components.module'; import { SyncDirective } from '../../manager-dashboard/sync.directive'; import { DialogsImagesComponent } from './dialogs-images.component'; import { DialogsVideoComponent } from './dialogs-video.component'; -import { DialogsAnnouncementComponent } from './dialogs-announcement.component'; +import { DialogsAnnouncementComponent, DialogsAnnouncementSuccessComponent } from './dialogs-announcement.component'; import { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-ratings.component'; @@ -41,8 +41,7 @@ import { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-rati DialogsRatingsComponent, DialogsRatingsDirective, ChangePasswordDirective, - SyncDirective, - DialogsAnnouncementComponent, + SyncDirective ], declarations: [ DialogsFormComponent, @@ -57,7 +56,8 @@ import { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-rati DialogsRatingsDirective, ChangePasswordDirective, SyncDirective, - DialogsAnnouncementComponent + DialogsAnnouncementComponent, + DialogsAnnouncementSuccessComponent ], providers: [ DialogsFormService, diff --git a/src/app/shared/user-challenge-status.service.ts b/src/app/shared/user-challenge-status.service.ts new file mode 100644 index 0000000000..5d00649880 --- /dev/null +++ b/src/app/shared/user-challenge-status.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class UserChallengeStatusService { + userStatus = { + joinedCourse: false, + surveyComplete: false, + hasPost: false, + amountEarned: 0 + }; + + updateStatus(key: string, value: boolean | number) { + this.userStatus[key] = value; + } + + getCompleteChallenge(): boolean { + const complete = Object.values(this.userStatus).every( + (value, index) => index !== 3 && value === true + ); + return complete; + } + + getStatus(key: string): boolean| number { + return this.userStatus[key]; + } + + printStatus(): any { + return this.userStatus; + } + + resetStatus() { + this.userStatus = { + joinedCourse: false, + surveyComplete: false, + hasPost: false, + amountEarned: 0 + }; + } +} From 937d33ecc3d41f95a7971ec9fb039ab04de2b0c1 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:28:51 -0800 Subject: [PATCH 002/113] courses: smoother steps loading (fixes #7779) (#7781) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../courses-step-view.component.html | 7 +- .../courses-step-view.component.ts | 2 + src/app/exams/exams-view.component.html | 173 +++++++++--------- src/app/exams/exams-view.component.ts | 4 + 5 files changed, 103 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index d4f944db42..baafe4868b 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.51", + "version": "0.15.52", "myplanet": { "latest": "v0.20.99", "min": "v0.19.99" diff --git a/src/app/courses/step-view-courses/courses-step-view.component.html b/src/app/courses/step-view-courses/courses-step-view.component.html index 9bff90643b..a08a3a2267 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.html +++ b/src/app/courses/step-view-courses/courses-step-view.component.html @@ -93,6 +93,11 @@ <h3 class="margin-lr-3 ellipsis-title"><ng-container i18n>Step</ng-container> {{ </ng-template> </div> <ng-template #emptyRecord> - <div class="view-container view-full-height" i18n>No description provided.</div> + <div class="view-container view-full-height" i18n> + <ng-container *ngIf="isLoading; else noContent"> + Loading Content... + </ng-container> + <ng-template #noContent>No description provided.</ng-template> + </div> </ng-template> </div> diff --git a/src/app/courses/step-view-courses/courses-step-view.component.ts b/src/app/courses/step-view-courses/courses-step-view.component.ts index eb4b692921..b06c03cff3 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.ts +++ b/src/app/courses/step-view-courses/courses-step-view.component.ts @@ -39,6 +39,7 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { isGridView = true; showChat = false; isOpenai = false; + isLoading = true; @ViewChild(MatMenuTrigger) previewButton: MatMenuTrigger; constructor( @@ -125,6 +126,7 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { user: this.userService.get(), type: 'exam' }); } + this.isLoading = false; } initResources(resources) { diff --git a/src/app/exams/exams-view.component.html b/src/app/exams/exams-view.component.html index fd36232ec8..55aa920eae 100644 --- a/src/app/exams/exams-view.component.html +++ b/src/app/exams/exams-view.component.html @@ -16,91 +16,98 @@ <button mat-icon-button [disabled]="questionNum === maxQuestions" (click)="nextQuestion({ nextClicked: true })" [planetSubmit]="spinnerOn"><mat-icon>navigate_next</mat-icon></button> </mat-toolbar> <div class="view-container" [ngClass]="{ 'view-full-height': !isDialog }"> - <td-markdown [content]="question?.body"></td-markdown> - <div [ngSwitch]="mode"> - <ng-container *ngSwitchCase="'take'"> - <ng-container [ngSwitch]="question?.type"> - <mat-form-field class="full-width" *ngSwitchCase="'input'"> - <input matInput i18n-placeholder placeholder="Enter answer here" [formControl]="answer"> - </mat-form-field> - <mat-form-field *ngSwitchCase="'textarea'" class="full-width mat-form-field-type-no-underline"> - <planet-markdown-textbox [formControl]="answer"></planet-markdown-textbox> - </mat-form-field> - <mat-radio-group *ngSwitchCase="'select'" class="question-list" [formControl]="answer"> - <mat-radio-button [value]="option" *ngFor="let option of question?.choices"> - <span class="multiple-choice-text">{{option.text}}</span> - </mat-radio-button> - </mat-radio-group> - <div *ngSwitchCase="'selectMultiple'" class="question-list"> - <span class="mat-caption" i18n> - {examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}} - </span> - <mat-checkbox *ngFor="let option of question?.choices" [value]="option" (change)="setAnswer($event, option)" [checked]="checkboxState[option.id]"> - <span class="multiple-choice-text">{{option.text}}</span> - </mat-checkbox> - </div> + <ng-container *ngIf="!isLoading; else LoadingContent"> + <td-markdown [content]="question?.body"></td-markdown> + <div [ngSwitch]="mode"> + <ng-container *ngSwitchCase="'take'"> + <ng-container [ngSwitch]="question?.type"> + <mat-form-field class="full-width" *ngSwitchCase="'input'"> + <input matInput i18n-placeholder placeholder="Enter answer here" [formControl]="answer"> + </mat-form-field> + <mat-form-field *ngSwitchCase="'textarea'" class="full-width mat-form-field-type-no-underline"> + <planet-markdown-textbox [formControl]="answer"></planet-markdown-textbox> + </mat-form-field> + <mat-radio-group *ngSwitchCase="'select'" class="question-list" [formControl]="answer"> + <mat-radio-button [value]="option" *ngFor="let option of question?.choices"> + <span class="multiple-choice-text">{{option.text}}</span> + </mat-radio-button> + </mat-radio-group> + <div *ngSwitchCase="'selectMultiple'" class="question-list"> + <span class="mat-caption" i18n> + {examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}} + </span> + <mat-checkbox *ngFor="let option of question?.choices" [value]="option" (change)="setAnswer($event, option)" [checked]="checkboxState[option.id]"> + <span class="multiple-choice-text">{{option.text}}</span> + </mat-checkbox> + </div> + </ng-container> </ng-container> - </ng-container> - <ng-container *ngSwitchCase="'grade'"> - <p><b i18n>Submitted answer:</b></p> - <td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown> - <mat-radio-group [(ngModel)]="grade" [disabled]="question?.type === 'select' || question?.type === 'selectMultiple'"> - <mat-radio-button [value]="1" class="planet-radio-button" i18n>Correct</mat-radio-button> - <mat-radio-button [value]="0" class="planet-radio-button" i18n>Incorrect</mat-radio-button> - </mat-radio-group> - <mat-form-field class="full-width mat-form-field-type-no-underline"> - <planet-markdown-textbox class="full-width" i18n-placeholder placeholder="Comment" [(ngModel)]="comment"></planet-markdown-textbox> - </mat-form-field> - </ng-container> - <ng-container *ngSwitchCase="'view'"> - <p><b i18n>Response:</b></p> - <td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown> - <ng-container *ngIf="grade>=0"> - <p><b i18n>Grade:</b></p> - <p *ngIf="grade===1" i18n>Correct</p> - <p *ngIf="grade===0" i18n>Incorrect</p> + <ng-container *ngSwitchCase="'grade'"> + <p><b i18n>Submitted answer:</b></p> + <td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown> + <mat-radio-group [(ngModel)]="grade" [disabled]="question?.type === 'select' || question?.type === 'selectMultiple'"> + <mat-radio-button [value]="1" class="planet-radio-button" i18n>Correct</mat-radio-button> + <mat-radio-button [value]="0" class="planet-radio-button" i18n>Incorrect</mat-radio-button> + </mat-radio-group> + <mat-form-field class="full-width mat-form-field-type-no-underline"> + <planet-markdown-textbox class="full-width" i18n-placeholder placeholder="Comment" [(ngModel)]="comment"></planet-markdown-textbox> + </mat-form-field> </ng-container> - <ng-container *ngIf="comment"> - <p><b i18n>Feedback:</b></p> - <td-markdown [content]="comment"></td-markdown> + <ng-container *ngSwitchCase="'view'"> + <p><b i18n>Response:</b></p> + <td-markdown [content]="answer?.value?.text || answer?.value"></td-markdown> + <ng-container *ngIf="grade>=0"> + <p><b i18n>Grade:</b></p> + <p *ngIf="grade===1" i18n>Correct</p> + <p *ngIf="grade===0" i18n>Incorrect</p> + </ng-container> + <ng-container *ngIf="comment"> + <p><b i18n>Feedback:</b></p> + <td-markdown [content]="comment"></td-markdown> + </ng-container> </ng-container> - </ng-container> - </div> - <div class="v-align-center action-buttons"> - <button - *ngIf="mode !== 'view'" - mat-raised-button - color="primary" - (click)="nextQuestion()" - [planetSubmit]="spinnerOn" - [disabled]="!answer.valid || grade === undefined || grade === null"> - <ng-container i18n>Submit Answer</ng-container> - </button> - <button - *ngIf="mode === 'take'" - mat-raised-button - color="accent" - (click)="nextQuestion({ isFinish: true })" - [disabled]="!isComplete || !answer.valid || grade === undefined || grade === null" - i18n> - {examType, select, survey {Finish Survey} exam {Finish Test}} - </button> - <span class="v-align-center small" [ngSwitch]="statusMessage"> - <div *ngSwitchCase="'incorrect'" class="warn-text-color"> - <mat-icon>error</mat-icon><span i18n>Incorrect answer, please try again</span> - </div> - <div *ngSwitchCase="'complete'" class="primary-text-color"> - <mat-icon>check_circle</mat-icon> - <span> - <ng-container i18n>{examType, select, survey {Survey is complete.} exam {Test is complete.}}</ng-container> - {{' '}} - <ng-container i18n *ngIf="previewMode; else finishExam">You can close the preview.</ng-container> - <ng-template #finishExam> - <ng-container i18n>{examType, select, survey {Click Finish Survey to submit for review.} exam {Click Finish Test to submit for review.}}</ng-container> - </ng-template> - </span> - </div> - </span> - </div> + </div> + <div class="v-align-center action-buttons"> + <button + *ngIf="mode !== 'view'" + mat-raised-button + color="primary" + (click)="nextQuestion()" + [planetSubmit]="spinnerOn" + [disabled]="!answer.valid || grade === undefined || grade === null"> + <ng-container i18n>Submit Answer</ng-container> + </button> + <button + *ngIf="mode === 'take'" + mat-raised-button + color="accent" + (click)="nextQuestion({ isFinish: true })" + [disabled]="!isComplete || !answer.valid || grade === undefined || grade === null" + i18n> + {examType, select, survey {Finish Survey} exam {Finish Test}} + </button> + <span class="v-align-center small" [ngSwitch]="statusMessage"> + <div *ngSwitchCase="'incorrect'" class="warn-text-color"> + <mat-icon>error</mat-icon><span i18n>Incorrect answer, please try again</span> + </div> + <div *ngSwitchCase="'complete'" class="primary-text-color"> + <mat-icon>check_circle</mat-icon> + <span> + <ng-container i18n>{examType, select, survey {Survey is complete.} exam {Test is complete.}}</ng-container> + {{' '}} + <ng-container i18n *ngIf="previewMode; else finishExam">You can close the preview.</ng-container> + <ng-template #finishExam> + <ng-container i18n>{examType, select, survey {Click Finish Survey to submit for review.} exam {Click Finish Test to submit for review.}}</ng-container> + </ng-template> + </span> + </div> + </span> + </div> + </ng-container> + <ng-template #LoadingContent> + <div i18n> + Loading content... + </div> + </ng-template> </div> </div> diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 244c0df035..563efc7bc5 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -48,6 +48,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { unansweredQuestions: number[]; isComplete = false; comment: string; + isLoading = true; courseId: string; constructor( @@ -126,6 +127,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { this.updatedOn = this.submission.lastUpdateTime; this.setViewAnswerText(this.submission.answers[this.questionNum - 1]); } + this.isLoading = false; } nextQuestion({ nextClicked = false, isFinish = false }: { nextClicked?: boolean, isFinish?: boolean } = {}) { @@ -196,6 +198,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { } goBack() { + this.isLoading = false; this.router.navigate([ '../', this.mode === 'take' ? {} : { type: this.mode === 'grade' ? 'exam' : 'survey' } @@ -232,6 +235,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { const type = this.examType; const takingExam = exam ? exam : step[type]; this.setTakingExam(takingExam, takingExam._id + '@' + course._id, type); + this.isLoading = false; }); } From 3d2f3aabbf9ff5b3f2a0eb11f7fd2842b56de977 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:34:53 -0800 Subject: [PATCH 003/113] resources: smoother collection tags (fixes #5776) (#7778) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../forms/planet-tag-input-dialog.component.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index baafe4868b..98c1229c3d 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.52", + "version": "0.15.53", "myplanet": { "latest": "v0.20.99", "min": "v0.19.99" diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index 6c335053c1..fdff521c14 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -203,6 +203,7 @@ export class PlanetTagInputDialogComponent { this.data.initTags(); this.deleteDialog.close(); this.planetMessageService.showMessage($localize`Tag deleted: ${tag.name}`); + this.resetValidationAndCheck(this.addTagForm); }, onError: (error) => this.planetMessageService.showAlert($localize`There was a problem deleting this tag.`) }; @@ -230,6 +231,21 @@ export class PlanetTagInputDialogComponent { ); } + resetValidationAndCheck(form: FormGroup) { + Object.keys(form.controls).forEach(key => { + const control = form.get(key); + control?.clearValidators(); + + if (key === 'name') { + control?.setValidators(this.tagNameSyncValidator()); + control?.setAsyncValidators(ac => this.tagNameAsyncValidator(ac)); + } + + control?.markAsUntouched(); + control?.updateValueAndValidity(); + }); + } + toggleSubcollection(event, tagId) { event.stopPropagation(); const newState = !this.subcollectionIsOpen.get(tagId); From ed1bb2d10d085e21f86c29f80f28b54e519af65d Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:38:43 -0500 Subject: [PATCH 004/113] mylife: smoother achievements (fixes #7782) (#7783) Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements.component.html | 4 ++-- .../users-achievements/users-achievements.scss | 14 +++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 98c1229c3d..a691fc9ad5 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.53", + "version": "0.15.54", "myplanet": { "latest": "v0.20.99", "min": "v0.19.99" diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index 8988c3abc6..ff40b83dad 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -39,7 +39,7 @@ <h3 i18n>My Goals</h3> <ng-container *planetBeta> <div *ngIf="certifications.length > 0"> <h3 i18n>My Certifications</h3> - <mat-list> + <mat-list class="certs-list"> <mat-list-item *ngFor="let certification of certifications"> {{certification.name}} </mat-list-item> @@ -79,7 +79,7 @@ <h3 i18n>My Achievements</h3> </div> <div *ngIf="achievements.references.length > 0"> <h3 i18n>My References</h3> - <mat-list> + <mat-list class="references-list"> <mat-list-item class="mat-list-item-word-wrap" *ngFor="let reference of achievements.references"> <h4 mat-line>{{reference.name}}</h4> <p mat-line *ngIf="reference?.relationship"><ng-container i18n>Relationship:</ng-container> {{reference.relationship}}</p> diff --git a/src/app/users/users-achievements/users-achievements.scss b/src/app/users/users-achievements/users-achievements.scss index f99e871f44..17470dd780 100644 --- a/src/app/users/users-achievements/users-achievements.scss +++ b/src/app/users/users-achievements/users-achievements.scss @@ -5,6 +5,18 @@ margin: 0 auto; text-align: center; + .references-list { + display: flex; + justify-content: center; + text-align: center; + } + + .certs-list .mat-list-item{ + display: flex; + justify-content: center; + text-align: center; + } + & mat-list, & ul, & ol { text-align: left; } @@ -105,4 +117,4 @@ .center-text div { margin: 0 auto; } -} \ No newline at end of file +} From fc1c01b9cc40be11e3171f508e9649fb79ffde22 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:44:19 -0800 Subject: [PATCH 005/113] teams: smoother voices creation (fixes #4800) (#7793) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/forms/planet-markdown-textbox.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a691fc9ad5..482d6f2ca5 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.54", + "version": "0.15.55", "myplanet": { "latest": "v0.20.99", "min": "v0.19.99" diff --git a/src/app/shared/forms/planet-markdown-textbox.scss b/src/app/shared/forms/planet-markdown-textbox.scss index cac82c94c1..9ef7201751 100644 --- a/src/app/shared/forms/planet-markdown-textbox.scss +++ b/src/app/shared/forms/planet-markdown-textbox.scss @@ -36,6 +36,7 @@ planet-markdown-textbox { } .editor-statusbar { + opacity: 0; margin-bottom: -2.75em; } From 99b77d1ff36d5bace127591ea6fbca7aafc4c8e4 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:57:50 -0800 Subject: [PATCH 006/113] dashboard: complete profile reminder (fixes #7758) (#7765) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 ++-- src/app/dashboard/dashboard.component.html | 6 ++++ src/app/dashboard/dashboard.component.ts | 33 +++++++++++++++----- src/app/dashboard/dashboard.scss | 36 +++++++++++++++++++++- src/app/shared/user.service.ts | 10 ++++++ 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 482d6f2ca5..f54ab96bcf 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.55", + "version": "0.15.56", "myplanet": { - "latest": "v0.20.99", - "min": "v0.19.99" + "latest": "v0.21.2", + "min": "v0.20.2" }, "scripts": { "ng": "ng", diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 292b296d2b..942932614c 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -1,3 +1,9 @@ +<div *ngIf="showBanner" class="alert"> + <a [routerLink]="['/users/update', user.name]"> + <span class="banner-text" i18n>Please complete your profile to enjoy full features!</span> + </a> + <span class="closebtn" (click)="closeBanner()">×</span> +</div> <mat-card class="horizontal"> <img [src]="profileImg"> <div class="dashboard-name"> diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index e6ccedd30d..5b462ae6c1 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -22,8 +22,9 @@ import { CertificationsService } from '../manager-dashboard/certifications/certi }) export class DashboardComponent implements OnInit, OnDestroy { + user = this.userService.get(); data = { resources: [], courses: [], meetups: [], myTeams: [] }; - urlPrefix = environment.couchAddress + '/_users/org.couchdb.user:' + this.userService.get().name + '/'; + urlPrefix = environment.couchAddress + '/_users/org.couchdb.user:' + this.user.name + '/'; displayName: string; roles: string[]; planetName: string; @@ -37,6 +38,7 @@ export class DashboardComponent implements OnInit, OnDestroy { examsCount = 0; leaderIds = []; onDestroy$ = new Subject<void>(); + showBanner = false; myLifeItems: any[] = [ { firstLine: $localize`my`, title: $localize`Submissions`, link: 'submissions', authorization: 'leader,manager', @@ -57,7 +59,7 @@ export class DashboardComponent implements OnInit, OnDestroy { private certificationsService: CertificationsService, private dialog: MatDialog ) { - const currRoles = this.userService.get().roles; + const currRoles = this.user.roles; this.roles = currRoles.reduce(dedupeShelfReduce, currRoles.length ? [ 'learner' ] : [ 'Inactive' ]); this.userService.shelfChange$.pipe() .subscribe(() => { @@ -74,13 +76,12 @@ export class DashboardComponent implements OnInit, OnDestroy { } ngOnInit() { - const user = this.userService.get(); - this.displayName = user.firstName !== undefined ? user.firstName + ' ' + user.lastName : user.name; + this.displayName = this.user.firstName !== undefined ? this.user.firstName + ' ' + this.user.lastName : this.user.name; this.planetName = this.stateService.configuration.name; this.getSurveys(); this.getExams(); this.initDashboard(); - this.couchService.findAll('login_activities', findDocuments({ 'user': this.userService.get().name }, [ 'user' ], [], 1000)) + this.couchService.findAll('login_activities', findDocuments({ 'user': this.user.name }, [ 'user' ], [], 1000)) .pipe( catchError(() => { return of([]); @@ -88,6 +89,7 @@ export class DashboardComponent implements OnInit, OnDestroy { ).subscribe((res: any) => { this.visits = res.length; }); + this.reminderBanner(); } ngOnDestroy() { @@ -132,7 +134,7 @@ export class DashboardComponent implements OnInit, OnDestroy { getTeamMembership() { const configuration = this.stateService.configuration; return this.couchService.findAll( - 'teams', findDocuments({ userPlanetCode: configuration.code, userId: this.userService.get()._id, docType: 'membership' }) + 'teams', findDocuments({ userPlanetCode: configuration.code, userId: this.user._id, docType: 'membership' }) ).pipe( switchMap((memberships) => forkJoin([ of(memberships), @@ -147,7 +149,7 @@ export class DashboardComponent implements OnInit, OnDestroy { } get profileImg() { - const attachments = this.userService.get()._attachments; + const attachments = this.user._attachments; if (attachments) { return this.urlPrefix + Object.keys(attachments)[0]; } @@ -170,7 +172,7 @@ export class DashboardComponent implements OnInit, OnDestroy { } getSurveys() { - this.getSubmissions('survey', 'pending', this.userService.get().name).subscribe((surveys) => { + this.getSubmissions('survey', 'pending', this.user.name).subscribe((surveys) => { this.surveysCount = dedupeObjectArray(surveys, [ 'parentId' ]).length; this.myLifeItems = this.myLifeItems.map(item => item.link === 'mySurveys' ? { ...item, badge: this.surveysCount } : item); }); @@ -211,4 +213,19 @@ export class DashboardComponent implements OnInit, OnDestroy { this.badgeGroups = [ ...foundations, 'none' ].filter(group => this.badgesCourses[group] && this.badgesCourses[group].length); } + reminderBanner() { + this.userService.isProfileComplete(); + combineLatest([ + this.userService.profileBanner, + this.userService.profileComplete$ + ]).pipe(takeUntil(this.onDestroy$)).subscribe(([ profileBanner, profileComplete ]) => { + this.showBanner = profileBanner && !profileComplete; + }); + } + + closeBanner() { + this.userService.profileBanner.next(false); + this.showBanner = false; + } + } diff --git a/src/app/dashboard/dashboard.scss b/src/app/dashboard/dashboard.scss index 5640f7923b..2f1edbe817 100644 --- a/src/app/dashboard/dashboard.scss +++ b/src/app/dashboard/dashboard.scss @@ -93,7 +93,41 @@ $top-row-height: calc(#{$dashboard-tile-width} + 0.5rem); p { margin: 0; } - } +} + +.alert { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + width: 90%; + padding: 20px; + background-color: #fdee7f; + color: rgb(0, 0, 0); + text-align: center; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + z-index: 1000; +} + +.banner-text { + display: block; + margin: 0 2rem; +} + +.closebtn { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + color: rgb(126, 126, 126); + font-weight: bold; + font-size: 22px; + cursor: pointer; + transition: 0.5s; +} +.closebtn:hover { + color: rgb(0, 0, 0); } diff --git a/src/app/shared/user.service.ts b/src/app/shared/user.service.ts index 091f8208d5..172de224c9 100644 --- a/src/app/shared/user.service.ts +++ b/src/app/shared/user.service.ts @@ -46,6 +46,10 @@ export class UserService { userLogout$ = this.userLogout.asObservable(); private notificationStateChange = new Subject<void>(); notificationStateChange$ = this.notificationStateChange.asObservable(); + profileComplete = new BehaviorSubject<boolean>(false); + profileComplete$ = this.profileComplete.asObservable(); + profileBanner = new BehaviorSubject<boolean>(true); + profileBanner$ = this.profileBanner.asObservable(); minBirthDate = new Date(1900, 0, 1); @@ -295,4 +299,10 @@ export class UserService { }))); } + isProfileComplete() { + const isComplete = !!(this.user.firstName && this.user.lastName && this.user.email && this.user.birthDate && + this.user.gender && this.user.language && this.user.phoneNumber && this.user.level); + this.profileComplete.next(isComplete); + } + } From 1c7b9554a41184426e3840119ffb66618ef22f50 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:09:11 -0500 Subject: [PATCH 007/113] courses: smoother resuming (fixes #2716) (#7794) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/exams/exams-view.component.ts | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f54ab96bcf..d9305172f8 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.56", + "version": "0.15.57", "myplanet": { "latest": "v0.21.2", "min": "v0.20.2" diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 563efc7bc5..380bcb1937 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -48,6 +48,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { unansweredQuestions: number[]; isComplete = false; comment: string; + initialLoad = true; isLoading = true; courseId: string; @@ -133,7 +134,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { nextQuestion({ nextClicked = false, isFinish = false }: { nextClicked?: boolean, isFinish?: boolean } = {}) { const { correctAnswer, obs }: { correctAnswer?: boolean | undefined, obs: any } = this.createAnswerObservable(isFinish); const previousStatus = this.previewMode ? 'preview' : this.submissionsService.submission.status; - // Only navigate away from page until after successful post (ensures DB is updated for submission list) +// Only navigate away from page until after successful post (ensures DB is updated for submission list) obs.subscribe(({ nextQuestion }) => { if (correctAnswer === false) { this.statusMessage = 'incorrect'; @@ -141,7 +142,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { this.question.choices.forEach(choice => this.checkboxState[choice.id] = false); this.spinnerOn = false; } else { - this.routeToNext(nextClicked ? this.questionNum : nextQuestion, previousStatus); + this.routeToNext(nextQuestion, previousStatus); // Challenge option only if ( isFinish && @@ -255,6 +256,17 @@ export class ExamsViewComponent implements OnInit, OnDestroy { this.grade = (ans && ans.grade !== undefined) ? ans.grade : this.grade; this.comment = ans && ans.gradeComment; } + if (this.initialLoad && this.mode === 'take' && this.unansweredQuestions.length > 0) { + const nextUnansweredQuestion = this.unansweredQuestions[0]; + if (this.questionNum !== nextUnansweredQuestion) { + this.questionNum = nextUnansweredQuestion; + this.router.navigate([ { + ...this.route.snapshot.params, + questionNum: this.questionNum + } ], { relativeTo: this.route }); + } + this.initialLoad = false; + } if (this.mode === 'take' && this.isNewQuestion) { this.setAnswerForRetake(ans); } else if (this.mode !== 'take') { @@ -262,8 +274,8 @@ export class ExamsViewComponent implements OnInit, OnDestroy { } this.isNewQuestion = false; this.isComplete = this.unansweredQuestions && this.unansweredQuestions.every(number => this.questionNum === number); - }); - } + }); +} setAnswer(event, option) { this.answer.setValue(Array.isArray(this.answer.value) ? this.answer.value : []); From 544d562b5ea1080a43add7d4be31f4d5e22f28dd Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:17:18 +0300 Subject: [PATCH 008/113] all: smoother challenge validation (fixes #7822) (#7824) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../dialogs/dialogs-announcement.component.html | 4 ++-- .../dialogs/dialogs-announcement.component.ts | 15 +++++++++------ src/app/shared/user-challenge-status.service.ts | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d9305172f8..882b45a62b 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.57", + "version": "0.15.58", "myplanet": { "latest": "v0.21.2", "min": "v0.20.2" diff --git a/src/app/shared/dialogs/dialogs-announcement.component.html b/src/app/shared/dialogs/dialogs-announcement.component.html index 4998d47191..f06747ea37 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.html +++ b/src/app/shared/dialogs/dialogs-announcement.component.html @@ -49,7 +49,7 @@ <mat-icon color="primary"> {{ getStatus('hasPost') ? 'check_circle' : 'radio_button_unchecked' }} </mat-icon> - <span>Comparte tu opinión en Nuestras Voces. <br><span>{{getStatus('amountEarned')}} de 5 Voces diarias</span> </span> + <span>Comparte tu opinión en Nuestras Voces. <br><span>{{getStatus('userPosts')}} de 5 Voces diarias</span> </span> <a *ngIf="getStatus('joinedCourse') && getStatus('surveyComplete') && !getStatus('hasPost'); else dailyVoices" mat-button mat-raised-button color="primary" type="button" (click)="chatNShare()"> Chatea y comparte </a> @@ -58,7 +58,7 @@ <div *ngFor="let dot of [0, 1, 2, 3, 4]; let i = index" class="dot" - [ngClass]="{ completed: i < getStatus('amountEarned') }" + [ngClass]="{ completed: i < getStatus('userPosts') }" ></div> </div> </ng-template> diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts index 9f45a636d3..780fe1caf3 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.ts +++ b/src/app/shared/dialogs/dialogs-announcement.component.ts @@ -50,6 +50,8 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { startDate = new Date(2024, 9, 31); endDate = new Date(2024, 11, 1); isLoading = true; + submissionValue = 5; + goal = 500; constructor( public dialogRef: MatDialogRef<DialogsAnnouncementComponent>, @@ -195,7 +197,7 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { if (!this.groupSummary.some(m => m.name === member.name)) { this.groupSummary.push({ ...member, - amountEarned: userAmount + userPosts: userAmount }); } } @@ -204,7 +206,7 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { // Individual stats this.userStatusService.updateStatus('surveyComplete', this.hasCompletedSurvey(this.currentUserName)); this.userStatusService.updateStatus('hasPost', this.hasSubmittedVoice(news, this.currentUserName) > 0); - this.userStatusService.updateStatus('amountEarned', this.hasSubmittedVoice(news, this.currentUserName)); + this.userStatusService.updateStatus('userPosts', this.hasSubmittedVoice(news, this.currentUserName)); this.enrolledMembers.some(member => { if (member.name === this.currentUserName) { this.userStatusService.updateStatus('joinedCourse', this.hasEnrolledCourse(member)); @@ -216,16 +218,17 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { } getTotalMoneyEarned(): number { - return this.groupSummary.reduce((total, member) => { - const amount = Number(member.amountEarned); + const totalEarned = this.groupSummary.reduce((total, member) => { + const amount = Number(member.userPosts * this.submissionValue); return total + (isNaN(amount) ? 0 : amount); }, 0); + + return Math.min(totalEarned, this.goal); } getGoalPercentage(): number { - const goal = 500; const totalMoneyEarned = this.getTotalMoneyEarned(); - return (totalMoneyEarned / goal) * 100; + return (totalMoneyEarned / this.goal) * 100; } getStatus(key: string) { diff --git a/src/app/shared/user-challenge-status.service.ts b/src/app/shared/user-challenge-status.service.ts index 5d00649880..7825124d62 100644 --- a/src/app/shared/user-challenge-status.service.ts +++ b/src/app/shared/user-challenge-status.service.ts @@ -8,7 +8,7 @@ export class UserChallengeStatusService { joinedCourse: false, surveyComplete: false, hasPost: false, - amountEarned: 0 + userPosts: 0 }; updateStatus(key: string, value: boolean | number) { @@ -35,7 +35,7 @@ export class UserChallengeStatusService { joinedCourse: false, surveyComplete: false, hasPost: false, - amountEarned: 0 + userPosts: 0 }; } } From a8da2c713c4eca364dc69d6a06ca7e320f17406e Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:27:56 -0500 Subject: [PATCH 009/113] courses: smoother menu entries (fixes #4019) (#7790) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/courses.component.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 882b45a62b..146e4f48c9 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.58", + "version": "0.15.59", "myplanet": { - "latest": "v0.21.2", - "min": "v0.20.2" + "latest": "v0.21.4", + "min": "v0.20.4" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/courses.component.html b/src/app/courses/courses.component.html index 23a4ab5adb..840bcd8da2 100644 --- a/src/app/courses/courses.component.html +++ b/src/app/courses/courses.component.html @@ -180,8 +180,8 @@ <h3 class="header"> <span i18n>Feedback</span> </a> <a mat-menu-item *ngIf="element.canManage" (click)="updateCourse(element.doc)"> - <mat-icon>folder</mat-icon> - <span i18n>Manage</span> + <mat-icon>edit</mat-icon> + <span i18n>Edit Course</span> </a> <a mat-menu-item [routerLink]="['/courses/view', element._id]"> <mat-icon>visibility</mat-icon> From 4770eaa2d0d64158ce06bf7543d2e2282432fcab Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:32:16 -0500 Subject: [PATCH 010/113] dashboard: smoother avatar linking (fixes #4532) (#7792) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/dashboard/dashboard.component.html | 52 +++++++++++++--------- src/app/dashboard/dashboard.component.ts | 3 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 146e4f48c9..d0f090ef0f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.59", + "version": "0.15.60", "myplanet": { "latest": "v0.21.4", "min": "v0.20.4" diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 942932614c..ffa7443106 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -4,28 +4,38 @@ </a> <span class="closebtn" (click)="closeBanner()">×</span> </div> -<mat-card class="horizontal"> - <img [src]="profileImg"> - <div class="dashboard-name"> - <h1 class="mat-title">{{displayName}} ({{visits | number}})</h1> - <h1 class="mat-title"> - <span class="mat-subheading-2"><ng-container *ngFor="let role of roles; last as last"> - <planet-role [role]="role"></planet-role><span *ngIf="!last">, </span> - </ng-container></span> - </h1> - </div> - <div class="date"> - <p>{{dateNow | date:'longDate'}}</p> +<mat-card class="horizontal" style="display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start;"> + <div style="display: flex; align-items: flex-start;"> + <a [routerLink]="['/users/profile', user.name]" class="profile-link"> + <img [src]="profileImg" class="profile-avatar"> + </a> + <div class="dashboard-name" style="margin-left: 16px;"> + <a [routerLink]="['/users/profile', user.name]" class="profile-link"> + <h1 class="mat-title">{{displayName}} ({{visits | number}})</h1> + </a> + <h1 class="mat-title"> + <span class="mat-subheading-2"> + <ng-container *ngFor="let role of roles; last as last"> + <planet-role [role]="role"></planet-role><span *ngIf="!last">, </span> + </ng-container> + </span> + </h1> + </div> </div> - <div class="badges"> - <div *ngFor="let badgeGroup of badgeGroups"> - <span *ngFor="let course of badgesCourses[badgeGroup]" class="cursor-pointer" (click)="openCourseView(course)" [matTooltip]="course.doc.courseTitle"> - <mat-icon - [ngClass]="{ 'primary-text-color': course.inCertification, 'grey-text-color': !course.inCertification }" - fontSet="fa" - [fontIcon]="badgeIcons[badgeGroup]"> - </mat-icon> - </span> + <div style="display: flex; flex-direction: column; align-items: flex-end;"> + <div class="date" style="text-align: right;"> + <p>{{dateNow | date:'longDate'}}</p> + </div> + <div class="badges" style="display: flex; flex-wrap: wrap; justify-content: flex-end; max-width: 100%; margin-top: 8px;"> + <div *ngFor="let badgeGroup of badgeGroups" style="margin-right: 4px;"> + <span *ngFor="let course of badgesCourses[badgeGroup]" class="cursor-pointer" (click)="openCourseView(course)" [matTooltip]="course.doc.courseTitle"> + <mat-icon + [ngClass]="{ 'primary-text-color': course.inCertification, 'grey-text-color': !course.inCertification }" + fontSet="fa" + [fontIcon]="badgeIcons[badgeGroup]"> + </mat-icon> + </span> + </div> </div> </div> </mat-card> diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index 5b462ae6c1..97ff21b988 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -76,7 +76,7 @@ export class DashboardComponent implements OnInit, OnDestroy { } ngOnInit() { - this.displayName = this.user.firstName !== undefined ? this.user.firstName + ' ' + this.user.lastName : this.user.name; + this.displayName = this.user.firstName !== undefined ? `${this.user.firstName} ${this.user.lastName}` : this.user.name; this.planetName = this.stateService.configuration.name; this.getSurveys(); this.getExams(); @@ -84,6 +84,7 @@ export class DashboardComponent implements OnInit, OnDestroy { this.couchService.findAll('login_activities', findDocuments({ 'user': this.user.name }, [ 'user' ], [], 1000)) .pipe( catchError(() => { + console.warn('Error fetching login activities'); return of([]); }) ).subscribe((res: any) => { From 2f111054f2724ddbb2dd68d5b3a90a90e801da21 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:45:48 -0500 Subject: [PATCH 011/113] all: smoother feedback buttons (fixes #7825) (#7810) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/feedback/feedback-view.component.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d0f090ef0f..2aefa95510 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.60", + "version": "0.15.61", "myplanet": { "latest": "v0.21.4", "min": "v0.20.4" diff --git a/src/app/feedback/feedback-view.component.html b/src/app/feedback/feedback-view.component.html index 865515231c..412e2f6189 100644 --- a/src/app/feedback/feedback-view.component.html +++ b/src/app/feedback/feedback-view.component.html @@ -32,13 +32,13 @@ </a> </ng-template> <span class="toolbar-fill"></span> - <a *ngIf="feedback?.state" [routerLink]="['/', feedback.state, 'view', feedback.item]" mat-icon-button> + <a *ngIf="feedback?.state" [routerLink]="['/', feedback.state, 'view', feedback.item]" mat-raised-button color="accent"> <ng-container [ngSwitch]="feedback.state"> - <mat-icon *ngSwitchCase="'resources'" svgIcon="myLibrary"></mat-icon> - <mat-icon *ngSwitchCase="'courses'" svgIcon="myCourses"></mat-icon> - <mat-icon *ngSwitchCase="'meetups'" svgIcon="myMeetups"></mat-icon> - <mat-icon *ngSwitchCase="'teams'" svgIcon="myTeams"></mat-icon> - <mat-icon *ngSwitchCase="'enterprises'">business</mat-icon> + <span *ngSwitchCase="'resources'" i18n>Go to Resource</span> + <span *ngSwitchCase="'courses'" i18n>Go to Course</span> + <span *ngSwitchCase="'meetups'" i18n>Go to Meetup</span> + <span *ngSwitchCase="'teams'" i18n>Go to Team</span> + <span *ngSwitchCase="'enterprises'" i18n>Go to Enterprise</span> </ng-container> </a> </mat-toolbar-row> From c59e1a390c80f8a7f5f3ce2faec0697a1d31dada Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:04:17 -0500 Subject: [PATCH 012/113] courses: add participants export (fixes #7815) (#7817) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../courses-enroll.component.html | 3 +++ .../courses-enroll.component.ts | 22 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2aefa95510..dec870e481 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.61", + "version": "0.15.62", "myplanet": { "latest": "v0.21.4", "min": "v0.20.4" diff --git a/src/app/courses/enroll-courses/courses-enroll.component.html b/src/app/courses/enroll-courses/courses-enroll.component.html index cf50e4eaea..fab9af1283 100644 --- a/src/app/courses/enroll-courses/courses-enroll.component.html +++ b/src/app/courses/enroll-courses/courses-enroll.component.html @@ -8,6 +8,9 @@ <mat-toolbar class="primary-color font-size-1"> {{ course }} <span class="toolbar-fill"></span> + <button mat-raised-button color="accent" (click)="exportCSV()"> + Export + </button> </mat-toolbar> <ng-container *ngIf="!emptyData; else notFoundMessage"> diff --git a/src/app/courses/enroll-courses/courses-enroll.component.ts b/src/app/courses/enroll-courses/courses-enroll.component.ts index b9527d4ea5..dbcb54e23d 100644 --- a/src/app/courses/enroll-courses/courses-enroll.component.ts +++ b/src/app/courses/enroll-courses/courses-enroll.component.ts @@ -9,6 +9,7 @@ import { TableState } from '../../users/users-table.component'; import { StateService } from '../../shared/state.service'; import { ManagerService } from '../../manager-dashboard/manager.service'; import { attachNamesToPlanets } from '../../manager-dashboard/reports/reports.utils'; +import { CsvService } from '../../shared/csv.service'; @Component({ @@ -37,7 +38,8 @@ export class CoursesEnrollComponent { private usersService: UsersService, private coursesService: CoursesService, private stateService: StateService, - private managerService: ManagerService + private managerService: ManagerService, + private csvService: CsvService ) { this.coursesService.requestCourses(); this.usersService.requestUserData(); @@ -83,4 +85,22 @@ export class CoursesEnrollComponent { this.emptyData = this.members.length === 0; } + exportCSV() { + // Prepare CSV data + const csvData = this.members.map((user: any) => { + return { + username: user.doc.name, + dateStarted: user.activityDates.createdDate + ? new Date(user.activityDates.createdDate).toLocaleDateString() + : 'N/A', + mostRecentActivity: user.activityDates.updatedDate + ? new Date(user.activityDates.updatedDate).toLocaleDateString() + : 'N/A', + }; + }); + this.csvService.exportCSV({ + data: csvData, + title: `Course Enrollment Data - ${this.course}`, + }); + } } From 09259c0f7beb8b699529a786bce9c8512afd4efe Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:21:41 -0800 Subject: [PATCH 013/113] chat: smoother share (fixes #7812) (#7828) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/chat/chat-sidebar/chat-sidebar.component.ts | 10 +++++++--- src/app/shared/dialogs/dialogs-chat-share.component.ts | 6 +++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index dec870e481..637989c306 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.62", + "version": "0.15.63", "myplanet": { - "latest": "v0.21.4", - "min": "v0.20.4" + "latest": "v0.21.7", + "min": "v0.20.7" }, "scripts": { "ng": "ng", diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.ts b/src/app/chat/chat-sidebar/chat-sidebar.component.ts index b41dd7a11b..1e72876eb1 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.ts +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.ts @@ -211,14 +211,18 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { } openShareDialog(conversation) { - this.dialog.open(DialogsChatShareComponent, { + const dialogRef = this.dialog.open(DialogsChatShareComponent, { width: '50vw', maxHeight: '90vh', data: { news: conversation, } }); - this.updateConversation(conversation, null, true); - } + dialogRef.afterClosed().subscribe((result) => { + if (result) { + this.updateConversation(conversation, null, true); + } + }); + } } diff --git a/src/app/shared/dialogs/dialogs-chat-share.component.ts b/src/app/shared/dialogs/dialogs-chat-share.component.ts index a968fb1b39..9a5c65d82a 100644 --- a/src/app/shared/dialogs/dialogs-chat-share.component.ts +++ b/src/app/shared/dialogs/dialogs-chat-share.component.ts @@ -124,7 +124,6 @@ export class DialogsChatShareComponent implements OnInit { }) ).subscribe((membersData) => { this.conversation.chat = true; - this.newsService.postNews({ viewIn: [ { '_id': linkId, section: 'teams', public: false } ], messageType: teamType, @@ -134,6 +133,7 @@ export class DialogsChatShareComponent implements OnInit { switchMap(() => this.sendNotifications('message', membersData, teamType)), ).subscribe(); }); + this.interact(); } shareWithCommunity() { @@ -142,6 +142,7 @@ export class DialogsChatShareComponent implements OnInit { this.conversation.message = message ? { text: message, images: [] } : { text: '</br>', images: [] }; } this.conversation.chat = true; + this.interact(); this.newsService.shareNews(this.conversation, null, $localize`Chat has been successfully shared to community`).subscribe(() => {}); if ( this.userStatusService.getStatus('joinedCourse') && @@ -155,4 +156,7 @@ export class DialogsChatShareComponent implements OnInit { } } + interact() { + this.dialogRef.close({ interacted: true }); + } } From 914d7f6e2f277d7cdb57f9c21a871ab1e1ef35ed Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:41:32 -0800 Subject: [PATCH 014/113] chat: smoother provider switching (fixes #7638)(fixes #7809) (#7814) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../chat-sidebar/chat-sidebar.component.ts | 36 +++++++++++++++++-- .../chat/chat-window/chat-window.component.ts | 1 + src/app/chat/chat.component.ts | 11 ++++++ src/app/shared/chat.service.ts | 11 +++++- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 637989c306..eece41d638 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.63", + "version": "0.15.64", "myplanet": { "latest": "v0.21.7", "min": "v0.20.7" diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.ts b/src/app/chat/chat-sidebar/chat-sidebar.component.ts index 1e72876eb1..6170dd0b7b 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.ts +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.ts @@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { Conversation } from '../chat.model'; +import { Conversation, AIProvider } from '../chat.model'; import { ChatService } from '../../shared/chat.service'; import { CouchService } from '../../shared/couchdb.service'; import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; @@ -33,6 +33,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { selectedConversation: Conversation; lastRenderedConversation: number; isEditing: boolean; + provider: AIProvider; fullTextSearch = false; searchType: 'questions' | 'responses'; overlayOpen = false; @@ -56,6 +57,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { this.titleSearch = ''; this.getChatHistory(); this.subscribeToNewChats(); + this.subscribeToAIService(); } ngOnDestroy() { @@ -74,11 +76,36 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { .subscribe(() => this.getChatHistory()); } + subscribeToAIService() { + this.chatService.toggleAIService$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe((aiService => { + this.provider = { + name: aiService + }; + this.hasProviderChanged(); + })); + } + newChat() { this.chatService.sendNewChatSelectedSignal(); + this.chatService.setChatAIProvider(undefined); this.selectedConversation = null; } + hasProviderChanged() { + const currentProvider = this.chatService.getChatAIProvider(); + if (!currentProvider) { + // That means it's a brand-new chat + return; + } + if (currentProvider.name === this.provider.name) { + // That means the same model is still being used + return; + } + this.newChat(); + } + toggleEditTitle() { this.isEditing = !this.isEditing; } @@ -94,7 +121,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { title: title !== undefined && title !== null ? title : conversation.title, shared: shared, updatedDate: this.couchService.datePlaceholder - } + } ).subscribe((data) => { this.getChatHistory(); return data; @@ -141,6 +168,11 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { selectConversation(conversation, index: number) { this.selectedConversation = conversation; + const aiProvider: AIProvider = { + name: this.selectedConversation['aiProvider'], + }; + this.chatService.setChatAIProvider(aiProvider); + const currentProvider = this.chatService.getChatAIProvider(); this.chatService.setSelectedConversationId({ '_id': conversation?._id, '_rev': conversation?._rev diff --git a/src/app/chat/chat-window/chat-window.component.ts b/src/app/chat/chat-window/chat-window.component.ts index 3927ca3ea5..cbeb6908d7 100644 --- a/src/app/chat/chat-window/chat-window.component.ts +++ b/src/app/chat/chat-window/chat-window.component.ts @@ -205,6 +205,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy { const content = this.promptForm.get('prompt').value; this.data = { ...this.data, content, aiProvider: this.provider }; + this.chatService.setChatAIProvider(this.data.aiProvider); this.setSelectedConversation(); if (this.context) { diff --git a/src/app/chat/chat.component.ts b/src/app/chat/chat.component.ts index 6aed46a070..4660ab9615 100644 --- a/src/app/chat/chat.component.ts +++ b/src/app/chat/chat.component.ts @@ -27,6 +27,17 @@ export class ChatComponent implements OnInit { this.displayToggle = this.aiServices.length > 0; this.chatService.toggleAIServiceSignal(this.activeService); }); + this.subscribeToAIService(); + } + + subscribeToAIService() { + this.chatService.currentChatAIProvider$ + .subscribe((aiService => { + if (aiService) { + this.activeService = aiService.name; + this.toggleAIService(); + } + })); } goBack(): void { diff --git a/src/app/shared/chat.service.ts b/src/app/shared/chat.service.ts index 387be33bb8..fa95048284 100644 --- a/src/app/shared/chat.service.ts +++ b/src/app/shared/chat.service.ts @@ -23,13 +23,14 @@ import { AIServices, AIProvider } from '../chat/chat.model'; private toggleAIService = new Subject<string>(); private selectedConversationIdSubject = new BehaviorSubject<object | null>(null); private aiProvidersSubject = new BehaviorSubject<Array<AIProvider>>([]); + private currentChatAIProvider = new BehaviorSubject<AIProvider>(undefined); newChatAdded$ = this.newChatAdded.asObservable(); newChatSelected$ = this.newChatSelected.asObservable(); toggleAIService$ = this.toggleAIService.asObservable(); aiProviders$ = this.aiProvidersSubject.asObservable(); selectedConversationId$: Observable<object | null> = this.selectedConversationIdSubject.asObservable(); - + currentChatAIProvider$: Observable<AIProvider> = this.currentChatAIProvider.asObservable(); constructor( private httpClient: HttpClient, @@ -123,6 +124,14 @@ import { AIServices, AIProvider } from '../chat/chat.model'; this.toggleAIService.next(aiService); } + setChatAIProvider(aiProvider: AIProvider) { + this.currentChatAIProvider.next(aiProvider); + } + + getChatAIProvider(): AIProvider { + return this.currentChatAIProvider.getValue(); + } + setSelectedConversationId(conversationId: object) { this.selectedConversationIdSubject.next(conversationId); } From c57e52983ebed1022b0bac89f08d0981f9653a5c Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:55:34 -0800 Subject: [PATCH 015/113] courses: smoother steps loading (fixes #7780) (#7808) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../step-view-courses/courses-step-view.component.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eece41d638..68545c356e 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.64", + "version": "0.15.65", "myplanet": { "latest": "v0.21.7", "min": "v0.20.7" diff --git a/src/app/courses/step-view-courses/courses-step-view.component.ts b/src/app/courses/step-view-courses/courses-step-view.component.ts index b06c03cff3..f380e36a80 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.ts +++ b/src/app/courses/step-view-courses/courses-step-view.component.ts @@ -137,11 +137,18 @@ export class CoursesStepViewComponent implements OnInit, OnDestroy { // direction = -1 for previous, 1 for next changeStep(direction) { + this.isLoading = true; this.router.navigate([ '../' + (this.stepNum + direction) ], { relativeTo: this.route }); - this.resource = undefined; + this.resetCourseStep(); this.countActivity = true; } + resetCourseStep() { + this.resource = undefined; + this.stepDetail = { stepTitle: '', description: '', resources: [] }; + this.attempts = 0; + } + backToCourseDetail() { this.router.navigate([ '../../' ], { relativeTo: this.route }); // Challenge option only From 0e1277f6216a818de40f0b64580f6d6e43947e90 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:05:32 -0800 Subject: [PATCH 016/113] all: smoother challenge chat select (fixes #7813) (#7832) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/chat/chat-sidebar/chat-sidebar.component.ts | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 68545c356e..5b51b545a3 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.65", + "version": "0.15.66", "myplanet": { - "latest": "v0.21.7", - "min": "v0.20.7" + "latest": "v0.21.10", + "min": "v0.20.10" }, "scripts": { "ng": "ng", diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.ts b/src/app/chat/chat-sidebar/chat-sidebar.component.ts index 6170dd0b7b..ad0c43b8c8 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.ts +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.ts @@ -73,7 +73,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { subscribeToNewChats() { this.chatService.newChatAdded$ .pipe(takeUntil(this.onDestroy$)) - .subscribe(() => this.getChatHistory()); + .subscribe(() => this.getChatHistory(true)); } subscribeToAIService() { @@ -146,7 +146,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { }); } - getChatHistory() { + getChatHistory(newChat: boolean = false) { this.chatService .findConversations([], [ this.userService.get().name ]) .subscribe( @@ -160,6 +160,9 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { return dateB - dateA; }); this.filteredConversations = [ ...this.conversations ]; + if (newChat) { + this.selectConversation(this.filteredConversations[0], 0); + } this.initializeFormGroups(); }, (error) => console.log(error) From f1b77c2dc9cb7605bb898c904fe3c95b10bb5928 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:11:22 -0500 Subject: [PATCH 017/113] mylife: smoother myhealth form (fixes #7833) (#7834) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health.constants.ts | 2 +- src/app/shared/label.component.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5b51b545a3..1f18c138f9 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.66", + "version": "0.15.67", "myplanet": { "latest": "v0.21.10", "min": "v0.20.10" diff --git a/src/app/health/health.constants.ts b/src/app/health/health.constants.ts index 1f37311ee3..3f4bb4192e 100644 --- a/src/app/health/health.constants.ts +++ b/src/app/health/health.constants.ts @@ -10,7 +10,7 @@ export const conditions = [ $localize`FGM`, $localize`HIV/AIDS`, $localize`Influenza`, - $localize`Ischaemic heat disease`, + $localize`Ischaemic heart disease`, $localize`Malaria`, $localize`Malnutrition`, $localize`Measles`, diff --git a/src/app/shared/label.component.ts b/src/app/shared/label.component.ts index 97a7359d54..ec9f10b1dd 100644 --- a/src/app/shared/label.component.ts +++ b/src/app/shared/label.component.ts @@ -25,7 +25,7 @@ import { Component, Input } from '@angular/core'; Epilepsy {Epilepsy} FGM {FGM} Influenza {Influenza} - Ischaemic heat disease {Ischaemic heat disease} + Ischaemic heart disease {Ischaemic heart disease} Malaria {Malaria} Malnutrition {Malnutrition} Measles {Measles} From efb22c1f4a90deeb33647eed71693e9b40e4a28d Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:40:19 -0500 Subject: [PATCH 018/113] teams: smoother joining (fixes #4530) (#7837) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/teams/teams.component.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1f18c138f9..b5ac785138 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.67", + "version": "0.15.68", "myplanet": { - "latest": "v0.21.10", - "min": "v0.20.10" + "latest": "v0.21.11", + "min": "v0.20.11" }, "scripts": { "ng": "ng", diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 00093e4cfe..719df2ab02 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -269,7 +269,7 @@ export class TeamsComponent implements OnInit, AfterViewInit { finalize(() => this.dialogsLoadingService.stop()) ).subscribe(() => { this.teams.data = this.teamList(this.teams.data); - this.planetMessageService.showMessage($localize`Request to join team sent`); + this.planetMessageService.showMessage($localize`Request to join ${team.name} sent`); }); } From 5401d9d96f120c79bd1b4f9b768a433c94baa7be Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:45:33 -0500 Subject: [PATCH 019/113] mylife: smoother myachievements (fixes #7639) (#7806) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements.component.html | 17 ++++++-- .../users-achievements.component.ts | 10 +++++ .../users-achievements.scss | 41 +++++++++++++++++-- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b5ac785138..b6581862d0 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.68", + "version": "0.15.69", "myplanet": { "latest": "v0.21.11", "min": "v0.20.11" diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index ff40b83dad..4f2f70d554 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -23,19 +23,25 @@ </mat-toolbar> <div class="view-container"> <div *ngIf="achievements !== undefined" class="achievements-container"> - <h2>{{user.firstName}} {{user.middleName}} {{user.lastName}}</h2> - <div> - <ng-container *ngIf="user.birthDate"><span i18n>Birthdate: {{' ' + (user.birthDate | date: medium) + ' '}}</span></ng-container><br/><br/> - <span *ngIf="user.birthplace" i18n>Birthplace: {{' ' + user.birthplace}} </span> + <div class="user-info"> + <img class="profile-image" [src]="profileImg"> + <h2>{{user.firstName}} {{user.middleName}} {{user.lastName}}</h2> + <div class="birth-info"> + <ng-container *ngIf="user.birthDate"><span i18n>Birthdate: {{' ' + (user.birthDate | date: medium) + ' '}}</span></ng-container><br/><br/> + <span *ngIf="user.birthplace" i18n>Birthplace: {{' ' + user.birthplace}} </span> + </div> </div> + <mat-divider></mat-divider> <div *ngIf="achievements.purpose"> <h3 i18n>My Purpose</h3> <td-markdown [content]="achievements.purpose"></td-markdown> </div> + <mat-divider></mat-divider> <div *ngIf="achievements.goals"> <h3 i18n>My Goals</h3> <td-markdown [content]="achievements.goals"></td-markdown> </div> + <mat-divider></mat-divider> <ng-container *planetBeta> <div *ngIf="certifications.length > 0"> <h3 i18n>My Certifications</h3> @@ -46,6 +52,7 @@ <h3 i18n>My Certifications</h3> </mat-list> </div> </ng-container> + <mat-divider></mat-divider> <div *ngIf="achievements?.links?.length > 0"> <h3 i18n>My Links</h3> <mat-list> @@ -55,6 +62,7 @@ <h4 mat-line>{{link.title}}:</h4> </mat-list-item> </mat-list> </div> + <mat-divider></mat-divider> <div *ngIf="achievements.achievementsHeader || achievements.achievements.length > 0"> <h3 i18n>My Achievements</h3> <td-markdown [content]="achievements.achievementsHeader"></td-markdown> @@ -77,6 +85,7 @@ <h3 i18n>My Achievements</h3> </mat-list-item> </mat-list> </div> + <mat-divider></mat-divider> <div *ngIf="achievements.references.length > 0"> <h3 i18n>My References</h3> <mat-list class="references-list"> diff --git a/src/app/users/users-achievements/users-achievements.component.ts b/src/app/users/users-achievements/users-achievements.component.ts index aef47056e6..de990a2643 100644 --- a/src/app/users/users-achievements/users-achievements.component.ts +++ b/src/app/users/users-achievements/users-achievements.component.ts @@ -9,6 +9,7 @@ import { catchError, auditTime } from 'rxjs/operators'; import { throwError, combineLatest } from 'rxjs'; import { StateService } from '../../shared/state.service'; import { CoursesService } from '../../courses/courses.service'; +import { environment } from '../../../environments/environment'; import { CertificationsService } from '../../manager-dashboard/certifications/certifications.service'; const pdfMake = require('pdfmake/build/pdfmake'); @@ -25,6 +26,7 @@ export class UsersAchievementsComponent implements OnInit { achievements: any; achievementNotFound = false; ownAchievements = false; + urlPrefix = environment.couchAddress + '/_users/org.couchdb.user:' + this.userService.get().name + '/'; openAchievementIndex = -1; certifications: any[] = []; @@ -103,6 +105,14 @@ export class UsersAchievementsComponent implements OnInit { return achievement.description.length > 0; } + get profileImg() { + const attachments = this.userService.get()._attachments; + if (attachments) { + return this.urlPrefix + Object.keys(attachments)[0]; + } + return 'assets/image.png'; + } + setCertifications(courses = [], progress = [], certifications = []) { this.certifications = certifications.filter(certification => { const certificateCourses = courses diff --git a/src/app/users/users-achievements/users-achievements.scss b/src/app/users/users-achievements/users-achievements.scss index 17470dd780..c280f57f51 100644 --- a/src/app/users/users-achievements/users-achievements.scss +++ b/src/app/users/users-achievements/users-achievements.scss @@ -1,9 +1,8 @@ @import '../../variables'; .achievements-container { - max-width: 800px; + max-width: 95%; margin: 0 auto; - text-align: center; .references-list { display: flex; @@ -21,6 +20,41 @@ text-align: left; } + mat-divider { + margin-top: 1rem; + background-color: $primary; + } + + .profile-image{ + max-width: 15vh; + border-radius: 50%; + width: 100px; + height: 100px; + } + + .user-info { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + margin-bottom: 2rem; + } + + .birth-info { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + } + + .logo { + display: flex; + justify-content: center; + align-items: center; + margin: 2rem; + } + .mat-list-base .mat-list-item { &.mat-list-item-word-wrap { height: initial; @@ -56,7 +90,6 @@ margin-right: 0; } } - } .styled-link { @@ -100,7 +133,7 @@ gap: 0.2rem; width: 100%; } - + .auto-adjust-buttons a { flex-grow: 1; text-align: center; From d484314eab4488c04dff8ef445d2c11f516d5807 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:59:24 -0500 Subject: [PATCH 020/113] courses: smoother submissions toolbar (fixes #7631) (#7649) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +- src/app/submissions/submission.component.scss | 46 ++++++----- .../submissions/submissions.component.html | 77 ++++++++++++------- src/app/submissions/submissions.component.ts | 18 ++++- 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index b6581862d0..828eca331c 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.69", + "version": "0.15.70", "myplanet": { - "latest": "v0.21.11", - "min": "v0.20.11" + "latest": "v0.21.12", + "min": "v0.20.12" }, "scripts": { "ng": "ng", diff --git a/src/app/submissions/submission.component.scss b/src/app/submissions/submission.component.scss index 2cef47733b..6f1c2635b9 100644 --- a/src/app/submissions/submission.component.scss +++ b/src/app/submissions/submission.component.scss @@ -1,21 +1,25 @@ -@import "../variables"; - -.mat-column-name { - max-width: 25vw; -} -.mat-column-stepNum { - max-width: 90px; -} - -@media (max-width: $screen-sm) { - .responsive-table { - display: block; - width: 100%; - overflow-x: auto; - } - - mat-header-cell, - mat-cell { - min-width: 130px; - } -} +@import "../variables"; + +.mat-column-name { + max-width: 25vw; +} +.mat-column-stepNum { + max-width: 90px; +} + +@media (max-width: $screen-sm) { + .responsive-table { + display: block; + width: 100%; + overflow-x: auto; + } + + mat-header-cell, + mat-cell { + min-width: 130px; + } + + .status-dropdown { + max-width: 100px; + } +} diff --git a/src/app/submissions/submissions.component.html b/src/app/submissions/submissions.component.html index 0f6bcfd33d..2a977bab28 100644 --- a/src/app/submissions/submissions.component.html +++ b/src/app/submissions/submissions.component.html @@ -1,36 +1,57 @@ - <mat-toolbar *ngIf="!isDialog"> - <button mat-icon-button (click)="goBack()"> - <mat-icon>arrow_back</mat-icon> - </button> - <span i18n>{mode, select, survey {mySurveys} grade {Submissions}}</span> - <span class="toolbar-fill"></span> - <ng-container *ngIf="this.mode !== 'survey'"> - <mat-form-field class="font-size-1 margin-lr-3"> - <mat-select i18n-placeholder placeholder="Status" [value]="filter.status || 'All'" (selectionChange)="onFilterChange($event.value, 'status')"> - <mat-option i18n value="All">All</mat-option> - <ng-container *ngFor="let option of statusOptions"> - <mat-option *ngIf="this.filter.type !== 'survey' || option.text !== 'Not Graded'" [value]="option.value">{{option.text}}</mat-option> - </ng-container> - </mat-select> - </mat-form-field> - <mat-button-toggle-group - class="margin-lr-5 font-size-1" - (change)="onFilterChange($event.value, 'type')" - #filterGroup="matButtonToggleGroup"> - <mat-button-toggle value="exam" [checked]="this.filter.type === 'exam'" i18n> - Tests - </mat-button-toggle> - <mat-button-toggle value="survey" [checked]="this.filter.type === 'survey'" i18n> - Surveys - </mat-button-toggle> - </mat-button-toggle-group> - </ng-container> + <mat-toolbar-row> + <button mat-icon-button (click)="goBack()"> + <mat-icon>arrow_back</mat-icon> + </button> + <span i18n>{mode, select, survey {mySurveys} grade {Submissions}}</span> + <span class="toolbar-fill"></span> + <ng-container *ngIf="!isMobile"> + <ng-container *ngTemplateOutlet="filterDropdown"></ng-container> + <ng-container *ngTemplateOutlet="filterToggleGroup"></ng-container> + <ng-container *ngTemplateOutlet="filterSearch"></ng-container> + </ng-container> + <button mat-icon-button *ngIf="isMobile" (click)="showFiltersRow = !showFiltersRow"> + <mat-icon>filter_list</mat-icon> + </button> + </mat-toolbar-row> + <mat-toolbar-row *ngIf="showFiltersRow && isMobile"> + <ng-container *ngTemplateOutlet="filterDropdown"></ng-container> + <ng-container *ngTemplateOutlet="filterToggleGroup"></ng-container> + </mat-toolbar-row> + <mat-toolbar-row *ngIf="showFiltersRow && isMobile"> + <ng-container *ngTemplateOutlet="filterSearch"></ng-container> + </mat-toolbar-row> +</mat-toolbar> + +<ng-template #filterDropdown> + <mat-form-field class="font-size-1 margin-lr-3 status-dropdown"> + <mat-select i18n-placeholder placeholder="Status" [value]="filter.status || 'All'" (selectionChange)="onFilterChange($event.value, 'status')"> + <mat-option i18n value="All">All</mat-option> + <ng-container *ngFor="let option of statusOptions"> + <mat-option *ngIf="this.filter.type !== 'survey' || option.text !== 'Not Graded'" [value]="option.value">{{option.text}}</mat-option> + </ng-container> + </mat-select> + </mat-form-field> +</ng-template> +<ng-template #filterToggleGroup> + <mat-button-toggle-group + class="margin-lr-5 font-size-1" + (change)="onFilterChange($event.value, 'type')" + #filterGroup="matButtonToggleGroup"> + <mat-button-toggle value="exam" [checked]="this.filter.type === 'exam'" i18n> + Tests + </mat-button-toggle> + <mat-button-toggle value="survey" [checked]="this.filter.type === 'survey'" i18n> + Surveys + </mat-button-toggle> + </mat-button-toggle-group> +</ng-template> +<ng-template #filterSearch> <mat-icon>search</mat-icon> <mat-form-field class="font-size-1"> <input matInput (keyup)="applyFilter($event.target.value)" i18n-placeholder placeholder="Type name to search..."> </mat-form-field> -</mat-toolbar> +</ng-template> <div class="primary-link-hover" [ngClass]="{ 'space-container': !isDialog }"> <div class="view-container view-table responsive-table" [ngClass]="{ 'view-full-height no-toolbar': !isDialog }" *ngIf="!emptyData; else notFoundMessage"> diff --git a/src/app/submissions/submissions.component.ts b/src/app/submissions/submissions.component.ts index c178b3eda5..1752a06fc5 100644 --- a/src/app/submissions/submissions.component.ts +++ b/src/app/submissions/submissions.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild, AfterViewChecked, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, HostListener, ViewChild, AfterViewChecked, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; @@ -11,7 +11,7 @@ import { UserService } from '../shared/user.service'; import { findDocuments } from '../shared/mangoQueries'; import { DialogsLoadingService } from '../shared/dialogs/dialogs-loading.service'; import { CoursesService } from '../courses/courses.service'; - +import { DeviceInfoService, DeviceType } from '../shared/device-info.service'; const columnsByFilterAndMode = { exam: { grade: [ 'name', 'courseTitle', 'stepNum', 'status', 'grade', 'user', 'lastUpdateTime', 'gradeTime' ] @@ -38,6 +38,9 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; initTable = true; + isMobile: boolean; + deviceType: DeviceType; + showFiltersRow = false; statusOptions: any = [ { text: $localize`Pending`, value: 'pending' }, { text: $localize`Not Graded`, value: 'requires grading' }, @@ -56,9 +59,12 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy private submissionsService: SubmissionsService, private userService: UserService, private coursesService: CoursesService, - private dialogsLoadingService: DialogsLoadingService + private dialogsLoadingService: DialogsLoadingService, + private deviceInfoService: DeviceInfoService, ) { this.dialogsLoadingService.start(); + this.deviceType = this.deviceInfoService.getDeviceType(); + this.isMobile = this.deviceType === DeviceType.MOBILE; } ngOnInit() { @@ -97,6 +103,12 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy this.setupTable(); } + @HostListener('window:resize') onResize() { + this.deviceType = this.deviceInfoService.getDeviceType(); + this.isMobile = this.deviceType === DeviceType.MOBILE; + this.showFiltersRow = false; + } + ngAfterViewChecked() { if (this.initTable === true) { this.submissions.paginator = this.paginator; From e13d0b082df27662a75dca8b0c120f83ef0412c9 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:04:55 -0500 Subject: [PATCH 021/113] courses: smoother details creation date (fixes #7839) (#7840) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/courses/view-courses/courses-view-detail.component.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 828eca331c..e7ae8c7daa 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.70", + "version": "0.15.71", "myplanet": { "latest": "v0.21.12", "min": "v0.20.12" diff --git a/src/app/courses/view-courses/courses-view-detail.component.html b/src/app/courses/view-courses/courses-view-detail.component.html index c0f14d6783..f96641a797 100644 --- a/src/app/courses/view-courses/courses-view-detail.component.html +++ b/src/app/courses/view-courses/courses-view-detail.component.html @@ -3,6 +3,7 @@ <p><b i18n>Grade Level:</b> <planet-language-label [options]="gradeOptions" [label]="courseDetail?.gradeLevel"></planet-language-label></p> <p *ngIf="courseDetail?.languageOfInstruction"><b i18n>Language of Instruction:</b> <planet-language-label [options]="languageOptions" [label]="courseDetail?.languageOfInstruction || 'N/A'"></planet-language-label></p> <p *ngIf="courseDetail?.creatorDoc"><b i18n>Creator:</b> {{courseDetail?.creatorDoc?.fullName}} </p> +<p><b i18n>Created on:</b> {{(courseDetail?.createdDate | date: 'mediumDate') || 'N/A'}}</p> <p *ngIf="courseDetail?.sourcePlanet !== planetConfiguration.code && courseDetail?.sourcePlanet"><b i18n>Source:</b> {{courseDetail?.sourcePlanet}}</p> <p><b i18n>Description:</b></p> <planet-markdown [content]="courseDetail?.description" [imageSource]="imageSource" class="img-resize"></planet-markdown> From 0839125dbbfddad8b047c00438a2c4ea92aab80a Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:17:29 -0500 Subject: [PATCH 022/113] chat: smoother input autofocus (fixes #7836) (#7844) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../chat/chat-window/chat-window.component.html | 2 +- .../chat/chat-window/chat-window.component.ts | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e7ae8c7daa..90a532ee57 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.71", + "version": "0.15.72", "myplanet": { "latest": "v0.21.12", "min": "v0.20.12" diff --git a/src/app/chat/chat-window/chat-window.component.html b/src/app/chat/chat-window/chat-window.component.html index 3c06a04609..97496763a1 100644 --- a/src/app/chat/chat-window/chat-window.component.html +++ b/src/app/chat/chat-window/chat-window.component.html @@ -11,7 +11,7 @@ <mat-form-field class="full-width mat-form-field-type-no-underline"> <mat-label i18n>Chat</mat-label> <div class="textarea-container"> - <textarea matInput [formControl]="promptForm.controls.prompt" rows="5" placeholder="Send a message" i18n-placeholder></textarea> + <textarea #chatInput matInput [formControl]="promptForm.controls.prompt" rows="5" placeholder="Send a message" i18n-placeholder></textarea> <button mat-icon-button [planetSubmit]="promptForm.valid && spinnerOn" diff --git a/src/app/chat/chat-window/chat-window.component.ts b/src/app/chat/chat-window/chat-window.component.ts index cbeb6908d7..85cdf7ce7d 100644 --- a/src/app/chat/chat-window/chat-window.component.ts +++ b/src/app/chat/chat-window/chat-window.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Input } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Input, AfterViewInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -15,7 +15,7 @@ import { StateService } from '../../shared/state.service'; templateUrl: './chat-window.component.html', styleUrls: [ './chat-window.scss' ], }) -export class ChatWindowComponent implements OnInit, OnDestroy { +export class ChatWindowComponent implements OnInit, OnDestroy, AfterViewInit { private onDestroy$ = new Subject<void>(); spinnerOn = true; streaming: boolean; @@ -34,8 +34,8 @@ export class ChatWindowComponent implements OnInit, OnDestroy { context: '', }; providers: AIProvider[] = []; - @Input() context: any; + @ViewChild('chatInput') chatInput: ElementRef; @ViewChild('chat') chatContainer: ElementRef; constructor( @@ -57,6 +57,10 @@ export class ChatWindowComponent implements OnInit, OnDestroy { }); } + ngAfterViewInit() { + this.focusInput(); + } + ngOnDestroy() { this.onDestroy$.next(); this.onDestroy$.complete(); @@ -69,6 +73,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy { .subscribe(() => { this.selectedConversationId = null; this.conversations = []; + this.focusInput(); }, error => { console.error('Error subscribing to newChatSelected$', error); }); @@ -80,6 +85,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy { .subscribe((conversationId) => { this.selectedConversationId = conversationId; this.fetchConversation(this.selectedConversationId?._id); + this.focusInput(); }, error => { console.error('Error subscribing to selectedConversationId$', error); }); @@ -92,6 +98,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy { this.provider = { name: aiService }; + this.focusInput(); })); } @@ -234,4 +241,8 @@ export class ChatWindowComponent implements OnInit, OnDestroy { ); } } + + focusInput() { + this.chatInput?.nativeElement.focus(); + } } From 2919c7ef1d2a803b1e700123fb09c51f366e3b83 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:39:34 -0500 Subject: [PATCH 023/113] dashboard: smoother calendar recurrence (fixes #7859) (#7863) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/meetups/view-meetups/meetups-view.component.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 90a532ee57..12134b5179 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.72", + "version": "0.15.73", "myplanet": { - "latest": "v0.21.12", - "min": "v0.20.12" + "latest": "v0.21.20", + "min": "v0.20.20" }, "scripts": { "ng": "ng", diff --git a/src/app/meetups/view-meetups/meetups-view.component.html b/src/app/meetups/view-meetups/meetups-view.component.html index 6f4fd2b735..cbdded6dd5 100644 --- a/src/app/meetups/view-meetups/meetups-view.component.html +++ b/src/app/meetups/view-meetups/meetups-view.component.html @@ -36,7 +36,7 @@ <h3 class="margin-lr-3 ellipsis-title">{{meetupDetail?.title}}</h3> <p *ngIf="meetupDetail?.deadline"><b i18n>Deadline:</b> {{meetupDetail?.deadline | date: 'fullDate'}} {{meetupDetail?.deadline | date: 'shortTime'}}</p> <p *ngIf="meetupDetail?.completedTime"><b i18n>Completed On:</b> {{meetupDetail?.completedTime | date: 'fullDate'}} {{meetupDetail?.completedTime | date: 'shortTime'}}</p> <p *ngIf="meetupDetail?.startTime || meetupDetail?.endTime" i18n><b>Time:</b> {{meetupDetail?.startTime}} {{ meetupDetail?.endTime ? '-' : '' }} {{meetupDetail?.endTime}}</p> - <p *ngIf="meetupDetail?.recurring"><b i18n>Recurring:</b> {{meetupDetail?.recurring | titlecase}}</p> + <p *ngIf="meetupDetail?.recurring && meetupDetail?.recurring !== 'none'"><b i18n>Recurring:</b> {{meetupDetail?.recurring | titlecase}} for {{ meetupDetail?.recurringNumber }} {{ meetupDetail?.recurring === 'daily' ? 'days' : 'weeks' }}</p> <p *ngIf="meetupDetail?.recurring === 'weekly'"><b i18n>Recurring Days: </b><span *ngFor="let day of meetupDetail?.day; let isLast= last">{{day}}{{isLast ? '' : ', '}}</span></p> <p *ngIf="meetupDetail?.meetupLocation"><b i18n>Location:</b> {{meetupDetail?.meetupLocation}}</p> <p *ngIf="meetupDetail?.assignee"><b i18n>Assigned to:</b><a class="cursor-pointer" (click)="openProfile(meetupDetail?.assignee?.name, meetupDetail?.assignee?.userPlanetCode)">{{' ' + meetupDetail?.assignee?.name}}</a></p> From 28628eea25dd07a0d434a75b10aff27f925ea348 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:46:06 -0500 Subject: [PATCH 024/113] teams: smoother deletion snackbar (fixes #7852) (#7853) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams.component.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 12134b5179..a2d94c1c61 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.73", + "version": "0.15.74", "myplanet": { "latest": "v0.21.20", "min": "v0.20.20" diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 719df2ab02..19ca325108 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -238,7 +238,12 @@ export class TeamsComponent implements OnInit, AfterViewInit { request: this.teamsService.archiveTeam(team)().pipe(switchMap(() => this.teamsService.deleteCommunityLink(team))), onNext: () => { this.deleteDialog.close(); - this.planetMessageService.showMessage($localize`You have deleted a team.`); + if (this.mode == 'enterprise'){ + this.planetMessageService.showMessage($localize`You have deleted an ${toProperCase(this.mode)}.`); + } + else{ + this.planetMessageService.showMessage($localize`You have deleted a ${toProperCase(this.mode)}.`); + } this.removeTeamFromTable(team); }, onError: () => this.planetMessageService.showAlert($localize`There was a problem deleting this team.`) From 4159fd4a2b26c7fd0159e88e97b5f5e9fe77eb6c Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:51:48 -0500 Subject: [PATCH 025/113] enterprises: smoother list checkmark (fixes #5231) (#7848) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a2d94c1c61..ae08e36115 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.74", + "version": "0.15.75", "myplanet": { "latest": "v0.21.20", "min": "v0.20.20" diff --git a/src/app/teams/teams.component.html b/src/app/teams/teams.component.html index eff232045c..c523f72a3f 100644 --- a/src/app/teams/teams.component.html +++ b/src/app/teams/teams.component.html @@ -45,7 +45,7 @@ <mat-cell *matCellDef="let element"> <h3> {{element.doc.name}} - <mat-icon class="margin-lr-3" i18n-title title="Joined Team" [inline]="true" *ngIf="element.userStatus=='member'">check</mat-icon> + <mat-icon class="margin-lr-3" i18n-title title="{{ mode === 'enterprise' ? 'Joined Enterprise' : 'Joined Team' }}" [inline]="true" *ngIf="element.userStatus=='member'">check</mat-icon> </h3> </mat-cell> </ng-container> From d453e65835e44fbdfe555187bb2cd5ffaeab3caa Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:55:44 -0500 Subject: [PATCH 026/113] teams: smoother document deletion (fixes #4956) (#7847) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/dialogs/dialogs-prompt.component.html | 1 + src/app/teams/teams-view.component.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ae08e36115..63fb90bfb6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.75", + "version": "0.15.76", "myplanet": { "latest": "v0.21.20", "min": "v0.20.20" diff --git a/src/app/shared/dialogs/dialogs-prompt.component.html b/src/app/shared/dialogs/dialogs-prompt.component.html index 78b9e9f782..0157aec2be 100644 --- a/src/app/shared/dialogs/dialogs-prompt.component.html +++ b/src/app/shared/dialogs/dialogs-prompt.component.html @@ -7,6 +7,7 @@ nation {nation} course {course} resource {resource} + document {document} meetup {meetup} user {member} change {change} diff --git a/src/app/teams/teams-view.component.html b/src/app/teams/teams-view.component.html index b4b7caa6f8..9f2796632d 100644 --- a/src/app/teams/teams-view.component.html +++ b/src/app/teams/teams-view.component.html @@ -171,7 +171,7 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration <mat-icon>more_vert</mat-icon> </button> <mat-menu #resourceMenu="matMenu"> - <button mat-menu-item (click)="openDialogPrompt(resource, 'resource', { changeType: 'remove', type: 'resource' })"> + <button mat-menu-item (click)="openDialogPrompt(resource, 'resource', { changeType: 'remove', type: 'document' })"> <mat-icon>clear</mat-icon> <span i18n>Remove</span> </button> From d8fae2a43299442e564b37a63237a46b8f863934 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:35:23 -0500 Subject: [PATCH 027/113] courses: smoother steps icons (fixes #7870) (#7876) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/view-courses/courses-view.scss | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 63fb90bfb6..e296fe9cb5 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.76", + "version": "0.15.77", "myplanet": { - "latest": "v0.21.20", - "min": "v0.20.20" + "latest": "v0.21.24", + "min": "v0.20.24" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/view-courses/courses-view.scss b/src/app/courses/view-courses/courses-view.scss index ca04a3bff8..5b8d6caa87 100644 --- a/src/app/courses/view-courses/courses-view.scss +++ b/src/app/courses/view-courses/courses-view.scss @@ -26,6 +26,14 @@ grid-column-gap: 0; } + .mat-expansion-panel-header-description { + justify-content: flex-end; + } + + .mat-expansion-panel-header-description planet-course-icon { + margin-left: 8px; + } + @media (max-width: $screen-sm) { .course-container { grid-template-columns: 1fr; From 39f16f9446f482d5991794da959d1a52c7ddf367 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:57:05 +0300 Subject: [PATCH 028/113] all: december challenge (fixes #7838) (#7850) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../dialogs-announcement.component.html | 36 +++-- .../dialogs/dialogs-announcement.component.ts | 149 ++++++++++++------ .../shared/user-challenge-status.service.ts | 42 +++-- src/app/teams/teams.component.ts | 5 +- 5 files changed, 152 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index e296fe9cb5..8ae4bd2fb2 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.77", + "version": "0.15.78", "myplanet": { "latest": "v0.21.24", "min": "v0.20.24" diff --git a/src/app/shared/dialogs/dialogs-announcement.component.html b/src/app/shared/dialogs/dialogs-announcement.component.html index f06747ea37..4754422790 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.html +++ b/src/app/shared/dialogs/dialogs-announcement.component.html @@ -1,6 +1,6 @@ <div class="announcement-container"> <img - src="https://res.cloudinary.com/mutugiii/image/upload/v1730395098/challenge_horizontal_new_tnco4v.jpg" + src="https://res.cloudinary.com/mutugiii/image/upload/v1733224910/dec_challenge_svcbi3.jpg" alt="Issues Challenge" class="announcement-banner" /> @@ -12,7 +12,7 @@ class="thermometer-label" [ngClass]="{ outside: getGoalPercentage() < 8 }" > - {{ "$" + getTotalMoneyEarned() }} + ${{ getGroupMoneyEarned() }} / ${{ goal }} </div> </div> </div> @@ -29,28 +29,18 @@ <mat-icon color="primary"> {{ getStatus('joinedCourse') ? 'check_circle' : 'radio_button_unchecked' }} </mat-icon> - <span>Únete al curso Reto noviembre.</span> + <span>Únete al curso Curso Planet & myPlanet. [${{courseStepValue}}]</span> <a *ngIf="!getStatus('joinedCourse')" mat-button mat-raised-button color="primary" type="button" (click)="joinCourse()"> Unirse </a> </div> - <div class="step"> - <mat-icon color="primary"> - {{ getStatus('surveyComplete') ? 'check_circle' : 'radio_button_unchecked' }} - </mat-icon> - <span>¡Encuesta finalizada!</span> - <a *ngIf="getStatus('joinedCourse') && !getStatus('surveyComplete')" mat-button mat-raised-button color="primary" type="button" (click)="doSurvey()"> - Encuesta - </a> - </div> - <div class="step"> <mat-icon color="primary"> {{ getStatus('hasPost') ? 'check_circle' : 'radio_button_unchecked' }} </mat-icon> - <span>Comparte tu opinión en Nuestras Voces. <br><span>{{getStatus('userPosts')}} de 5 Voces diarias</span> </span> - <a *ngIf="getStatus('joinedCourse') && getStatus('surveyComplete') && !getStatus('hasPost'); else dailyVoices" mat-button mat-raised-button color="primary" type="button" (click)="chatNShare()"> + <span>Comparte tu opinión en Nuestras Voces. [${{postStepValue}}/voz]<br><span>{{getPosts()}} de 5 Voces diarias</span> </span> + <a *ngIf="getStatus('joinedCourse') && !getStatus('hasPost'); else dailyVoices" mat-button mat-raised-button color="primary" type="button" (click)="shareVoice()"> Chatea y comparte </a> <ng-template #dailyVoices> @@ -58,11 +48,25 @@ <div *ngFor="let dot of [0, 1, 2, 3, 4]; let i = index" class="dot" - [ngClass]="{ completed: i < getStatus('userPosts') }" + [ngClass]="{ completed: i < getPosts() }" ></div> </div> </ng-template> </div> + + <div class="step"> + <mat-icon color="primary"> + {{ getStatus('surveyComplete') ? 'check_circle' : 'radio_button_unchecked' }} + </mat-icon> + <span>¡Encuesta finalizada! [${{surveyStepValue}}]</span> + <a *ngIf="getStatus('joinedCourse') && getStatus('hasPost') && !getStatus('surveyComplete')" mat-button mat-raised-button color="primary" type="button" (click)="doSurvey()"> + Encuesta + </a> + </div> + + <div> + <span>Mis ganancias: <b>${{getIndividualMoneyEarned()}}</b> / $11</span> + </div> </div> </ng-template> </div> diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts index 780fe1caf3..ec17b7215b 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.ts +++ b/src/app/shared/dialogs/dialogs-announcement.component.ts @@ -11,18 +11,20 @@ import { NewsService } from '../../news/news.service'; import { StateService } from '../state.service'; import { SubmissionsService } from '../../submissions/submissions.service'; import { UserService } from '../user.service'; +import { UsersService } from '../../users/users.service'; import { UserChallengeStatusService } from '../user-challenge-status.service'; import { planetAndParentId } from '../../manager-dashboard/reports/reports.utils'; -export const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'ollonde', 'okuro', 'uriur', 'mutugi', 'vi' ]; -export const challengeCourseId = '9517e3b45a5bb63e69bb8f269216974d'; -export const challengePeriod = (new Date() > new Date(2024, 9, 31)) && (new Date() < new Date(2024, 11, 1)); +export const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'okuro', 'uriur', 'mutugi', 'vi' ]; +export const challengeCourseId = '4e6b78800b6ad18b4e8b0e1e38a98cac'; +export const examId = '4e6b78800b6ad18b4e8b0e1e38b382ab'; +export const challengePeriod = (new Date() > new Date(2024, 10, 31)) && (new Date() < new Date(2024, 12, 1)); @Component({ template: ` <div class="announcement-container"> <img - src="https://res.cloudinary.com/mutugiii/image/upload/v1730395098/challenge_horizontal_new_tnco4v.jpg" + src="https://res.cloudinary.com/mutugiii/image/upload/v1733224910/dec_challenge_svcbi3.jpg" alt="Issues Challenge" class="announcement-banner" /> @@ -45,12 +47,15 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { teamId = planetAndParentId(this.stateService.configuration); submissions = []; groupSummary = []; + members: any; enrolledMembers: any; courseId = challengeCourseId; - startDate = new Date(2024, 9, 31); - endDate = new Date(2024, 11, 1); + startDate = new Date(2024, 10, 31); + endDate = new Date(2024, 12, 1); isLoading = true; - submissionValue = 5; + postStepValue = 2; + courseStepValue = 0; + surveyStepValue = 1; goal = 500; constructor( @@ -62,6 +67,7 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { private stateService: StateService, private submissionsService: SubmissionsService, private userService: UserService, + private usersService: UsersService, private userStatusService: UserChallengeStatusService ) {} @@ -94,8 +100,9 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { }, viewId: this.teamId }); + this.fetchMembers(); this.fetchCourseAndNews(); - this.fetchEnrolled(); + this.fetchEnrolledMembers(); } joinCourse() { @@ -107,27 +114,27 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { } doSurvey() { - this.router.navigate([ `/courses/view/${this.courseId}/step/3/exam`, { + this.router.navigate([ `/courses/view/${this.courseId}/step/5/exam`, { id: this.courseId, - stepNum: 3, + stepNum: 5, questionNum: 1, type: 'survey', preview: 'false', - examId: '83fe016d8a983de6f7112e761c014545' + examId: examId } ]); this.dialogRef.close(); } - chatNShare() { - this.router.navigate([ '/chat' ]); + shareVoice() { + this.router.navigate([ '/' ]); this.dialogRef.close(); } - hasCompletedSurvey(userName: string) { + hasCompletedSurvey(userName: string): boolean { return this.submissions.some(submission => submission.name === userName && submission.status === 'complete'); } - hasSubmittedVoice(news: any[], userName: string) { + hasSubmittedVoice(news: any[], userName: string): number { const uniqueDays = new Set<string>(); news.forEach(post => { @@ -143,11 +150,27 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { return Math.min(uniqueDays.size, 5); } - hasEnrolledCourse(member) { - return member.courseIds.includes(this.courseId); + hasEnrolledCourse(member: any): boolean { + return this.enrolledMembers.some( + (enrolledMember) => + enrolledMember._id === member._id && + enrolledMember.courseIds?.includes(this.courseId) + ); } - fetchEnrolled() { + fetchMembers() { + this.usersService.getAllUsers().subscribe((users: any) => { + this.members = users.map((member: any) => { + const [ , memberName ] = member?._id.split(':'); + return { + ...member, + name: memberName, + }; + }); + }); + } + + fetchEnrolledMembers() { this.couchService.findAll('shelf', { selector: { courseIds: { $elemMatch: { $eq: this.courseId } } }, }).subscribe((members) => { @@ -161,6 +184,43 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { }); } + fetchGroupSummary(news) { + this.members.forEach((member) => { + const hasJoinedCourse = this.hasEnrolledCourse(member); + const hasCompletedSurvey = this.hasCompletedSurvey(member.name); + const userPosts = this.hasSubmittedVoice(news, member.name); + + if (!this.groupSummary.some(m => m.name === member.name)) { + this.groupSummary.push({ + ...member, + userPosts, + courseAmount: hasJoinedCourse ? this.courseStepValue : 0, + surveyAmount: hasCompletedSurvey ? this.surveyStepValue : 0 + }); + } + }); + } + + fetchIndividualSummary(news) { + this.userStatusService.updateStatus('surveyComplete', { + status: this.hasCompletedSurvey(this.currentUserName), + amount: this.surveyStepValue + }); + this.userStatusService.updateStatus('hasPost', { + status: this.hasSubmittedVoice(news, this.currentUserName) > 0, + amount: this.postStepValue + }); + this.userStatusService.updateStatus('userPosts', this.hasSubmittedVoice(news, this.currentUserName)); + this.members.some(member => { + if (member.name === this.currentUserName) { + this.userStatusService.updateStatus('joinedCourse', { + status: this.hasEnrolledCourse(member), + amount: this.courseStepValue + }); + } + }); + } + fetchCourseAndNews() { this.newsService.newsUpdated$.pipe( takeUntil(this.onDestroy$) @@ -175,7 +235,6 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { ) || {} ).public, })); - this.submissionsService.getSubmissions(findDocuments({ type: 'survey' })) .subscribe((submissions: any[]) => { const filteredSubmissions = submissions.filter(submission => submission.parentId.includes(this.courseId)); @@ -184,54 +243,40 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { status: submission.status, time: submission.lastUpdateTime })); - - - // Group Summary - this.enrolledMembers.forEach((member) => { - const hasJoinedCourse = this.hasEnrolledCourse(member); - const hasCompletedSurvey = this.hasCompletedSurvey(member.name); - const hasPosted = this.hasSubmittedVoice(news, member.name) > 0; - const userAmount = this.hasSubmittedVoice(news, member.name); - - if (hasCompletedSurvey && hasPosted && hasJoinedCourse && userAmount > 0) { - if (!this.groupSummary.some(m => m.name === member.name)) { - this.groupSummary.push({ - ...member, - userPosts: userAmount - }); - } - } - }); - - // Individual stats - this.userStatusService.updateStatus('surveyComplete', this.hasCompletedSurvey(this.currentUserName)); - this.userStatusService.updateStatus('hasPost', this.hasSubmittedVoice(news, this.currentUserName) > 0); - this.userStatusService.updateStatus('userPosts', this.hasSubmittedVoice(news, this.currentUserName)); - this.enrolledMembers.some(member => { - if (member.name === this.currentUserName) { - this.userStatusService.updateStatus('joinedCourse', this.hasEnrolledCourse(member)); - } - }); + this.fetchGroupSummary(news); + this.fetchIndividualSummary(news); this.isLoading = false; }, () => this.isLoading = false); }, () => this.isLoading = false); } - getTotalMoneyEarned(): number { + getIndividualMoneyEarned(): number { + const userStatus = this.userStatusService.printStatus(); + const postsEarnings = Number(userStatus.userPosts) * this.postStepValue; + const courseAmount = userStatus.joinedCourse.status ? userStatus.joinedCourse.amount : 0; + const surveyAmount = userStatus.surveyComplete.status ? userStatus.surveyComplete.amount : 0; + return postsEarnings + courseAmount + surveyAmount; + } + + getGroupMoneyEarned(): number { const totalEarned = this.groupSummary.reduce((total, member) => { - const amount = Number(member.userPosts * this.submissionValue); - return total + (isNaN(amount) ? 0 : amount); + const postAmount = Number(member.userPosts * this.postStepValue); + const stepAmounts = member.courseAmount + member.surveyAmount; + return total + (isNaN(postAmount) ? 0 : postAmount) + (isNaN(stepAmounts) ? 0 : stepAmounts); }, 0); - return Math.min(totalEarned, this.goal); } getGoalPercentage(): number { - const totalMoneyEarned = this.getTotalMoneyEarned(); + const totalMoneyEarned = this.getGroupMoneyEarned(); return (totalMoneyEarned / this.goal) * 100; } getStatus(key: string) { return this.userStatusService.getStatus(key); } + + getPosts() { + return this.userStatusService.getPosts(); + } } diff --git a/src/app/shared/user-challenge-status.service.ts b/src/app/shared/user-challenge-status.service.ts index 7825124d62..d779831e4d 100644 --- a/src/app/shared/user-challenge-status.service.ts +++ b/src/app/shared/user-challenge-status.service.ts @@ -5,25 +5,38 @@ import { Injectable } from '@angular/core'; }) export class UserChallengeStatusService { userStatus = { - joinedCourse: false, - surveyComplete: false, - hasPost: false, + joinedCourse: { + status: false, + amount: 0 + }, + surveyComplete: { + status: false, + amount: 0 + }, + hasPost: { + status: false, + amount: 0 + }, userPosts: 0 }; - updateStatus(key: string, value: boolean | number) { + updateStatus(key: string, value: Object | number) { this.userStatus[key] = value; } getCompleteChallenge(): boolean { const complete = Object.values(this.userStatus).every( - (value, index) => index !== 3 && value === true + (value, index) => typeof value === 'object' && 'status' in value && value.status === true ); return complete; } - getStatus(key: string): boolean| number { - return this.userStatus[key]; + getPosts(): number { + return this.userStatus.userPosts; + } + + getStatus(key: string): boolean { + return this.userStatus[key].status; } printStatus(): any { @@ -32,9 +45,18 @@ export class UserChallengeStatusService { resetStatus() { this.userStatus = { - joinedCourse: false, - surveyComplete: false, - hasPost: false, + joinedCourse: { + status: false, + amount: 0 + }, + surveyComplete: { + status: false, + amount: 0 + }, + hasPost: { + status: false, + amount: 0 + }, userPosts: 0 }; } diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 19ca325108..7a39a9ab93 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -238,10 +238,9 @@ export class TeamsComponent implements OnInit, AfterViewInit { request: this.teamsService.archiveTeam(team)().pipe(switchMap(() => this.teamsService.deleteCommunityLink(team))), onNext: () => { this.deleteDialog.close(); - if (this.mode == 'enterprise'){ + if (this.mode === 'enterprise') { this.planetMessageService.showMessage($localize`You have deleted an ${toProperCase(this.mode)}.`); - } - else{ + } else { this.planetMessageService.showMessage($localize`You have deleted a ${toProperCase(this.mode)}.`); } this.removeTeamFromTable(team); From 1e377ed3f270022014e1ba8f80ccc48c0068491e Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:32:00 -0800 Subject: [PATCH 029/113] surveys: smoother sorting (fixes #7802) (#7875) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/submissions/submissions.component.ts | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 8ae4bd2fb2..8c95148d31 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.78", + "version": "0.15.79", "myplanet": { - "latest": "v0.21.24", - "min": "v0.20.24" + "latest": "v0.21.27", + "min": "v0.20.27" }, "scripts": { "ng": "ng", diff --git a/src/app/submissions/submissions.component.ts b/src/app/submissions/submissions.component.ts index 1752a06fc5..7c1bd8f946 100644 --- a/src/app/submissions/submissions.component.ts +++ b/src/app/submissions/submissions.component.ts @@ -37,7 +37,6 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy onDestroy$ = new Subject<void>(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; - initTable = true; isMobile: boolean; deviceType: DeviceType; showFiltersRow = false; @@ -110,10 +109,9 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy } ngAfterViewChecked() { - if (this.initTable === true) { + if (this.sort && this.paginator && !this.submissions.paginator && !this.submissions.sort) { this.submissions.paginator = this.paginator; this.submissions.sort = this.sort; - this.initTable = false; } } @@ -166,8 +164,9 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy // Force filter to update by setting it to a space if empty this.submissions.filter = this.submissions.filter || ' '; this.emptyData = !this.submissions.filteredData.length; - this.initTable = !this.emptyData; this.displayedColumns = columnsByFilterAndMode[this.filter.type][this.mode] || this.displayedColumns; + this.submissions.paginator = undefined; + this.submissions.sort = undefined; } dropdownsFill() { From 101078b5a09c175d052906e440ff7df648ab6acf Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:44:39 -0800 Subject: [PATCH 030/113] chat: smoother textbox size (fixes #7874) (#7881) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/chat/chat-window/chat-window.scss | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8c95148d31..36553c32d5 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.79", + "version": "0.15.80", "myplanet": { "latest": "v0.21.27", "min": "v0.20.27" diff --git a/src/app/chat/chat-window/chat-window.scss b/src/app/chat/chat-window/chat-window.scss index 4a31686b10..d395950bd5 100644 --- a/src/app/chat/chat-window/chat-window.scss +++ b/src/app/chat/chat-window/chat-window.scss @@ -27,9 +27,14 @@ border-radius: 5px; } +.textarea-container textarea { + resize: none; + height: 100px; +} + .textarea-container button { position: absolute; - top: 50%; + top: 70%; right: 8px; transform: translateY(-50%); } From f3d6b62eccd9ddfe9d02334c20dfbbcfb04aa515 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:02:50 -0500 Subject: [PATCH 031/113] courses: smoother steps titles (fixes #7867) (#7873) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/courses/add-courses/courses-step.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 36553c32d5..f728fb8f3a 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.80", + "version": "0.15.81", "myplanet": { "latest": "v0.21.27", "min": "v0.20.27" diff --git a/src/app/courses/add-courses/courses-step.component.html b/src/app/courses/add-courses/courses-step.component.html index a5e1111b91..94db40f307 100644 --- a/src/app/courses/add-courses/courses-step.component.html +++ b/src/app/courses/add-courses/courses-step.component.html @@ -1,6 +1,6 @@ <planet-step-list [steps]="steps" (stepsChange)="stepsMoved($event)" [nameProp]="'stepTitle'" (stepClicked)="stepClick($event)"> <planet-step-list-item *ngFor="let step of steps; index as i; first as isFirst; last as isLast"> - <span i18n>{{step.stepTitle || 'Step ' + (i+1)}}</span> + <span i18n>{{(step.stepTitle || 'Step ' + (i+1)) | slice:0:50}}{{(step.stepTitle || 'Step ' + (i+1)).length > 50 ? '...' : ''}}</span> <planet-course-icon *ngIf="step?.exam?.questions.length" [icon]="'assignment'"></planet-course-icon> <planet-course-icon *ngIf="step?.resources?.length" [icon]="'attach_file'"></planet-course-icon> <planet-course-icon *ngIf="step?.survey?.questions.length" [icon]="'description'"></planet-course-icon> @@ -13,7 +13,7 @@ <div planetStepListForm> <form [formGroup]="stepForm"> <mat-form-field class="full-width"> - <input matInput i18n-placeholder placeholder="Step title" formControlName="stepTitle"> + <input matInput i18n-placeholder placeholder="Step title" maxlength="200" formControlName="stepTitle"> </mat-form-field> <mat-form-field class="full-width mat-form-field-type-no-underline"> <planet-markdown-textbox class="full-width" i18n-placeholder placeholder="Description" [formControl]="stepForm.controls.description" imageGroup="community"></planet-markdown-textbox> From f3f04992496fc037ff44f84ae3ed70ef6647b9f0 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:36:38 -0500 Subject: [PATCH 032/113] courses: smoother progress navigation (fixes #7769) (#7773) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +- .../courses-progress-leader.component.html | 62 +++++++++++-------- .../courses-progress-leader.component.ts | 18 ++++-- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index f728fb8f3a..05e6afc14e 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.81", + "version": "0.15.82", "myplanet": { - "latest": "v0.21.27", - "min": "v0.20.27" + "latest": "v0.21.28", + "min": "v0.20.28" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/progress-courses/courses-progress-leader.component.html b/src/app/courses/progress-courses/courses-progress-leader.component.html index e978628454..0cff81fd1f 100644 --- a/src/app/courses/progress-courses/courses-progress-leader.component.html +++ b/src/app/courses/progress-courses/courses-progress-leader.component.html @@ -1,31 +1,43 @@ <mat-toolbar> <a mat-icon-button (click)="navigateBack()"><mat-icon>arrow_back</mat-icon></a> + <span> + <ng-container *ngIf="selectedStep === undefined" i18n>Course Progress</ng-container> + <ng-container *ngIf="selectedStep !== undefined" i18n>Test Progress</ng-container> + </span> </mat-toolbar> -<div class="space-container"> - <mat-toolbar class="primary-color font-size-1 action-buttons"> - <span> - {{headingStart ? headingStart + ' ' : ''}} - <ng-container *ngIf="selectedStep === undefined" i18n>Course Progress</ng-container> - <ng-container *ngIf="selectedStep !== undefined" i18n>Test Progress</ng-container> - </span> +<mat-toolbar class="primary-color font-size-1"> + <span><h3 class="margin-lr-3 ellipsis-title">{{ headingStart }}</h3></span> + <ng-container *ngIf="deviceType === deviceTypes.DESKTOP"> <span class="toolbar-fill"></span> - <planet-selector *ngIf="planetCodes.length > 1" [planetCodes]="planetCodes" (selectionChange)="planetSelectionChange($event)"></planet-selector> - <mat-form-field *ngIf="submittedExamSteps?.length > 1"> - <mat-select i18n-placeholder placeholder="View Test Progress" [value]="selectedStep" (selectionChange)="onStepChange($event.value)"> - <mat-option *ngFor="let step of submittedExamSteps" [value]="step" i18n> - {{ step.stepTitle || 'Step ' + (step.index + 1) }} - </mat-option> - </mat-select> - </mat-form-field> - <button mat-raised-button color="accent" class="margin-lr-3" *ngIf="selectedStep !== undefined" (click)="resetToFullCourse()" i18n>Show full course</button> - <button *ngIf="chartData?.length" class="margin-lr-10" color="accent" mat-raised-button i18n (click)="exportChartData()"> - Export - </button> - </mat-toolbar> - <div class="view-container view-full-height"> - <planet-courses-progress-chart *ngIf="chartData?.length; else noProgress" [label]="chartLabel" [inputs]="chartData" [height]="yAxisLength" [showAvatar]="true" (clickAction)="memberClick($event)" (changeData)="changeData($event)"> - </planet-courses-progress-chart> - <ng-template #noProgress i18n>No Progress record available</ng-template> - </div> + <ng-container *ngTemplateOutlet="filterSelectors"></ng-container> + <ng-container *ngTemplateOutlet="actionButtons"></ng-container> + </ng-container> + <ng-container *ngIf="deviceType !== deviceTypes.DESKTOP"> + <span class="toolbar-fill"></span> + <button class="menu" *ngIf="chartData?.length" mat-icon-button [matMenuTriggerFor]="actionsMenu"><mat-icon>more_vert</mat-icon></button> + <mat-menu #actionsMenu="matMenu" class="actions-menu"> + <ng-container *ngTemplateOutlet="filterSelectors"></ng-container> + <ng-container *ngTemplateOutlet="actionButtons"></ng-container> + </mat-menu> + </ng-container> +</mat-toolbar> +<ng-template #filterSelectors> + <planet-selector *ngIf="planetCodes.length > 1" [planetCodes]="planetCodes" (selectionChange)="planetSelectionChange($event)"></planet-selector> + <mat-form-field *ngIf="submittedExamSteps?.length > 1" class="margin-lr-3"> + <mat-select i18n-placeholder placeholder="View Test Progress" [value]="selectedStep" (selectionChange)="onStepChange($event.value)"> + <mat-option *ngFor="let step of submittedExamSteps" [value]="step" i18n> + {{ step.stepTitle || 'Step ' + (step.index + 1) }} + </mat-option> + </mat-select> + </mat-form-field> +</ng-template> +<ng-template #actionButtons> + <button mat-raised-button color="accent" *ngIf="selectedStep !== undefined" (click)="resetToFullCourse()" class="margin-lr-3" i18n>Show full course</button> + <button mat-raised-button color="accent" *ngIf="chartData?.length" (click)="exportChartData()" class="margin-lr-3" i18n>Export</button> +</ng-template> +<div class="view-container view-full-height"> + <planet-courses-progress-chart *ngIf="chartData?.length; else noProgress" [label]="chartLabel" [inputs]="chartData" [height]="yAxisLength" [showAvatar]="true" (clickAction)="memberClick($event)" (changeData)="changeData($event)"> + </planet-courses-progress-chart> + <ng-template #noProgress i18n>No Progress record available</ng-template> </div> diff --git a/src/app/courses/progress-courses/courses-progress-leader.component.ts b/src/app/courses/progress-courses/courses-progress-leader.component.ts index 8ab04286f6..c6a36c59d9 100644 --- a/src/app/courses/progress-courses/courses-progress-leader.component.ts +++ b/src/app/courses/progress-courses/courses-progress-leader.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { Subject } from 'rxjs'; @@ -10,15 +10,14 @@ import { dedupeObjectArray } from '../../shared/utils'; import { DialogsLoadingService } from '../../shared/dialogs/dialogs-loading.service'; import { findDocuments } from '../../shared/mangoQueries'; import { UserProfileDialogComponent } from '../../users/users-profile/users-profile-dialog.component'; +import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; @Component({ - templateUrl: 'courses-progress-leader.component.html', - styleUrls: [ 'courses-progress.scss' ] + templateUrl: 'courses-progress-leader.component.html' }) export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { course: any; - // Need to define this variable for template which is shared with CoursesProgressLearner headingStart = ''; chartLabel = $localize`Steps`; selectedStep: any; @@ -32,6 +31,8 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { submittedExamSteps: any[] = []; planetCodes: string[] = []; selectedPlanetCode: string; + deviceType: DeviceType; + deviceTypes = DeviceType; constructor( private router: Router, @@ -40,9 +41,11 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { private submissionsService: SubmissionsService, private csvService: CsvService, private dialogsLoadingService: DialogsLoadingService, - private dialog: MatDialog + private dialog: MatDialog, + private deviceInfoService: DeviceInfoService ) { this.dialogsLoadingService.start(); + this.deviceType = this.deviceInfoService.getDeviceType(); } ngOnInit() { @@ -66,6 +69,11 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { this.onDestroy$.complete(); } + @HostListener('window:resize') + onResize() { + this.deviceType = this.deviceInfoService.getDeviceType(); + } + setProgress(course) { this.coursesService.findProgress([ course._id ], { allUsers: true }).subscribe((progress) => { this.progress = progress; From cf3f47c67775fa579345e1d7b36ded73b641759e Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:05:10 -0800 Subject: [PATCH 033/113] courses: smoother steps preview title (fixes #7868) (#7879) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../view-courses/courses-view.component.html | 2 +- .../courses/view-courses/courses-view.scss | 26 ++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 05e6afc14e..5a3593831e 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.82", + "version": "0.15.83", "myplanet": { "latest": "v0.21.28", "min": "v0.20.28" diff --git a/src/app/courses/view-courses/courses-view.component.html b/src/app/courses/view-courses/courses-view.component.html index b6eedb7993..3e33f0bd70 100644 --- a/src/app/courses/view-courses/courses-view.component.html +++ b/src/app/courses/view-courses/courses-view.component.html @@ -65,7 +65,7 @@ <h3 class="margin-lr-3 ellipsis-title">{{courseDetail.courseTitle}}</h3> <ng-container *ngFor="let step of courseDetail.steps; let stepNum = index; trackBy: trackBySteps"> <mat-expansion-panel hideToggle="true" (opened)="setStepButtonStatus(step, stepNum)"> <mat-expansion-panel-header> - <mat-panel-title i18n> + <mat-panel-title class="step-title" i18n> {{step.stepTitle || 'Step ' + (stepNum + 1)}} </mat-panel-title> <mat-panel-description> diff --git a/src/app/courses/view-courses/courses-view.scss b/src/app/courses/view-courses/courses-view.scss index 5b8d6caa87..5db279dfb1 100644 --- a/src/app/courses/view-courses/courses-view.scss +++ b/src/app/courses/view-courses/courses-view.scss @@ -47,10 +47,30 @@ grid-template-columns: 1fr; grid-template-rows: 0 1fr; } + + } +} - .course-detail, .course-view { - overflow: visible; - } +mat-expansion-panel-header { + display: flex; + justify-content: space-between; +} + +mat-panel-title { + display: block; +} + +.step-title { + max-width: 150ch; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + @media (max-width: $screen-md) { + max-width: 90ch; } + @media (max-width: $screen-sm) { + max-width: 45ch; + } } From af73fc9a664f976abd2b9ffd1a8735827998c6c9 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:37:07 +0300 Subject: [PATCH 034/113] all: smoother empty tables (fixes #7882) (#7883) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/courses.component.html | 9 ++++----- src/app/courses/courses.component.ts | 3 --- .../enroll-courses/courses-enroll.component.html | 7 +------ .../enroll-courses/courses-enroll.component.ts | 2 -- src/app/feedback/feedback.component.html | 11 +++++------ src/app/feedback/feedback.component.ts | 2 -- src/app/health/health-list.component.html | 7 +------ src/app/health/health-list.component.ts | 2 -- .../certifications/certifications.component.html | 8 ++++---- .../certifications/certifications.component.ts | 2 -- .../manager-dashboard/manager-fetch.component.html | 8 ++++---- src/app/manager-dashboard/manager-fetch.component.ts | 2 -- src/app/meetups/meetups.component.html | 8 ++++---- src/app/meetups/meetups.component.ts | 2 -- src/app/notifications/notifications.component.html | 8 ++++---- src/app/notifications/notifications.component.ts | 2 -- src/app/resources/resources.component.html | 8 ++++---- src/app/resources/resources.component.ts | 3 --- src/app/submissions/submissions.component.html | 8 ++++---- src/app/submissions/submissions.component.ts | 3 --- src/app/surveys/surveys.component.html | 10 +++++----- src/app/surveys/surveys.component.ts | 2 -- src/app/teams/teams.component.html | 8 ++++---- src/app/teams/teams.component.ts | 2 -- src/app/users/users-table.component.html | 3 +++ src/app/users/users.component.html | 7 +------ src/app/users/users.component.ts | 1 - 28 files changed, 51 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 5a3593831e..cd6d82b9d9 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.83", + "version": "0.15.84", "myplanet": { - "latest": "v0.21.28", - "min": "v0.20.28" + "latest": "v0.21.29", + "min": "v0.20.29" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/courses.component.html b/src/app/courses/courses.component.html index 840bcd8da2..554f750401 100644 --- a/src/app/courses/courses.component.html +++ b/src/app/courses/courses.component.html @@ -104,7 +104,7 @@ </ng-container> </ng-template> - <div [ngClass]="{ 'view-container view-full-height view-table': !isForm }" *ngIf="!emptyData; else notFoundMessage"> + <div [ngClass]="{ 'view-container view-full-height view-table': !isForm }"> <mat-table #table [dataSource]="courses" matSort [matSortDisableClear]="true" [trackBy]="trackById"> <ng-container matColumnDef="select"> <mat-header-cell *matHeaderCellDef> @@ -231,15 +231,14 @@ <h3 class="header"> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="{highlight:selection.isSelected(row._id)}"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Course Found</div></td> + </tr> </mat-table> - <mat-paginator #paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 20, 50, 100, 200]" (page)="onPaginateChange($event)"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Course Found</div> - </ng-template> </div> diff --git a/src/app/courses/courses.component.ts b/src/app/courses/courses.component.ts index 51f489444e..f036d867cb 100644 --- a/src/app/courses/courses.component.ts +++ b/src/app/courses/courses.component.ts @@ -88,7 +88,6 @@ export class CoursesComponent implements OnInit, OnChanges, AfterViewInit, OnDes userShelf: any = []; private onDestroy$ = new Subject<void>(); planetType = this.planetConfiguration.planetType; - emptyData = false; isAuthorized = false; tagFilter = new FormControl([]); tagFilterValue = []; @@ -158,7 +157,6 @@ export class CoursesComponent implements OnInit, OnChanges, AfterViewInit, OnDes this.userShelf = this.userService.shelf; this.courses.data = this.setupList(courses, this.userShelf.courseIds) .filter((course: any) => this.excludeIds.indexOf(course._id) === -1); - this.emptyData = !this.courses.data.length; this.dialogsLoadingService.stop(); }); this.selection.changed.subscribe(({ source }) => { @@ -335,7 +333,6 @@ export class CoursesComponent implements OnInit, OnChanges, AfterViewInit, OnDes removeFilteredFromSelection() { this.selection.deselect(...selectedOutOfFilter(this.courses.filteredData, this.selection, this.paginator)); - this.emptyData = this.courses.filteredData.length === 0; } onSearchChange({ items, category }) { diff --git a/src/app/courses/enroll-courses/courses-enroll.component.html b/src/app/courses/enroll-courses/courses-enroll.component.html index fab9af1283..e05788bd80 100644 --- a/src/app/courses/enroll-courses/courses-enroll.component.html +++ b/src/app/courses/enroll-courses/courses-enroll.component.html @@ -13,10 +13,5 @@ </button> </mat-toolbar> - <ng-container *ngIf="!emptyData; else notFoundMessage"> - <planet-users-table [users]="members" [(tableState)]="tableState" [displayedColumns]="userTableColumns" [shouldOpenProfileDialog]="true" containerClass="view-container view-full-height view-table"></planet-users-table> - </ng-container> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No User Found</div> - </ng-template> + <planet-users-table [users]="members" [(tableState)]="tableState" [displayedColumns]="userTableColumns" [shouldOpenProfileDialog]="true" containerClass="view-container view-full-height view-table"></planet-users-table> </div> diff --git a/src/app/courses/enroll-courses/courses-enroll.component.ts b/src/app/courses/enroll-courses/courses-enroll.component.ts index dbcb54e23d..79fc7e7319 100644 --- a/src/app/courses/enroll-courses/courses-enroll.component.ts +++ b/src/app/courses/enroll-courses/courses-enroll.component.ts @@ -22,7 +22,6 @@ export class CoursesEnrollComponent { course: any; members: any[] = []; tableState = new TableState(); - emptyData = false; userTableColumns = [ 'profile', 'name', @@ -82,7 +81,6 @@ export class CoursesEnrollComponent { ), planet: planets.find(planet => planet.doc.code === user.doc.planetCode) })).filter(doc => doc.planet !== undefined && (doc.activityDates.createdDate || shelfUsers.find((u: any) => u._id === doc._id))); - this.emptyData = this.members.length === 0; } exportCSV() { diff --git a/src/app/feedback/feedback.component.html b/src/app/feedback/feedback.component.html index f98b6ffdc2..3990964b1d 100644 --- a/src/app/feedback/feedback.component.html +++ b/src/app/feedback/feedback.component.html @@ -63,7 +63,7 @@ </mat-toolbar-row> </mat-toolbar> - <div class="view-container view-full-height view-table" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-full-height view-table"> <mat-table #table [dataSource]="feedback" matSort [matSortDisableClear]="true"> <!-- Title Column --> <ng-container matColumnDef="title"> @@ -148,12 +148,11 @@ </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> - <mat-row *matRowDef="let row; columns: displayedColumns" class="cursor-pointer" [routerLink]="['view', row._id]"> - </mat-row> + <mat-row *matRowDef="let row; columns: displayedColumns" class="cursor-pointer" [routerLink]="['view', row._id]"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Feedback Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 20, 50, 100, 200]" i18n></mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Feedback Found</div> - </ng-template> </div> diff --git a/src/app/feedback/feedback.component.ts b/src/app/feedback/feedback.component.ts index d7ac201d0c..494792b1d6 100644 --- a/src/app/feedback/feedback.component.ts +++ b/src/app/feedback/feedback.component.ts @@ -53,7 +53,6 @@ export class FeedbackComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild(MatSort) sort: MatSort; user: any = {}; private onDestroy$ = new Subject<void>(); - emptyData = false; users = []; deviceType: DeviceType; deviceTypes: typeof DeviceType = DeviceType; @@ -115,7 +114,6 @@ export class FeedbackComponent implements OnInit, AfterViewInit, OnDestroy { const selector = !this.user.isUserAdmin ? { 'owner': this.user.name } : { '_id': { '$gt': null } }; this.couchService.findAll(this.dbName, findDocuments(selector, 0, [ { 'openTime': 'desc' } ])).subscribe((feedbackData: any[]) => { this.feedback.data = feedbackData.map(feedback => ({ ...feedback, user: this.users.find(u => u.doc.name === feedback.owner) })); - this.emptyData = !this.feedback.data.length; this.dialogsLoadingService.stop(); }, (error) => this.message = $localize`There is a problem of getting data.`); } diff --git a/src/app/health/health-list.component.html b/src/app/health/health-list.component.html index 4cbda7cc81..1ed73b87bd 100644 --- a/src/app/health/health-list.component.html +++ b/src/app/health/health-list.component.html @@ -10,10 +10,5 @@ </mat-toolbar> <div class="space-container"> - <ng-container *ngIf="!emptyData; else notFoundMessage"> - <planet-users-table #table [users]="users" [search]="searchValue" [filter]="{}" [(tableState)]="tableState" [displayedColumns]="displayedColumns" containerClass="view-container view-full-height no-toolbar view-table" (tableDataChange)="tableDataChange($event)"></planet-users-table> - </ng-container> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No User Found</div> - </ng-template> + <planet-users-table #table [users]="users" [search]="searchValue" [filter]="{}" [(tableState)]="tableState" [displayedColumns]="displayedColumns" containerClass="view-container view-full-height no-toolbar view-table" (tableDataChange)="tableDataChange($event)"></planet-users-table> </div> diff --git a/src/app/health/health-list.component.ts b/src/app/health/health-list.component.ts index 0cc6a03bc5..7b23924d05 100644 --- a/src/app/health/health-list.component.ts +++ b/src/app/health/health-list.component.ts @@ -16,7 +16,6 @@ export class HealthListComponent implements OnInit, OnDestroy { users: any[] = []; displayedColumns = [ 'profile', 'name', 'contact', 'birthDate', 'lastVisit' ]; tableState = new TableState(); - emptyData = true; healthRequests: string[] = []; constructor( @@ -29,7 +28,6 @@ export class HealthListComponent implements OnInit, OnDestroy { ngOnInit() { this.usersService.usersListener().pipe(takeUntil(this.onDestroy$)).subscribe(users => { this.users = users; - this.emptyData = this.users.length === 0; }); this.usersService.requestUserData(); } diff --git a/src/app/manager-dashboard/certifications/certifications.component.html b/src/app/manager-dashboard/certifications/certifications.component.html index 3b77224266..2795550906 100644 --- a/src/app/manager-dashboard/certifications/certifications.component.html +++ b/src/app/manager-dashboard/certifications/certifications.component.html @@ -15,7 +15,7 @@ <mat-toolbar class="primary-color font-size-1"> <a i18n mat-mini-fab routerLink="add"><mat-icon>add</mat-icon></a> </mat-toolbar> - <div *ngIf="!emptyData; else notFoundMessage" class="view-container view-full-height view-table"> + <div class="view-container view-full-height view-table"> <mat-table #table [dataSource]="certifications" matSort [matSortDisableClear]="true"> <ng-container matColumnDef="name"> <mat-header-cell i18n *matHeaderCellDef mat-sort-header="name">Certification Name</mat-header-cell> @@ -34,13 +34,13 @@ </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row class="cursor-pointer" *matRowDef="let row; columns: displayedColumns;" [routerLink]="[ 'view/' + row._id ]"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div i18n class="view-container" i18n>No Certifications Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 20, 50, 100, 200]"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div i18n class="view-container" i18n>No Certifications Found</div> - </ng-template> </div> diff --git a/src/app/manager-dashboard/certifications/certifications.component.ts b/src/app/manager-dashboard/certifications/certifications.component.ts index d2e0a06ef4..c1bccf735a 100644 --- a/src/app/manager-dashboard/certifications/certifications.component.ts +++ b/src/app/manager-dashboard/certifications/certifications.component.ts @@ -13,7 +13,6 @@ export class CertificationsComponent implements OnInit, AfterViewInit { certifications = new MatTableDataSource(); selection = new SelectionModel(true, []); - emptyData = false; displayedColumns = [ 'name', 'action' @@ -54,7 +53,6 @@ export class CertificationsComponent implements OnInit, AfterViewInit { getCertifications() { this.certificationsService.getCertifications().subscribe((certifications: any) => { this.certifications.data = certifications; - this.emptyData = !this.certifications.data.length; }); } diff --git a/src/app/manager-dashboard/manager-fetch.component.html b/src/app/manager-dashboard/manager-fetch.component.html index e8870be32b..285a4fdb5c 100644 --- a/src/app/manager-dashboard/manager-fetch.component.html +++ b/src/app/manager-dashboard/manager-fetch.component.html @@ -11,7 +11,7 @@ </button> </mat-toolbar> - <div class="view-container view-full-height view-table" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-full-height view-table"> <mat-table #table [dataSource]="pushedItems" matSort> <ng-container matColumnDef="select"> <mat-header-cell *matHeaderCellDef> @@ -41,6 +41,9 @@ </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;" class="cursor-pointer" [ngClass]="{highlight:selection.isSelected(row._id)}" (click)="selection.toggle(row._id)"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Record Found</div></td> + </tr> </mat-table> <mat-paginator #paginator @@ -49,7 +52,4 @@ (page)="onPaginateChange($event)"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Record Found</div> - </ng-template> </div> diff --git a/src/app/manager-dashboard/manager-fetch.component.ts b/src/app/manager-dashboard/manager-fetch.component.ts index 6d02ce7757..b2dde6d9c0 100644 --- a/src/app/manager-dashboard/manager-fetch.component.ts +++ b/src/app/manager-dashboard/manager-fetch.component.ts @@ -32,7 +32,6 @@ export class ManagerFetchComponent implements OnInit, AfterViewInit { planetConfiguration = this.stateService.configuration; displayedColumns = [ 'select', 'item', 'date' ]; pushedItems = new MatTableDataSource(); - emptyData = false; constructor( private couchService: CouchService, @@ -46,7 +45,6 @@ export class ManagerFetchComponent implements OnInit, AfterViewInit { ngOnInit() { this.managerService.getPushedList().subscribe((pushedList: any) => { this.pushedItems.data = pushedList; - this.emptyData = !this.pushedItems.data.length; }); } diff --git a/src/app/meetups/meetups.component.html b/src/app/meetups/meetups.component.html index f797434d05..36d84e5b81 100644 --- a/src/app/meetups/meetups.component.html +++ b/src/app/meetups/meetups.component.html @@ -34,7 +34,7 @@ </ng-container> </mat-toolbar> - <div class="view-container view-full-height view-table" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-full-height view-table"> <mat-table #table [dataSource]="meetups" matSort [matSortDisableClear]="true"> <ng-container matColumnDef="select"> <mat-header-cell *matHeaderCellDef> @@ -103,6 +103,9 @@ <h3 class="header"> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns" class="hide"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="{highlight:selection.isSelected(row._id)}"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Meetups Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" @@ -110,7 +113,4 @@ <h3 class="header"> (page)="onPaginateChange($event)"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Meetups Found</div> - </ng-template> </div> diff --git a/src/app/meetups/meetups.component.ts b/src/app/meetups/meetups.component.ts index 3bc929eee7..33210263a6 100644 --- a/src/app/meetups/meetups.component.ts +++ b/src/app/meetups/meetups.component.ts @@ -43,7 +43,6 @@ export class MeetupsComponent implements OnInit, AfterViewInit, OnDestroy { getOpts = this.parent ? { domain: this.stateService.configuration.parentDomain } : {}; pageEvent: PageEvent; currentUser = this.userService.get(); - emptyData = false; selectedNotJoined = 0; selectedJoined = 0; isAuthorized = false; @@ -69,7 +68,6 @@ export class MeetupsComponent implements OnInit, AfterViewInit, OnDestroy { // Sort in descending createdDate order, so the new meetup can be shown on the top meetups.sort((a, b) => b.createdDate - a.createdDate); this.meetups.data = meetups; - this.emptyData = !this.meetups.data.length; this.dialogsLoadingService.stop(); }); this.meetupService.updateMeetups({ opts: this.getOpts }); diff --git a/src/app/notifications/notifications.component.html b/src/app/notifications/notifications.component.html index 14635947b7..3788896032 100644 --- a/src/app/notifications/notifications.component.html +++ b/src/app/notifications/notifications.component.html @@ -11,7 +11,7 @@ </mat-select> </mat-form-field> </mat-toolbar> - <ng-container *ngIf="!emptyData; else notFoundMessage"> + <ng-container> <mat-table #table [dataSource]="notifications"> <ng-container matColumnDef="message"> <mat-cell *matCellDef="let element" (click)="readNotification(element)"> @@ -37,13 +37,13 @@ </mat-cell> </ng-container> <mat-row *matRowDef="let row; columns: displayedColumns;" ></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Notification Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 20, 50, 100, 200]"> </mat-paginator> </ng-container> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Notification Found</div> - </ng-template> </div> diff --git a/src/app/notifications/notifications.component.ts b/src/app/notifications/notifications.component.ts index 0dec83f4c4..19198b770b 100644 --- a/src/app/notifications/notifications.component.ts +++ b/src/app/notifications/notifications.component.ts @@ -22,7 +22,6 @@ export class NotificationsComponent implements OnInit, AfterViewInit { notifications = new MatTableDataSource<any>(); displayedColumns = [ 'message', 'read' ]; private onDestroy$ = new Subject<void>(); - emptyData = false; notificationStatus = [ 'All', 'Read', 'Unread' ]; filter = { 'status': '' }; anyUnread = true; @@ -65,7 +64,6 @@ export class NotificationsComponent implements OnInit, AfterViewInit { .subscribe(notifications => { this.notifications.data = notifications; this.anyUnread = this.notifications.data.some(notification => notification.status === 'unread'); - this.emptyData = !this.notifications.data.length; }, (err) => console.log(err.error.reason)); } diff --git a/src/app/resources/resources.component.html b/src/app/resources/resources.component.html index 50375e431e..7d8ad39798 100644 --- a/src/app/resources/resources.component.html +++ b/src/app/resources/resources.component.html @@ -103,7 +103,7 @@ </mat-menu> </ng-template> - <div class="view-container view-full-height view-table" [ngClass]="{'view-with-search':showFilters}" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-full-height view-table" [ngClass]="{'view-with-search':showFilters}"> <mat-table #table [dataSource]="resources" [matSortActive]="initialSort" matSortDirection="desc" matSort [matSortDisableClear]="true" [trackBy]="trackById"> <ng-container matColumnDef="select"> <mat-header-cell *matHeaderCellDef> @@ -208,6 +208,9 @@ <h3 class="header"> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns" class="hide"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="{highlight:selection.isSelected(row._id)}"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Resource Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" @@ -215,7 +218,4 @@ <h3 class="header"> (page)="onPaginateChange($event)"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Resource Found</div> - </ng-template> </div> diff --git a/src/app/resources/resources.component.ts b/src/app/resources/resources.component.ts index 27a9975646..91cae48c56 100644 --- a/src/app/resources/resources.component.ts +++ b/src/app/resources/resources.component.ts @@ -71,7 +71,6 @@ export class ResourcesComponent implements OnInit, AfterViewInit, OnDestroy { this.removeFilteredFromSelection(); } myView = this.route.snapshot.data.view; - emptyData = false; selectedNotAdded = 0; selectedAdded = 0; selectedSync = []; @@ -138,7 +137,6 @@ export class ResourcesComponent implements OnInit, AfterViewInit, OnDestroy { (resource.doc.private === true && (resource.doc.privateFor || {}).users === this.userService.get()._id) : resource.doc.private !== true) ); - this.emptyData = !this.resources.data.length; this.resources.paginator = this.paginator; this.dialogsLoadingService.stop(); }); @@ -168,7 +166,6 @@ export class ResourcesComponent implements OnInit, AfterViewInit, OnDestroy { removeFilteredFromSelection() { this.selection.deselect(...selectedOutOfFilter(this.resources.filteredData, this.selection, this.paginator)); - this.emptyData = this.resources.filteredData.length === 0; } diff --git a/src/app/submissions/submissions.component.html b/src/app/submissions/submissions.component.html index 2a977bab28..950e0b2fbf 100644 --- a/src/app/submissions/submissions.component.html +++ b/src/app/submissions/submissions.component.html @@ -54,7 +54,7 @@ </ng-template> <div class="primary-link-hover" [ngClass]="{ 'space-container': !isDialog }"> - <div class="view-container view-table responsive-table" [ngClass]="{ 'view-full-height no-toolbar': !isDialog }" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-table responsive-table" [ngClass]="{ 'view-full-height no-toolbar': !isDialog }"> <mat-table #table [dataSource]="submissions" matSort [matSortDisableClear]="true"> <ng-container matColumnDef="name"> <mat-header-cell *matHeaderCellDef mat-sort-header="name" i18n>Name</mat-header-cell> @@ -103,13 +103,13 @@ </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns" class="hide"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;" (click)="submissionAction(row)" [ngClass]="{'cursor-pointer': row.status!=='pending' || mode === 'survey'}"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Record Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 20, 50, 100, 200]"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Record Found</div> - </ng-template> </div> diff --git a/src/app/submissions/submissions.component.ts b/src/app/submissions/submissions.component.ts index 7c1bd8f946..dbc3a39ed9 100644 --- a/src/app/submissions/submissions.component.ts +++ b/src/app/submissions/submissions.component.ts @@ -46,7 +46,6 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy { text: $localize`Completed`, value: 'complete' } ]; mode = 'grade'; - emptyData = false; filter = { type: 'exam', status: 'requires grading' @@ -96,7 +95,6 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy })); this.dialogsLoadingService.stop(); this.applyFilter(''); - this.emptyData = !this.submissions.filteredData.length; }); this.submissionsService.updateSubmissions({ query: this.submissionQuery() }); this.setupTable(); @@ -163,7 +161,6 @@ export class SubmissionsComponent implements OnInit, AfterViewChecked, OnDestroy this.filter[field] = filterValue === 'All' ? '' : filterValue; // Force filter to update by setting it to a space if empty this.submissions.filter = this.submissions.filter || ' '; - this.emptyData = !this.submissions.filteredData.length; this.displayedColumns = columnsByFilterAndMode[this.filter.type][this.mode] || this.displayedColumns; this.submissions.paginator = undefined; this.submissions.sort = undefined; diff --git a/src/app/surveys/surveys.component.html b/src/app/surveys/surveys.component.html index 37ac02a856..cd3302a9bd 100644 --- a/src/app/surveys/surveys.component.html +++ b/src/app/surveys/surveys.component.html @@ -17,13 +17,13 @@ </button> <span class="toolbar-fill"></span> <ng-container *planetAuthorizedRoles="'manager'"> - <button mat-button (click)="deleteSelected()" [disabled]="!selection.selected.length" *ngIf="!emptyData"> + <button mat-button (click)="deleteSelected()" [disabled]="!selection.selected.length"> <mat-icon aria-hidden="true" class="margin-lr-3">delete_forever</mat-icon><span i18n>Delete</span> <span *ngIf="selection?.selected?.length"> ({{selection?.selected?.length}})</span> </button> </ng-container> </mat-toolbar> - <div class="view-container view-full-height view-table" *ngIf="!emptyData; else notFoundMessage"> + <div class="view-container view-full-height view-table"> <mat-table #table [dataSource]="surveys" matSort matSortActive="createdDate" matSortDirection="desc" [matSortDisableClear]="true"> <ng-container matColumnDef="select"> <mat-header-cell *matHeaderCellDef> @@ -81,6 +81,9 @@ </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No Survey Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" @@ -88,7 +91,4 @@ (page)="onPaginateChange($event)"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No Survey Found</div> - </ng-template> </div> diff --git a/src/app/surveys/surveys.component.ts b/src/app/surveys/surveys.component.ts index 78ed9b4b0c..8df508e2fc 100644 --- a/src/app/surveys/surveys.component.ts +++ b/src/app/surveys/surveys.component.ts @@ -39,7 +39,6 @@ export class SurveysComponent implements OnInit, AfterViewInit, OnDestroy { dialogRef: MatDialogRef<DialogsAddTableComponent>; private onDestroy$ = new Subject<void>(); readonly dbName = 'exams'; - emptyData = false; isAuthorized = false; deleteDialog: any; message = ''; @@ -82,7 +81,6 @@ export class SurveysComponent implements OnInit, AfterViewInit, OnDestroy { ...this.createParentSurveys(submissions) ]; this.surveys.data = this.surveys.data.map((data: any) => ({ ...data, courseTitle: data.course ? data.course.courseTitle : '' })); - this.emptyData = !this.surveys.data.length; this.dialogsLoadingService.stop(); }); this.couchService.checkAuthorization(this.dbName).subscribe((isAuthorized) => this.isAuthorized = isAuthorized); diff --git a/src/app/teams/teams.component.html b/src/app/teams/teams.component.html index c523f72a3f..033295d432 100644 --- a/src/app/teams/teams.component.html +++ b/src/app/teams/teams.component.html @@ -38,7 +38,7 @@ </ng-container> </mat-toolbar> - <div class="primary-link-hover" *ngIf="!emptyData; else notFoundMessage" [ngClass]="{'view-container view-table view-full-height':!isDialog}"> + <div class="primary-link-hover" [ngClass]="{'view-container view-table view-full-height':!isDialog}"> <mat-table #table [dataSource]="teams" matSortActive="visitLog.visitCount" matSortDirection="desc" matSort [matSortDisableClear]="true"> <ng-container matColumnDef="doc.name"> <mat-header-cell *matHeaderCellDef mat-sort-header="doc.name" i18n>Name</mat-header-cell> @@ -116,6 +116,9 @@ <h3> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row (click)="teamClick(row.doc._id, row.doc.teamType)" *matRowDef="let row; columns: displayedColumns;"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No { mode, select, team {Teams} enterprise {Enterprises} } Found</div></td> + </tr> </mat-table> <div *ngIf="userNotInShelf" i18n style="text-align: center;"> Oops ... Error: no full access in Teams and Enterprises (data missing) ... please contact the manager of this site @@ -125,7 +128,4 @@ <h3> [pageSizeOptions]="[5, 10, 20, 50, 100, 200]"> </mat-paginator> </div> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No { mode, select, team {Teams} enterprise {Enterprises} } Found</div> - </ng-template> </div> diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 7a39a9ab93..758e5d7fd2 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -33,7 +33,6 @@ export class TeamsComponent implements OnInit, AfterViewInit { userMembership: any[] = []; teamActivities: any[] = []; dbName = 'teams'; - emptyData = false; user = this.userService.get(); isAuthorized = false; planetType = this.stateService.configuration.planetType; @@ -123,7 +122,6 @@ export class TeamsComponent implements OnInit, AfterViewInit { )) { this.userService.addImageForReplication(true).subscribe(() => {}); } - this.emptyData = !this.teams.data.length; this.dialogsLoadingService.stop(); }, (error) => { if (this.userNotInShelf) { diff --git a/src/app/users/users-table.component.html b/src/app/users/users-table.component.html index 6a6b009457..613bf4c617 100644 --- a/src/app/users/users-table.component.html +++ b/src/app/users/users-table.component.html @@ -136,6 +136,9 @@ </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row [ngClass]="{'cursor-pointer':!isDialog}" *matRowDef= "let row; columns: displayedColumns;" (click)="gotoProfileView(row.doc.name)" [ngClass]="{highlight:isSelected(row.doc)}"></mat-row> + <tr class="mat-row" *matNoDataRow> + <td><div class="view-container" i18n>No User Found</div></td> + </tr> </mat-table> <mat-paginator #paginator [pageSize]="50" diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html index 754f62e9a6..62da847bea 100644 --- a/src/app/users/users.component.html +++ b/src/app/users/users.component.html @@ -82,10 +82,5 @@ </div> </mat-toolbar> - <ng-container *ngIf="!emptyData; else notFoundMessage"> - <planet-users-table #table [users]="users" [search]="searchValue" [filter]="filter" [(tableState)]="tableState" containerClass="view-container view-full-height view-table" [isDialog]="isDialog"></planet-users-table> - </ng-container> - <ng-template #notFoundMessage> - <div class="view-container" i18n>No User Found</div> - </ng-template> + <planet-users-table #table [users]="users" [search]="searchValue" [filter]="filter" [(tableState)]="tableState" containerClass="view-container view-full-height view-table" [isDialog]="isDialog"></planet-users-table> </div> diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts index 5023c17003..688b602736 100644 --- a/src/app/users/users.component.ts +++ b/src/app/users/users.component.ts @@ -46,7 +46,6 @@ export class UsersComponent implements OnInit, OnDestroy { filteredRole: string; userShelf = this.userService.shelf; private onDestroy$ = new Subject<void>(); - emptyData = false; private searchChange = new Subject<string>(); configuration = this.stateService.configuration; tableState = new TableState(); From 7298f8b8362f4d690e4eb10f32811624cbe4ef95 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:45:49 -0800 Subject: [PATCH 035/113] surveys: smoother loading (fixes #7877) (#7903) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/exams/exams-view.component.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cd6d82b9d9..9dc4a39345 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.84", + "version": "0.15.85", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 380bcb1937..04cd67d8c1 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -274,6 +274,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { } this.isNewQuestion = false; this.isComplete = this.unansweredQuestions && this.unansweredQuestions.every(number => this.questionNum === number); + this.isLoading = false; }); } From 6d3a25023fa7e8bab2cbeaa7fcb89e0e39360f56 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:54:42 -0500 Subject: [PATCH 036/113] teams: smoother delete message (fixes #7885) (#7899) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams.component.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9dc4a39345..3d2849eef3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.85", + "version": "0.15.86", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 758e5d7fd2..9fdb471b82 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -236,11 +236,8 @@ export class TeamsComponent implements OnInit, AfterViewInit { request: this.teamsService.archiveTeam(team)().pipe(switchMap(() => this.teamsService.deleteCommunityLink(team))), onNext: () => { this.deleteDialog.close(); - if (this.mode === 'enterprise') { - this.planetMessageService.showMessage($localize`You have deleted an ${toProperCase(this.mode)}.`); - } else { - this.planetMessageService.showMessage($localize`You have deleted a ${toProperCase(this.mode)}.`); - } + const entityType = this.mode === 'enterprise' ? 'enterprise' : 'team'; + this.planetMessageService.showMessage($localize`You have deleted ${entityType} ${team.name}.`); this.removeTeamFromTable(team); }, onError: () => this.planetMessageService.showAlert($localize`There was a problem deleting this team.`) From f65801e19eff46948cd7fcfa5f8b45798d005216 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:00:53 +0300 Subject: [PATCH 037/113] courses: smoother filter list (fixes #7880) (#7901) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/courses/search-courses/courses-search.component.ts | 3 ++- .../resources/search-resources/resources-search.component.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3d2849eef3..f41223f140 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.86", + "version": "0.15.87", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/courses/search-courses/courses-search.component.ts b/src/app/courses/search-courses/courses-search.component.ts index f48e0e80fc..96479f0486 100644 --- a/src/app/courses/search-courses/courses-search.component.ts +++ b/src/app/courses/search-courses/courses-search.component.ts @@ -25,7 +25,7 @@ import { MatSelectionList } from '@angular/material/list'; </span> <mat-selection-list (selectionChange)="selectionChange($event)"> <mat-list-option *ngFor="let item of items" [value]="item.value" [selected]="isSelected(item)" checkboxPosition="before"> - {{item.label}} + {{item?.label || 'N/A'}} </mat-list-option> </mat-selection-list> `, @@ -116,6 +116,7 @@ export class CoursesSearchComponent implements OnInit, OnChanges { items: data.reduce((list, { doc }) => list.concat(doc[category.label]), []).reduce(dedupeShelfReduce, []).filter(item => item) .filter(item => typeof item === 'string' && item.trim() !== '') .sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1).map(item => category.options.find(opt => opt.value === item)) + .filter(item => item) }); } diff --git a/src/app/resources/search-resources/resources-search.component.ts b/src/app/resources/search-resources/resources-search.component.ts index 81faad4cb0..560dac1e3d 100644 --- a/src/app/resources/search-resources/resources-search.component.ts +++ b/src/app/resources/search-resources/resources-search.component.ts @@ -26,7 +26,7 @@ import { MatSelectionList } from '@angular/material/list'; </span> <mat-selection-list (selectionChange)="selectionChange($event)"> <mat-list-option *ngFor="let item of items" [value]="item.value" [selected]="isSelected(item)" checkboxPosition="before"> - {{item.label}} + {{item?.label || 'N/A'}} </mat-list-option> </mat-selection-list> `, @@ -117,6 +117,7 @@ export class ResourcesSearchComponent implements OnInit, OnChanges { category: category.label, items: data.reduce((list, { doc }) => list.concat(doc[category.label]), []).reduce(dedupeShelfReduce, []).filter(item => item) .sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1).map(item => category.options.find(opt => opt.value === item)) + .filter(item => item) }); } From 7c83d31fba218c0c1358b6905342f216715f0911 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:10:43 -0500 Subject: [PATCH 038/113] resources: smoother buttons (fixes #7889) (#7897) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/resources/resources.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f41223f140..4c6a833d45 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.87", + "version": "0.15.88", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/resources/resources.component.html b/src/app/resources/resources.component.html index 7d8ad39798..61657b0ac4 100644 --- a/src/app/resources/resources.component.html +++ b/src/app/resources/resources.component.html @@ -174,8 +174,8 @@ <h3 class="header"> </ng-container> <ng-container *ngIf="element.canManage && myView !== 'myPersonals'"> <a mat-menu-item (click)="updateResource(element)"> - <mat-icon>folder</mat-icon> - <span i18n>Manage</span> + <mat-icon>edit</mat-icon> + <span i18n>Edit Resource</span> </a> <button mat-menu-item (click)="deleteClick(element)"> <mat-icon>delete_forever</mat-icon> From b1459c1549185c0b6db388122e348b1020cd608a Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:19:21 -0500 Subject: [PATCH 039/113] enterprises: smoother csv exports (fixes #7887)(fixes #7888) (#7898) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams-reports.component.ts | 14 ++++++++++++-- src/app/teams/teams-view-finances.component.ts | 9 ++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4c6a833d45..a3ca0208ae 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.88", + "version": "0.15.89", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/teams/teams-reports.component.ts b/src/app/teams/teams-reports.component.ts index a9a658cb85..f7cccabeba 100644 --- a/src/app/teams/teams-reports.component.ts +++ b/src/app/teams/teams-reports.component.ts @@ -11,6 +11,7 @@ import { DialogsPromptComponent } from '../shared/dialogs/dialogs-prompt.compone import { tap } from 'rxjs/operators'; import { convertUtcDate } from './teams.utils'; import { CsvService } from '../shared/csv.service'; +import { StateService } from '../shared/state.service'; @Component({ selector: 'planet-teams-reports', @@ -25,6 +26,8 @@ export class TeamsReportsComponent implements DoCheck { @Output() reportsChanged = new EventEmitter<void>(); columns = 4; minColumnWidth = 300; + configuration: any = {}; + planetName: any; constructor( private couchService: CouchService, @@ -33,7 +36,8 @@ export class TeamsReportsComponent implements DoCheck { private dialogsLoadingService: DialogsLoadingService, private teamsService: TeamsService, private csvService: CsvService, - private elementRef: ElementRef + private elementRef: ElementRef, + private stateService: StateService, ) {} ngDoCheck() { @@ -161,7 +165,13 @@ export class TeamsReportsComponent implements DoCheck { 'Profit/Loss': report.sales + report.otherIncome - report.wages - report.otherExpenses, 'Ending Balance': report.beginningBalance + report.sales + report.otherIncome - report.wages - report.otherExpenses })); - this.csvService.exportCSV({ data: exportData, title: $localize`${this.team.name} Financial Report Summary` }); + const planetName = this.stateService.configuration.name || 'Unnamed'; + const entityLabel = this.configuration.planetType === 'nation' ? 'Nation' : 'Community'; + const titleName = this.team.name || `${entityLabel} ${planetName}`; + this.csvService.exportCSV({ + data: exportData, + title: $localize`Financial Summary for ${titleName}` + }); } } diff --git a/src/app/teams/teams-view-finances.component.ts b/src/app/teams/teams-view-finances.component.ts index dcd9d68500..797173a57f 100644 --- a/src/app/teams/teams-view-finances.component.ts +++ b/src/app/teams/teams-view-finances.component.ts @@ -34,7 +34,8 @@ export class TeamsViewFinancesComponent implements OnInit, OnChanges { emptyTable = true; showBalanceWarning = false; curCode = this.stateService.configuration.currency || {}; - + configuration: any = {}; + planetName: any; constructor( private csvService: CsvService, private couchService: CouchService, @@ -198,10 +199,12 @@ export class TeamsViewFinancesComponent implements OnInit, OnChanges { debit: row.debit, balance: row.balance })); - + const planetName = this.stateService.configuration.name || 'Unnamed'; + const entityLabel = this.configuration.planetType === 'nation' ? 'Nation' : 'Community'; + const titleName = this.team.name || `${entityLabel} ${planetName}`; this.csvService.exportCSV({ data: updatedData, - title: $localize`Financial Transactions for ${this.team.name} Enterprise` + title: $localize`Financial Transactions for ${titleName}` }); } From b5502335e4bbfd0a5a8e898cf7d625ee63a8f964 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:24:53 -0500 Subject: [PATCH 040/113] dashboard: smoother drag and drop (fixes #7886) (#7902) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/dashboard/dashboard-tile.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a3ca0208ae..26544e38e9 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.89", + "version": "0.15.90", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/dashboard/dashboard-tile.component.html b/src/app/dashboard/dashboard-tile.component.html index a66844b4f9..6cda7acb0e 100644 --- a/src/app/dashboard/dashboard-tile.component.html +++ b/src/app/dashboard/dashboard-tile.component.html @@ -25,7 +25,7 @@ #dashboardTile > <p [matBadge]="item.badge" [matBadgeHidden]="item.badge===0" matBadgeOverlap="false">{{item.firstLine}}</p> - <p class="dashboard-text" [ngStyle]="{ '-webkit-line-clamp': tileLines }">{{item.title}}</p> + <p class="dashboard-text" [ngStyle]="{ '-webkit-line-clamp': tileLines }">{{item.title | slice:0:80}}</p> <button mat-icon-button class="delete-item" (click)="removeFromShelf($event, item)" *ngIf="cardType!=='myLife' && !item?.canRemove"> <mat-icon i18n-matTooltip [matTooltip]="'Remove from ' + cardTitle" [inline]="true">clear</mat-icon> </button> From 54c9b79fcd16860747d869337ff6d668d985b6c9 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:29:36 -0500 Subject: [PATCH 041/113] courses: smoother toolbar (fixes #7878) (#7904) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/courses/view-courses/courses-view.component.html | 2 +- src/app/courses/view-courses/courses-view.scss | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 26544e38e9..8e475f7603 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.90", + "version": "0.15.91", "myplanet": { "latest": "v0.21.29", "min": "v0.20.29" diff --git a/src/app/courses/view-courses/courses-view.component.html b/src/app/courses/view-courses/courses-view.component.html index 3e33f0bd70..208a55281d 100644 --- a/src/app/courses/view-courses/courses-view.component.html +++ b/src/app/courses/view-courses/courses-view.component.html @@ -49,7 +49,7 @@ <h3 class="margin-lr-3 ellipsis-title">{{courseDetail.courseTitle}}</h3> Leave </button> </ng-container> - <button mat-raised-button color="accent" (click)="viewStep()" i18n [disabled]="!courseDetail?.steps?.length">View Step</button> + <button class="view-step-button" mat-raised-button color="accent" (click)="viewStep()" i18n [disabled]="!courseDetail?.steps?.length">View Step</button> </ng-template> <div class="view-container view-full-height"> diff --git a/src/app/courses/view-courses/courses-view.scss b/src/app/courses/view-courses/courses-view.scss index 5db279dfb1..810af1acae 100644 --- a/src/app/courses/view-courses/courses-view.scss +++ b/src/app/courses/view-courses/courses-view.scss @@ -34,6 +34,10 @@ margin-left: 8px; } + .view-step-button{ + flex-shrink: 0; + } + @media (max-width: $screen-sm) { .course-container { grid-template-columns: 1fr; From e078d848d40aa9392864c4b52c0dbf4ea4562b7c Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:47:37 -0500 Subject: [PATCH 042/113] teams: smoother list (fixes #6546) (#7864) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/teams/teams.scss | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 8e475f7603..c0f250749e 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.91", + "version": "0.15.92", "myplanet": { - "latest": "v0.21.29", - "min": "v0.20.29" + "latest": "v0.21.32", + "min": "v0.20.32" }, "scripts": { "ng": "ng", diff --git a/src/app/teams/teams.scss b/src/app/teams/teams.scss index 50f96491f6..0e6a2cfae0 100644 --- a/src/app/teams/teams.scss +++ b/src/app/teams/teams.scss @@ -1,23 +1,22 @@ @import "../variables.scss"; :host { - /* Column Widths */ .mat-column-doc-teamType { max-width: 150px; padding-right: 0.5rem; } .mat-column-visitLog-visitCount { + justify-content: center; max-width: 80px; - padding-right: 0.5rem; - } - .mat-column-visitLog-lastVisit { - max-width: 180px; - padding-right: 0.5rem; + padding-right: 1rem; + margin-right: 1rem } + mat-row { cursor: pointer; } + .button-container { display: flex; flex-direction: row; From e5aae0c5a4d81ceb0cb103010e33ad689e71cdb7 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:54:06 -0500 Subject: [PATCH 043/113] enterprises: smoother report dialog (fixes #6946) (#7908) Co-authored-by: Sahil <sahilvunnam423@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams-reports-dialog.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c0f250749e..b0d8bcb3fe 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.92", + "version": "0.15.93", "myplanet": { "latest": "v0.21.32", "min": "v0.20.32" diff --git a/src/app/teams/teams-reports-dialog.component.html b/src/app/teams/teams-reports-dialog.component.html index 408eb3fd1a..e2d00fd271 100644 --- a/src/app/teams/teams-reports-dialog.component.html +++ b/src/app/teams/teams-reports-dialog.component.html @@ -6,5 +6,5 @@ <h3>{{teamName}} <ng-container i18n>Financial Report</ng-container></h3> <planet-teams-reports-detail [report]="report" [showSummary]="true"></planet-teams-reports-detail> </mat-dialog-content> <mat-dialog-actions> - <button mat-raised-button color="primary" mat-dialog-close i18n>Close</button> + <button mat-raised-button mat-dialog-close i18n>Close</button> </mat-dialog-actions> From ec6de361b960e5ab15fffab7dd15b2a3828475e1 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:59:13 -0500 Subject: [PATCH 044/113] courses: smoother progress title (fixes #7906) (#7909) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../progress-courses/courses-progress-leader.component.html | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b0d8bcb3fe..45ca1ecc2f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.93", + "version": "0.15.94", "myplanet": { "latest": "v0.21.32", "min": "v0.20.32" diff --git a/src/app/courses/progress-courses/courses-progress-leader.component.html b/src/app/courses/progress-courses/courses-progress-leader.component.html index 0cff81fd1f..6a189f4016 100644 --- a/src/app/courses/progress-courses/courses-progress-leader.component.html +++ b/src/app/courses/progress-courses/courses-progress-leader.component.html @@ -7,7 +7,9 @@ </mat-toolbar> <mat-toolbar class="primary-color font-size-1"> - <span><h3 class="margin-lr-3 ellipsis-title">{{ headingStart }}</h3></span> + <h3 class="ellipsis-title margin-lr-3"> + {{ headingStart | slice:0:140 }} + </h3> <ng-container *ngIf="deviceType === deviceTypes.DESKTOP"> <span class="toolbar-fill"></span> <ng-container *ngTemplateOutlet="filterSelectors"></ng-container> From 4e4763a393edcf86d617c6460d2ac334665e272a Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:02:07 -0800 Subject: [PATCH 045/113] courses: smoother ratings (fixes #7726) (#7737) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- .../courses-view-detail.component.ts | 2 -- .../forms/planet-rating-stars.component.ts | 11 ++++++++++- .../shared/forms/planet-rating.component.html | 3 ++- .../shared/forms/planet-rating.component.ts | 18 ++++++++++++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 45ca1ecc2f..38dc830819 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.94", + "version": "0.15.95", "myplanet": { - "latest": "v0.21.32", - "min": "v0.20.32" + "latest": "v0.21.34", + "min": "v0.20.34" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/view-courses/courses-view-detail.component.ts b/src/app/courses/view-courses/courses-view-detail.component.ts index cc3be81e73..f601c8857b 100644 --- a/src/app/courses/view-courses/courses-view-detail.component.ts +++ b/src/app/courses/view-courses/courses-view-detail.component.ts @@ -29,7 +29,6 @@ export class CoursesViewDetailComponent implements OnChanges { ngOnChanges() { this.imageSource = this.parent === true ? 'parent' : 'local'; } - } @Component({ @@ -68,5 +67,4 @@ export class CoursesViewDetailDialogComponent implements OnInit { routeToCourses(courseId) { this.router.navigate([ '../../courses/view/', courseId ], { relativeTo: this.route }); } - } diff --git a/src/app/shared/forms/planet-rating-stars.component.ts b/src/app/shared/forms/planet-rating-stars.component.ts index 7d8a3346eb..9dfdb78834 100644 --- a/src/app/shared/forms/planet-rating-stars.component.ts +++ b/src/app/shared/forms/planet-rating-stars.component.ts @@ -3,6 +3,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { MatFormFieldControl } from '@angular/material/form-field'; import { Subject } from 'rxjs'; import { ControlValueAccessor, NgControl } from '@angular/forms'; +import { UserService } from '../user.service'; @Component({ selector: 'planet-rating-stars', @@ -51,11 +52,14 @@ export class PlanetRatingStarsComponent implements MatFormFieldControl<number>, this.onChange(rating); this.stateChanges.next(); } + @Input() isEnrolled: (id: any, type: any) => boolean; + @Input() itemId: (id: any) => void; + @Input() type: string; private _value = 0; onChange(_: any) {} - constructor(@Optional() @Self() public ngControl: NgControl) { + constructor(@Optional() @Self() public ngControl: NgControl, private userService: UserService) { if (this.ngControl) { this.ngControl.valueAccessor = this; } @@ -101,6 +105,11 @@ export class PlanetRatingStarsComponent implements MatFormFieldControl<number>, } onStarClick(rating: number): void { + if (this.isEnrolled) { + if (!this.isEnrolled(this.itemId, this.type)) { + return; + } + } this.writeValue(rating); } diff --git a/src/app/shared/forms/planet-rating.component.html b/src/app/shared/forms/planet-rating.component.html index 4bd0e0133e..06f04a5efb 100644 --- a/src/app/shared/forms/planet-rating.component.html +++ b/src/app/shared/forms/planet-rating.component.html @@ -14,7 +14,8 @@ <div class="your-rating" *ngIf="!parent"> <form [formGroup]="rateForm" novalidate> - <planet-rating-stars (click)="onStarClick()" formControlName="rate" [disabled]="disabled"></planet-rating-stars> + <planet-rating-stars (click)="onStarClick()" formControlName="rate" [disabled]="disabled" [isEnrolled]="isEnrolled" [itemId]="item._id" [type]="ratingType"> + </planet-rating-stars> </form> </div> diff --git a/src/app/shared/forms/planet-rating.component.ts b/src/app/shared/forms/planet-rating.component.ts index eaaa7b2109..9e2a9e15c0 100644 --- a/src/app/shared/forms/planet-rating.component.ts +++ b/src/app/shared/forms/planet-rating.component.ts @@ -45,6 +45,7 @@ export class PlanetRatingComponent implements OnChanges { popupForm: FormGroup; isPopupOpen = false; stackedBarData = []; + enrolled = true; get rateFormField() { return { rate: this.rating.userRating.rate || 0 }; } @@ -83,7 +84,24 @@ export class PlanetRatingComponent implements OnChanges { this.popupForm.setValue(Object.assign({}, this.rateFormField, this.commentField)); } + isEnrolled(id: any, type: any): boolean { + const idType = type === 'course' ? 'courseIds' : 'resourceIds'; + const { inShelf } = this.userService.countInShelf([ id ], idType); + return inShelf; + } + onStarClick(form = this.rateForm) { + if (!this.isEnrolled(this.item._id, this.ratingType)) { + if (this.ratingType === 'course') { + this.planetMessage.showMessage($localize`Please join the ${this.ratingType} before rating!`); + } else { + this.planetMessage.showMessage($localize`Please add the ${this.ratingType} to your library before rating!`); + } + this.enrolled = false; + return; + } + + this.enrolled = true; if (this.disabled || form.controls.rate.value === 0) { return; } From b71b1ff2fc620be7c65a17f45003d833577e2d1d Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:11:25 -0800 Subject: [PATCH 046/113] manager: smoother reports subheading (fixes #4418) (#7914) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../reports/reports-detail.component.html | 34 +++++++++---------- .../reports/reports-detail.scss | 4 +++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 38dc830819..0e5ba002f4 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.95", + "version": "0.15.96", "myplanet": { "latest": "v0.21.34", "min": "v0.20.34" diff --git a/src/app/manager-dashboard/reports/reports-detail.component.html b/src/app/manager-dashboard/reports/reports-detail.component.html index 97026c3c21..2d65d55362 100644 --- a/src/app/manager-dashboard/reports/reports-detail.component.html +++ b/src/app/manager-dashboard/reports/reports-detail.component.html @@ -77,7 +77,7 @@ <div> <h1 class="mat-title" i18n>Members</h1> <mat-grid-list cols="2" rowHeight="2rem"> - <mat-grid-tile class="label" i18n>Total Members</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Members</mat-grid-tile> <mat-grid-tile>{{reports?.totalUsers}}</mat-grid-tile> <mat-grid-tile class="label"> <mat-icon>subdirectory_arrow_right</mat-icon> @@ -94,10 +94,10 @@ <h1 class="mat-title" i18n>Members</h1> <span i18n>Did not specify</span> </mat-grid-tile> <mat-grid-tile>{{reports?.usersByGender?.didNotSpecify || 0}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Total Visits</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Visits</mat-grid-tile> <mat-grid-tile>{{reports?.totalMemberVisits}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Members by Visits</mat-grid-tile> - <mat-grid-tile i18n>Total Visits by Member</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Members by Visits</mat-grid-tile> + <mat-grid-tile i18n class="subheading">Total Visits by Member</mat-grid-tile> <ng-container *ngIf="!reports?.visits || reports?.visits.length === 0"> <mat-grid-tile colspan="2" i18n>There are no visits</mat-grid-tile> </ng-container> @@ -113,12 +113,12 @@ <h1 class="mat-title" i18n>Members</h1> <div> <h1 class="mat-title" i18n>Resources</h1> <mat-grid-list cols="2" rowHeight="2rem"> - <mat-grid-tile class="label" i18n>Total Resources</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Resources</mat-grid-tile> <mat-grid-tile>{{reports?.totalResources}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Total Views</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Views</mat-grid-tile> <mat-grid-tile>{{reports?.totalResourceViews}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Most Visited</mat-grid-tile> - <mat-grid-tile i18n>Total Visits by Resource</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Most Visited</mat-grid-tile> + <mat-grid-tile class="subheading" i18n>Total Visits by Resource</mat-grid-tile> <ng-container *ngIf="!reports?.resources || reports?.resources.length === 0"> <mat-grid-tile colspan="2" i18n>No resources have been visited</mat-grid-tile> </ng-container> @@ -129,8 +129,8 @@ <h1 class="mat-title" i18n>Resources</h1> </mat-grid-tile> <mat-grid-tile>{{resource.count}}</mat-grid-tile> </ng-container> - <mat-grid-tile class="label" i18n>Highest Rated</mat-grid-tile> - <mat-grid-tile i18n>Average Rating</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Highest Rated</mat-grid-tile> + <mat-grid-tile class="subheading" i18n>Average Rating</mat-grid-tile> <ng-container *ngIf="!reports?.resourceRatings || reports?.resourceRatings.length === 0"> <mat-grid-tile colspan="2" i18n>There are no resource ratings</mat-grid-tile> </ng-container> @@ -146,14 +146,14 @@ <h1 class="mat-title" i18n>Resources</h1> <div> <h1 class="mat-title" i18n>Courses</h1> <mat-grid-list cols="2" rowHeight="2rem"> - <mat-grid-tile class="label" i18n>Total Courses</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Courses</mat-grid-tile> <mat-grid-tile>{{reports?.totalCourses}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Total Views</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Total Views</mat-grid-tile> <mat-grid-tile>{{reports?.totalCourseViews}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Steps Completed</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Steps Completed</mat-grid-tile> <mat-grid-tile>{{reports?.totalStepCompleted}}</mat-grid-tile> - <mat-grid-tile class="label" i18n>Most Visited</mat-grid-tile> - <mat-grid-tile i18n>Total Visits by Course</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Most Visited</mat-grid-tile> + <mat-grid-tile class="subheading" i18n>Total Visits by Course</mat-grid-tile> <ng-container *ngIf="!reports?.courses || reports?.courses.length === 0"> <mat-grid-tile colspan="2" i18n>No courses have been visited</mat-grid-tile> </ng-container> @@ -164,8 +164,8 @@ <h1 class="mat-title" i18n>Courses</h1> </mat-grid-tile> <mat-grid-tile>{{course.count}}</mat-grid-tile> </ng-container> - <mat-grid-tile class="label" i18n>Highest Rated</mat-grid-tile> - <mat-grid-tile i18n>Average Rating</mat-grid-tile> + <mat-grid-tile class="subheading label" i18n>Highest Rated</mat-grid-tile> + <mat-grid-tile class="subheading" i18n>Average Rating</mat-grid-tile> <ng-container *ngIf="!reports?.courseRatings || reports?.courseRatings.length === 0"> <mat-grid-tile colspan="2" i18n>There are no course ratings</mat-grid-tile> </ng-container> diff --git a/src/app/manager-dashboard/reports/reports-detail.scss b/src/app/manager-dashboard/reports/reports-detail.scss index 1ed5d76211..9931af899f 100644 --- a/src/app/manager-dashboard/reports/reports-detail.scss +++ b/src/app/manager-dashboard/reports/reports-detail.scss @@ -11,6 +11,10 @@ mat-toolbar mat-form-field { line-height: 3.7em } +.subheading { + font-weight: bold; +} + .manager-reports-detail { mat-grid-tile.label figure { justify-content: start; From 297cd4d655083df969c6a4c1b29ade5fb8d04e2c Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:18:21 -0800 Subject: [PATCH 047/113] manager: initial chat reports (fixes #7896) (#7916) Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../reports/reports-detail.component.html | 1 + .../reports/reports-detail.component.ts | 15 +++++++++++++++ .../manager-dashboard/reports/reports.service.ts | 13 +++++++++++++ .../manager-dashboard/reports/reports.utils.ts | 3 ++- 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0e5ba002f4..e480c6539f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.96", + "version": "0.15.97", "myplanet": { "latest": "v0.21.34", "min": "v0.20.34" diff --git a/src/app/manager-dashboard/reports/reports-detail.component.html b/src/app/manager-dashboard/reports/reports-detail.component.html index 2d65d55362..fcf29da727 100644 --- a/src/app/manager-dashboard/reports/reports-detail.component.html +++ b/src/app/manager-dashboard/reports/reports-detail.component.html @@ -73,6 +73,7 @@ <canvas id="resourceViewChart"></canvas> <canvas id="courseViewChart"></canvas> <canvas id="stepCompletedChart"></canvas> + <canvas id="chatUsageChart"></canvas> <div class="reports-table-container"> <div> <h1 class="mat-title" i18n>Members</h1> diff --git a/src/app/manager-dashboard/reports/reports-detail.component.ts b/src/app/manager-dashboard/reports/reports-detail.component.ts index 8f60740a01..87375f3d14 100644 --- a/src/app/manager-dashboard/reports/reports-detail.component.ts +++ b/src/app/manager-dashboard/reports/reports-detail.component.ts @@ -51,6 +51,7 @@ export class ReportsDetailComponent implements OnInit, OnDestroy { completions: new ReportsDetailData('time'), steps: new ReportsDetailData('time') }; + chatActivities = new ReportsDetailData('createdDate'); today: Date; minDate: Date; ratings = { total: new ReportsDetailData('time'), resources: [], courses: [] }; @@ -121,6 +122,7 @@ export class ReportsDetailComponent implements OnInit, OnDestroy { this.getDocVisits('courseActivities'); this.getPlanetCounts(local); this.getTeams(); + this.getChatUsage(); this.dialogsLoadingService.stop(); }); } @@ -165,6 +167,8 @@ export class ReportsDetailComponent implements OnInit, OnDestroy { ) ) )); + this.chatActivities.filter(this.filter); + this.setChatUsage(); } getLoginActivities() { @@ -278,6 +282,17 @@ export class ReportsDetailComponent implements OnInit, OnDestroy { }); } + getChatUsage() { + this.activityService.getChatHistory().subscribe((data) => { + this.chatActivities.data = data; + }); + } + + setChatUsage() { + const { byMonth } = this.activityService.groupChatUsage(this.chatActivities.filteredData); + this.setChart({ ...this.setGenderDatasets(byMonth), chartName: 'chatUsageChart' }); + } + getTeamMembers(team: any) { if (team === 'All') { return of([]); diff --git a/src/app/manager-dashboard/reports/reports.service.ts b/src/app/manager-dashboard/reports/reports.service.ts index 6d0b54e61d..21241d2947 100644 --- a/src/app/manager-dashboard/reports/reports.service.ts +++ b/src/app/manager-dashboard/reports/reports.service.ts @@ -262,6 +262,19 @@ export class ReportsService { })); } + getChatHistory() { + return this.couchService.get('chat_history/_all_docs', { params: { include_docs: 'true' } }) + .pipe( + map((data: any) => data.rows.map((row: any) => row.doc)) + ); + } + + groupChatUsage(chats: any) { + return ({ + byMonth: this.groupByMonth(this.appendGender(chats), 'createdDate', '_id') + }); + } + groupStepCompletion(steps: any[]) { return ({ byMonth: this.groupByMonth(this.appendGender(steps), 'time', 'userId') diff --git a/src/app/manager-dashboard/reports/reports.utils.ts b/src/app/manager-dashboard/reports/reports.utils.ts index b2c743330f..464b09aa85 100644 --- a/src/app/manager-dashboard/reports/reports.utils.ts +++ b/src/app/manager-dashboard/reports/reports.utils.ts @@ -83,7 +83,8 @@ export const titleOfChartName = (chartName: string) => { courseViewChart: $localize`Course Views by Month`, visitChart: $localize`Total Member Visits by Month`, uniqueVisitChart: $localize`Unique Member Visits by Month`, - stepCompletedChart: $localize`Steps Completed by Month` + stepCompletedChart: $localize`Steps Completed by Month`, + chatUsageChart: $localize`Chats Created by Month` }; return chartNames[chartName]; }; From ea374622b250cc56880a84240647de94205d2ccb Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:42:54 -0500 Subject: [PATCH 048/113] courses: smoother titles (fixes #7664) (#7922) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/courses.component.html | 5 +++-- src/app/courses/courses.scss | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e480c6539f..c57a6351fd 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.97", + "version": "0.15.98", "myplanet": { - "latest": "v0.21.34", - "min": "v0.20.34" + "latest": "v0.21.35", + "min": "v0.20.35" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/courses.component.html b/src/app/courses/courses.component.html index 554f750401..586eb3df19 100644 --- a/src/app/courses/courses.component.html +++ b/src/app/courses/courses.component.html @@ -131,8 +131,9 @@ <h3 class="header"> </ng-container> <ng-container *ngTemplateOutlet="headerText"></ng-container> <ng-template #headerText> - <a *ngIf="!isDialog && !isForm; else newTabLink" [routerLink]="['view', element._id]">{{ element.doc.courseTitle.length > 180 ? element.doc.courseTitle.slice(0, 180) + '...' : element.doc.courseTitle }}</a> - <ng-template #newTabLink><a class="cursor-pointer" (click)="openCourseViewDialog(element._id)">{{ element.doc.courseTitle.length > 180 ? element.doc.courseTitle.slice(0, 180) + '...' : element.doc.courseTitle }}</a></ng-template> + <a *ngIf="!isDialog && !isForm; else newTabLink" [routerLink]="['view', element._id]" class="break-word">{{ element.doc.courseTitle.length > 180 ? element.doc.courseTitle.slice(0, 180) + '...' : element.doc.courseTitle }}</a> + <ng-template #newTabLink><a class="cursor-pointer break-word" (click)="openCourseViewDialog(element._id)">{{ element.doc.courseTitle.length > 180 ? element.doc.courseTitle.slice(0, 180) + '...' : element.doc.courseTitle }}</a> + </ng-template> <span *ngIf="!parent && !isDialog" [ngClass]="{ 'cursor-pointer': !isForm }"> <mat-icon class="margin-lr-3" i18n-matTooltip matTooltip="In myCourses" [inline]="true" *ngIf="element.admission" (click)="courseToggle(element._id, 'resign')">bookmark</mat-icon> <mat-icon class="margin-lr-3" i18n-matTooltip matTooltip="Not in myCourses" [inline]="true" *ngIf="!element.admission && element.doc.steps.length" (click)="courseToggle(element._id, 'admission')">bookmark_border</mat-icon> diff --git a/src/app/courses/courses.scss b/src/app/courses/courses.scss index 6ec414dfe1..efe18f5943 100644 --- a/src/app/courses/courses.scss +++ b/src/app/courses/courses.scss @@ -55,6 +55,10 @@ $label-height: 1rem; height: $toolbar-height; } +.break-word { + word-break: break-word; +} + @media(max-width: $screen-md) { .mat-column-info { max-width: 120px; From 0f4a9ca715aab737fd8703cdde7184fdf54861bc Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:45:52 -0500 Subject: [PATCH 049/113] dashboard: smoother tiles (fixes #7924) (#7926) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/dashboard/dashboard-tile.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c57a6351fd..dfdb51b2c3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.98", + "version": "0.15.99", "myplanet": { "latest": "v0.21.35", "min": "v0.20.35" diff --git a/src/app/dashboard/dashboard-tile.component.html b/src/app/dashboard/dashboard-tile.component.html index 6cda7acb0e..9941b3c936 100644 --- a/src/app/dashboard/dashboard-tile.component.html +++ b/src/app/dashboard/dashboard-tile.component.html @@ -25,7 +25,7 @@ #dashboardTile > <p [matBadge]="item.badge" [matBadgeHidden]="item.badge===0" matBadgeOverlap="false">{{item.firstLine}}</p> - <p class="dashboard-text" [ngStyle]="{ '-webkit-line-clamp': tileLines }">{{item.title | slice:0:80}}</p> + <p class="dashboard-text" [ngStyle]="{ '-webkit-line-clamp': tileLines,'word-wrap': 'break-word' }">{{item.title | slice:0:80}}</p> <button mat-icon-button class="delete-item" (click)="removeFromShelf($event, item)" *ngIf="cardType!=='myLife' && !item?.canRemove"> <mat-icon i18n-matTooltip [matTooltip]="'Remove from ' + cardTitle" [inline]="true">clear</mat-icon> </button> From 6e18f46b98e81df8d85c847b6d5f870746531abd Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Fri, 13 Dec 2024 23:55:37 +0300 Subject: [PATCH 050/113] manager: smoother myplanet logs (fixes #7634)(fixes #7816) (#7913) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +-- src/app/home/home-router.module.ts | 2 - src/app/home/home.module.ts | 2 +- .../manager-dashboard-router.module.ts | 2 + .../manager-dashboard.component.html | 1 + .../reports}/logs-myplanet.component.html | 9 +++- .../reports}/logs-myplanet.component.ts | 47 ++++++++++++++--- .../reports/reports-myplanet.component.html | 6 +++ .../reports/reports-myplanet.component.ts | 52 ++++++++++++++++++- 9 files changed, 112 insertions(+), 15 deletions(-) rename src/app/{logs-myplanet => manager-dashboard/reports}/logs-myplanet.component.html (76%) rename src/app/{logs-myplanet => manager-dashboard/reports}/logs-myplanet.component.ts (58%) diff --git a/package.json b/package.json index dfdb51b2c3..2d26be0a9e 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.15.99", + "version": "0.16.0", "myplanet": { - "latest": "v0.21.35", - "min": "v0.20.35" + "latest": "v0.21.40", + "min": "v0.20.40" }, "scripts": { "ng": "ng", diff --git a/src/app/home/home-router.module.ts b/src/app/home/home-router.module.ts index 7a4e71a88f..8eda6e64d8 100644 --- a/src/app/home/home-router.module.ts +++ b/src/app/home/home-router.module.ts @@ -6,7 +6,6 @@ import { NotificationsComponent } from '../notifications/notifications.component import { UpgradeComponent } from '../upgrade/upgrade.component'; import { UsersAchievementsComponent } from '../users/users-achievements/users-achievements.component'; import { UsersAchievementsUpdateComponent } from '../users/users-achievements/users-achievements-update.component'; -import { LogsMyPlanetComponent } from '../logs-myplanet/logs-myplanet.component'; import { TeamsViewComponent } from '../teams/teams-view.component'; import { HealthListComponent } from '../health/health-list.component'; import { CommunityComponent } from '../community/community.component'; @@ -35,7 +34,6 @@ const routes: Routes = [ { path: 'upgrade/myplanet', component: UpgradeComponent, data: { myPlanet: true } }, { path: 'teams', loadChildren: () => import('../teams/teams.module').then(m => m.TeamsModule) }, { path: 'enterprises', loadChildren: () => import('../teams/teams.module').then(m => m.TeamsModule), data: { mode: 'enterprise' } }, - { path: 'logs/myplanet', component: LogsMyPlanetComponent }, { path: 'health', component: HealthListComponent }, { path: 'health/profile/:id', loadChildren: () => import('../health/health.module').then(m => m.HealthModule) }, { path: 'nation', component: TeamsViewComponent, data: { mode: 'services' } }, diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index cc9c52acae..8ee5378d75 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -17,7 +17,7 @@ import { UpgradeComponent } from '../upgrade/upgrade.component'; import { SharedComponentsModule } from '../shared/shared-components.module'; import { UsersAchievementsModule } from '../users/users-achievements/users-achievements.module'; import { NewsModule } from '../news/news.module'; -import { LogsMyPlanetComponent } from '../logs-myplanet/logs-myplanet.component'; +import { LogsMyPlanetComponent } from '../manager-dashboard/reports/logs-myplanet.component'; import { TeamsModule } from '../teams/teams.module'; import { CommunityComponent } from '../community/community.component'; import { PlanetCalendarModule } from '../shared/calendar.module'; diff --git a/src/app/manager-dashboard/manager-dashboard-router.module.ts b/src/app/manager-dashboard/manager-dashboard-router.module.ts index 9249544115..c5b7fd16e4 100644 --- a/src/app/manager-dashboard/manager-dashboard-router.module.ts +++ b/src/app/manager-dashboard/manager-dashboard-router.module.ts @@ -10,6 +10,7 @@ import { ReportsDetailComponent } from './reports/reports-detail.component'; import { ReportsPendingComponent } from './reports/reports-pending.component'; import { ReportsMyPlanetComponent } from './reports/reports-myplanet.component'; import { RequestsComponent } from './requests/requests.component'; +import { LogsMyPlanetComponent } from './reports/logs-myplanet.component'; const routes: Routes = [ { path: '', component: ManagerDashboardComponent }, @@ -27,6 +28,7 @@ const routes: Routes = [ { path: 'reports/detail', component: ReportsDetailComponent }, { path: 'reports/pending', component: ReportsPendingComponent }, { path: 'reports/myplanet', component: ReportsMyPlanetComponent }, + { path: 'logs/myplanet', component: LogsMyPlanetComponent }, { path: 'requests', component: RequestsComponent } ]; diff --git a/src/app/manager-dashboard/manager-dashboard.component.html b/src/app/manager-dashboard/manager-dashboard.component.html index c31840c7e0..85d2f7bb5b 100644 --- a/src/app/manager-dashboard/manager-dashboard.component.html +++ b/src/app/manager-dashboard/manager-dashboard.component.html @@ -10,6 +10,7 @@ </ng-container> <a *ngIf="planetType !== 'community' || isHub" [routerLink]="['reports', isHub ? { 'hubId': planetConfiguration._id } : {}]" i18n mat-raised-button>Reports</a> <a [routerLink]="['reports', 'myplanet', isHub ? { 'hubId': planetConfiguration._id } : {}]" i18n mat-raised-button>myPlanet Reports</a> + <a [routerLink]="['logs', 'myplanet', isHub ? { 'hubId': planetConfiguration._id } : {}]" i18n mat-raised-button>myPlanet Logs</a> <ng-container *planetAuthorizedRoles="'manager'"> <a [routerLink]="['surveys']" mat-raised-button i18n>Surveys</a> <button *ngIf="planetType !== center && showResendConfiguration" diff --git a/src/app/logs-myplanet/logs-myplanet.component.html b/src/app/manager-dashboard/reports/logs-myplanet.component.html similarity index 76% rename from src/app/logs-myplanet/logs-myplanet.component.html rename to src/app/manager-dashboard/reports/logs-myplanet.component.html index 01822b8053..5b0f0318b4 100644 --- a/src/app/logs-myplanet/logs-myplanet.component.html +++ b/src/app/manager-dashboard/reports/logs-myplanet.component.html @@ -1,5 +1,5 @@ <mat-toolbar> - <button class="btnBack" mat-icon-button routerLink="/"> + <button class="btnBack" mat-icon-button routerLink="/manager"> <mat-icon>arrow_back</mat-icon> </button> <span i18n>Logs</span> @@ -13,6 +13,10 @@ <mat-toolbar> <mat-toolbar-row class="primary-color font-size-1"> <span i18n>myPlanet</span> + <span class="toolbar-fill"></span> + <button mat-raised-button color="accent" (click)="exportAll()"> + Export All + </button> </mat-toolbar-row> </mat-toolbar> <div class="view-container view-full-height"> @@ -22,6 +26,9 @@ <mat-panel-title>{{planet.nameDoc?.name || planet.doc?.name}} ({{planet.children.length}})</mat-panel-title> </mat-expansion-panel-header> <planet-myplanet-table dataType="logs" [data]="planet.children"></planet-myplanet-table> + <button mat-raised-button color="accent" (click)="exportSingle(planet)" style="margin-right: 2rem;"> + Export + </button> </mat-expansion-panel> </ng-container> <ng-container *ngIf="isEmpty"> diff --git a/src/app/logs-myplanet/logs-myplanet.component.ts b/src/app/manager-dashboard/reports/logs-myplanet.component.ts similarity index 58% rename from src/app/logs-myplanet/logs-myplanet.component.ts rename to src/app/manager-dashboard/reports/logs-myplanet.component.ts index c71a58706a..4e6443c94c 100644 --- a/src/app/logs-myplanet/logs-myplanet.component.ts +++ b/src/app/manager-dashboard/reports/logs-myplanet.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit } from '@angular/core'; -import { CouchService } from '../shared/couchdb.service'; +import { CouchService } from '../../shared/couchdb.service'; import { forkJoin } from 'rxjs'; -import { StateService } from '../shared/state.service'; -import { PlanetMessageService } from '../shared/planet-message.service'; -import { ManagerService } from '../manager-dashboard/manager.service'; -import { filterSpecificFields } from '../shared/table-helpers'; -import { attachNamesToPlanets, areNoChildren } from '../manager-dashboard/reports/reports.utils'; - +import { StateService } from '../../shared/state.service'; +import { PlanetMessageService } from '../../shared/planet-message.service'; +import { ManagerService } from '../manager.service'; +import { filterSpecificFields } from '../../shared/table-helpers'; +import { attachNamesToPlanets, areNoChildren } from './reports.utils'; +import { CsvService } from '../../shared/csv.service'; @Component({ templateUrl: './logs-myplanet.component.html' @@ -23,6 +23,7 @@ export class LogsMyPlanetComponent implements OnInit { } constructor( + private csvService: CsvService, private couchService: CouchService, private stateService: StateService, private planetMessageService: PlanetMessageService, @@ -64,4 +65,36 @@ export class LogsMyPlanetComponent implements OnInit { }, (error) => this.planetMessageService.showAlert($localize`There was a problem getting myPlanet activity.`)); } + private mapToCsvData(children: any[], planetName?: string): any[] { + return children.map((data: any) => ({ + ...(planetName ? { 'Planet Name': planetName } : {}), + 'ID': data.androidId, + 'Name': data.deviceName || data.customDeviceName, + 'Type': data.type, + 'Time': new Date(Number(data.time)), + 'Version': data.version, + 'Error': data.error || 'N/A', + })); + } + + exportAll(): void { + const csvData: any[] = this.apklogs.flatMap((planet: any) => { + return this.mapToCsvData(planet.children, planet.name); + }); + + this.csvService.exportCSV({ + data: csvData, + title: 'myPlanet Logs', + }); + } + + exportSingle(planet: any): void { + const csvData = this.mapToCsvData(planet.children); + + this.csvService.exportCSV({ + data: csvData, + title: `myPlanet Logs for ${planet.name}`, + }); + } + } diff --git a/src/app/manager-dashboard/reports/reports-myplanet.component.html b/src/app/manager-dashboard/reports/reports-myplanet.component.html index e581313d35..8f10c8ea16 100755 --- a/src/app/manager-dashboard/reports/reports-myplanet.component.html +++ b/src/app/manager-dashboard/reports/reports-myplanet.component.html @@ -29,6 +29,9 @@ <mat-toolbar-row class="primary-color font-size-1"> <span i18n>myPlanet on { planetType, select, center {Nations} other {Communities} }</span> <span class="toolbar-fill"></span> + <button mat-raised-button color="accent" (click)="exportAll()"> + Export All + </button> </mat-toolbar-row> </mat-toolbar> <div class="view-container view-full-height"> @@ -38,6 +41,9 @@ <mat-panel-title>{{planet.nameDoc?.name || planet.doc?.name}} ({{planet.children.length}})</mat-panel-title> </mat-expansion-panel-header> <planet-myplanet-table [data]="planet.children"></planet-myplanet-table> + <button mat-raised-button color="accent" (click)="exportSingle(planet)" style="margin-right: 2rem;"> + Export + </button> </mat-expansion-panel> </ng-container> <ng-container *ngIf="isEmpty"> diff --git a/src/app/manager-dashboard/reports/reports-myplanet.component.ts b/src/app/manager-dashboard/reports/reports-myplanet.component.ts index 8e4dc06efb..134c0e2255 100755 --- a/src/app/manager-dashboard/reports/reports-myplanet.component.ts +++ b/src/app/manager-dashboard/reports/reports-myplanet.component.ts @@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router'; import { switchMap, map } from 'rxjs/operators'; import { findDocuments } from '../../shared/mangoQueries'; import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; +import { CsvService } from '../../shared/csv.service'; @Component({ templateUrl: './reports-myplanet.component.html' @@ -33,13 +34,14 @@ export class ReportsMyPlanetComponent implements OnInit { hub = { spokes: [] }; constructor( + private csvService: CsvService, private couchService: CouchService, private stateService: StateService, private planetMessageService: PlanetMessageService, private managerService: ManagerService, private reportsService: ReportsService, private route: ActivatedRoute, - private deviceInfoService: DeviceInfoService, + private deviceInfoService: DeviceInfoService ) { this.deviceType = this.deviceInfoService.getDeviceType(); this.isMobile = this.deviceType === DeviceType.MOBILE; @@ -112,4 +114,52 @@ export class ReportsMyPlanetComponent implements OnInit { ); } + private formatTotalTime(totalMilliseconds: number): string { + if (!totalMilliseconds || totalMilliseconds === 0) { + return '00:00:00'; + } + const totalSeconds = Math.floor(totalMilliseconds / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } + + private mapToCsvData(children: any[], planetName?: string): any[] { + return children.map((data: any) => ({ + ...(planetName ? { 'Planet Name': planetName } : {}), + 'ID': data.androidId.toString() || data.uniqueAndroidId.toString(), + 'Name': data.deviceName || data.customDeviceName, + 'Last Synced': data.time && data.time !== 0 ? + new Date(data.time).toDateString() : + data.last_synced && data.last_synced !== 0 ? + new Date(data.last_synced).toDateString() : + 'N/A', + 'Version': data.versionName, + 'No of Visits': data.count, + 'Used Time': this.formatTotalTime(data.totalUsedTime), + })); + } + + exportAll(): void { + const csvData: any[] = this.planets.flatMap((planet: any) => { + return this.mapToCsvData(planet.children, planet.name); + }); + + this.csvService.exportCSV({ + data: csvData, + title: 'myPlanet Reports', + }); + } + + exportSingle(planet: any): void { + const csvData = this.mapToCsvData(planet.children); + + this.csvService.exportCSV({ + data: csvData, + title: `myPlanet Reports for ${planet.name}`, + }); + } + } From 6a2f4ece25258e817d9c18003a49e002d7f02a55 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:11:57 -0500 Subject: [PATCH 051/113] teams: smoother calendar recursions (fixes #7858) (#7862) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../add-meetups/meetups-add.component.html | 7 +- .../add-meetups/meetups-add.component.ts | 75 ++++++++++++------- src/app/shared/calendar.component.ts | 15 ++-- src/app/validators/custom-validators.ts | 11 +++ 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 2d26be0a9e..625d9ddf90 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.0", + "version": "0.16.1", "myplanet": { "latest": "v0.21.40", "min": "v0.20.40" diff --git a/src/app/meetups/add-meetups/meetups-add.component.html b/src/app/meetups/add-meetups/meetups-add.component.html index cca2895936..f44c7ed018 100644 --- a/src/app/meetups/add-meetups/meetups-add.component.html +++ b/src/app/meetups/add-meetups/meetups-add.component.html @@ -53,8 +53,11 @@ <mat-error><planet-form-error-messages [control]="meetupForm.controls.recurring"></planet-form-error-messages></mat-error> </mat-radio-group> </div> - <div *ngIf="meetupForm.controls.recurring.value==='weekly'" class="full-width"> - <mat-checkbox (change)="onDayChange(day, $event.checked)" *ngFor="let day of days" class="margin-lr" [checked]="isClassDay(day)">{{day}}</mat-checkbox> + <div *ngIf="meetupForm.controls.recurring.value === 'weekly'" class="full-width"> + <mat-checkbox (change)="onDayChange(day, $event.checked)" *ngFor="let day of days" class="margin-lr" [checked]="meetupForm.controls.day.value.includes(day)"> {{ day }}</mat-checkbox> + <mat-error *ngIf="meetupForm.controls.day.hasError('noDaysSelected')"> + <span i18n>Please select at least one day.</span> + </mat-error> </div> <ng-container *ngIf="meetupForm.controls.recurring.value==='weekly' || meetupForm.controls.recurring.value==='daily'"> <mat-form-field> diff --git a/src/app/meetups/add-meetups/meetups-add.component.ts b/src/app/meetups/add-meetups/meetups-add.component.ts index 9b26da9cee..9b22185699 100644 --- a/src/app/meetups/add-meetups/meetups-add.component.ts +++ b/src/app/meetups/add-meetups/meetups-add.component.ts @@ -103,22 +103,24 @@ export class MeetupsAddComponent implements OnInit { }, { validators: CustomValidators.meetupTimeValidator() }); - } +} - onSubmit() { - if (!this.meetupForm.valid) { - showFormErrors(this.meetupForm.controls); - return; - } - this.meetupForm.value.startTime = this.changeTimeFormat(this.meetupForm.value.startTime); - this.meetupForm.value.endTime = this.changeTimeFormat(this.meetupForm.value.endTime); - const meetup = { ...this.meetupForm.value, link: this.link, sync: this.sync }; - if (this.pageType === 'Update') { +onSubmit() { + if (!this.meetupForm.valid) { + showFormErrors(this.meetupForm.controls); + return; + } + const dayFormArray = this.meetupForm.get('day') as FormArray; + dayFormArray.updateValueAndValidity(); + this.meetupForm.value.startTime = this.changeTimeFormat(this.meetupForm.value.startTime); + this.meetupForm.value.endTime = this.changeTimeFormat(this.meetupForm.value.endTime); + const meetup = { ...this.meetupForm.value, link: this.link, sync: this.sync }; + if (this.pageType === 'Update') { this.updateMeetup(meetup); } else { this.addMeetup(meetup); - } } +} changeTimeFormat(time: string): string { if (time && time.length < 5) { @@ -174,35 +176,50 @@ export class MeetupsAddComponent implements OnInit { } } - isClassDay(day) { - return this.meetupFrequency.includes(day) ? true : false; - } - onDayChange(day: string, isChecked: boolean) { const dayFormArray = <FormArray>this.meetupForm.controls.day; if (isChecked) { // add to day array if checked dayFormArray.push(new FormControl(day)); } else { - // remove from day array if unchecked - const index = dayFormArray.controls.findIndex(x => x.value === day); - dayFormArray.removeAt(index); + // remove from day array if unchecked + const index = dayFormArray.controls.findIndex(x => x.value === day); + if (index >= 0) { + dayFormArray.removeAt(index); + } } - } + dayFormArray.updateValueAndValidity(); +} - toggleDaily(val, showCheckbox) { - // empty the array - this.meetupForm.setControl('day', this.fb.array([])); - switch (val) { +toggleDaily(val: string, showCheckbox: boolean) { + const dayFormArray = this.meetupForm.get('day') as FormArray; + dayFormArray.clear(); + dayFormArray.clearValidators(); + + switch (val) { + // add all days to the array if the course is daily case 'daily': - // add all days to the array if the course is daily - this.meetupForm.setControl('day', this.fb.array(this.days)); - break; + this.days.forEach((day) => { + dayFormArray.push(new FormControl(day)); + }); + break; case 'weekly': - this.meetupForm.setControl('day', this.fb.array(this.meetupFrequency)); - break; - } + dayFormArray.setValidators(CustomValidators.atLeastOneDaySelected()); + const startDate = this.meetupForm.controls.startDate.value; + if (startDate) { + const startDateObj = new Date(startDate); + const dayOfWeek = this.days[startDateObj.getDay()]; + if (dayOfWeek) { + dayFormArray.push(new FormControl(dayOfWeek)); + } + } + break; + + default: + break; } + dayFormArray.updateValueAndValidity(); +} meetupChangeNotifications(users, meetupInfo, meetupId) { return { docs: users.map((user) => ({ diff --git a/src/app/shared/calendar.component.ts b/src/app/shared/calendar.component.ts index 31cc504025..a45d582a21 100644 --- a/src/app/shared/calendar.component.ts +++ b/src/app/shared/calendar.component.ts @@ -171,13 +171,16 @@ export class PlanetCalendarComponent implements OnInit, OnChanges { } openAddEventDialog(event) { - let meetup; - if (event?.start) { - meetup = { - startDate: event?.start, - endDate: this.adjustEndDate(event?.end) - }; + const today = new Date(); + const meetup = event?.start + ? { + startDate: event.start, + endDate: this.adjustEndDate(event.end), } + : { + startDate: today, + endDate: today, + }; this.dialog.open(DialogsAddMeetupsComponent, { data: { meetup: meetup, link: this.link, sync: this.sync, onMeetupsChange: this.onMeetupsChange.bind(this), editable: this.editable } }); diff --git a/src/app/validators/custom-validators.ts b/src/app/validators/custom-validators.ts index c3c9311230..da950a37c8 100755 --- a/src/app/validators/custom-validators.ts +++ b/src/app/validators/custom-validators.ts @@ -255,4 +255,15 @@ export class CustomValidators { }); } + static atLeastOneDaySelected(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.parent) { return null; } + const recurringControl = control.parent.get('recurring'); + if (!recurringControl || recurringControl.value !== 'weekly') { + return null; + } + const selectedDays = control.value; + return selectedDays && selectedDays.length > 0 ? null : { noDaysSelected: true }; + }; + } } From 432e29325b7056bdd34f0002a4791e19f1af815f Mon Sep 17 00:00:00 2001 From: sahilvunnam <118228103+sahilvunnam@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:23:30 -0800 Subject: [PATCH 052/113] resources: smoother collections display (fixes #7772) (#7776) Co-authored-by: Axel Lorens <axelnlorens@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/forms/planet-tag-input-dialog.component.html | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 625d9ddf90..aaa098482b 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.1", + "version": "0.16.2", "myplanet": { "latest": "v0.21.40", "min": "v0.20.40" diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.html b/src/app/shared/forms/planet-tag-input-dialog.component.html index 150940285e..eeb210d37a 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.html +++ b/src/app/shared/forms/planet-tag-input-dialog.component.html @@ -54,8 +54,10 @@ <mat-nav-list *ngIf="!selectMany"> <mat-list-item (click)="selectOne('')" i18n>All</mat-list-item> <ng-container *ngFor="let tag of tags"> - <mat-list-item (click)="tag.subTags.length === 0 ? selectOne(tag._id || tag.name) : toggleSubcollection($event,tag._id)" [ngClass]="{ 'mat-body-2': tag.subTags.length > 0 }"> - {{tag.name + ' (' + (tag.count || 0) + ')'}} + <mat-list-item class="tag-text" (click)="tag.subTags.length === 0 ? selectOne(tag._id || tag.name) : toggleSubcollection($event, tag._id)" + [ngClass]="{ 'mat-body-2': tag.subTags.length > 0 }" + > + {{ (tag.name + ' (' + (tag.count || 0) + ')') | slice:0:30 }}{{ (tag.name.length + tag.count.toString().length > 30) ? '...' : '' }} <planet-tag-input-toggle-icon *ngIf="tag.subTags.length > 0" [isOpen]="subcollectionIsOpen.get(tag._id)"></planet-tag-input-toggle-icon> <span class="toolbar-fill"></span> <button mat-stroked-button *ngIf="isUserAdmin" (click)="editTagClick($event,tag)" i18n>Edit</button> From 6d7e593461a19e44880828f03b5e58cc98712eb3 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:56:59 -0500 Subject: [PATCH 053/113] courses: smoother progress export (fixes #7811) (#7912) Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../courses-progress-leader.component.ts | 43 ++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index aaa098482b..e97348854d 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.2", + "version": "0.16.3", "myplanet": { "latest": "v0.21.40", "min": "v0.20.40" diff --git a/src/app/courses/progress-courses/courses-progress-leader.component.ts b/src/app/courses/progress-courses/courses-progress-leader.component.ts index c6a36c59d9..2a9faa7947 100644 --- a/src/app/courses/progress-courses/courses-progress-leader.component.ts +++ b/src/app/courses/progress-courses/courses-progress-leader.component.ts @@ -10,6 +10,7 @@ import { dedupeObjectArray } from '../../shared/utils'; import { DialogsLoadingService } from '../../shared/dialogs/dialogs-loading.service'; import { findDocuments } from '../../shared/mangoQueries'; import { UserProfileDialogComponent } from '../../users/users-profile/users-profile-dialog.component'; +import { StateService } from '../../shared/state.service'; import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; @Component({ @@ -31,6 +32,7 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { submittedExamSteps: any[] = []; planetCodes: string[] = []; selectedPlanetCode: string; + configuration: any = {}; deviceType: DeviceType; deviceTypes = DeviceType; @@ -42,6 +44,7 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { private csvService: CsvService, private dialogsLoadingService: DialogsLoadingService, private dialog: MatDialog, + private stateService: StateService, private deviceInfoService: DeviceInfoService ) { this.dialogsLoadingService.start(); @@ -221,23 +224,41 @@ export class CoursesProgressLeaderComponent implements OnInit, OnDestroy { } structureChartData(data) { - const dataArr = []; - data.forEach(element => { - const dataDict = {}; - dataDict['Username'] = element.label; - for (let i = 0; i < element.items.length; i++) { - dataDict[`Step ${(i + 1)}`] = element.items[i].number; - } + return data.map(element => { + let successfulSteps = 0; + let totalSteps = 0; + let totalErrors = 0; + const steps = {}; - dataArr.push(dataDict); + element.items.forEach((item, index) => { + const stepErrors = item.number || 0; + totalSteps++; + if (stepErrors === 0) { + successfulSteps++; + } + totalErrors += stepErrors; + steps[`Step ${(index + 1)}`] = stepErrors; + }); + + return { + 'Username': element.label, + 'Success Percentage': `${((successfulSteps / totalSteps) * 100).toFixed(2)}%`, + 'Total Errors': totalErrors, + ...steps + }; }); - return dataArr; } exportChartData() { + const planetName = this.stateService.configuration.name; + const courseTitle = this.course.courseTitle; + const entityLabel = this.configuration.planetType === 'nation' ? 'Nation' : 'Community'; + const title = $localize`${courseTitle} Course Progress for ${entityLabel} ${planetName}`; + + const structuredData = this.structureChartData(this.chartData); this.csvService.exportCSV({ - data: this.structureChartData(this.chartData), - title: $localize`Course Progress Data` + data: structuredData, + title: title }); } From dad08fdd8b65b27eab3c50dd2ec694363e0160b4 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:14:48 -0500 Subject: [PATCH 054/113] resources: smoother navigation (fixes #7927) (#7929) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../resources/view-resources/resources-view.component.html | 6 +++--- src/app/resources/view-resources/resources-view.scss | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e97348854d..e5e7f9ef39 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.3", + "version": "0.16.4", "myplanet": { "latest": "v0.21.40", "min": "v0.20.40" diff --git a/src/app/resources/view-resources/resources-view.component.html b/src/app/resources/view-resources/resources-view.component.html index cedf7e1f91..9c040b3a7f 100644 --- a/src/app/resources/view-resources/resources-view.component.html +++ b/src/app/resources/view-resources/resources-view.component.html @@ -43,11 +43,11 @@ <ng-template #actionButtons> <ng-container *ngIf="!parent"> - <a mat-raised-button *ngIf="resource.doc?._attachments" [href]="resourceSrc" target="_blank" color="accent" i18n>Open in new tab</a> - <button mat-raised-button color="accent" (click)="libraryToggle(resource._id, 'add')" i18n class="margin-lr-3" *ngIf="!isUserEnrolled"> + <a mat-raised-button *ngIf="resource.doc?._attachments" [href]="resourceSrc" target="_blank" color="accent" i18n class = "toolbar-button margin-lr-3">Open in new tab</a> + <button mat-raised-button color="accent" (click)="libraryToggle(resource._id, 'add')" i18n class="toolbar-button margin-lr-3" *ngIf="!isUserEnrolled"> Add to myLibrary </button> - <button mat-raised-button color="accent" (click)="libraryToggle(resource._id, 'remove')" i18n class="margin-lr-3" *ngIf="isUserEnrolled"> + <button mat-raised-button color="accent" (click)="libraryToggle(resource._id, 'remove')" i18n class="toolbar-button margin-lr-3" *ngIf="isUserEnrolled"> Remove from myLibrary </button> </ng-container> diff --git a/src/app/resources/view-resources/resources-view.scss b/src/app/resources/view-resources/resources-view.scss index 937c77f616..e33ad07cb0 100644 --- a/src/app/resources/view-resources/resources-view.scss +++ b/src/app/resources/view-resources/resources-view.scss @@ -34,6 +34,10 @@ max-height: 60vh; } } + + .toolbar-button { + flex-shrink: 0; + } @media (max-width: $screen-sm) { .view-container { From 56d4b398b9c8fd2732e60eb5ad926a5351f12c45 Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:22:56 -0800 Subject: [PATCH 055/113] chat: smoother ux (fixes #7915) (#7931) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../chat/chat-window/chat-window.component.ts | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e5e7f9ef39..6cfbdb5399 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.4", + "version": "0.16.5", "myplanet": { "latest": "v0.21.40", "min": "v0.20.40" diff --git a/src/app/chat/chat-window/chat-window.component.ts b/src/app/chat/chat-window/chat-window.component.ts index 85cdf7ce7d..2b987c78cf 100644 --- a/src/app/chat/chat-window/chat-window.component.ts +++ b/src/app/chat/chat-window/chat-window.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Input, AfterViewInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { filter, takeUntil } from 'rxjs/operators'; import { CustomValidators } from '../../validators/custom-validators'; import { ConversationForm, AIProvider } from '../chat.model'; @@ -20,6 +20,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy, AfterViewInit { spinnerOn = true; streaming: boolean; disabled = false; + clearChat = true; provider: AIProvider; conversations: any[] = []; selectedConversationId: any; @@ -71,8 +72,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy, AfterViewInit { this.chatService.newChatSelected$ .pipe(takeUntil(this.onDestroy$)) .subscribe(() => { - this.selectedConversationId = null; - this.conversations = []; + this.resetConversation(); this.focusInput(); }, error => { console.error('Error subscribing to newChatSelected$', error); @@ -81,7 +81,16 @@ export class ChatWindowComponent implements OnInit, OnDestroy, AfterViewInit { subscribeToSelectedConversation() { this.chatService.selectedConversationId$ - .pipe(takeUntil(this.onDestroy$)) + .pipe( + takeUntil(this.onDestroy$), + filter(() => { + if (this.clearChat) { + this.clearChat = false; + return false; + } + return true; + }) + ) .subscribe((conversationId) => { this.selectedConversationId = conversationId; this.fetchConversation(this.selectedConversationId?._id); @@ -102,6 +111,11 @@ export class ChatWindowComponent implements OnInit, OnDestroy, AfterViewInit { })); } + resetConversation() { + this.conversations = []; + this.selectedConversationId = null; + } + createForm() { this.promptForm = this.formBuilder.group({ prompt: [ '', CustomValidators.required ], From 7f741bd80e263546966dca3e06d49e686d9c8f5f Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:19:21 -0500 Subject: [PATCH 056/113] courses: smoother snackbar (fixes #7934) (#7935) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/courses.service.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6cfbdb5399..be624f1aa5 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.5", + "version": "0.16.6", "myplanet": { - "latest": "v0.21.40", - "min": "v0.20.40" + "latest": "v0.21.42", + "min": "v0.20.42" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/courses.service.ts b/src/app/courses/courses.service.ts index 072e656893..78866f6d07 100644 --- a/src/app/courses/courses.service.ts +++ b/src/app/courses/courses.service.ts @@ -203,6 +203,7 @@ export class CoursesService { courseResignAdmission(courseId, type, courseTitle?) { const title = courseTitle ? courseTitle : this.getCourseNameFromId(courseId); + const truncatedTitle = title.length > 180 ? `${title.slice(0, 180)}...` : title; const courseIds: any = [ ...this.userService.shelf.courseIds ]; if (type === 'resign') { const myCourseIndex = courseIds.indexOf(courseId); @@ -211,8 +212,9 @@ export class CoursesService { courseIds.push(courseId); } return this.userService.updateShelf(courseIds, 'courseIds').pipe(map((res) => { - const admissionMessage = type === 'resign' ? $localize`${title} successfully removed from myCourses` : - $localize`${title} added to your dashboard`; + const admissionMessage = type === 'resign' + ? $localize`${truncatedTitle} successfully removed from myCourses` + : $localize`${truncatedTitle} added to your dashboard`; this.planetMessageService.showMessage(admissionMessage); return res; })); From 555417caa55b973268f3dfa01dbee13c0e7743cb Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:24:29 -0500 Subject: [PATCH 057/113] courses: smoother progress navigation (fixes #7911)(fixes #7936) (#7938) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../progress-courses/courses-progress-chart.component.html | 2 +- .../courses-progress-learner.component.html | 2 +- .../progress-courses/courses-progress-learner.component.ts | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index be624f1aa5..7c07e2bd36 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.6", + "version": "0.16.7", "myplanet": { "latest": "v0.21.42", "min": "v0.20.42" diff --git a/src/app/courses/progress-courses/courses-progress-chart.component.html b/src/app/courses/progress-courses/courses-progress-chart.component.html index fb3711547f..1b5508212b 100644 --- a/src/app/courses/progress-courses/courses-progress-chart.component.html +++ b/src/app/courses/progress-courses/courses-progress-chart.component.html @@ -41,6 +41,6 @@ <div class="errors" *ngFor="let set of sets"> <div>{{set.total}}</div> <planet-avatar *ngIf="showAvatar" class="cursor-pointer" (click)="labelClick(set)" [username]="set.label" [planetCode]="set.planetCode" imgClass="profile-image-large"></planet-avatar> - <div class="wrap-content cursor-pointer" (click)="labelClick(set)" i18n-matTooltip [matTooltip]="set.label?.length > 3 ? set.label : null "><p>{{set.label}}</p></div> + <div class="wrap-content cursor-pointer" (click)="labelClick(set)" i18n-matTooltip [matTooltip]="set.label && set.label.length > 3 ? (set.label.length > 180 ? (set.label | slice:0:180) + '...' : set.label) : null"><p>{{set.label}}</p></div> </div> </div> diff --git a/src/app/courses/progress-courses/courses-progress-learner.component.html b/src/app/courses/progress-courses/courses-progress-learner.component.html index e46c021040..1258573afe 100644 --- a/src/app/courses/progress-courses/courses-progress-learner.component.html +++ b/src/app/courses/progress-courses/courses-progress-learner.component.html @@ -7,7 +7,7 @@ <span i18n>Courses: myProgress</span> </mat-toolbar> <div class="view-container view-full-height"> - <planet-courses-progress-chart *ngIf="chartData?.length; else noProgress" [inputs]="chartData" [height]="yAxisLength" [showTotals]="false" (changeData)="changeData($event)"> + <planet-courses-progress-chart *ngIf="chartData?.length; else noProgress" [inputs]="chartData" [height]="yAxisLength" [showTotals]="false" (clickAction)="handleCourseClick($event)" (changeData)="changeData($event)"> </planet-courses-progress-chart> <ng-template #noProgress i18n>No Progress record available</ng-template> </div> diff --git a/src/app/courses/progress-courses/courses-progress-learner.component.ts b/src/app/courses/progress-courses/courses-progress-learner.component.ts index 9c59bab079..c030e26b8e 100644 --- a/src/app/courses/progress-courses/courses-progress-learner.component.ts +++ b/src/app/courses/progress-courses/courses-progress-learner.component.ts @@ -62,6 +62,7 @@ export class CoursesProgressLearnerComponent implements OnInit, OnDestroy { createChartData(courses = [], submissions) { return courses.map((course: any) => ({ label: course.doc.courseTitle, + courseId: course._id, items: this.courseBySteps( course, submissions.filter(submission => submission.parentId.indexOf(course._id) > -1) @@ -106,4 +107,10 @@ export class CoursesProgressLearnerComponent implements OnInit, OnDestroy { changeData(event) {} + handleCourseClick(event: any) { + if (event.courseId) { + this.router.navigate([ '/courses', 'view', event.courseId ]); + } + } + } From 8c8a9551d87237398fcbc6d643ea6dafcb58740d Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:01:19 -0800 Subject: [PATCH 058/113] courses: smoother multiple choice (fixes #7939) (#7946) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/exams/exams-view.component.ts | 30 ++++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7c07e2bd36..6899ed7369 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.7", + "version": "0.16.8", "myplanet": { "latest": "v0.21.42", "min": "v0.20.42" diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 04cd67d8c1..c9b70fe540 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -279,13 +279,22 @@ export class ExamsViewComponent implements OnInit, OnDestroy { } setAnswer(event, option) { - this.answer.setValue(Array.isArray(this.answer.value) ? this.answer.value : []); - const value = this.answer.value; - if (event.checked === true) { - value.push(option); - } else if (event.checked === false) { - value.splice(value.indexOf(option), 1); + const value = this.answer.value || []; + + + if (event.checked) { + if (!value.includes(option)) { + value.push(option); + } + } else { + const index = value.indexOf(option); + if (index > -1) { + value.splice(index, 1); + } } + + this.answer.setValue(value); + this.answer.updateValueAndValidity(); this.checkboxState[option.id] = event.checked; } @@ -343,9 +352,14 @@ export class ExamsViewComponent implements OnInit, OnDestroy { answerValidator(ac: AbstractControl) { if (typeof ac.value === 'string') { - return CustomValidators.required(ac); + return ac.value.trim() ? null : { required: true }; } - return ac.value !== null ? null : { required: true }; + + if (Array.isArray(ac.value)) { + return ac.value.length > 0 ? null : { required: true }; + } + + return ac.value !== null && ac.value !== undefined ? null : { required: true }; } setViewAnswerText(answer: any) { From 6e06cff4299d0e568a410ae154b7d6c7b5f927b4 Mon Sep 17 00:00:00 2001 From: sahilvunnam <118228103+sahilvunnam@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:26:57 -0800 Subject: [PATCH 059/113] community: smoother calendar legend (fixes #6572) (#7770) Co-authored-by: Axel Lorens <axelnlorens@gmail.com> Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/community/community.component.html | 2 +- src/app/community/community.scss | 23 +++++++++++++++++++++- src/app/shared/calendar.component.ts | 15 ++++++++++++++ src/app/teams/teams-view.component.html | 2 +- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6899ed7369..78a26d8913 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.8", + "version": "0.16.9", "myplanet": { "latest": "v0.21.42", "min": "v0.20.42" diff --git a/src/app/community/community.component.html b/src/app/community/community.component.html index e8416ebb39..aec7227687 100644 --- a/src/app/community/community.component.html +++ b/src/app/community/community.component.html @@ -72,7 +72,7 @@ <h3 style="text-align: right; margin-right: 0.5rem;"> <planet-teams-reports [reports]="reports" [editable]="isCommunityLeader && !planetCode" [team]="team" (reportsChanged)="dataChanged()"></planet-teams-reports> </mat-tab> <mat-tab i18n-label label="Calendar" *ngIf="deviceType !== deviceTypes.DESKTOP"> - <planet-calendar [resizeCalendar]="resizeCalendar" [link]="{ teams: teamId }" [sync]="{ type: 'sync', planetCode: planetCode || configuration.code }"></planet-calendar> + <planet-calendar [resizeCalendar]="resizeCalendar" [type]="'community'" [link]="{ teams: teamId }" [sync]="{ type: 'sync', planetCode: planetCode || configuration.code }"></planet-calendar> </mat-tab> </mat-tab-group> </div> diff --git a/src/app/community/community.scss b/src/app/community/community.scss index 293fb05afe..3d8e70b249 100644 --- a/src/app/community/community.scss +++ b/src/app/community/community.scss @@ -35,6 +35,27 @@ planet-calendar { overflow-y: auto; } +.calendar-legend { + padding-top: 10px; + text-align: right; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 10px; +} + +.calendar-legend .legend-item { + display: inline-flex; + align-items: center; +} + +.calendar-legend .legend-color { + width: 20px; + height: 20px; + margin-right: 10px; + border-radius: 4px; +} + .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); @@ -60,6 +81,6 @@ mat-tab-group, mat-tab { padding-top: 0.5rem; } -.toggle-button{ +.toggle-button { margin-bottom: 1rem; } diff --git a/src/app/shared/calendar.component.ts b/src/app/shared/calendar.component.ts index a45d582a21..80a05b48b9 100644 --- a/src/app/shared/calendar.component.ts +++ b/src/app/shared/calendar.component.ts @@ -16,6 +16,14 @@ import { addDateAndTime, styleVariables } from './utils'; selector: 'planet-calendar', template: ` <full-calendar #calendar [options]="calendarOptions"></full-calendar> + <div class="calendar-legend" *ngIf="showLegend"> + <div *ngFor="let legend of eventLegend"> + <div class="legend-item" *ngIf="!legend.type || legend.type === type"> + <div class="legend-color" [style.backgroundColor]="legend.color"></div> + <span>{{ legend.label }}</span> + </div> + </div> + </div> ` }) export class PlanetCalendarComponent implements OnInit, OnChanges { @@ -25,6 +33,7 @@ export class PlanetCalendarComponent implements OnInit, OnChanges { @Input() link: any = {}; @Input() sync: { type: 'local' | 'sync', planetCode: string }; @Input() editable = true; + @Input() type = ''; @Input() header?: any = { left: 'title', @@ -47,6 +56,12 @@ export class PlanetCalendarComponent implements OnInit, OnChanges { dbName = 'meetups'; meetups: any[] = []; tasks: any[] = []; + showLegend = true; + eventLegend = [ + { color: styleVariables.primary, label: 'Event' }, + { color: 'orange', label: 'Uncompleted Task', type: 'team' }, + { color: 'grey', label: 'Completed Task', type: 'team' } + ]; calendarOptions: CalendarOptions = { initialView: 'dayGridMonth', diff --git a/src/app/teams/teams-view.component.html b/src/app/teams/teams-view.component.html index 9f2796632d..2a651303e3 100644 --- a/src/app/teams/teams-view.component.html +++ b/src/app/teams/teams-view.component.html @@ -131,7 +131,7 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration <ng-template mat-tab-label> <ng-container i18n>Calendar</ng-container> </ng-template> - <planet-calendar *ngIf="calendarTab.isActive" [link]="{ teams: teamId }" [sync]="{ type: team.teamType, planetCode: team.teamPlanetCode }" [editable]="userStatus === 'member'"></planet-calendar> + <planet-calendar *ngIf="calendarTab.isActive" [type]="'team'" [link]="{ teams: teamId }" [sync]="{ type: team.teamType, planetCode: team.teamPlanetCode }" [editable]="userStatus === 'member'"></planet-calendar> </mat-tab> <mat-tab *ngIf="mode!=='team'"> <ng-template mat-tab-label> From 119bef26411fb230d95f11a7ea1ed14d9f0ac1df Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:31:21 -0800 Subject: [PATCH 060/113] resources: smoother collections (fixes #7928) (#7932) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../planet-tag-selected-input.component.ts | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 78a26d8913..6896556574 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.9", + "version": "0.16.10", "myplanet": { "latest": "v0.21.42", "min": "v0.20.42" diff --git a/src/app/shared/forms/planet-tag-selected-input.component.ts b/src/app/shared/forms/planet-tag-selected-input.component.ts index 11f37d8f21..afc3d4308d 100644 --- a/src/app/shared/forms/planet-tag-selected-input.component.ts +++ b/src/app/shared/forms/planet-tag-selected-input.component.ts @@ -1,11 +1,13 @@ -import { Component, Input, OnChanges } from '@angular/core'; +import { Component, Input, OnChanges, HostListener } from '@angular/core'; import { TagsService } from './tags.service'; +import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; @Component({ template: ` <span [ngSwitch]="selectedIds.length" class="small margin-lr-5"> <span *ngSwitchCase="0" i18n>No collections selected</span> - <span *ngSwitchCase="1"><span i18n>Selected:</span>{{' ' + tooltipLabels}}</span> + <span *ngSwitchCase="1"><span i18n>Selected:</span> + {{ truncatedTooltip }} <span *ngSwitchDefault [matTooltip]="tooltipLabels" i18n>Hover to see selected collections</span> </span> `, @@ -17,18 +19,33 @@ export class PlanetTagSelectedInputComponent implements OnChanges { @Input() allTags: any[] = []; tooltipLabels = ''; + deviceType: DeviceType; + deviceTypes: typeof DeviceType = DeviceType; constructor( - private tagsService: TagsService + private tagsService: TagsService, + private deviceInfoService: DeviceInfoService ) {} ngOnChanges() { this.setTooltipLabels(this.selectedIds, this.allTags); } + @HostListener('window:resize') OnResize() { + this.deviceType = this.deviceInfoService.getDeviceType(); + } + setTooltipLabels(selectedIds, allTags) { const tagsNames = selectedIds.map((tag: any) => this.tagsService.findTag(tag, allTags).name); this.tooltipLabels = tagsNames.join(', '); } + get truncatedTooltip(): string { + const maxLength = this.deviceType === this.deviceTypes.DESKTOP ? 50 : + this.deviceType === this.deviceTypes.TABLET ? 35 : 20; + return this.tooltipLabels.length > maxLength + ? this.tooltipLabels.slice(0, maxLength) + '...' + : this.tooltipLabels; + } + } From bab2894c6b9dae28954bf022d8314e1fbc4dbd8b Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:40:21 -0800 Subject: [PATCH 061/113] resources: smoother subcollections (fixes #7948) (#7958) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- .../planet-tag-input-dialog.component.html | 2 +- .../planet-tag-input-dialog.component.ts | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6896556574..50e223ccfd 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.10", + "version": "0.16.11", "myplanet": { - "latest": "v0.21.42", - "min": "v0.20.42" + "latest": "v0.21.43", + "min": "v0.20.43" }, "scripts": { "ng": "ng", diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.html b/src/app/shared/forms/planet-tag-input-dialog.component.html index eeb210d37a..b26ea2446b 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.html +++ b/src/app/shared/forms/planet-tag-input-dialog.component.html @@ -68,7 +68,7 @@ <ng-container *ngIf="subcollectionIsOpen.get(tag._id)"> <mat-list-item *ngFor="let subTag of tag.subTags" (click)="selectOne(subTag._id || subTag.name)"> <mat-icon>subdirectory_arrow_right</mat-icon> - {{subTag.name + ' (' + (subTag.count || 0) + ')'}} + {{ truncateTagName(subTag) }} <span class="toolbar-fill"></span> <button mat-stroked-button *ngIf="isUserAdmin" (click)="editTagClick($event,subTag)" i18n>Edit</button> <button mat-stroked-button *ngIf="isUserAdmin" (click)="deleteTag($event,subTag)" i18n>Delete</button> diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index fdff521c14..9b86062c86 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input } from '@angular/core'; +import { Component, Inject, Input, HostListener } from '@angular/core'; import { FormGroup, FormBuilder } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog'; import { TagsService } from './tags.service'; @@ -6,6 +6,7 @@ import { PlanetMessageService } from '../planet-message.service'; import { ValidatorService } from '../../validators/validator.service'; import { DialogsFormService } from '../dialogs/dialogs-form.service'; import { UserService } from '../user.service'; +import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; import { CustomValidators } from '../../validators/custom-validators'; import { mapToArray, isInMap } from '../utils'; import { DialogsLoadingService } from '../../shared/dialogs/dialogs-loading.service'; @@ -52,6 +53,8 @@ export class PlanetTagInputDialogComponent { get okClickValue() { return { wasOkClicked: true, indeterminate: this.indeterminate ? mapToArray(this.indeterminate, true) : [] }; } + deviceType: DeviceType; + deviceTypes: typeof DeviceType = DeviceType; constructor( public dialogRef: MatDialogRef<PlanetTagInputDialogComponent>, @@ -63,7 +66,8 @@ export class PlanetTagInputDialogComponent { private dialogsFormService: DialogsFormService, private userService: UserService, private dialogsLoadingService: DialogsLoadingService, - private dialog: MatDialog + private dialog: MatDialog, + private deviceInfoService: DeviceInfoService, ) { this.dataInit(); // April 17, 2019: Removing selectMany toggle, but may revisit later @@ -80,8 +84,13 @@ export class PlanetTagInputDialogComponent { attachedTo: [ [] ] }); this.isUserAdmin = this.userService.get().isUserAdmin; + this.deviceType = this.deviceInfoService.getDeviceType(); } + @HostListener('window:resize') OnResize() { + this.deviceType = this.deviceInfoService.getDeviceType(); + } + dataInit() { this.tags = this.filterTags(this.filterValue); this.mode = this.data.mode; @@ -268,6 +277,12 @@ export class PlanetTagInputDialogComponent { return checkValue(this.selected.entries()); } + truncateTagName(subTag: { name: string; count?: number }, maxLength: number): string { + if (this.deviceType === this.deviceTypes.DESKTOP) { maxLength = 50; } else { maxLength = 25; } + const truncatedName = subTag.name.length > maxLength ? subTag.name.slice(0, maxLength) + '...' : subTag.name; + return `${truncatedName} (${subTag.count || 0})`; + } + } @Component({ From d0784fdc92f960b4984cd35be1d7b945fbb0fae7 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:49:48 -0500 Subject: [PATCH 062/113] courses: smoother exam preview header (fixes #7949) (#7951) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/exams/exams-view.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 50e223ccfd..0741a48c4a 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.11", + "version": "0.16.12", "myplanet": { "latest": "v0.21.43", "min": "v0.20.43" diff --git a/src/app/exams/exams-view.component.html b/src/app/exams/exams-view.component.html index 55aa920eae..2f63d20bcb 100644 --- a/src/app/exams/exams-view.component.html +++ b/src/app/exams/exams-view.component.html @@ -9,7 +9,7 @@ <span class="ellipsis-title">{{title ? title + ': ' : ''}}</span> <span> <ng-container i18n>Question</ng-container>{{' ' + questionNum + ' '}}<ng-container i18n>of</ng-container>{{' ' + maxQuestions }} - <span i18n *ngIf="mode === 'grade' || mode === 'view'"> By{{' ' + submittedBy}}</span><span i18n *ngIf="!submittedBy">Unknown</span><span i18n> on {{(updatedOn | date: 'short') || '--'}}</span> + <span i18n *ngIf="mode === 'grade' || mode === 'view'"> by{{' ' + submittedBy}}</span><span i18n *ngIf="!submittedBy">(Preview)</span><span *ngIf="updatedOn" i18n> on {{updatedOn | date: 'short'}}</span> </span> <span class="toolbar-fill"></span> <button mat-icon-button [disabled]="questionNum === 1" (click)="moveQuestion(-1)" [planetSubmit]="spinnerOn"><mat-icon>navigate_before</mat-icon></button> From 9a65a043591172f04eb61df40f80ec11373343d9 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:11:04 -0500 Subject: [PATCH 063/113] all: smoother dialog prompts (fixes #7945) (#7947) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/dialogs/dialogs-prompt.component.html | 2 +- src/app/shared/dialogs/dialogs-prompt.component.ts | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0741a48c4a..579a25e4e4 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.12", + "version": "0.16.13", "myplanet": { "latest": "v0.21.43", "min": "v0.20.43" diff --git a/src/app/shared/dialogs/dialogs-prompt.component.html b/src/app/shared/dialogs/dialogs-prompt.component.html index 0157aec2be..3c67eab8ba 100644 --- a/src/app/shared/dialogs/dialogs-prompt.component.html +++ b/src/app/shared/dialogs/dialogs-prompt.component.html @@ -31,7 +31,7 @@ } </p> <p [innerHTML]=data.extraMessage></p> - <b>{{data.displayName}}</b> + <b class = break-word>{{data.displayName | slice:0:140}}<span *ngIf="data.displayName.length > 140">...</span></b> <b *ngIf="data.displayDates">{{data.displayDates.startDate | date : 'mediumDate' : isDateUtc ? '+0000' : undefined }} - {{data.displayDates.endDate | date: 'mediumDate' : isDateUtc ? '+0000' : undefined }}</b> <ng-container *ngFor="let label of labels; last as last"> <p><b><planet-label [label]="label.field"></planet-label>: {{label.value}}</b></p> diff --git a/src/app/shared/dialogs/dialogs-prompt.component.ts b/src/app/shared/dialogs/dialogs-prompt.component.ts index 2d5fb6460b..3ee85901d6 100644 --- a/src/app/shared/dialogs/dialogs-prompt.component.ts +++ b/src/app/shared/dialogs/dialogs-prompt.component.ts @@ -16,7 +16,14 @@ import { timer, throwError } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; @Component({ - templateUrl: './dialogs-prompt.component.html' + templateUrl: './dialogs-prompt.component.html', + styles: [ ` + .break-word { + word-wrap: break-word; + white-space: normal; + word-break: break-word; + } + ` ] }) export class DialogsPromptComponent { From 20469f7bf3dbfdc1dd295af38d0ec52405163abd Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:46:51 -0500 Subject: [PATCH 064/113] teams: smoother title overflow (fixes #7964) (#7967) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/teams/teams-view.component.html | 12 ++++++------ src/app/teams/teams-view.scss | 4 ++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 579a25e4e4..07782eaab9 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.13", + "version": "0.16.14", "myplanet": { - "latest": "v0.21.43", - "min": "v0.20.43" + "latest": "v0.21.45", + "min": "v0.20.45" }, "scripts": { "ng": "ng", diff --git a/src/app/teams/teams-view.component.html b/src/app/teams/teams-view.component.html index 2a651303e3..4f2a7d5b8b 100644 --- a/src/app/teams/teams-view.component.html +++ b/src/app/teams/teams-view.component.html @@ -43,26 +43,26 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration <ng-template #actionButtons> <ng-container [ngSwitch]="userStatus" *ngIf="user.isUserAdmin || user.roles.length"> <ng-container *ngSwitchCase="'member'"> - <button *ngIf="mode!=='services'" mat-stroked-button mat-button class="margin-lr-3" (click)="openInviteMemberDialog()" i18n [disabled]="disableAddingMembers"> + <button *ngIf="mode!=='services'" mat-stroked-button mat-button class=" toolbar-button margin-lr-3" (click)="openInviteMemberDialog()" i18n [disabled]="disableAddingMembers"> Add Members </button> - <button mat-raised-button color="accent" class="margin-lr-3" (click)="openResourcesDialog()" i18n> + <button mat-raised-button color="accent" class="toolbar-button margin-lr-3" (click)="openResourcesDialog()" i18n> { mode, select, team {Add Resources} enterprise {Add Documents} services {Add Documents} } </button> - <button mat-raised-button color="accent" class="margin-lr-3" (click)="openCourseDialog()" i18n> + <button mat-raised-button color="accent" class="toolbar-button margin-lr-3" (click)="openCourseDialog()" i18n> Add Courses </button> <ng-container *ngIf="!isUserLeader && mode !== 'services'"> - <button mat-raised-button color="accent" class="margin-lr-3" (click)="openDialogPrompt(team, 'leave', { changeType: 'leave', type: 'team' })" i18n>Leave</button> + <button mat-raised-button color="accent" class="toolbar-button margin-lr-3" (click)="openDialogPrompt(team, 'leave', { changeType: 'leave', type: 'team' })" i18n>Leave</button> </ng-container> </ng-container> - <button mat-raised-button color="accent" *ngSwitchCase="'unrelated'" class="margin-lr-3" (click)="changeMembership('request')"> + <button mat-raised-button color="accent" *ngSwitchCase="'unrelated'" class="toolbar-button margin-lr-3" (click)="changeMembership('request')"> <ng-container *ngIf="mode!=='services'" i18n>Request to Join</ng-container> </button> <mat-chip-list *ngSwitchCase="'requesting'"><mat-chip color="accent" class="chip-no-style" selected i18n>Request pending</mat-chip></mat-chip-list> </ng-container> <ng-container *ngIf="mode!=='services'"> - <button *planetAuthorizedRoles="''" mat-raised-button color="accent" class="margin-lr-3" (click)="openDialogPrompt(team, 'archive', { changeType: 'delete', type: 'team' })" i18n>Delete</button> + <button *planetAuthorizedRoles="''" mat-raised-button color="accent" class=" toolbar-button margin-lr-3" (click)="openDialogPrompt(team, 'archive', { changeType: 'delete', type: 'team' })" i18n>Delete</button> </ng-container> </ng-template> diff --git a/src/app/teams/teams-view.scss b/src/app/teams/teams-view.scss index 0603a3c8b9..91ff662210 100644 --- a/src/app/teams/teams-view.scss +++ b/src/app/teams/teams-view.scss @@ -32,4 +32,8 @@ margin: 0; } + .toolbar-button{ + flex-shrink: 0; + } + } From 186062232bdbb518b65e2ebe2946cbcddcaed7d4 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:52:58 -0500 Subject: [PATCH 065/113] community: smoother calendar event deletion (fixes #5401) (#7960) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/meetups/meetups.service.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 07782eaab9..c2b460adae 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.14", + "version": "0.16.15", "myplanet": { "latest": "v0.21.45", "min": "v0.20.45" diff --git a/src/app/meetups/meetups.service.ts b/src/app/meetups/meetups.service.ts index d71c0333ab..c7fa5773ee 100644 --- a/src/app/meetups/meetups.service.ts +++ b/src/app/meetups/meetups.service.ts @@ -96,13 +96,17 @@ export class MeetupService { openDeleteDialog(meetups: any[] | any, callback) { const isMany = meetups.length > 1; const displayName = isMany ? '' : (meetups[0] || meetups).title; + const recurringInfo = (meetups[0] || meetups).recurring && (meetups[0] || meetups).recurring !== 'none' && (meetups[0] || meetups).recurringNumber + ? `(Recurs ${ (meetups[0] || meetups).recurring} for ${ (meetups[0] || meetups).recurringNumber } ${ (meetups[0] || meetups).recurring === 'daily' ? 'days' : 'weeks' })` + : ''; this.deleteDialog = this.dialog.open(DialogsPromptComponent, { data: { okClick: this.deleteMeetups([ meetups ].flat(), displayName, callback), changeType: 'delete', type: 'event', amount: isMany ? 'many' : 'single', - displayName + displayName, + extraMessage: recurringInfo } }); } From ad4790af4162dd1f044ab916d675855203685575 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:00:50 -0500 Subject: [PATCH 066/113] resources: smoother collections scrolling (fixes #6280) (#7943) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/forms/planet-tag-input-dialog.component.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c2b460adae..9687570aa6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.15", + "version": "0.16.16", "myplanet": { "latest": "v0.21.45", "min": "v0.20.45" diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index 9b86062c86..8c549c019e 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -15,6 +15,14 @@ import { DialogsPromptComponent } from '../../shared/dialogs/dialogs-prompt.comp @Component({ 'templateUrl': 'planet-tag-input-dialog.component.html', 'styles': [ ` + :host { + display: block; + overflow: hidden; + } + :host mat-dialog-content { + overflow-y: auto; + max-height: calc(100vh - 100px); + } :host .mat-list-option span { font-weight: inherit; } From 5dbc0bd327f43f2d110c78021c3e378a0a86d83a Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:05:07 -0500 Subject: [PATCH 067/113] resources: smoother snackbar (fixes #7954)(fixes #7955) (#7956) Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/courses/courses.service.ts | 3 ++- src/app/resources/resources.service.ts | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9687570aa6..139d7817c6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.16", + "version": "0.16.17", "myplanet": { "latest": "v0.21.45", "min": "v0.20.45" diff --git a/src/app/courses/courses.service.ts b/src/app/courses/courses.service.ts index 78866f6d07..2c69e183be 100644 --- a/src/app/courses/courses.service.ts +++ b/src/app/courses/courses.service.ts @@ -227,7 +227,8 @@ export class CoursesService { courseAdmissionMany(courseIds, type) { return this.userService.changeShelf(courseIds, 'courseIds', type).pipe(map(({ shelf, countChanged }) => { const prefix = countChanged > 1 ? $localize`${countChanged} courses` : this.getCourseNameFromId(courseIds[courseIds.length - 1]); - const message = type === 'remove' ? $localize`${prefix} successfully removed from myCourses` : $localize`${prefix} added to your dashboard`; + const message = type === 'remove' ? $localize`${prefix} successfully removed from myCourses` : + $localize`${prefix} added to myCourses`; this.planetMessageService.showMessage(message); return shelf; })); diff --git a/src/app/resources/resources.service.ts b/src/app/resources/resources.service.ts index ea00aa17de..aac7f085eb 100644 --- a/src/app/resources/resources.service.ts +++ b/src/app/resources/resources.service.ts @@ -90,8 +90,13 @@ export class ResourcesService { libraryAddRemove(resourceIds, type) { return this.userService.changeShelf(resourceIds, 'resourceIds', type).pipe(map(({ shelf, countChanged }) => { + const resource = this.resources.local.find(r => r._id === resourceIds[0]); + const resourceTitle = resource ? resource.doc.title : ''; const message = type === 'remove' ? - countChanged + $localize` Resources successfully removed from myLibrary` : countChanged + $localize` Resources added to your dashboard`; + (countChanged === 1 ? $localize`${resourceTitle} successfully removed from myLibrary` : + `${countChanged} ${$localize`Resources`} successfully removed from myLibrary`) : + (countChanged === 1 ? $localize`${resourceTitle} added to myLibrary` : + `${countChanged} ${$localize`Resources`} added to myLibrary`); this.planetMessageService.showMessage(message); return shelf; })); From e92959c964be3b7797284552d5bd11f118f05eb0 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:15:13 -0500 Subject: [PATCH 068/113] chat: smoother placeholder text (#7973) (#7974) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/chat/chat-sidebar/chat-sidebar.component.html | 2 +- src/app/chat/chat-sidebar/chat-sidebar.scss | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 139d7817c6..d2b03ae947 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.17", + "version": "0.16.18", "myplanet": { - "latest": "v0.21.45", - "min": "v0.20.45" + "latest": "v0.21.51", + "min": "v0.20.51" }, "scripts": { "ng": "ng", diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.html b/src/app/chat/chat-sidebar/chat-sidebar.component.html index 14625e5cee..10bd193118 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.html +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.html @@ -69,7 +69,7 @@ </ul> </ng-container> <ng-template #noChats> - <div i18n>No previous conversations.</div> + <div class="no-chats-message" i18n>No previous conversations.</div> </ng-template> </mat-drawer> diff --git a/src/app/chat/chat-sidebar/chat-sidebar.scss b/src/app/chat/chat-sidebar/chat-sidebar.scss index e974ca4f80..4ef09f2260 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.scss +++ b/src/app/chat/chat-sidebar/chat-sidebar.scss @@ -77,6 +77,10 @@ li:hover { margin: 0; } +.no-chats-message { + margin-left: 4px; +} + @media only screen and (max-width: $screen-md) { .expand-button { top: 10px; From 3698db5ecc3f09252ce58ef48f5ede92a6aba300 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:21:30 -0500 Subject: [PATCH 069/113] teams: smoother resources titles (fixes #7978) (#7981) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams-view.scss | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d2b03ae947..f6a5235790 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.18", + "version": "0.16.19", "myplanet": { "latest": "v0.21.51", "min": "v0.20.51" diff --git a/src/app/teams/teams-view.scss b/src/app/teams/teams-view.scss index 91ff662210..a5b4639e48 100644 --- a/src/app/teams/teams-view.scss +++ b/src/app/teams/teams-view.scss @@ -32,6 +32,14 @@ margin: 0; } + .mat-subheading-2 b { + display: inline-block; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .toolbar-button{ flex-shrink: 0; } From 97fc1bbb2ff60aa92a6df6778ff8d02b980fd7b6 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:43:10 -0500 Subject: [PATCH 070/113] all: smoother navigation tooltips (fixes #7979) (#7984) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/home/home.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f6a5235790..3620c97dce 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.19", + "version": "0.16.20", "myplanet": { "latest": "v0.21.51", "min": "v0.20.51" diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 8e1f338f34..6296f3e295 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -44,8 +44,8 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> </ng-container> <span *ngIf="layout === 'classic' && !forceModern"> <button mat-icon-button routerLink="/chat" i18n-title title="Chat"><mat-icon>chat_bubble_outline</mat-icon></button> - <button mat-icon-button planetFeedback i18n-title title="Feedback"><mat-icon>feedback_outline</mat-icon></button> - <button mat-icon-button routerLink="/feedback" i18n-title title="Messages"><mat-icon>mail_outline</mat-icon></button> + <button mat-icon-button planetFeedback i18n-title title="Submit Feedback"><mat-icon>feedback_outline</mat-icon></button> + <button mat-icon-button routerLink="/feedback" i18n-title title="Review Feedback"><mat-icon>mail_outline</mat-icon></button> <ng-container *planetAuthorizedRoles> <button mat-icon-button planetSync i18n-title title="Sync" *ngIf="onlineStatus === 'accepted'"><mat-icon svgIcon="feedback"></mat-icon></button> </ng-container> From c52e42d538384cb618870ac475f3546ddfef5f4c Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:23:03 -0500 Subject: [PATCH 071/113] resources: smoother collections labels (fixes #7983) (#7988) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/courses/courses.scss | 8 ++++++++ src/app/resources/resources.scss | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3620c97dce..b0371a0bb6 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.20", + "version": "0.16.21", "myplanet": { - "latest": "v0.21.51", - "min": "v0.20.51" + "latest": "v0.21.55", + "min": "v0.20.55" }, "scripts": { "ng": "ng", diff --git a/src/app/courses/courses.scss b/src/app/courses/courses.scss index efe18f5943..e13f0980d8 100644 --- a/src/app/courses/courses.scss +++ b/src/app/courses/courses.scss @@ -59,6 +59,14 @@ $label-height: 1rem; word-break: break-word; } +.tags-list mat-chip { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; +} + @media(max-width: $screen-md) { .mat-column-info { max-width: 120px; diff --git a/src/app/resources/resources.scss b/src/app/resources/resources.scss index b73f711a42..1de20c3419 100644 --- a/src/app/resources/resources.scss +++ b/src/app/resources/resources.scss @@ -56,6 +56,14 @@ $label-height: 1rem; justify-content: center; } +.tags-list mat-chip { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; +} + @media(max-width: $screen-md) { .resources-list { .mat-column-createdDate { From a8297efcfb19c70baaa858711ea9334baf6f1f9d Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:10:35 -0500 Subject: [PATCH 072/113] resources: smoother create (fixes #7798) (#7801) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/resources/resources-add.component.html | 2 +- src/app/resources/resources-add.scss | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b0371a0bb6..8e33f77734 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.21", + "version": "0.16.22", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/resources/resources-add.component.html b/src/app/resources/resources-add.component.html index 3ac3c770a9..f5978236eb 100644 --- a/src/app/resources/resources-add.component.html +++ b/src/app/resources/resources-add.component.html @@ -86,7 +86,7 @@ </mat-autocomplete> <mat-error><planet-form-error-messages [control]="resourceForm.controls.openWhichFile"></planet-form-error-messages></mat-error> </mat-form-field> - <div class="inner-gaps by-column full-width"> + <div class="file-upload inner-gaps by-column full-width"> <label i18n>File Upload:</label> <planet-file-input (fileChange)="bindFile($event)"></planet-file-input> <label i18n class="warn-text-color" *ngIf="resourceForm?.errors?.fileTooBig">File size cannot exceed more than 512 MB</label> diff --git a/src/app/resources/resources-add.scss b/src/app/resources/resources-add.scss index 4f4a5cb9ac..b93107fbbd 100644 --- a/src/app/resources/resources-add.scss +++ b/src/app/resources/resources-add.scss @@ -22,5 +22,11 @@ .form-container { width: auto; } + + .file-upload { + display: flex; + flex-direction: column; + } + } } From f0368b87f2fbc724d6dcb90b852bfd2691442296 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:21:00 -0500 Subject: [PATCH 073/113] teams: smoother tasks deadlines (fixes #5377) (#7842) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/tasks/tasks.component.html | 3 ++- src/app/tasks/tasks.component.ts | 20 ++++++++++++++++++++ src/app/tasks/tasks.scss | 8 ++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8e33f77734..67bedc05bf 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.22", + "version": "0.16.23", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/tasks/tasks.component.html b/src/app/tasks/tasks.component.html index 73ad09a824..16522e4336 100644 --- a/src/app/tasks/tasks.component.html +++ b/src/app/tasks/tasks.component.html @@ -15,7 +15,8 @@ <button mat-list-item *ngFor="let task of filteredTasks; trackBy: trackById" (click)="openTaskDetail(task)" [disabled]="!editable"> <mat-checkbox [checked]="task.completed" [disabled]="!editable" (change)="toggleTaskComplete(task)" (click)="$event.stopPropagation()"></mat-checkbox> <p matLine>{{task.title}}</p> - <p matLine><ng-container i18n>Deadline:</ng-container> {{task.deadline | date}} {{task.deadline | date: 'shortTime'}}</p> + <p matLine [ngClass]="{'deadline-soon': isTaskDueSoon(task), + 'deadline-passed': isTaskOverdue(task)}"><ng-container i18n>Deadline:</ng-container> {{task.deadline | date}} {{task.deadline | date: 'shortTime'}}</p> <p matLine *ngIf="task.completed"><ng-container i18n>Completed:</ng-container> {{task.completedTime | date}} {{task.completedTime | date: 'shortTime'}}</p> <img (click)="openMemberDialog(task.assignee); $event.stopPropagation()" matTooltip="{{task.assignee | assigneeName}}" *ngIf="task.assignee" matListAvatar [src]=" task.assignee.attachmentDoc ? diff --git a/src/app/tasks/tasks.component.ts b/src/app/tasks/tasks.component.ts index 9c7b7f64c7..2e22cd2544 100644 --- a/src/app/tasks/tasks.component.ts +++ b/src/app/tasks/tasks.component.ts @@ -72,6 +72,26 @@ export class TasksComponent implements OnInit { }); } + isTaskDueSoon(task): boolean { + if (!task || task.completed || !task.deadline) return false; + + const now = new Date(); + const deadline = new Date(task.deadline); + const twentyFourHoursFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000); + + const isWithinNextDay = deadline <= twentyFourHoursFromNow && deadline > now; + + return isWithinNextDay; + } + + isTaskOverdue(task): boolean { + if (task.completed || !task.deadline) return false; + + const now = new Date(); + const deadline = new Date(task.deadline); + return deadline < now; + } + openAddDialog(additionalFields, task: any = {}, onSuccess = (res) => {}) { const { fields, formGroup } = this.tasksService.addDialogForm(task); this.dialogsFormService.openDialogsForm(task.title ? $localize`Edit Task` : $localize`Add Task`, fields, formGroup, { diff --git a/src/app/tasks/tasks.scss b/src/app/tasks/tasks.scss index 65c5ece91c..14e3ba9115 100644 --- a/src/app/tasks/tasks.scss +++ b/src/app/tasks/tasks.scss @@ -19,6 +19,14 @@ planet-tasks { } } + .deadline-soon{ + color: #F6BE00 + } + + .deadline-passed{ + color: red + } + mat-button-toggle-group { font-size: 14px; From fe5d6d0cc55dcc3b8c559b0f6400ecb7d3dc96d2 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:25:32 -0500 Subject: [PATCH 074/113] mylife: smoother achievements certifications (fixes #7975) (#7995) Co-authored-by: Mutugi <48474421+Mutugiii@users.noreply.github.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements.component.html | 2 +- .../users/users-achievements/users-achievements.scss | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 67bedc05bf..81dd2eb708 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.23", + "version": "0.16.24", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index 4f2f70d554..55b80d2471 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -47,7 +47,7 @@ <h3 i18n>My Goals</h3> <h3 i18n>My Certifications</h3> <mat-list class="certs-list"> <mat-list-item *ngFor="let certification of certifications"> - {{certification.name}} + <span class="cert-item">{{certification.name}}</span> </mat-list-item> </mat-list> </div> diff --git a/src/app/users/users-achievements/users-achievements.scss b/src/app/users/users-achievements/users-achievements.scss index c280f57f51..782c8baf52 100644 --- a/src/app/users/users-achievements/users-achievements.scss +++ b/src/app/users/users-achievements/users-achievements.scss @@ -10,10 +10,12 @@ text-align: center; } - .certs-list .mat-list-item{ - display: flex; - justify-content: center; - text-align: center; + .certs-list { + .cert-item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } & mat-list, & ul, & ol { From 017ca20e07de624e8f147d2402d328157cdd3ef4 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:32:43 -0500 Subject: [PATCH 075/113] mylife: smoother achievements spacing (fixes #7996) (#7997) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/meetups/meetups.service.ts | 13 ++++++++++--- .../forms/planet-tag-input-dialog.component.ts | 2 +- .../users-achievements.component.html | 2 +- .../users-achievements/users-achievements.scss | 5 +++++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 81dd2eb708..2b601c30ff 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.24", + "version": "0.16.25", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/meetups/meetups.service.ts b/src/app/meetups/meetups.service.ts index c7fa5773ee..d1f1f37820 100644 --- a/src/app/meetups/meetups.service.ts +++ b/src/app/meetups/meetups.service.ts @@ -96,9 +96,16 @@ export class MeetupService { openDeleteDialog(meetups: any[] | any, callback) { const isMany = meetups.length > 1; const displayName = isMany ? '' : (meetups[0] || meetups).title; - const recurringInfo = (meetups[0] || meetups).recurring && (meetups[0] || meetups).recurring !== 'none' && (meetups[0] || meetups).recurringNumber - ? `(Recurs ${ (meetups[0] || meetups).recurring} for ${ (meetups[0] || meetups).recurringNumber } ${ (meetups[0] || meetups).recurring === 'daily' ? 'days' : 'weeks' })` - : ''; + const recurringInfo = + (meetups[0] || meetups).recurring && + (meetups[0] || meetups).recurring !== 'none' && + (meetups[0] || meetups).recurringNumber + ? `(Recurs ${(meetups[0] || meetups).recurring} for ${ + (meetups[0] || meetups).recurringNumber + } ${ + (meetups[0] || meetups).recurring === 'daily' ? 'days' : 'weeks' + })` + : ''; this.deleteDialog = this.dialog.open(DialogsPromptComponent, { data: { okClick: this.deleteMeetups([ meetups ].flat(), displayName, callback), diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index 8c549c019e..1e26049b29 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -21,7 +21,7 @@ import { DialogsPromptComponent } from '../../shared/dialogs/dialogs-prompt.comp } :host mat-dialog-content { overflow-y: auto; - max-height: calc(100vh - 100px); + max-height: calc(100vh - 100px); } :host .mat-list-option span { font-weight: inherit; diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index 55b80d2471..d6d29168eb 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -57,7 +57,7 @@ <h3 i18n>My Certifications</h3> <h3 i18n>My Links</h3> <mat-list> <mat-list-item class="mat-list-item-word-wrap" *ngFor="let link of achievements.links"> - <h4 mat-line>{{link.title}}:</h4> + <h4 class="link-title" mat-line>{{link.title}}:</h4> <a href="{{link.url}}" target="_blank" class="styled-link"> {{link.url}} </a> </mat-list-item> </mat-list> diff --git a/src/app/users/users-achievements/users-achievements.scss b/src/app/users/users-achievements/users-achievements.scss index 782c8baf52..27a8894a54 100644 --- a/src/app/users/users-achievements/users-achievements.scss +++ b/src/app/users/users-achievements/users-achievements.scss @@ -78,6 +78,7 @@ grid-area: txt; display: flex; align-items: center; + max-width: 75%; } .achievement-date { @@ -94,6 +95,10 @@ } } + .link-title { + max-width: 75%; + } + .styled-link { color: #007bff; text-decoration: none; From d62c5aace38bcba7a432ff268d555eb6f9412d4d Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:15:12 -0500 Subject: [PATCH 076/113] dashboard: smoother icon colors (fixes #7993) (#7999) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/dashboard/dashboard-tile.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2b601c30ff..9557fb14cf 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.25", + "version": "0.16.26", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/dashboard/dashboard-tile.scss b/src/app/dashboard/dashboard-tile.scss index 63e24d4bdb..ca1765f696 100644 --- a/src/app/dashboard/dashboard-tile.scss +++ b/src/app/dashboard/dashboard-tile.scss @@ -25,7 +25,7 @@ flex-wrap: wrap; font-size: 1.25rem; } - + color: #fff; } .right-tile { From ecda72027aecc5c59ecc709b1a00fc2ced52008b Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:18:58 -0500 Subject: [PATCH 077/113] chat: smoother full search (fixes #8004) (#8009) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../chat/chat-sidebar/chat-sidebar.component.html | 2 +- src/app/chat/chat-sidebar/chat-sidebar.scss | 4 ++++ src/app/tasks/tasks.component.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 9557fb14cf..bf145e9684 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.26", + "version": "0.16.27", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.html b/src/app/chat/chat-sidebar/chat-sidebar.component.html index 10bd193118..e95768a217 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.html +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.html @@ -13,8 +13,8 @@ <button mat-icon-button color="primary" (click)="resetFilter()" [disabled]="!titleSearch && !searchType" matTooltip="Clear search" i18n-matTooltip [matTooltipDisabled]="!titleSearch && !searchType"> <mat-icon>delete</mat-icon> </button><br> - <span style="font-size: small; font-style: italic;" i18n>Full Conversation Search </span> <mat-checkbox [checked]="fullTextSearch" (change)="toggleSearchType()"></mat-checkbox> + <span style="font-size: small; font-style: italic;" i18n>Full Conversation Search </span> <button style="text-align: end;" mat-icon-button (mouseenter)="toggleOverlay()" (mouseleave)="toggleOverlay()" cdkOverlayOrigin #trigger="cdkOverlayOrigin"> <mat-icon>help_outline</mat-icon> </button> diff --git a/src/app/chat/chat-sidebar/chat-sidebar.scss b/src/app/chat/chat-sidebar/chat-sidebar.scss index 4ef09f2260..f76ee91517 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.scss +++ b/src/app/chat/chat-sidebar/chat-sidebar.scss @@ -81,6 +81,10 @@ li:hover { margin-left: 4px; } +.mat-checkbox { + margin-right: 4px; +} + @media only screen and (max-width: $screen-md) { .expand-button { top: 10px; diff --git a/src/app/tasks/tasks.component.ts b/src/app/tasks/tasks.component.ts index 2e22cd2544..111d2bff72 100644 --- a/src/app/tasks/tasks.component.ts +++ b/src/app/tasks/tasks.component.ts @@ -73,20 +73,20 @@ export class TasksComponent implements OnInit { } isTaskDueSoon(task): boolean { - if (!task || task.completed || !task.deadline) return false; - + if (!task || task.completed || !task.deadline) { return false; } + const now = new Date(); const deadline = new Date(task.deadline); const twentyFourHoursFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000); - + const isWithinNextDay = deadline <= twentyFourHoursFromNow && deadline > now; - + return isWithinNextDay; } isTaskOverdue(task): boolean { - if (task.completed || !task.deadline) return false; - + if (task.completed || !task.deadline) { return false; } + const now = new Date(); const deadline = new Date(task.deadline); return deadline < now; From 653ff59f18808262ddb3601a201ddd6a3f8b0d45 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:23:15 -0500 Subject: [PATCH 078/113] mylife: smoother myhealth toolbar (fixes #8008) (#8010) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health.component.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bf145e9684..e2f415cd88 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.27", + "version": "0.16.28", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/health/health.component.html b/src/app/health/health.component.html index cc68e701dd..b1591cf81e 100644 --- a/src/app/health/health.component.html +++ b/src/app/health/health.component.html @@ -6,6 +6,7 @@ <div class="space-container"> <mat-toolbar class="primary-color font-size-1 action-buttons"> + <span class="toolbar-fill"></span> <a mat-raised-button color="accent" i18n [routerLink]="['update']" *ngIf="isOwnUser">Update Details</a> <a mat-raised-button color="accent" i18n [routerLink]="['event', { id: userDetail._id }]">Add Examination</a> <span *ngIf="userDetail.firstName === undefined"><ng-container i18n>Member login:</ng-container> {{userDetail.name}}</span> From 783cd87d7a7c05ee578dff36f6f17d2253abed45 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:26:37 -0500 Subject: [PATCH 079/113] mylife: smoother myhealth countdown (fixes #8007) (#8011) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health-event-dialog.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e2f415cd88..f350927b22 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.28", + "version": "0.16.29", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/health/health-event-dialog.component.html b/src/app/health/health-event-dialog.component.html index 8b594d91f9..5879b833db 100644 --- a/src/app/health/health-event-dialog.component.html +++ b/src/app/health/health-event-dialog.component.html @@ -27,6 +27,6 @@ <h4 class="primary-text-color" *ngIf="hasConditionAndTreatment" i18n>Other Notes <mat-dialog-actions> <button type="button" mat-raised-button mat-dialog-close i18n>Close</button> <button type="button" color="primary" mat-dialog-close (click)="editExam(event)" *ngIf="canUpdate" mat-raised-button> - <span i18n>Edit</span> <ng-container *ngIf="minutes">({{ minutes }}:{{ seconds }})</ng-container> + <span i18n>Edit <ng-container *ngIf="minutes">({{ minutes }}:{{ seconds }})</ng-container></span> </button> </mat-dialog-actions> From bb694afee9648045049d1e0653bb4a5e28c15e6d Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:31:33 -0500 Subject: [PATCH 080/113] community: smoother leaders tiles (fixes #8005) (#8015) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/community/community.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f350927b22..368c5ece47 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.29", + "version": "0.16.30", "myplanet": { "latest": "v0.21.55", "min": "v0.20.55" diff --git a/src/app/community/community.scss b/src/app/community/community.scss index 3d8e70b249..5f6aa4a2ff 100644 --- a/src/app/community/community.scss +++ b/src/app/community/community.scss @@ -66,6 +66,7 @@ planet-calendar { top: -0.25rem; right: -0.25rem; } + word-break: break-word; } mat-tab-group, mat-tab { From d0302de4744c8f982c1aa285653b10e49ba334dd Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:44:53 -0500 Subject: [PATCH 081/113] manager: smoother myplanet pin (fixes #7920) (#8023) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/manager-dashboard/manager-dashboard.component.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 368c5ece47..6b4c903c21 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.30", + "version": "0.16.31", "myplanet": { - "latest": "v0.21.55", - "min": "v0.20.55" + "latest": "v0.21.58", + "min": "v0.20.58" }, "scripts": { "ng": "ng", diff --git a/src/app/manager-dashboard/manager-dashboard.component.html b/src/app/manager-dashboard/manager-dashboard.component.html index 85d2f7bb5b..0103f5a15b 100644 --- a/src/app/manager-dashboard/manager-dashboard.component.html +++ b/src/app/manager-dashboard/manager-dashboard.component.html @@ -75,7 +75,7 @@ <h3 i18n *ngIf="showParentList">{{ planetType === 'community' ? 'Nation' : 'Cent <p *ngSwitchDefault i18n><b>Your request has not yet been accepted by { planetType, select, community {nation} nation {center}}</b></p> </ng-container> </div> - <div *ngIf="pin"><span i18n>Your tablet pin number is:</span> <b class="pinClass">{{ ' ' + pin + ' ' }}</b> + <div *ngIf="pin"><span i18n>myPlanet Server pin:</span> <b class="pinClass">{{ ' ' + pin + ' ' }}</b> <button mat-raised-button i18n (click)="resetPin()">Reset Pin</button> </div> </ng-container> From 8c546a9e4bce148b2101b6358edff9294b07ed76 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 2 Jan 2025 22:50:33 +0300 Subject: [PATCH 082/113] all: smoother december challenge (fixes #8024) (#8026) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/dialogs/dialogs-announcement.component.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6b4c903c21..5f747c5ecf 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.31", + "version": "0.16.32", "myplanet": { "latest": "v0.21.58", "min": "v0.20.58" diff --git a/src/app/shared/dialogs/dialogs-announcement.component.scss b/src/app/shared/dialogs/dialogs-announcement.component.scss index f3d53594ca..fd1a6d80aa 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.scss +++ b/src/app/shared/dialogs/dialogs-announcement.component.scss @@ -55,8 +55,9 @@ background-color: $primary; transition: width 0.5s; display: flex; - justify-content: center; align-items: center; + white-space: nowrap; + padding: 0 10px; } .thermometer-label { From 30bf903a64ec3ed0653eb80d103dc1123e731e8e Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:56:37 -0500 Subject: [PATCH 083/113] teams: smoother voices deletion (fixes #8016)(fixes #8017) (#8019) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/news/news-list.component.ts | 5 +++-- src/app/news/news.service.ts | 6 +++--- src/app/shared/dialogs/dialogs-prompt.component.html | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5f747c5ecf..a8122962bd 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.32", + "version": "0.16.33", "myplanet": { "latest": "v0.21.58", "min": "v0.20.58" diff --git a/src/app/news/news-list.component.ts b/src/app/news/news-list.component.ts index 61d3ea892a..fb0b0cf5bd 100644 --- a/src/app/news/news-list.component.ts +++ b/src/app/news/news-list.component.ts @@ -115,7 +115,8 @@ export class NewsListComponent implements OnChanges { data: { okClick: this.deleteNews(news), changeType: 'delete', - type: 'news' + type: 'news', + displayName: news.message } }); } @@ -134,7 +135,7 @@ export class NewsListComponent implements OnChanges { this.deleteDialog.close(); }, onError: (error) => { - this.planetMessageService.showAlert($localize`There was a problem deleting this news.`); + this.planetMessageService.showAlert($localize`There was a problem deleting this message.`); } }; } diff --git a/src/app/news/news.service.ts b/src/app/news/news.service.ts index 32bfcab631..de318dab2c 100644 --- a/src/app/news/news.service.ts +++ b/src/app/news/news.service.ts @@ -54,7 +54,7 @@ export class NewsService { return ((item.viewIn || []).find(view => view._id === viewId) || {}).sharedDate; } - postNews(post, successMessage = $localize`Thank you for submitting your news`, isMessageEdit = true) { + postNews(post, successMessage = $localize`Thank you for submitting your message`, isMessageEdit = true) { const { configuration } = this.stateService; const message = typeof post.message === 'string' ? post.message : post.message.text; const images = this.createImagesArray(post, message); @@ -76,7 +76,7 @@ export class NewsService { } deleteNews(post) { - return this.postNews({ ...post, _deleted: true }, $localize`Post deleted`); + return this.postNews({ ...post, _deleted: true }, $localize`Message deleted`); } createImagesArray(post, message) { @@ -90,7 +90,7 @@ export class NewsService { return this.couchService.bulkDocs(this.dbName, replies.map(reply => ({ ...reply.doc, replyTo: newReplyToId }))); } - shareNews(news, planets?: any[], successMessage = $localize`News has been successfully shared`) { + shareNews(news, planets?: any[], successMessage = $localize`Message has been successfully shared`) { const viewInObject = (planet) => ( { '_id': `${planet.code}@${planet.parentCode}`, section: 'community', sharedDate: this.couchService.datePlaceholder } ); diff --git a/src/app/shared/dialogs/dialogs-prompt.component.html b/src/app/shared/dialogs/dialogs-prompt.component.html index 3c67eab8ba..456c4b973f 100644 --- a/src/app/shared/dialogs/dialogs-prompt.component.html +++ b/src/app/shared/dialogs/dialogs-prompt.component.html @@ -17,6 +17,7 @@ event {event} transaction {transaction} link {link} + news {message} report {report}}? } many From dfd9e5da88c006dfacb4a10d3d1fa021535c54ee Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:18:50 -0500 Subject: [PATCH 084/113] manager: smoother members buttons (fixes #8003) (#8018) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/users/users-table.component.html | 4 ++-- src/app/users/users-table.scss | 14 +++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index a8122962bd..dcb3d2269a 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.33", + "version": "0.16.34", "myplanet": { - "latest": "v0.21.58", - "min": "v0.20.58" + "latest": "v0.21.62", + "min": "v0.20.62" }, "scripts": { "ng": "ng", diff --git a/src/app/users/users-table.component.html b/src/app/users/users-table.component.html index 613bf4c617..f49f5f43bd 100644 --- a/src/app/users/users-table.component.html +++ b/src/app/users/users-table.component.html @@ -127,10 +127,10 @@ <label *ngIf="!isMobile"> Demote </label> </button> </span> - <a class="visibility-cell" (click)="gotoProfileView(element.doc.name)" mat-raised-button color="primary" i18n> + <button (click)="gotoProfileView(element.doc.name)" mat-raised-button color="primary" i18n> <mat-icon>visibility</mat-icon> <label *ngIf="!isMobile"> View Profile </label> - </a> + </button> </div> </mat-cell> </ng-container> diff --git a/src/app/users/users-table.scss b/src/app/users/users-table.scss index dc3458c0cd..68bc3af1fb 100644 --- a/src/app/users/users-table.scss +++ b/src/app/users/users-table.scss @@ -23,6 +23,15 @@ button-container { justify-content: center; } +.mat-cell button, .mat-cell div button[mat-raised-button] { + width: 150px; + text-align: center; + padding: 8px 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + @media(max-width: $screen-md) { .mat-column-visitCount, .mat-column-joinDate, .mat-column-lastLogin { max-width: 70px; @@ -30,8 +39,7 @@ button-container { } @media (max-width: $screen-sm) { - mat-cell button, .visibility-cell { - min-width: 0; - padding: 0 3px; + .mat-cell button, .mat-cell div button[mat-raised-button] { + width: 50px; } } From c0c6167075478d321f4be56113284b4c463fed47 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:35:56 -0500 Subject: [PATCH 085/113] resources: smoother list titles (fixes #8030) (#8031) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/resources/resources.scss | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index dcb3d2269a..c64a0b38d6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.34", + "version": "0.16.35", "myplanet": { "latest": "v0.21.62", "min": "v0.20.62" diff --git a/src/app/resources/resources.scss b/src/app/resources/resources.scss index 1de20c3419..5e803920cf 100644 --- a/src/app/resources/resources.scss +++ b/src/app/resources/resources.scss @@ -44,6 +44,19 @@ $label-height: 1rem; } } +.list-content-menu { + &.list-content-menu-auto { + .header { + word-break: break-word; + max-width: 100%; + + a { + display: inline-block; + } + } + } +} + .ellipsis-menu { text-align: center; } From c6797c0a0bf401aaade3119ab32eaa5d75a47e52 Mon Sep 17 00:00:00 2001 From: sahilvunnam <118228103+sahilvunnam@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:27:53 -0800 Subject: [PATCH 086/113] mylife: smoother myachievements (fixes #6429) (#7753) Co-authored-by: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 ++-- .../users-achievements-update.component.html | 5 +-- .../users-achievements-update.component.ts | 35 +++++++++++-------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index c64a0b38d6..b183cd06fc 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.35", + "version": "0.16.36", "myplanet": { - "latest": "v0.21.62", - "min": "v0.20.62" + "latest": "v0.21.74", + "min": "v0.20.74" }, "scripts": { "ng": "ng", diff --git a/src/app/users/users-achievements/users-achievements-update.component.html b/src/app/users/users-achievements/users-achievements-update.component.html index 2240d819ae..7147bde432 100644 --- a/src/app/users/users-achievements/users-achievements-update.component.html +++ b/src/app/users/users-achievements/users-achievements-update.component.html @@ -1,6 +1,7 @@ <mat-toolbar> <button mat-icon-button (click)="goBack()"><mat-icon>arrow_back</mat-icon></button> - <span i18n>Edit Achievements</span> + <span *ngIf="achievementNotFound" i18n>Add Achievements</span> + <span *ngIf="!achievementNotFound" i18n>Edit Achievements</span> </mat-toolbar> <div class="planet-users-achievements-update space-container"> @@ -91,7 +92,7 @@ </form> </div> <div class="achievement-button"> - <button type="button" (click)="onSubmit()" mat-raised-button [planetSubmit]="editForm.valid && profileForm.valid" color="primary" i18n>Update</button> + <button type="button" (click)="onSubmit()" mat-raised-button [planetSubmit]="editForm.valid && profileForm.valid" color="primary" i18n>Submit</button> <button type="button" mat-raised-button color="warn" (click)="goBack()" i18n>Cancel</button> </div> </div> diff --git a/src/app/users/users-achievements/users-achievements-update.component.ts b/src/app/users/users-achievements/users-achievements-update.component.ts index 162823a75d..0b6c8fd501 100644 --- a/src/app/users/users-achievements/users-achievements-update.component.ts +++ b/src/app/users/users-achievements/users-achievements-update.component.ts @@ -20,11 +20,11 @@ import { showFormErrors } from '../../shared/table-helpers'; encapsulation: ViewEncapsulation.None }) export class UsersAchievementsUpdateComponent implements OnInit, OnDestroy { - user = this.userService.get(); configuration = this.stateService.configuration; docInfo = { '_id': this.user._id + '@' + this.configuration.code, '_rev': undefined }; readonly dbName = 'achievements'; + achievementNotFound = false; editForm: FormGroup; profileForm: FormGroup; private onDestroy$ = new Subject<void>(); @@ -59,20 +59,25 @@ export class UsersAchievementsUpdateComponent implements OnInit, OnDestroy { ngOnInit() { this.profileForm.patchValue(this.user); this.usersAchievementsService.getAchievements(this.docInfo._id) - .pipe(catchError(() => this.usersAchievementsService.getAchievements(this.user._id))) - .subscribe((achievements) => { - this.editForm.patchValue(achievements); - this.editForm.controls.achievements = this.fb.array(achievements.achievements || []); - this.editForm.controls.references = this.fb.array(achievements.references || []); - this.editForm.controls.links = this.fb.array(achievements.links || []); - // Keeping older otherInfo property so we don't lose this info on database - this.editForm.controls.otherInfo = this.fb.array(achievements.otherInfo || []); - if (this.docInfo._id === achievements._id) { - this.docInfo._rev = achievements._rev; - } - }, (error) => { - console.log(error); - }); + .pipe( + catchError(() => this.usersAchievementsService.getAchievements(this.user._id)) + ) + .subscribe((achievements) => { + this.editForm.patchValue(achievements); + this.editForm.controls.achievements = this.fb.array(achievements.achievements || []); + this.editForm.controls.references = this.fb.array(achievements.references || []); + this.editForm.controls.links = this.fb.array(achievements.links || []); + // Keeping older otherInfo property so we don't lose this info on database + this.editForm.controls.otherInfo = this.fb.array(achievements.otherInfo || []); + + if (this.docInfo._id === achievements._id) { + this.docInfo._rev = achievements._rev; + } + }, (error) => { + console.log(error); + this.achievementNotFound = true; + }); + this.planetStepListService.stepMoveClick$.pipe(takeUntil(this.onDestroy$)).subscribe( () => this.editForm.controls.dateSortOrder.setValue('none') ); From b8681b79e003deed233d22e0723899bc73043912 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:32:29 -0500 Subject: [PATCH 087/113] teams: smoother list titles (fixes #8028) (#8029) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams.component.html | 4 ++-- src/app/teams/teams.scss | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b183cd06fc..9b3b0d3d23 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.36", + "version": "0.16.37", "myplanet": { "latest": "v0.21.74", "min": "v0.20.74" diff --git a/src/app/teams/teams.component.html b/src/app/teams/teams.component.html index 033295d432..b2f889b592 100644 --- a/src/app/teams/teams.component.html +++ b/src/app/teams/teams.component.html @@ -43,8 +43,8 @@ <ng-container matColumnDef="doc.name"> <mat-header-cell *matHeaderCellDef mat-sort-header="doc.name" i18n>Name</mat-header-cell> <mat-cell *matCellDef="let element"> - <h3> - {{element.doc.name}} + <h3 class="team-name"> + {{element.doc.name.length > 200 ? element.doc.name.slice(0, 200) + '...': element.doc.name }} <mat-icon class="margin-lr-3" i18n-title title="{{ mode === 'enterprise' ? 'Joined Enterprise' : 'Joined Team' }}" [inline]="true" *ngIf="element.userStatus=='member'">check</mat-icon> </h3> </mat-cell> diff --git a/src/app/teams/teams.scss b/src/app/teams/teams.scss index 0e6a2cfae0..04b1fac865 100644 --- a/src/app/teams/teams.scss +++ b/src/app/teams/teams.scss @@ -26,6 +26,11 @@ justify-content: center; } + .team-name{ + max-width: 95%; + overflow: hidden; + } + @media screen and (max-width: #{$screen-sm}) { mat-cell button { min-width: 0; From 773fca5a7429f1a4187b3ad5e6832fc34385ff6c Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:43:23 -0500 Subject: [PATCH 088/113] mylife: smoother myachievements view (fixes #8040) (#8041) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements.component.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 9b3b0d3d23..d5109497cb 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.37", + "version": "0.16.38", "myplanet": { "latest": "v0.21.74", "min": "v0.20.74" diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index d6d29168eb..1ccb50c49a 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -31,19 +31,19 @@ <h2>{{user.firstName}} {{user.middleName}} {{user.lastName}}</h2> <span *ngIf="user.birthplace" i18n>Birthplace: {{' ' + user.birthplace}} </span> </div> </div> - <mat-divider></mat-divider> <div *ngIf="achievements.purpose"> + <mat-divider></mat-divider> <h3 i18n>My Purpose</h3> <td-markdown [content]="achievements.purpose"></td-markdown> </div> - <mat-divider></mat-divider> <div *ngIf="achievements.goals"> + <mat-divider></mat-divider> <h3 i18n>My Goals</h3> <td-markdown [content]="achievements.goals"></td-markdown> </div> - <mat-divider></mat-divider> <ng-container *planetBeta> <div *ngIf="certifications.length > 0"> + <mat-divider></mat-divider> <h3 i18n>My Certifications</h3> <mat-list class="certs-list"> <mat-list-item *ngFor="let certification of certifications"> @@ -52,8 +52,8 @@ <h3 i18n>My Certifications</h3> </mat-list> </div> </ng-container> - <mat-divider></mat-divider> <div *ngIf="achievements?.links?.length > 0"> + <mat-divider></mat-divider> <h3 i18n>My Links</h3> <mat-list> <mat-list-item class="mat-list-item-word-wrap" *ngFor="let link of achievements.links"> @@ -62,8 +62,8 @@ <h4 class="link-title" mat-line>{{link.title}}:</h4> </mat-list-item> </mat-list> </div> - <mat-divider></mat-divider> <div *ngIf="achievements.achievementsHeader || achievements.achievements.length > 0"> + <mat-divider></mat-divider> <h3 i18n>My Achievements</h3> <td-markdown [content]="achievements.achievementsHeader"></td-markdown> <mat-list> @@ -85,8 +85,8 @@ <h3 i18n>My Achievements</h3> </mat-list-item> </mat-list> </div> - <mat-divider></mat-divider> <div *ngIf="achievements.references.length > 0"> + <mat-divider></mat-divider> <h3 i18n>My References</h3> <mat-list class="references-list"> <mat-list-item class="mat-list-item-word-wrap" *ngFor="let reference of achievements.references"> From b768259f48f398ba77c2c49695bd917e404c91ec Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:00:28 +0300 Subject: [PATCH 089/113] all: extend december challenge (fixes #8044) (#8046) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/dialogs/dialogs-announcement.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d5109497cb..1c8eb63673 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.38", + "version": "0.16.39", "myplanet": { "latest": "v0.21.74", "min": "v0.20.74" diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts index ec17b7215b..0651bfcbee 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.ts +++ b/src/app/shared/dialogs/dialogs-announcement.component.ts @@ -18,7 +18,7 @@ import { planetAndParentId } from '../../manager-dashboard/reports/reports.utils export const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'okuro', 'uriur', 'mutugi', 'vi' ]; export const challengeCourseId = '4e6b78800b6ad18b4e8b0e1e38a98cac'; export const examId = '4e6b78800b6ad18b4e8b0e1e38b382ab'; -export const challengePeriod = (new Date() > new Date(2024, 10, 31)) && (new Date() < new Date(2024, 12, 1)); +export const challengePeriod = (new Date() > new Date(2024, 10, 31)) && (new Date() < new Date(2025, 0, 16)); @Component({ template: ` From d5aee9fa9d29589835ef9c20f6af06f1435697d4 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:13:56 -0500 Subject: [PATCH 090/113] mylife: smoother myhealth exams (fixes #8035) (#8045) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c8eb63673..cf7b6f7bdb 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.39", + "version": "0.16.40", "myplanet": { "latest": "v0.21.74", "min": "v0.20.74" diff --git a/src/app/health/health.scss b/src/app/health/health.scss index 08c79a7c97..cab22985ca 100644 --- a/src/app/health/health.scss +++ b/src/app/health/health.scss @@ -68,3 +68,7 @@ mat-table { min-width: 110px; } } + +mat-header-cell div .header-date { + min-height: 1.7rem; +} From ad1261401ff595454dbba2400e000daae626f7a0 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:26:31 -0500 Subject: [PATCH 091/113] mylife: smoother myhealth emergency contact (fixes #8034) (#8049) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf7b6f7bdb..0e2c23a02f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.40", + "version": "0.16.41", "myplanet": { "latest": "v0.21.74", "min": "v0.20.74" diff --git a/src/app/health/health.component.html b/src/app/health/health.component.html index b1591cf81e..8f236c079b 100644 --- a/src/app/health/health.component.html +++ b/src/app/health/health.component.html @@ -57,7 +57,7 @@ <h4 class="primary-text-color" i18n>Birthplace</h4> </div> <mat-divider></mat-divider> </div> - <div> + <div class="full-width"> <div> <h4 class="primary-text-color" i18n>Emergency Contact</h4> <p><b i18n>Name: </b>{{healthDetail?.emergencyContactName || 'N/A'}}</p> From 8470fc3ed525cbc507c64debe0437def46749931 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:52:01 +0300 Subject: [PATCH 092/113] dashboard: smoother challenge initial load (fixes #7917) (#7933) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +-- .../dialogs-announcement.component.html | 2 +- .../dialogs/dialogs-announcement.component.ts | 36 ++++++++++-------- src/assets/challenge/dec challenge.jpeg | Bin 0 -> 192227 bytes 4 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 src/assets/challenge/dec challenge.jpeg diff --git a/package.json b/package.json index 0e2c23a02f..767ee0823b 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.41", + "version": "0.16.42", "myplanet": { - "latest": "v0.21.74", - "min": "v0.20.74" + "latest": "v0.21.75", + "min": "v0.20.75" }, "scripts": { "ng": "ng", diff --git a/src/app/shared/dialogs/dialogs-announcement.component.html b/src/app/shared/dialogs/dialogs-announcement.component.html index 4754422790..79ad83396a 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.html +++ b/src/app/shared/dialogs/dialogs-announcement.component.html @@ -1,6 +1,6 @@ <div class="announcement-container"> <img - src="https://res.cloudinary.com/mutugiii/image/upload/v1733224910/dec_challenge_svcbi3.jpg" + src="assets/challenge/dec challenge.jpeg" alt="Issues Challenge" class="announcement-banner" /> diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts index 0651bfcbee..93cbade868 100644 --- a/src/app/shared/dialogs/dialogs-announcement.component.ts +++ b/src/app/shared/dialogs/dialogs-announcement.component.ts @@ -1,8 +1,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { Subject, of, Observable } from 'rxjs'; +import { takeUntil, catchError, map, switchMap } from 'rxjs/operators'; import { findDocuments } from '../../shared/mangoQueries'; import { CouchService } from '../couchdb.service'; @@ -11,7 +11,6 @@ import { NewsService } from '../../news/news.service'; import { StateService } from '../state.service'; import { SubmissionsService } from '../../submissions/submissions.service'; import { UserService } from '../user.service'; -import { UsersService } from '../../users/users.service'; import { UserChallengeStatusService } from '../user-challenge-status.service'; import { planetAndParentId } from '../../manager-dashboard/reports/reports.utils'; @@ -24,7 +23,7 @@ export const challengePeriod = (new Date() > new Date(2024, 10, 31)) && (new Dat template: ` <div class="announcement-container"> <img - src="https://res.cloudinary.com/mutugiii/image/upload/v1733224910/dec_challenge_svcbi3.jpg" + src="assets/challenge/dec challenge.jpeg" alt="Issues Challenge" class="announcement-banner" /> @@ -67,7 +66,6 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { private stateService: StateService, private submissionsService: SubmissionsService, private userService: UserService, - private usersService: UsersService, private userStatusService: UserChallengeStatusService ) {} @@ -90,6 +88,7 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { } initializeData() { + this.fetchMembers().subscribe(members => { this.members = members; }); this.coursesService.requestCourses(); this.newsService.requestNews({ selectors: { @@ -100,7 +99,6 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { }, viewId: this.teamId }); - this.fetchMembers(); this.fetchCourseAndNews(); this.fetchEnrolledMembers(); } @@ -158,16 +156,22 @@ export class DialogsAnnouncementComponent implements OnInit, OnDestroy { ); } - fetchMembers() { - this.usersService.getAllUsers().subscribe((users: any) => { - this.members = users.map((member: any) => { - const [ , memberName ] = member?._id.split(':'); - return { - ...member, - name: memberName, - }; - }); - }); + fetchMembers(): Observable<any[]> { + return this.couchService.findAll('login_activities', findDocuments({ + type: 'login', + loginTime: { $gte: this.startDate.getTime() } + }, [ 'user' ])).pipe( + catchError(() => of([])), + map((res: any[]) => Array.from(new Set(res.map(doc => doc.user)))), + switchMap(uniqueUsers => { + if (uniqueUsers.length === 0) { return of([]); } + return this.couchService.findAll( + '_users', + findDocuments({ name: { $in: uniqueUsers } }, [ '_id', 'name' ]) + ); + }), + catchError(() => of([])) + ); } fetchEnrolledMembers() { diff --git a/src/assets/challenge/dec challenge.jpeg b/src/assets/challenge/dec challenge.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..944b8beae6aa8478ea1a7cb8eb06a6d0d1f67c9c GIT binary patch literal 192227 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 AT>t<8 literal 0 HcmV?d00001 From 154cf6901447982c7d0ad77495971cc162a0ad56 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 9 Jan 2025 06:02:07 -0500 Subject: [PATCH 093/113] mylife: smoother myhealth exams (fixes #8050) (#8051) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health.component.html | 2 +- src/app/health/health.scss | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 767ee0823b..8b8e755e3f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.42", + "version": "0.16.43", "myplanet": { "latest": "v0.21.75", "min": "v0.20.75" diff --git a/src/app/health/health.component.html b/src/app/health/health.component.html index 8f236c079b..397e14bef8 100644 --- a/src/app/health/health.component.html +++ b/src/app/health/health.component.html @@ -82,7 +82,7 @@ <h4 class="primary-text-color" i18n>Notes</h4> </div> </div> <ng-container *ngIf="events.length > 0"> - <h4 class="full-width" i18n>Examinations</h4> + <h3 class="full-width examinations-header" i18n>Examinations</h3> <div class="full-width table-container" #examsTable> <mat-table [dataSource]="eventTable" *ngIf="events.length > 0"> <ng-container *ngFor="let column of displayedColumns" [matColumnDef]="column" [sticky]="column === 'label'"> diff --git a/src/app/health/health.scss b/src/app/health/health.scss index cab22985ca..bc02abb366 100644 --- a/src/app/health/health.scss +++ b/src/app/health/health.scss @@ -69,6 +69,10 @@ mat-table { } } +.examinations-header { + text-align: center; +} + mat-header-cell div .header-date { min-height: 1.7rem; -} +} \ No newline at end of file From e8550ad79fc8f8fad782f83437ac41cea5492f47 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:19:27 +0300 Subject: [PATCH 094/113] teams: add surveys (fixes #7944) (#7957) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/exams/exams-add.component.ts | 4 ++++ src/app/home/home.module.ts | 4 +++- .../submissions/submissions.component.html | 2 +- src/app/surveys/surveys.component.html | 8 ++++---- src/app/surveys/surveys.component.ts | 19 ++++++++++++++++--- src/app/surveys/surveys.module.ts | 1 + src/app/teams/teams-router.module.ts | 4 ++++ src/app/teams/teams-view.component.html | 6 +++++- src/app/teams/teams.module.ts | 2 ++ 10 files changed, 41 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 8b8e755e3f..d3e93eabe9 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.43", + "version": "0.16.44", "myplanet": { "latest": "v0.21.75", "min": "v0.20.75" diff --git a/src/app/exams/exams-add.component.ts b/src/app/exams/exams-add.component.ts index d3f786937e..abf7c6d3e9 100644 --- a/src/app/exams/exams-add.component.ts +++ b/src/app/exams/exams-add.component.ts @@ -37,6 +37,7 @@ export class ExamsAddComponent implements OnInit { pageType: 'Add' | 'Update' | 'Copy' = 'Add'; courseName = ''; examType: 'exam' | 'survey' = <'exam' | 'survey'>this.route.snapshot.paramMap.get('type') || 'exam'; + teamId = this.route.parent?.snapshot.paramMap.get('teamId') || null; successMessage = this.examType === 'survey' ? $localize`New survey added` : $localize`New test added`; steps = []; showFormError = false; @@ -120,6 +121,9 @@ export class ExamsAddComponent implements OnInit { onSubmit(reRoute = false) { if (this.examForm.valid) { + if (this.teamId) { + this.examForm.value.teamId = this.teamId; + } this.showFormError = false; this.addExam(Object.assign({}, this.examForm.value, this.documentInfo), reRoute); } else { diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index 8ee5378d75..5611dc04be 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -27,6 +27,7 @@ import { UsersModule } from '../users/users.module'; import { PlanetComponent } from './planet.component'; import { CoursesViewDetailModule } from '../courses/view-courses/courses-view-detail.module'; import { ChatModule } from '../chat/chat.module'; +import { SurveysModule } from '../surveys/surveys.module'; @NgModule({ imports: [ @@ -46,7 +47,8 @@ import { ChatModule } from '../chat/chat.module'; PlanetCalendarModule, UsersModule, CoursesViewDetailModule, - ChatModule + ChatModule, + SurveysModule ], declarations: [ HomeComponent, diff --git a/src/app/submissions/submissions.component.html b/src/app/submissions/submissions.component.html index 950e0b2fbf..cd9fd16f52 100644 --- a/src/app/submissions/submissions.component.html +++ b/src/app/submissions/submissions.component.html @@ -62,7 +62,7 @@ </ng-container> <ng-container matColumnDef="courseTitle"> <mat-header-cell *matHeaderCellDef mat-sort-header="courseTitle" i18n>Course Name</mat-header-cell> - <mat-cell *matCellDef="let element">{{element.courseTitle}}</mat-cell> + <mat-cell *matCellDef="let element">{{element.courseTitle || element.parent.courseTitle}}</mat-cell> </ng-container> <ng-container matColumnDef="stepNum"> <mat-header-cell *matHeaderCellDef mat-sort-header="stepNum" i18n>Course Step</mat-header-cell> diff --git a/src/app/surveys/surveys.component.html b/src/app/surveys/surveys.component.html index cd3302a9bd..a94b93f4ed 100644 --- a/src/app/surveys/surveys.component.html +++ b/src/app/surveys/surveys.component.html @@ -1,4 +1,4 @@ -<mat-toolbar> +<mat-toolbar *ngIf="!teamId"> <button mat-icon-button (click)="goBack()"> <mat-icon>arrow_back</mat-icon> </button> @@ -11,7 +11,7 @@ </mat-toolbar> <div class="space-container primary-link-hover"> - <mat-toolbar class="primary-color font-size-1"> + <mat-toolbar *ngIf="!teamId" class="primary-color font-size-1"> <button mat-mini-fab (click)="routeToEditSurvey('add')" *ngIf="isAuthorized" > <mat-icon>add</mat-icon> </button> @@ -61,8 +61,8 @@ <mat-header-cell *matHeaderCellDef i18n>Action</mat-header-cell> <mat-cell *matCellDef="let element"> <ng-container *ngIf="!element.parent === true"> - <button mat-raised-button color="primary" (click)="routeToEditSurvey('update', element._id)" i18n><mat-icon>edit</mat-icon> Edit</button> - <button mat-raised-button color="primary" [disabled]="!element.questions.length" (click)="openSendSurveyDialog(element)" i18n><mat-icon>send</mat-icon> Send</button> + <button mat-raised-button color="primary" *ngIf="!teamId" (click)="routeToEditSurvey('update', element._id)" i18n><mat-icon>edit</mat-icon> Edit</button> + <button mat-raised-button color="primary" *ngIf="!teamId && !routeTeamId" [disabled]="!element.questions.length" (click)="openSendSurveyDialog(element)" i18n><mat-icon>send</mat-icon> Send</button> <button mat-raised-button color="primary" [disabled]="!element.questions.length" (click)="recordSurvey(element)" i18n-matTooltip matTooltip="Record survey information from a person who is not a member of {{configuration.name}}" i18n> <mat-icon>fiber_manual_record</mat-icon> Record diff --git a/src/app/surveys/surveys.component.ts b/src/app/surveys/surveys.component.ts index 8df508e2fc..5079e926d3 100644 --- a/src/app/surveys/surveys.component.ts +++ b/src/app/surveys/surveys.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, ViewChild, AfterViewInit, OnDestroy, Input } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormGroup } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -25,6 +25,7 @@ import { DialogsFormService } from '../shared/dialogs/dialogs-form.service'; import { DialogsAddTableComponent } from '../shared/dialogs/dialogs-add-table.component'; @Component({ + selector: 'planet-surveys', templateUrl: './surveys.component.html', styleUrls: [ './surveys.component.scss' ] }) @@ -44,6 +45,8 @@ export class SurveysComponent implements OnInit, AfterViewInit, OnDestroy { message = ''; configuration = this.stateService.configuration; parentCount = 0; + routeTeamId = this.route.parent?.snapshot.paramMap.get('teamId') || null; + @Input() teamId?: string; constructor( private couchService: CouchService, @@ -79,7 +82,17 @@ export class SurveysComponent implements OnInit, AfterViewInit, OnDestroy { }).length })), ...this.createParentSurveys(submissions) - ]; + ].filter(survey => { + if (this.routeTeamId) { + return survey.teamId === this.routeTeamId; + } + + if (this.teamId) { + return survey.teamId === this.teamId; + } else { + return true; + } + }); this.surveys.data = this.surveys.data.map((data: any) => ({ ...data, courseTitle: data.course ? data.course.courseTitle : '' })); this.dialogsLoadingService.stop(); }); @@ -245,7 +258,7 @@ export class SurveysComponent implements OnInit, AfterViewInit, OnDestroy { recordSurvey(survey: any) { this.submissionsService.createSubmission(survey, 'survey').subscribe((res: any) => { this.router.navigate([ - 'dispense', + this.teamId ? 'surveys/dispense' : 'dispense', { questionNum: 1, submissionId: res.id, status: 'pending', mode: 'take' } ], { relativeTo: this.route }); }); diff --git a/src/app/surveys/surveys.module.ts b/src/app/surveys/surveys.module.ts index c9594d40bb..35294a277a 100644 --- a/src/app/surveys/surveys.module.ts +++ b/src/app/surveys/surveys.module.ts @@ -23,6 +23,7 @@ import { SharedComponentsModule } from '../shared/shared-components.module'; SurveysRouterModule, SharedComponentsModule ], + exports: [ SurveysComponent ], declarations: [ SurveysComponent ] diff --git a/src/app/teams/teams-router.module.ts b/src/app/teams/teams-router.module.ts index b686e4f9bc..ba8a036766 100644 --- a/src/app/teams/teams-router.module.ts +++ b/src/app/teams/teams-router.module.ts @@ -6,6 +6,10 @@ import { TeamsViewComponent } from './teams-view.component'; const routes: Routes = [ { path: '', component: TeamsComponent }, { path: 'view/:teamId', component: TeamsViewComponent }, + { + path: 'view/:teamId/surveys', + loadChildren: () => import('../surveys/surveys.module').then(m => m.SurveysModule) + }, { path: 'users', loadChildren: () => import('../users/users.module').then(m => m.UsersModule) }, ]; @NgModule({ diff --git a/src/app/teams/teams-view.component.html b/src/app/teams/teams-view.component.html index 4f2a7d5b8b..ce128346a2 100644 --- a/src/app/teams/teams-view.component.html +++ b/src/app/teams/teams-view.component.html @@ -43,6 +43,7 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration <ng-template #actionButtons> <ng-container [ngSwitch]="userStatus" *ngIf="user.isUserAdmin || user.roles.length"> <ng-container *ngSwitchCase="'member'"> + <a class="margin-lr-3" [routerLink]="['surveys']" mat-stroked-button mat-button i18n>Manage Surveys</a> <button *ngIf="mode!=='services'" mat-stroked-button mat-button class=" toolbar-button margin-lr-3" (click)="openInviteMemberDialog()" i18n [disabled]="disableAddingMembers"> Add Members </button> @@ -184,7 +185,7 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration </div> </ng-template> </mat-tab> - <mat-tab label="Courses"> + <mat-tab i18n-label label="Courses"> <ng-template mat-tab-label> <ng-container i18n>Courses</ng-container> ({{team?.courses?.length || 0}}) </ng-template> @@ -211,6 +212,9 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration </div> </ng-template> </mat-tab> + <mat-tab i18n-label label="Surveys"> + <planet-surveys [teamId]="teamId"></planet-surveys> + </mat-tab> <mat-tab *ngIf="mode!=='services'" #applicantTab> <ng-template mat-tab-label> <ng-container i18n>{ mode, select, team {Join Requests} enterprise {Applicants} }</ng-container> ({{requests.length}}) diff --git a/src/app/teams/teams.module.ts b/src/app/teams/teams.module.ts index 11b2614760..c77877f90b 100644 --- a/src/app/teams/teams.module.ts +++ b/src/app/teams/teams.module.ts @@ -18,6 +18,7 @@ import { TeamsMemberComponent } from './teams-member.component'; import { TeamsReportsComponent } from './teams-reports.component'; import { TeamsReportsDialogComponent } from './teams-reports-dialog.component'; import { TeamsReportsDetailComponent } from './teams-reports-detail.component'; +import { SurveysModule } from '../surveys/surveys.module'; @NgModule({ exports: [ TeamsViewComponent, TeamsComponent, TeamsViewFinancesComponent, TeamsMemberComponent, TeamsReportsComponent ], @@ -34,6 +35,7 @@ import { TeamsReportsDetailComponent } from './teams-reports-detail.component'; SharedComponentsModule, PlanetCalendarModule, FormsModule, + SurveysModule ], declarations: [ TeamsComponent, From eb56a011cf928be7b62acbf40338f00a8e47c318 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:32:04 +0300 Subject: [PATCH 095/113] all: activate alternative login (fixes #8047) (#8048) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/home/home-router.module.ts | 4 ++-- src/app/shared/auth-guard.service.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d3e93eabe9..cd96df184f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.44", + "version": "0.16.45", "myplanet": { "latest": "v0.21.75", "min": "v0.20.75" diff --git a/src/app/home/home-router.module.ts b/src/app/home/home-router.module.ts index 8eda6e64d8..bf3a53ac97 100644 --- a/src/app/home/home-router.module.ts +++ b/src/app/home/home-router.module.ts @@ -73,8 +73,8 @@ const routes: Routes = [ } ] }, - { path: 'landing', component: LandingComponent }, - { path: 'landing', loadChildren: () => import('../landing/landing.module').then(m => m.LandingModule) } + { path: 'landing', component: LandingComponent, data: { requiresAuth: false } }, + { path: 'landing', loadChildren: () => import('../landing/landing.module').then(m => m.LandingModule), data: { requiresAuth: false } } ]; @NgModule({ diff --git a/src/app/shared/auth-guard.service.ts b/src/app/shared/auth-guard.service.ts index c19ec8debe..5461ff3422 100644 --- a/src/app/shared/auth-guard.service.ts +++ b/src/app/shared/auth-guard.service.ts @@ -45,6 +45,15 @@ export class AuthService { // For main app (which requires login). Uses canActivateChild to check on every route // change if session has expired canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { + let currentRoute: ActivatedRouteSnapshot | null = route; + + while (currentRoute) { + if (currentRoute.data && currentRoute.data.requiresAuth === false) { + return of(true); + } + currentRoute = currentRoute.parent; + } + return this.checkUser(state.url); } From 7fa7fa68ae2e2c62e17c8882368f3e98ad937d44 Mon Sep 17 00:00:00 2001 From: Gavin Porter <112116086+Gavinp14@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:31:50 -0500 Subject: [PATCH 096/113] mylife: smoother achievements resume (fixes #8062) (#8063) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- .../users-achievements/users-achievements.component.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cd96df184f..9d3b250457 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.45", + "version": "0.16.46", "myplanet": { - "latest": "v0.21.75", - "min": "v0.20.75" + "latest": "v0.21.81", + "min": "v0.20.81" }, "scripts": { "ng": "ng", diff --git a/src/app/users/users-achievements/users-achievements.component.ts b/src/app/users/users-achievements/users-achievements.component.ts index de990a2643..4c420cea89 100644 --- a/src/app/users/users-achievements/users-achievements.component.ts +++ b/src/app/users/users-achievements/users-achievements.component.ts @@ -133,8 +133,8 @@ export class UsersAchievementsComponent implements OnInit { { text: ` ${this.user.firstName} ${this.user.middleName ? this.user.middleName : ''} ${this.user.lastName} - ${formattedBirthDate ? $localize`Birthplace: ${formattedBirthDate}` : ''} - ${this.user.Birthplace ? $localize`Birthdate: ${this.user.Birthplace}` : ''} + ${formattedBirthDate ? $localize`Birthdate: ${formattedBirthDate}` : ''} + ${this.user.birthplace ? $localize`Birthplace: ${this.user.birthplace}` : ''} `, alignment: 'center', }, From 5d179559e6dba7dee9646f7dc69bac9fdfddfba2 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:44:35 -0500 Subject: [PATCH 097/113] mylife: smoother myhealth dialog (fixes #8064) (#8065) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/health/health-event.component.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d3b250457..cb86d48517 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.46", + "version": "0.16.47", "myplanet": { "latest": "v0.21.81", "min": "v0.20.81" diff --git a/src/app/health/health-event.component.ts b/src/app/health/health-event.component.ts index 832e826095..7f44e4c4b7 100644 --- a/src/app/health/health-event.component.ts +++ b/src/app/health/health-event.component.ts @@ -116,6 +116,7 @@ export class HealthEventComponent implements OnInit { this.goBack(); } }, + displayName: '', showMainParagraph: false, extraMessage: $localize`The value(s) of the following are not in the normal range. Click <b>Cancel</b> to fix or click <b>OK</b> to submit.`, showLabels: invalidFields From 736dc853041ed5382c23a44d8e42bba61e3b62d4 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:53:24 -0500 Subject: [PATCH 098/113] courses: smoother finish button (fixes #8059) (#8067) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../step-view-courses/courses-step-view.component.html | 2 +- src/app/courses/step-view-courses/courses-step-view.scss | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cb86d48517..a3fbcf8a19 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.47", + "version": "0.16.48", "myplanet": { "latest": "v0.21.81", "min": "v0.20.81" diff --git a/src/app/courses/step-view-courses/courses-step-view.component.html b/src/app/courses/step-view-courses/courses-step-view.component.html index a08a3a2267..d785b55e7f 100644 --- a/src/app/courses/step-view-courses/courses-step-view.component.html +++ b/src/app/courses/step-view-courses/courses-step-view.component.html @@ -66,7 +66,7 @@ <h3 class="margin-lr-3 ellipsis-title"><ng-container i18n>Step</ng-container> {{ <span>{{stepNum}}/{{maxStep}}</span> <button mat-icon-button *ngIf="maxStep !== 1" [disabled]="stepNum === 1" (click)="changeStep(-1)"><mat-icon>navigate_before</mat-icon></button> <button mat-icon-button *ngIf="stepNum !== maxStep" [disabled]="stepNum === maxStep || (!parent && stepDetail?.exam?.questions.length > 0 && !attempts)" (click)="changeStep(1)"><mat-icon>navigate_next</mat-icon></button> - <button mat-raised-button *ngIf="stepNum === maxStep && maxStep !== 1" [disabled]="!parent && stepDetail?.exam?.questions.length > 0 && !attempts" (click)="backToCourseDetail()" i18n>Finish</button> + <button mat-raised-button class="finish-button" *ngIf="stepNum === maxStep" [disabled]="!parent && stepDetail?.exam?.questions.length > 0 && !attempts" (click)="backToCourseDetail()" i18n>Finish</button> </div> </mat-toolbar> <div class="view-container view-full-height" [ngClass]="{'grid-view': showChat, 'flex-view': !isGridView && !showChat}" *ngIf="stepDetail?.description || resource?._attachments; else emptyRecord"> diff --git a/src/app/courses/step-view-courses/courses-step-view.scss b/src/app/courses/step-view-courses/courses-step-view.scss index 77862610d5..052748fcf0 100644 --- a/src/app/courses/step-view-courses/courses-step-view.scss +++ b/src/app/courses/step-view-courses/courses-step-view.scss @@ -56,4 +56,8 @@ text-overflow: ellipsis; } + .finish-button { + margin-left: 10px; + } + } From 1c0f87c0e9b48460072b30cd83951051607e0b5e Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:58:43 -0500 Subject: [PATCH 099/113] teams: smoother list buttons (fixes #8054) (#8066) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams.scss | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a3fbcf8a19..c6e522a4c7 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.48", + "version": "0.16.49", "myplanet": { "latest": "v0.21.81", "min": "v0.20.81" diff --git a/src/app/teams/teams.scss b/src/app/teams/teams.scss index 04b1fac865..0af5144e31 100644 --- a/src/app/teams/teams.scss +++ b/src/app/teams/teams.scss @@ -31,10 +31,28 @@ overflow: hidden; } - @media screen and (max-width: #{$screen-sm}) { + .mat-cell button, .mat-cell div button[mat-raised-button] { + width: 150px; + text-align: center; + padding: 8px 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + @media (max-width: $screen-sm) { mat-cell button { min-width: 0; padding: 0 3px; } + + .mat-cell button, .mat-cell div button[mat-raised-button] { + width: 50px; + } + + .button-container { + flex-direction: column; + align-items: flex-start; + } } } From 19a772aa449d875600d4f80483db930f7cf0e941 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:41:26 -0500 Subject: [PATCH 100/113] all: smoother navigation icons (fixes #7980) (#7982) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 ++--- src/app/home/home.component.html | 29 ++++++++++++----------- src/assets/icons/logout.svg | 40 ++++++++++---------------------- 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index c6e522a4c7..b8694a9665 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.49", + "version": "0.16.50", "myplanet": { - "latest": "v0.21.81", - "min": "v0.20.81" + "latest": "v0.21.88", + "min": "v0.20.88" }, "scripts": { "ng": "ng", diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 6296f3e295..4a3e34eca7 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -49,7 +49,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> <ng-container *planetAuthorizedRoles> <button mat-icon-button planetSync i18n-title title="Sync" *ngIf="onlineStatus === 'accepted'"><mat-icon svgIcon="feedback"></mat-icon></button> </ng-container> - <button mat-icon-button routerLink="/manager" i18n-title title="Manager Settings" *planetAuthorizedRoles="'manager,monitor'"><mat-icon svgIcon="usersettings"></mat-icon></button> + <button mat-icon-button routerLink="/manager" i18n-title title="Manager Settings" *planetAuthorizedRoles="'manager,monitor'"><mat-icon>settings</mat-icon></button> <planet-language></planet-language> </span> <ng-container *planetAuthorizedRoles="'learner'"> @@ -109,7 +109,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> planetPulsateIcon i18n-title title="Community" [routerLinkActiveOptions]="{exact:true}"> - <mat-icon svgIcon="home"></mat-icon> + <mat-icon>home</mat-icon> <label i18n>Community</label> </a> </li> @@ -119,7 +119,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> planetPulsateIcon i18n-title title="Nation" [routerLinkActiveOptions]="{exact:true}"> - <mat-icon svgIcon="home"></mat-icon> + <mat-icon>library_books</mat-icon> <label i18n>Nation</label> </a> </li> @@ -129,7 +129,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> routerLinkActive="active" planetPulsateIcon i18n-title title="Dashboard"> - <mat-icon>turned_in_not</mat-icon> + <mat-icon>dashboard</mat-icon> <label i18n>myDashboard</label> </a> </li> @@ -138,7 +138,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> routerLinkActive="active" planetPulsateIcon i18n-title title="Library"> - <mat-icon svgIcon="myLibrary"></mat-icon> + <mat-icon>folder</mat-icon> <label i18n>Library</label> </a> </li> @@ -147,7 +147,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> routerLinkActive="active" planetPulsateIcon i18n-title title="Courses"> - <mat-icon svgIcon="myCourses"></mat-icon> + <mat-icon>school</mat-icon> <label i18n>Courses</label> </a> </li> @@ -156,7 +156,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> routerLinkActive="active" planetPulsateIcon i18n-title title="Teams"> - <mat-icon svgIcon="myTeams"></mat-icon> + <mat-icon>groups</mat-icon> <label i18n>Teams</label> </a> </li> @@ -165,7 +165,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> routerLinkActive="active" planetPulsateIcon i18n-title title="Enterprises"> - <mat-icon>business</mat-icon> + <mat-icon>work</mat-icon> <label i18n>Enterprises</label> </a> </li> @@ -187,7 +187,7 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> <ul class="bottom-nav"> <li> <a mat-button routerLink="/chat" i18n-title title="Chat"> - <mat-icon>chat_bubble_outline</mat-icon> + <mat-icon>question_answer</mat-icon> <label i18n>Chat</label> </a> </li> @@ -199,20 +199,23 @@ <h1><ng-container>Planet</ng-container> {{planetName}}</h1> </li> <li> <a mat-button routerLink="/feedback" i18n-title title="Messages"> - <mat-icon>mail_outline</mat-icon> + <mat-icon>mail</mat-icon> <label i18n>Messages</label> </a> </li> <ng-container *planetAuthorizedRoles="'manager,monitor'"> <li> <a mat-button routerLink="/manager" i18n-title title="Manager Settings"> - <mat-icon svgIcon="usersettings"></mat-icon> + <mat-icon>settings</mat-icon> <label i18n>Manager Settings</label> </a> </li> </ng-container> - <li style="text-align: center;"> - <planet-language [iconOnly]="layout === 'modern' || forceModern"></planet-language> + <li> + <a mat-button (click)="openLanguageDialog()" i18n-title title="Change Language"> + <mat-icon>translate</mat-icon> + <label *ngIf="sidenavState === 'open'" i18n>Language</label> + </a> </li> <ng-container *planetAuthorizedRoles> <li *ngIf="onlineStatus === 'accepted'"> diff --git a/src/assets/icons/logout.svg b/src/assets/icons/logout.svg index 2464672fc3..185a3d42c9 100644 --- a/src/assets/icons/logout.svg +++ b/src/assets/icons/logout.svg @@ -1,28 +1,12 @@ -<?xml version="1.0" encoding="iso-8859-1"?> -<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 511.989 511.989" style="enable-background:new 0 0 511.989 511.989;" xml:space="preserve"> -<g> - <g> - <g> - <path d="M110.933,221.782c-4.71,0-8.533,3.823-8.533,8.533v51.2c0,4.71,3.823,8.533,8.533,8.533s8.533-3.823,8.533-8.533v-51.2 - C119.467,225.605,115.644,221.782,110.933,221.782z"/> - <path d="M111.855,2.304L31.172,34.586C8.448,43,0,54.418,0,76.715v358.477c0,22.298,8.448,33.715,30.959,42.061l81.058,32.427 - c4.011,1.519,8.038,2.287,11.981,2.287c17.152,0,29.602-14.336,29.602-34.091V34.049C153.6,9.78,134.246-6.126,111.855,2.304z - M136.533,477.876c0,10.18-5.035,17.024-12.535,17.024c-1.869,0-3.883-0.401-5.803-1.118L37.103,461.33 - c-16.102-5.965-20.036-11.102-20.036-26.138V76.715c0-15.036,3.934-20.164,20.241-26.206l80.725-32.29 - c2.082-0.785,4.087-1.186,5.956-1.186c7.501,0,12.544,6.835,12.544,17.016V477.876z"/> - <path d="M178.133,51.115h120.533c14.114,0,25.6,11.486,25.6,25.6v128c0,4.71,3.814,8.533,8.533,8.533 - c4.719,0,8.533-3.823,8.533-8.533v-128c0-23.526-19.14-42.667-42.667-42.667H178.133c-4.71,0-8.533,3.823-8.533,8.533 - S173.423,51.115,178.133,51.115z"/> - <path d="M332.8,298.582c-4.719,0-8.533,3.823-8.533,8.533v128c0,14.114-11.486,25.6-25.6,25.6H179.2 - c-4.71,0-8.533,3.823-8.533,8.533s3.823,8.533,8.533,8.533h119.467c23.526,0,42.667-19.14,42.667-42.667v-128 - C341.333,302.405,337.519,298.582,332.8,298.582z"/> - <path d="M511.343,252.655c-0.435-1.05-1.058-1.988-1.852-2.782l-85.325-85.333c-3.337-3.336-8.73-3.336-12.066,0 - c-3.337,3.337-3.337,8.73,0,12.066l70.767,70.775H196.267c-4.71,0-8.533,3.823-8.533,8.533c0,4.71,3.823,8.533,8.533,8.533 - h286.601L412.1,335.215c-3.337,3.337-3.337,8.73,0,12.066c1.664,1.664,3.849,2.5,6.033,2.5c2.185,0,4.369-0.836,6.033-2.5 - l85.325-85.325c0.794-0.794,1.417-1.732,1.852-2.782C512.205,257.093,512.205,254.738,511.343,252.655z"/> - </g> - </g> -</g> -</svg> +<?xml version="1.0" encoding="UTF-8"?> +<!-- Uploaded to: SVG Find, www.svgrepo.com, Generator: SVG Find Mixer Tools --> +<svg width="800px" height="800px" version="1.1" viewBox="144 144 512 512" xmlns="http://www.w3.org/2000/svg"> + <defs> + <clipPath id="a"> + <path d="m148.09 148.09h503.81v503.81h-503.81z"/> + </clipPath> + </defs> + <g clip-path="url(#a)"> + <path d="m649.6 405.56-70.848 70.848v0.003906c-1.4766 1.4766-3.4805 2.3047-5.5664 2.3047-2.0898 0-4.0898-0.82812-5.5664-2.3047l-23.617-23.617c-1.4766-1.4766-2.3047-3.4766-2.3047-5.5664 0-2.0859 0.82813-4.0898 2.3047-5.5664l18.051-18.051h-130.56c-4.3477 0-7.875-3.5234-7.875-7.8711v-31.488c0-4.3477 3.5273-7.8711 7.875-7.8711h130.56l-18.051-18.051c-1.4766-1.4766-2.3047-3.4766-2.3047-5.5664 0-2.0859 0.82813-4.0898 2.3047-5.5664l23.617-23.617v0.003907c1.4766-1.4766 3.4766-2.3086 5.5664-2.3086 2.0859 0 4.0898 0.83203 5.5664 2.3086l70.848 70.848c1.4766 1.4766 2.3047 3.4766 2.3047 5.5664 0 2.0859-0.82812 4.0898-2.3047 5.5625zm-241.73 10.18v-31.488c0.019531-6.2578 2.5117-12.254 6.9375-16.68 4.4258-4.4219 10.422-6.918 16.68-6.9375h78.719v-157.44c-0.011719-4.3438-3.5312-7.8594-7.8711-7.875h-149.57v409.35h149.57c4.3398-0.011719 7.8594-3.5312 7.8711-7.8711v-157.44h-78.719c-6.2578-0.019531-12.254-2.5117-16.68-6.9375-4.4258-4.4258-6.918-10.422-6.9375-16.68zm-125.95-23.617c-3.1836 0-6.0547 1.918-7.2734 4.8594s-0.54297 6.3281 1.707 8.582c2.25 2.25 5.6367 2.9219 8.5781 1.7031 2.9414-1.2148 4.8594-4.0859 4.8594-7.2695-0.003906-4.3477-3.5234-7.8711-7.8711-7.875zm55.105-236.16v488.07c0 2.5273-1.2109 4.9023-3.2617 6.3828-2.0469 1.4805-4.6797 1.8867-7.0781 1.0938l-173.18-57.203-0.003906 0.003906c-3.2266-1.0664-5.4023-4.082-5.4023-7.4805v-373.66c0-3.3984 2.1758-6.4141 5.4023-7.4766l173.18-57.203h0.003906c2.3984-0.79297 5.0312-0.38672 7.0781 1.0977 2.0508 1.4805 3.2617 3.8516 3.2617 6.3789zm-31.488 244.04c0-6.2656-2.4883-12.273-6.918-16.699-4.4297-4.4297-10.438-6.918-16.699-6.918s-12.27 2.4883-16.699 6.918c-4.4297 4.4258-6.918 10.434-6.918 16.699 0 6.2617 2.4883 12.27 6.918 16.699 4.4297 4.4258 10.438 6.9141 16.699 6.9141 6.2617-0.003906 12.266-2.4961 16.691-6.9219 4.4258-4.4297 6.918-10.43 6.9258-16.691z"/> + </g> +</svg> \ No newline at end of file From f118bc306f881aeaf441335dd505e2a6c194210b Mon Sep 17 00:00:00 2001 From: sahilvunnam <118228103+sahilvunnam@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:53:51 -0800 Subject: [PATCH 101/113] enterprises: smoother report titles (fixes #6917) (#7743) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/teams/teams-reports.component.html | 4 ++-- src/app/teams/teams-reports.component.ts | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b8694a9665..a4168c9a9e 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.50", + "version": "0.16.51", "myplanet": { "latest": "v0.21.88", "min": "v0.20.88" diff --git a/src/app/teams/teams-reports.component.html b/src/app/teams/teams-reports.component.html index 4c6f9d79b1..f22a7cfd42 100644 --- a/src/app/teams/teams-reports.component.html +++ b/src/app/teams/teams-reports.component.html @@ -1,5 +1,5 @@ <div class="fixed action-buttons" *ngIf="editable"> - <button mat-raised-button color="primary" i18n (click)="openAddReportDialog()">Add Report</button> + <button mat-raised-button color="primary" i18n (click)="openAddReportDialog(false)">Add Report</button> <button mat-raised-button color="accent" i18n *ngIf="reports && reports.length > 0" (click)="exportReports()"> Export as CSV </button> @@ -20,7 +20,7 @@ <mat-card-footer class="action-buttons margin-lr-10"> <ng-container *ngIf="editable"> <button mat-icon-button (click)="$event.stopPropagation(); openDeleteReportDialog(report)"><mat-icon>delete</mat-icon></button> - <button mat-icon-button (click)="$event.stopPropagation(); openAddReportDialog(report)"><mat-icon>edit</mat-icon></button> + <button mat-icon-button (click)="$event.stopPropagation(); openAddReportDialog(report, true)"><mat-icon>edit</mat-icon></button> </ng-container> <button mat-button i18n>View Report</button> </mat-card-footer> diff --git a/src/app/teams/teams-reports.component.ts b/src/app/teams/teams-reports.component.ts index f7cccabeba..c56c4013f9 100644 --- a/src/app/teams/teams-reports.component.ts +++ b/src/app/teams/teams-reports.component.ts @@ -51,13 +51,16 @@ export class TeamsReportsComponent implements DoCheck { } } - openAddReportDialog(oldReport = {}) { + openAddReportDialog(oldReport = {}, isEdit: boolean) { + const actionType = isEdit ? 'Edit' : 'Add'; + const dialogTitle = $localize`${actionType} Report`; + this.couchService.currentTime().subscribe((time: number) => { const currentDate = new Date(time); const lastMonthStart = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1); const lastMonthEnd = currentDate.setDate(0); this.dialogsFormService.openDialogsForm( - $localize`Add Report`, + dialogTitle, [ { name: 'startDate', placeholder: $localize`Start Date`, type: 'date', required: true }, { name: 'endDate', placeholder: $localize`End Date`, type: 'date', required: true }, From 9541b0ea165fbdd8de1df935c49baf2d0731a91c Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Sat, 11 Jan 2025 00:07:40 +0300 Subject: [PATCH 102/113] mylife: smoother achievements view (fixes #8060) (#8061) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users/users-achievements/users-achievements.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a4168c9a9e..ed9ffb55b3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.51", + "version": "0.16.52", "myplanet": { "latest": "v0.21.88", "min": "v0.20.88" diff --git a/src/app/users/users-achievements/users-achievements.component.ts b/src/app/users/users-achievements/users-achievements.component.ts index 4c420cea89..63001b25dd 100644 --- a/src/app/users/users-achievements/users-achievements.component.ts +++ b/src/app/users/users-achievements/users-achievements.component.ts @@ -177,7 +177,7 @@ export class UsersAchievementsComponent implements OnInit { ...this.achievements.achievements.map((achievement) => { return [ { text: achievement.title, bold: true, margin: [ 20, 5 ] }, - { text: format(new Date(achievement.date), 'MMM d, y'), marginLeft: 40 }, + { text: achievement.date ? format(new Date(achievement?.date), 'MMM d, y') : '', marginLeft: 40 }, { text: achievement.link, marginLeft: 40 }, { text: achievement.description, marginLeft: 40 }, ]; From 8c2492ae73021239de3c02378b2578b9be98f2ea Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:03:09 -0500 Subject: [PATCH 103/113] teams: smoother toolbar buttons (fixes #8077) (#8079) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/teams/teams-view.component.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ed9ffb55b3..63f4b379f6 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.52", + "version": "0.16.53", "myplanet": { - "latest": "v0.21.88", - "min": "v0.20.88" + "latest": "v0.21.96", + "min": "v0.20.96" }, "scripts": { "ng": "ng", diff --git a/src/app/teams/teams-view.component.html b/src/app/teams/teams-view.component.html index ce128346a2..171eadc7c1 100644 --- a/src/app/teams/teams-view.component.html +++ b/src/app/teams/teams-view.component.html @@ -43,7 +43,7 @@ <h3 *ngIf="mode==='services'" class="margin-lr-3 ellipsis-title">{{configuration <ng-template #actionButtons> <ng-container [ngSwitch]="userStatus" *ngIf="user.isUserAdmin || user.roles.length"> <ng-container *ngSwitchCase="'member'"> - <a class="margin-lr-3" [routerLink]="['surveys']" mat-stroked-button mat-button i18n>Manage Surveys</a> + <a class="toolbar-button margin-lr-3" [routerLink]="['surveys']" mat-stroked-button mat-button i18n>Manage Surveys</a> <button *ngIf="mode!=='services'" mat-stroked-button mat-button class=" toolbar-button margin-lr-3" (click)="openInviteMemberDialog()" i18n [disabled]="disableAddingMembers"> Add Members </button> From cbcb75f6a77f07890c18f49302612ba2d58c9786 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:16:57 -0500 Subject: [PATCH 104/113] resources: smoother collections dialog (fixes #8058) (#8080) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/shared/forms/planet-tag-input-dialog.component.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 63f4b379f6..583e14b096 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.53", + "version": "0.16.54", "myplanet": { "latest": "v0.21.96", "min": "v0.20.96" diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index 1e26049b29..418fdbbf44 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -22,6 +22,7 @@ import { DialogsPromptComponent } from '../../shared/dialogs/dialogs-prompt.comp :host mat-dialog-content { overflow-y: auto; max-height: calc(100vh - 100px); + width: 600px; } :host .mat-list-option span { font-weight: inherit; @@ -35,6 +36,9 @@ import { DialogsPromptComponent } from '../../shared/dialogs/dialogs-prompt.comp :host mat-dialog-actions { padding: 0; } + button[mat-stroked-button] { + min-width: 64px; + } ` ] }) export class PlanetTagInputDialogComponent { From 3db52efa85e7734e0ed3ed1ea5f15d1fad2bcae9 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:43:21 +0300 Subject: [PATCH 105/113] mylife: smoother myhealth access (fixes #8068) (#8069) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/home/home-router.module.ts | 4 ++-- src/app/shared/auth-guard.service.ts | 14 +++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 583e14b096..d5c756aa8a 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.54", + "version": "0.16.55", "myplanet": { "latest": "v0.21.96", "min": "v0.20.96" diff --git a/src/app/home/home-router.module.ts b/src/app/home/home-router.module.ts index bf3a53ac97..28a7f2105f 100644 --- a/src/app/home/home-router.module.ts +++ b/src/app/home/home-router.module.ts @@ -34,8 +34,8 @@ const routes: Routes = [ { path: 'upgrade/myplanet', component: UpgradeComponent, data: { myPlanet: true } }, { path: 'teams', loadChildren: () => import('../teams/teams.module').then(m => m.TeamsModule) }, { path: 'enterprises', loadChildren: () => import('../teams/teams.module').then(m => m.TeamsModule), data: { mode: 'enterprise' } }, - { path: 'health', component: HealthListComponent }, - { path: 'health/profile/:id', loadChildren: () => import('../health/health.module').then(m => m.HealthModule) }, + { path: 'health', component: HealthListComponent, data: { roles: [ '_admin', 'health' ] } }, + { path: 'health/profile/:id', loadChildren: () => import('../health/health.module').then(m => m.HealthModule), data: { roles: [ '_admin', 'health' ] } }, { path: 'nation', component: TeamsViewComponent, data: { mode: 'services' } }, { path: 'earth', component: TeamsViewComponent, data: { mode: 'services' } }, { path: myDashboardRoute, component: DashboardComponent }, diff --git a/src/app/shared/auth-guard.service.ts b/src/app/shared/auth-guard.service.ts index 5461ff3422..91272411ab 100644 --- a/src/app/shared/auth-guard.service.ts +++ b/src/app/shared/auth-guard.service.ts @@ -22,12 +22,17 @@ export class AuthService { return this.pouchAuthService.getSessionInfo(); } - private checkUser(url: any): Observable<boolean> { + private checkUser(url: any, roles: any[]): Observable<boolean> { return this.getSession$().pipe( switchMap((sessionInfo) => { if (sessionInfo.userCtx.name) { // If user already matches one on the user service, do not make additional call to CouchDB - if (sessionInfo.userCtx.name === this.userService.get().name) { + const user = this.userService.get(); + if (sessionInfo.userCtx.name === user.name) { + if (roles.length > 0) { + const hasRole = roles.some(role => user.roles.includes(role)); + return hasRole ? of(true) : of(false); + } return of(true); } this.stateService.requestBaseData(); @@ -46,15 +51,14 @@ export class AuthService { // change if session has expired canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { let currentRoute: ActivatedRouteSnapshot | null = route; - + const roles: Array<string> = currentRoute.data?.roles ?? []; while (currentRoute) { if (currentRoute.data && currentRoute.data.requiresAuth === false) { return of(true); } currentRoute = currentRoute.parent; } - - return this.checkUser(state.url); + return this.checkUser(state.url, roles); } // For login route will redirect to main app if there is an active session From 2ace99efe82eae88a78376d600e382ab759edf0a Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:45:06 -0500 Subject: [PATCH 106/113] teams: smoother resources handling (fixes #8072) (#8078) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- .../dialogs/dialogs-add-resources.component.html | 2 +- .../shared/dialogs/dialogs-add-resources.component.ts | 11 +++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d5c756aa8a..f2cbdad6a1 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.55", + "version": "0.16.56", "myplanet": { - "latest": "v0.21.96", - "min": "v0.20.96" + "latest": "v0.21.99", + "min": "v0.20.99" }, "scripts": { "ng": "ng", diff --git a/src/app/shared/dialogs/dialogs-add-resources.component.html b/src/app/shared/dialogs/dialogs-add-resources.component.html index b56c029b15..fa189446c6 100644 --- a/src/app/shared/dialogs/dialogs-add-resources.component.html +++ b/src/app/shared/dialogs/dialogs-add-resources.component.html @@ -10,5 +10,5 @@ <span class="margin-lr-8" i18n>OR</span> </ng-container> <button mat-raised-button mat-dialog-close i18n>Cancel</button> - <button color="primary" mat-raised-button [disabled]="okDisabled" (click)="ok()" i18n>OK</button> + <button color="primary" mat-raised-button [disabled]="okDisabled || isSubmitting" (click)="ok()" i18n>OK</button> </mat-dialog-actions> diff --git a/src/app/shared/dialogs/dialogs-add-resources.component.ts b/src/app/shared/dialogs/dialogs-add-resources.component.ts index 2e5ae0fbb0..d17aa4c862 100644 --- a/src/app/shared/dialogs/dialogs-add-resources.component.ts +++ b/src/app/shared/dialogs/dialogs-add-resources.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, ViewChild, AfterViewInit } from '@angular/core'; +import { Component, Inject, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ResourcesComponent } from '../../resources/resources.component'; import { ResourcesAddComponent } from '../../resources/resources-add.component'; @@ -16,11 +16,13 @@ export class DialogsAddResourcesComponent implements AfterViewInit { okDisabled = true; updateResource = false; existingResource: any = {}; + isSubmitting = false; constructor( public dialogRef: MatDialogRef<DialogsAddResourcesComponent>, @Inject(MAT_DIALOG_DATA) public data: any, - private dialogsLoadingService: DialogsLoadingService + private dialogsLoadingService: DialogsLoadingService, + private cdr: ChangeDetectorRef ) { this.linkInfo = this.data.db ? { [this.data.db]: this.data.linkId } : undefined; if (this.data.resource) { @@ -32,9 +34,14 @@ export class DialogsAddResourcesComponent implements AfterViewInit { ngAfterViewInit() { this.initOkDisableChange(); + this.cdr.detectChanges(); } ok() { + if (this.isSubmitting) { + return; + } + this.isSubmitting = true; this.dialogsLoadingService.start(); switch (this.view) { case 'resources': From 46473e7869b83da7fdb2ae1b7730eb21c132a77e Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:54:55 -0500 Subject: [PATCH 107/113] teams: smoother voices titles (fixes #8086)(fixes #8087) (#8089) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/news/news-list.component.ts | 2 +- src/app/shared/forms/planet-tag-input-dialog.component.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f2cbdad6a1..7bca8d8ea5 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.56", + "version": "0.16.57", "myplanet": { "latest": "v0.21.99", "min": "v0.20.99" diff --git a/src/app/news/news-list.component.ts b/src/app/news/news-list.component.ts index fb0b0cf5bd..daf1bf1d2c 100644 --- a/src/app/news/news-list.component.ts +++ b/src/app/news/news-list.component.ts @@ -22,7 +22,7 @@ import { dedupeShelfReduce } from '../shared/utils'; export class NewsListComponent implements OnChanges { @Input() items: any[] = []; - @Input() editSuccessMessage = $localize`News has been updated successfully.`; + @Input() editSuccessMessage = $localize`Message updated successfully.`; @Input() viewableBy = 'community'; @Input() viewableId: string; @Input() editable = true; diff --git a/src/app/shared/forms/planet-tag-input-dialog.component.ts b/src/app/shared/forms/planet-tag-input-dialog.component.ts index 418fdbbf44..35d967fb7d 100644 --- a/src/app/shared/forms/planet-tag-input-dialog.component.ts +++ b/src/app/shared/forms/planet-tag-input-dialog.component.ts @@ -223,10 +223,10 @@ export class PlanetTagInputDialogComponent { onNext: (data) => { this.data.initTags(); this.deleteDialog.close(); - this.planetMessageService.showMessage($localize`Tag deleted: ${tag.name}`); + this.planetMessageService.showMessage($localize`Collection deleted: ${tag.name}`); this.resetValidationAndCheck(this.addTagForm); }, - onError: (error) => this.planetMessageService.showAlert($localize`There was a problem deleting this tag.`) + onError: (error) => this.planetMessageService.showAlert($localize`There was a problem deleting this collection.`) }; } From 5c195345034fc454ffd53711ee00ff38678ae48b Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:59:33 -0500 Subject: [PATCH 108/113] enterprises: smoother delete dialog (fixes #8083) (#8091) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../dialogs/dialogs-prompt.component.html | 1 + src/app/teams/teams.component.ts | 17 ++++++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 7bca8d8ea5..bc9b22e5d6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.57", + "version": "0.16.58", "myplanet": { "latest": "v0.21.99", "min": "v0.20.99" diff --git a/src/app/shared/dialogs/dialogs-prompt.component.html b/src/app/shared/dialogs/dialogs-prompt.component.html index 456c4b973f..88b1c2e754 100644 --- a/src/app/shared/dialogs/dialogs-prompt.component.html +++ b/src/app/shared/dialogs/dialogs-prompt.component.html @@ -12,6 +12,7 @@ user {member} change {change} team {team} + enterprise {enterprise} task {task} certification {certification} event {event} diff --git a/src/app/teams/teams.component.ts b/src/app/teams/teams.component.ts index 9fdb471b82..db0f006ed1 100644 --- a/src/app/teams/teams.component.ts +++ b/src/app/teams/teams.component.ts @@ -195,7 +195,8 @@ export class TeamsComponent implements OnInit, AfterViewInit { this.teamsService.addTeamDialog(this.user._id, this.mode, { ...team, teamType }).subscribe(() => { this.getTeams(); const action = $localize`${team._id ? 'updated' : 'created'}`; - const msg = $localize`${toProperCase(this.mode)} ${action} successfully`; + const entityType = this.mode === 'enterprise' ? 'Enterprise' : 'Team'; + const msg = $localize`${entityType} ${action} successfully`; this.planetMessageService.showMessage(msg); }); } @@ -220,12 +221,13 @@ export class TeamsComponent implements OnInit, AfterViewInit { onNext: () => { this.leaveDialog.close(); this.teams.data = this.teamList(this.teams.data); - const msg = 'left'; - this.planetMessageService.showMessage($localize`You have ${msg} ${team.name}`); + const entityType = this.mode === 'enterprise' ? 'enterprise' : 'team'; + const msg = $localize`You have left ${entityType} ${team.name}`; + this.planetMessageService.showMessage(msg); }, }, changeType: 'leave', - type: 'team', + type: this.mode === 'enterprise' ? 'enterprise' : 'team', displayName: team.name } }); @@ -240,7 +242,7 @@ export class TeamsComponent implements OnInit, AfterViewInit { this.planetMessageService.showMessage($localize`You have deleted ${entityType} ${team.name}.`); this.removeTeamFromTable(team); }, - onError: () => this.planetMessageService.showAlert($localize`There was a problem deleting this team.`) + onError: () => this.planetMessageService.showAlert($localize`There was a problem deleting this ${this.mode === 'enterprise' ? 'enterprise' : 'team'}.`) }; } @@ -249,7 +251,7 @@ export class TeamsComponent implements OnInit, AfterViewInit { data: { okClick: this.archiveTeam(team), changeType: 'delete', - type: 'team', + type: this.mode === 'enterprise' ? 'enterprise' : 'team', displayName: team.name } }); @@ -268,7 +270,8 @@ export class TeamsComponent implements OnInit, AfterViewInit { finalize(() => this.dialogsLoadingService.stop()) ).subscribe(() => { this.teams.data = this.teamList(this.teams.data); - this.planetMessageService.showMessage($localize`Request to join ${team.name} sent`); + const entityType = this.mode === 'enterprise' ? 'enterprise' : 'team'; + this.planetMessageService.showMessage($localize`Request to join ${entityType} ${team.name} sent`); }); } From 107f00ee3943d1f92f7ba0957d7f9f1bed2e069d Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:00:20 -0500 Subject: [PATCH 109/113] teams: smoother reports dialogs (fixes #8084)(fixes #8085) (#8090) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/teams/teams-reports.component.ts | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bc9b22e5d6..04dd957c1e 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.58", + "version": "0.16.59", "myplanet": { - "latest": "v0.21.99", - "min": "v0.20.99" + "latest": "v0.22.7", + "min": "v0.21.7" }, "scripts": { "ng": "ng", diff --git a/src/app/teams/teams-reports.component.ts b/src/app/teams/teams-reports.component.ts index c56c4013f9..95b136ade9 100644 --- a/src/app/teams/teams-reports.component.ts +++ b/src/app/teams/teams-reports.component.ts @@ -12,6 +12,7 @@ import { tap } from 'rxjs/operators'; import { convertUtcDate } from './teams.utils'; import { CsvService } from '../shared/csv.service'; import { StateService } from '../shared/state.service'; +import { PlanetMessageService } from '../shared/planet-message.service'; @Component({ selector: 'planet-teams-reports', @@ -38,6 +39,7 @@ export class TeamsReportsComponent implements DoCheck { private csvService: CsvService, private elementRef: ElementRef, private stateService: StateService, + private planetMessageService: PlanetMessageService, ) {} ngDoCheck() { @@ -76,6 +78,8 @@ export class TeamsReportsComponent implements DoCheck { disableIfInvalid: true, onSubmit: (newReport) => this.updateReport(oldReport, newReport).subscribe(() => { this.dialogsFormService.closeDialogsForm(); + const action = isEdit ? 'edited' : 'added'; + this.planetMessageService.showMessage(`Report ${action}`); }) } ); @@ -87,11 +91,17 @@ export class TeamsReportsComponent implements DoCheck { data: { changeType: 'delete', type: 'report', - displayDates: report, + displayName: `${$localize`Report from`} ${new Date(report.startDate).toLocaleDateString('en-US', { timeZone: 'UTC' })} ${$localize`to`} ${new Date(report.endDate).toLocaleDateString('en-US', { timeZone: 'UTC' })}`, okClick: { request: this.updateReport(report), onNext: () => { + this.planetMessageService.showMessage('Report deleted'); + this.dialogsLoadingService.stop(); deleteDialog.close(); + }, + onError: () => { + this.planetMessageService.showAlert('There was a problem deleting the report.'); + this.dialogsLoadingService.stop(); } }, isDateUtc: true From 8af64363d579e2132406171cce9d5cb5745d7d8e Mon Sep 17 00:00:00 2001 From: Axel Lo <54468493+RheuX@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:07:06 -0800 Subject: [PATCH 110/113] teams: smoother calendar legend (fixes #8093) (#8094) Co-authored-by: mutugiii <mutugimutuma@gmail.com> Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- src/app/community/community.scss | 21 --------------------- src/app/shared/calendar.component.scss | 20 ++++++++++++++++++++ src/app/shared/calendar.component.ts | 1 + 4 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 src/app/shared/calendar.component.scss diff --git a/package.json b/package.json index 04dd957c1e..311fe6f150 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.59", + "version": "0.16.60", "myplanet": { "latest": "v0.22.7", "min": "v0.21.7" diff --git a/src/app/community/community.scss b/src/app/community/community.scss index 5f6aa4a2ff..c0e0e1c264 100644 --- a/src/app/community/community.scss +++ b/src/app/community/community.scss @@ -35,27 +35,6 @@ planet-calendar { overflow-y: auto; } -.calendar-legend { - padding-top: 10px; - text-align: right; - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - gap: 10px; -} - -.calendar-legend .legend-item { - display: inline-flex; - align-items: center; -} - -.calendar-legend .legend-color { - width: 20px; - height: 20px; - margin-right: 10px; - border-radius: 4px; -} - .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); diff --git a/src/app/shared/calendar.component.scss b/src/app/shared/calendar.component.scss new file mode 100644 index 0000000000..cb56cbab92 --- /dev/null +++ b/src/app/shared/calendar.component.scss @@ -0,0 +1,20 @@ +.calendar-legend { + padding-top: 10px; + text-align: right; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 10px; +} + +.calendar-legend .legend-item { + display: inline-flex; + align-items: center; +} + +.calendar-legend .legend-color { + width: 20px; + height: 20px; + margin-right: 10px; + border-radius: 4px; +} diff --git a/src/app/shared/calendar.component.ts b/src/app/shared/calendar.component.ts index 80a05b48b9..4c1abc1148 100644 --- a/src/app/shared/calendar.component.ts +++ b/src/app/shared/calendar.component.ts @@ -14,6 +14,7 @@ import { addDateAndTime, styleVariables } from './utils'; @Component({ selector: 'planet-calendar', + styleUrls: [ './calendar.component.scss' ], template: ` <full-calendar #calendar [options]="calendarOptions"></full-calendar> <div class="calendar-legend" *ngIf="showLegend"> From 2162bf2478e308429088aec3279606abc83382f5 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:12:28 +0300 Subject: [PATCH 111/113] mylife: smoother achievements sharing (fixes #8082) (#8095) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements.component.html | 7 +++++-- .../users-achievements/users-achievements.component.ts | 10 +++++++++- .../users-achievements/users-achievements.module.ts | 3 ++- .../users/users-profile/users-profile.component.html | 10 ---------- src/app/users/users-router.module.ts | 2 +- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 311fe6f150..459e02c8dc 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.60", + "version": "0.16.61", "myplanet": { "latest": "v0.22.7", "min": "v0.21.7" diff --git a/src/app/users/users-achievements/users-achievements.component.html b/src/app/users/users-achievements/users-achievements.component.html index 1ccb50c49a..e4f34d8301 100644 --- a/src/app/users/users-achievements/users-achievements.component.html +++ b/src/app/users/users-achievements/users-achievements.component.html @@ -1,4 +1,4 @@ -<mat-toolbar> +<mat-toolbar *ngIf="!publicView"> <button mat-icon-button (click)="goBack()"><mat-icon>arrow_back</mat-icon></button> <span i18n>Achievements</span> <span class="toolbar-fill"></span> @@ -19,6 +19,9 @@ <span *ngIf="achievementNotFound" i18n>Add Achievements</span> <span *ngIf="!achievementNotFound" i18n>Edit Achievements</span> </a> + <button mat-icon-button matTooltip="Copy Achievements Link" i18n-matTooltip (click)="copyLink()"> + <mat-icon>link</mat-icon> + </button> </div> </mat-toolbar> <div class="view-container"> @@ -26,7 +29,7 @@ <div class="user-info"> <img class="profile-image" [src]="profileImg"> <h2>{{user.firstName}} {{user.middleName}} {{user.lastName}}</h2> - <div class="birth-info"> + <div class="birth-info" *ngIf="!publicView"> <ng-container *ngIf="user.birthDate"><span i18n>Birthdate: {{' ' + (user.birthDate | date: medium) + ' '}}</span></ng-container><br/><br/> <span *ngIf="user.birthplace" i18n>Birthplace: {{' ' + user.birthplace}} </span> </div> diff --git a/src/app/users/users-achievements/users-achievements.component.ts b/src/app/users/users-achievements/users-achievements.component.ts index 63001b25dd..34d9258e02 100644 --- a/src/app/users/users-achievements/users-achievements.component.ts +++ b/src/app/users/users-achievements/users-achievements.component.ts @@ -1,6 +1,7 @@ import { format } from 'date-fns'; import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; +import { Clipboard } from '@angular/cdk/clipboard'; import { CouchService } from '../../shared/couchdb.service'; import { UserService } from '../../shared/user.service'; import { PlanetMessageService } from '../../shared/planet-message.service'; @@ -29,6 +30,7 @@ export class UsersAchievementsComponent implements OnInit { urlPrefix = environment.couchAddress + '/_users/org.couchdb.user:' + this.userService.get().name + '/'; openAchievementIndex = -1; certifications: any[] = []; + publicView = this.route.snapshot.data.requiresAuth === false; constructor( private couchService: CouchService, @@ -39,7 +41,8 @@ export class UsersAchievementsComponent implements OnInit { private usersAchievementsService: UsersAchievementsService, private stateService: StateService, private coursesService: CoursesService, - private certificationsService: CertificationsService + private certificationsService: CertificationsService, + private clipboard: Clipboard ) { } ngOnInit() { @@ -122,6 +125,11 @@ export class UsersAchievementsComponent implements OnInit { }); } + copyLink() { + const link = `${window.location.origin}/profile/${this.user.name}/achievements;planet=${this.stateService.configuration.code}`; + this.clipboard.copy(link); + } + generatePDF() { const formattedBirthDate = format(new Date(this.user.birthDate), 'MMM d, y'); let contentArray = [ diff --git a/src/app/users/users-achievements/users-achievements.module.ts b/src/app/users/users-achievements/users-achievements.module.ts index 62cc595f77..aad8ee2ae1 100644 --- a/src/app/users/users-achievements/users-achievements.module.ts +++ b/src/app/users/users-achievements/users-achievements.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { ClipboardModule } from '@angular/cdk/clipboard'; import { MaterialModule } from '../../shared/material.module'; import { PlanetFormsModule } from '../../shared/forms/planet-forms.module'; import { SharedComponentsModule } from '../../shared/shared-components.module'; @@ -10,7 +11,7 @@ import { UsersAchievementsUpdateComponent } from './users-achievements-update.co @NgModule({ imports: [ - CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MaterialModule, PlanetFormsModule, SharedComponentsModule + CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MaterialModule, PlanetFormsModule, SharedComponentsModule, ClipboardModule ], exports: [ UsersAchievementsUpdateComponent, diff --git a/src/app/users/users-profile/users-profile.component.html b/src/app/users/users-profile/users-profile.component.html index 03f2f084a0..610f5ef41d 100644 --- a/src/app/users/users-profile/users-profile.component.html +++ b/src/app/users/users-profile/users-profile.component.html @@ -1,13 +1,3 @@ -<mat-toolbar *ngIf="!isDialog"> - <mat-toolbar-row> - <button mat-icon-button (click)="goBack()"> - <mat-icon>arrow_back</mat-icon> - </button> - <span i18n>Member Profile</span> - <span class="toolbar-fill"></span> - </mat-toolbar-row> -</mat-toolbar> - <div class="space-container"> <mat-toolbar class="primary-color font-size-1"> <span>{{userDetail.name}}</span> diff --git a/src/app/users/users-router.module.ts b/src/app/users/users-router.module.ts index 23185ce4ed..0a864c1e97 100644 --- a/src/app/users/users-router.module.ts +++ b/src/app/users/users-router.module.ts @@ -13,7 +13,7 @@ const routes: Routes = [ { path: 'delete/request', component: UsersArchiveComponent }, { path: 'profile/:name', component: UsersProfileComponent }, { path: 'update/:name', component: UsersUpdateComponent }, - { path: 'profile/:name/achievements', component: UsersAchievementsComponent }, + { path: 'profile/:name/achievements', component: UsersAchievementsComponent, data: { requiresAuth: false } }, { path: 'profile/:name/achievements/update', component: UsersAchievementsUpdateComponent }, { path: 'submission', component: UsersUpdateComponent, data: { submission: true } } ]; From 8dea8ae90778da5aeef0a34d6260783ae85d81d4 Mon Sep 17 00:00:00 2001 From: Jesse Washburn <142361664+jessewashburn@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:17:27 -0500 Subject: [PATCH 112/113] mylife: smoother achievements links (fixes #8071) (#8098) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 2 +- .../users-achievements/users-achievements-update.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 459e02c8dc..db18039cfb 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.61", + "version": "0.16.62", "myplanet": { "latest": "v0.22.7", "min": "v0.21.7" diff --git a/src/app/users/users-achievements/users-achievements-update.component.ts b/src/app/users/users-achievements/users-achievements-update.component.ts index 0b6c8fd501..b994f5542d 100644 --- a/src/app/users/users-achievements/users-achievements-update.component.ts +++ b/src/app/users/users-achievements/users-achievements-update.component.ts @@ -133,7 +133,7 @@ export class UsersAchievementsUpdateComponent implements OnInit, OnDestroy { ...achievement, title: [ achievement.title, CustomValidators.required ], description: [ achievement.description ], - link: [ achievement.link, [], CustomValidators.validLink ], + link: [ achievement.link ], date: [ achievement.date, null, ac => this.validatorService.notDateInFuture$(ac) ] }), { onSubmit: (formValue, formGroup) => { From df8e748d1e17397de155bfc84baa353c229388bb Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:44:45 +0300 Subject: [PATCH 113/113] manager: smoother rights check (fixes #8105) (#8106) Co-authored-by: dogi <dogi@users.noreply.github.com> --- package.json | 6 +++--- src/app/home/home-router.module.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index db18039cfb..2d6e744a6f 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.16.62", + "version": "0.16.63", "myplanet": { - "latest": "v0.22.7", - "min": "v0.21.7" + "latest": "v0.22.13", + "min": "v0.21.13" }, "scripts": { "ng": "ng", diff --git a/src/app/home/home-router.module.ts b/src/app/home/home-router.module.ts index 28a7f2105f..00a9d12816 100644 --- a/src/app/home/home-router.module.ts +++ b/src/app/home/home-router.module.ts @@ -23,7 +23,11 @@ const routes: Routes = [ { path: '', component: CommunityComponent }, { path: 'community/:code', component: CommunityComponent }, { path: 'users', loadChildren: () => import('../users/users.module').then(m => m.UsersModule) }, - { path: 'manager', loadChildren: () => import('../manager-dashboard/manager-dashboard.module').then(m => m.ManagerDashboardModule) }, + { + path: 'manager', + loadChildren: () => import('../manager-dashboard/manager-dashboard.module').then(m => m.ManagerDashboardModule), + data: { roles: [ '_admin' ] } + }, { path: 'courses', loadChildren: () => import('../courses/courses.module').then(m => m.CoursesModule) }, { path: 'feedback', loadChildren: () => import('../feedback/feedback.module').then(m => m.FeedbackModule) }, { path: 'resources', loadChildren: () => import('../resources/resources.module').then(m => m.ResourcesModule) },