diff --git a/package.json b/package.json
index 2103c412c6..7155b82b80 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "planet",
"license": "AGPL-3.0",
- "version": "0.15.36",
+ "version": "0.15.37",
"myplanet": {
"latest": "v0.20.86",
"min": "v0.19.86"
diff --git a/src/app/community/community.component.ts b/src/app/community/community.component.ts
index 5e8fc082bd..479df194d3 100644
--- a/src/app/community/community.component.ts
+++ b/src/app/community/community.component.ts
@@ -8,6 +8,7 @@ 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';
@@ -48,6 +49,7 @@ export class CommunityComponent implements OnInit, OnDestroy {
resizeCalendar: any = false;
deviceType: DeviceType;
deviceTypes = DeviceType;
+ challengeActive: boolean;
isLoading: boolean;
constructor(
@@ -86,6 +88,7 @@ export class CommunityComponent implements OnInit, OnDestroy {
this.setCouncillors(users);
}
});
+ this.communityChallenge();
}
@HostListener('window:resize') onResize() {
@@ -97,6 +100,25 @@ export class CommunityComponent implements OnInit, OnDestroy {
this.onDestroy$.complete();
}
+ 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');
+
+ if (this.challengeActive && !popupShown) {
+ this.openAnnouncementDialog();
+ localStorage.setItem('announcementPopupShown', 'true');
+ }
+ }
+
+ openAnnouncementDialog() {
+ this.dialog.open(DialogsAnnouncementComponent, {
+ width: '50vw',
+ maxHeight: '100vh'
+ });
+ }
+
getCommunityData() {
const setShareTarget = (type) => type === 'center' ? 'nation' : type === 'nation' ? 'community' : undefined;
this.route.paramMap.pipe(
diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html
index 292b296d2b..b385727c58 100644
--- a/src/app/dashboard/dashboard.component.html
+++ b/src/app/dashboard/dashboard.component.html
@@ -21,6 +21,9 @@
+
+ stars
+
this.badgesCourses[group] && this.badgesCourses[group].length);
}
+ openChallengeView() {
+ 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 3f3e859b03..363357ca5e 100644
--- a/src/app/shared/database/pouch-auth.service.ts
+++ b/src/app/shared/database/pouch-auth.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { from, throwError, Observable, forkJoin } from 'rxjs';
-import { catchError, switchMap } from 'rxjs/operators';
+import { catchError, switchMap, tap } from 'rxjs/operators';
import { PouchService } from './pouch.service';
import { CouchService } from '../couchdb.service';
@@ -32,6 +32,10 @@ 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)
);
}
diff --git a/src/app/shared/dialogs/dialogs-announcement.component.html b/src/app/shared/dialogs/dialogs-announcement.component.html
new file mode 100644
index 0000000000..7cb42a2d72
--- /dev/null
+++ b/src/app/shared/dialogs/dialogs-announcement.component.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+ {{ userStatus.surveyComplete ? 'check_circle' : 'radio_button_unchecked' }}
+
+
¡Encuesta finalizada!
+
+ Encuesta
+
+
+
+
+
+ {{ userStatus.hasPost ? 'check_circle' : 'radio_button_unchecked' }}
+
+
Comparte tu opinión en Nuestras Voces.
+
+ Voces
+
+
+
+
+
+
+
+
+ {{ getGoalPercentage() | number:'1.0-2' }}% ({{ '$'+ groupSummary?.length }})
+
+
+
+
+ $0
+ $500
+
+
diff --git a/src/app/shared/dialogs/dialogs-announcement.component.scss b/src/app/shared/dialogs/dialogs-announcement.component.scss
new file mode 100644
index 0000000000..aec859b3d2
--- /dev/null
+++ b/src/app/shared/dialogs/dialogs-announcement.component.scss
@@ -0,0 +1,79 @@
+@import '../../variables';
+
+.announcement-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+}
+
+.announcement-banner {
+ max-width: 100%;
+ margin-bottom: 20px;
+}
+
+.steps-container {
+ width: 100%;
+ max-width: 600px;
+ margin-bottom: 20px;
+}
+
+.step {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.step mat-icon {
+ margin-right: 10px;
+}
+
+.step span {
+ flex-grow: 1;
+}
+
+.thermometer-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ margin-top: 10px;
+}
+
+.thermometer {
+ width: 100%;
+ height: 30px;
+ background-color: #e0e0e0;
+ border-radius: 15px;
+ overflow: hidden;
+ position: relative;
+ margin-bottom: 10px;
+}
+
+.thermometer-bar {
+ height: 100%;
+ background-color: $primary;
+ transition: width 0.5s;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.thermometer-label {
+ color: white;
+ font-size: 12px;
+ padding: 2px;
+ border-radius: 3px;
+}
+
+.thermometer-label.outside {
+ position: absolute;
+ left: 0.5rem;
+}
+
+.thermometer-goal {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ font-size: 14px;
+}
diff --git a/src/app/shared/dialogs/dialogs-announcement.component.ts b/src/app/shared/dialogs/dialogs-announcement.component.ts
new file mode 100644
index 0000000000..f1b141548c
--- /dev/null
+++ b/src/app/shared/dialogs/dialogs-announcement.component.ts
@@ -0,0 +1,179 @@
+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 { findDocuments } from '../../shared/mangoQueries';
+import { CouchService } from '../couchdb.service';
+import { CoursesService } from '../../courses/courses.service';
+import { NewsService } from '../../news/news.service';
+import { StateService } from '../state.service';
+import { SubmissionsService } from '../../submissions/submissions.service';
+import { UserService } from '../user.service';
+import { planetAndParentId } from '../../manager-dashboard/reports/reports.utils';
+
+@Component({
+ templateUrl: './dialogs-announcement.component.html',
+ styleUrls: [ './dialogs-announcement.component.scss' ]
+})
+export class DialogsAnnouncementComponent implements OnInit, OnDestroy {
+
+ private onDestroy$ = new Subject
();
+ currentUserName = this.userService.get().name;
+ configuration = this.stateService.configuration;
+ teamId = planetAndParentId(this.stateService.configuration);
+ submissionsSet = new Set();
+ groupSummary = [];
+ enrolledMembers: any;
+ courseId = '9517e3b45a5bb63e69bb8f269216974d';
+ startDate = new Date(2024, 9, 31);
+ endDate = new Date(2024, 11, 1);
+ userStatus = {
+ joinedCourse: false,
+ surveyComplete: false,
+ hasPost: false,
+ };
+
+ constructor(
+ public dialogRef: MatDialogRef,
+ private router: Router,
+ private couchService: CouchService,
+ private coursesService: CoursesService,
+ private newsService: NewsService,
+ private stateService: StateService,
+ private submissionsService: SubmissionsService,
+ private userService: UserService
+ ) {}
+
+ ngOnInit() {
+ const includedCodes = [ 'guatemala', 'san.pablo', 'xela', 'embakasi', 'uriur' ];
+
+ if (includedCodes.includes(this.configuration.code)) {
+ this.configuration = this.stateService.configuration;
+ this.initializeData();
+ }
+ }
+
+ ngOnDestroy() {
+ this.onDestroy$.next();
+ this.onDestroy$.complete();
+ }
+
+ onClose(): void {
+ this.dialogRef.close();
+ }
+
+ initializeData() {
+ this.coursesService.requestCourses();
+ this.newsService.requestNews({
+ selectors: {
+ '$or': [
+ { messagePlanetCode: this.configuration.code, viewableBy: 'community' },
+ { viewIn: { '$elemMatch': { '_id': this.teamId, section: 'community' } } }
+ ]
+ },
+ viewId: this.teamId
+ });
+ this.fetchCourseAndNews();
+ this.fetchEnrolled();
+ }
+
+ joinCourse() {
+ const courseTitle = this.coursesService.getCourseNameFromId(this.courseId);
+ this.coursesService.courseResignAdmission(this.courseId, 'admission', courseTitle).subscribe((res) => {
+ this.router.navigate([ '/courses/view', this.courseId ]);
+ }, (error) => ((error)));
+ this.dialogRef.close();
+ }
+
+ doSurvey() {
+ this.router.navigate([ `/courses/view/${this.courseId}/step/3` ]);
+ this.dialogRef.close();
+ }
+
+ postVoice() {
+ this.router.navigate([ '/' ]);
+ this.dialogRef.close();
+ }
+
+ fetchEnrolled() {
+ this.couchService.findAll('shelf', {
+ selector: { courseIds: { $elemMatch: { $eq: this.courseId } } },
+ }).subscribe((members) => {
+ this.enrolledMembers = members.map((member: any) => {
+ const [ , memberName ] = member?._id.split(':');
+ return {
+ ...member,
+ name: memberName,
+ };
+ });
+ });
+ }
+
+ 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$)
+ ).subscribe(news => {
+ news.map(post => ({
+ ...post,
+ public: (
+ (post.doc.viewIn || []).find(
+ (view) =>
+ view._id ===
+ `${this.configuration.code}@${this.configuration.parentCode}`
+ ) || {}
+ ).public,
+ }));
+
+ 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));
+
+ // 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);
+ }
+ });
+
+ // Individual stats
+ this.userStatus.surveyComplete = this.hasCompletedSurvey(this.currentUserName);
+ this.userStatus.hasPost = this.hasSubmittedVoice(news, this.currentUserName);
+ this.enrolledMembers.some(member => {
+ if (member.name === this.currentUserName) {
+ this.userStatus.joinedCourse = this.hasEnrolledCourse(member);
+ }
+ });
+ });
+ });
+ }
+
+ getGoalPercentage(): number {
+ const goal = 500;
+ return (this.groupSummary?.length / goal) * 100;
+ }
+}
diff --git a/src/app/shared/dialogs/planet-dialogs.module.ts b/src/app/shared/dialogs/planet-dialogs.module.ts
index 879ce81aa5..0a7f384566 100644
--- a/src/app/shared/dialogs/planet-dialogs.module.ts
+++ b/src/app/shared/dialogs/planet-dialogs.module.ts
@@ -16,6 +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 { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-ratings.component';
@@ -40,7 +41,8 @@ import { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-rati
DialogsRatingsComponent,
DialogsRatingsDirective,
ChangePasswordDirective,
- SyncDirective
+ SyncDirective,
+ DialogsAnnouncementComponent,
],
declarations: [
DialogsFormComponent,
@@ -54,7 +56,8 @@ import { DialogsRatingsComponent, DialogsRatingsDirective } from './dialogs-rati
DialogsRatingsComponent,
DialogsRatingsDirective,
ChangePasswordDirective,
- SyncDirective
+ SyncDirective,
+ DialogsAnnouncementComponent
],
providers: [
DialogsFormService,