diff --git a/src/app/main/component/comments/components/add-comment/add-comment.component.spec.ts b/src/app/main/component/comments/components/add-comment/add-comment.component.spec.ts index c547289f68..7c614bdac8 100644 --- a/src/app/main/component/comments/components/add-comment/add-comment.component.spec.ts +++ b/src/app/main/component/comments/components/add-comment/add-comment.component.spec.ts @@ -45,7 +45,7 @@ describe('AddCommentComponent', () => { rating: null, showEcoPlace: true, showLocation: true, - showShoppingList: true, + showToDoList: true, socialNetworks: [{ id: 1, url: defaultImagePath }] } as EditProfileModel; diff --git a/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.html b/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.html index 658146ba7b..c966f16a2e 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.html +++ b/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.html @@ -3,8 +3,14 @@
-

- {{ tag }} +

+ {{ tag }} + |

diff --git a/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.scss b/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.scss index 8ac6ca8456..62ec360539 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.scss +++ b/src/app/main/component/eco-news/components/news-list/news-list-list-view/news-list-list-view.component.scss @@ -19,13 +19,24 @@ div { display: none; } +.tag-divider { + color: var(--primary-light-green); + height: 100%; + padding: 0 4px; + margin: 0 4px; +} + +.divider-small { + margin: 0 2px; + padding: 0; +} + .eco-news_list-tag { font-family: var(--primary-font); font-size: 12px; line-height: 16px; text-transform: uppercase; color: var(--primary-green); - margin-right: 5px; } .eco-news_list-content { diff --git a/src/app/main/component/eco-news/components/news-list/news-list.component.html b/src/app/main/component/eco-news/components/news-list/news-list.component.html index 78c542a296..bb1fce2049 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list.component.html +++ b/src/app/main/component/eco-news/components/news-list/news-list.component.html @@ -3,6 +3,27 @@

{{ 'homepage.eco-news.title' | translate }}

+
+
+ + cancel search +
+
+ +
+
+ +
+
+ my-event +
+
{{ 'homepage.eco-news.create' | translate }} @@ -34,9 +55,17 @@

{{ 'homepage.eco-news.title' | translate }}

}" *ngFor="let data of elements" > -
+ + +
+ +
diff --git a/src/app/main/component/eco-news/components/news-list/news-list.component.scss b/src/app/main/component/eco-news/components/news-list/news-list.component.scss index f593de22f8..da2dde03a0 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list.component.scss +++ b/src/app/main/component/eco-news/components/news-list/news-list.component.scss @@ -48,6 +48,140 @@ div hr { } } +.create-container { + height: 40px; + display: flex; + flex-direction: row; + align-items: center; + gap: 24px; + flex: 1; + justify-content: flex-end; + margin-right: 30px; + + @include responsivePCFirst(sm) { + flex-direction: row; + gap: 18px; + } +} + +.container-img { + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--primary-white); + border-radius: 4px; + box-shadow: 0 0 1px 0 var(--primary-grey); + cursor: pointer; + + span { + transition: all 0.3s; + } + + .search-img { + min-width: 20px; + min-height: 20px; + background: url('/assets/events-icons/search.png'); + display: inline-block; + + &:hover { + background: url('/assets/events-icons/search-hover.png'); + } + } + + .search-img-active { + min-width: 20px; + min-height: 20px; + background: url('/assets/events-icons/search-active.png'); + } + + .bookmark-img { + width: 12px; + height: 17px; + background: url('/assets/events-icons/bookmark-grey.png'); + } + + .bookmark-img-active { + width: 12px; + height: 17px; + background: url('/assets/events-icons/bookmark-active.png'); + } +} + +.container-input { + position: relative; + margin-left: 15px; + flex: 1; + + .cross-position { + position: absolute; + top: 14px; + right: 10px; + cursor: pointer; + } + + .place-input { + border: none; + border-radius: 4px; + width: 100%; + box-shadow: 0 0 1px 0 var(--primary-grey); + height: 40px; + padding: 0 14px; + + &:active, + &:focus, + &:hover { + border: 1px solid var(--primary-grey); + outline: none; + } + } +} + +.link { + position: relative; +} + +.news-flags { + width: 40px; + height: 40px; + background-color: rgba(255 255 255/ 80%); + position: absolute; + top: 8px; + right: 8px; + border-radius: 5px; + display: flex; + flex-direction: row; + gap: 10px; + padding: 8px; + justify-content: center; + align-items: center; + box-shadow: 0 0 1px 0 var(--primary-grey); + cursor: pointer; + + span { + display: inline-block; + width: 12px; + height: 16.5px; + transition: all 0.3s; + } + + .flag { + background: url('/assets/img/places/bookmark-default.svg') no-repeat center; + background-size: contain; + } + + .flag-active { + background: url('/assets/img/places/bookmark-set.svg') no-repeat center; + background-size: contain; + } +} + +.my-events-img { + width: 17px; + height: 17px; +} + .gallery-view-active { display: grid; grid-template-columns: repeat(3, 359px); diff --git a/src/app/main/component/eco-news/components/news-list/news-list.component.spec.ts b/src/app/main/component/eco-news/components/news-list/news-list.component.spec.ts index 09c7237de9..845bf3fe47 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list.component.spec.ts +++ b/src/app/main/component/eco-news/components/news-list/news-list.component.spec.ts @@ -18,17 +18,15 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { Store } from '@ngrx/store'; import { Language } from 'src/app/main/i18n/Language'; +import { MatDialog } from '@angular/material/dialog'; +import { ChangeEcoNewsFavoriteStatusAction } from 'src/app/store/actions/ecoNews.actions'; describe('NewsListComponent', () => { let component: NewsListComponent; let fixture: ComponentFixture; - const ecoNewsServiceMock: EcoNewsService = jasmine.createSpyObj('EcoNewsService', [ - 'getAllPresentTags', - 'getNewsListByTags', - 'getEcoNewsListByPage' - ]); - ecoNewsServiceMock.getNewsListByTags = () => new Observable(); + const ecoNewsServiceMock: EcoNewsService = jasmine.createSpyObj('EcoNewsService', ['getAllPresentTags', 'getEcoNewsListByPage']); + ecoNewsServiceMock.getEcoNewsListByPage = () => new Observable(); const localStorageServiceMock: LocalStorageService = jasmine.createSpyObj('LocalStorageService', [ @@ -46,6 +44,31 @@ describe('NewsListComponent', () => { userOwnAuthServiceMock.credentialDataSubject = new Subject(); userOwnAuthServiceMock.isLoginUserSubject = new BehaviorSubject(true); + const newsMock = { + countComments: 5, + id: 13578, + imagePath: null, + title: '', + text: '', + content: '', + shortInfo: '', + tags: ['News', 'Events'], + tagsEn: ['News'], + tagsUa: ['Новини'], + creationDate: '2021-11-25T22:32:30.555088+02:00', + likes: 0, + source: '', + author: { id: 312, name: 'taqcTestName' } + }; + + const matDialogRefMock = { + afterClosed: jasmine.createSpy('afterClosed').and.returnValue(of(true)) + }; + + const matDialogMock = { + open: jasmine.createSpy('open').and.returnValue(matDialogRefMock) + }; + const storeMock = jasmine.createSpyObj('store', ['select', 'dispatch']); storeMock.select = () => of({ ecoNews: {}, pages: [], pageNumber: 1, error: 'error' }); @@ -70,7 +93,8 @@ describe('NewsListComponent', () => { { provide: LocalStorageService, useValue: localStorageServiceMock }, { provide: EcoNewsService, useValue: ecoNewsServiceMock }, { provide: UserOwnAuthService, useValue: userOwnAuthServiceMock }, - { provide: Store, useValue: storeMock } + { provide: Store, useValue: storeMock }, + { provide: MatDialog, useValue: matDialogMock } ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); @@ -80,6 +104,7 @@ describe('NewsListComponent', () => { fixture = TestBed.createComponent(NewsListComponent); component = fixture.componentInstance; fixture.detectChanges(); + storeMock.dispatch.calls.reset(); }); it('should create', () => { @@ -89,6 +114,15 @@ describe('NewsListComponent', () => { it('should add elements to current list if scroll', () => { spyOn(component, 'dispatchStore'); component.onScroll(); + expect(component.dispatchStore).toHaveBeenCalledTimes(0); + }); + + it('should dispatch store action on scroll when elements are present', () => { + component.elements = [newsMock, { ...newsMock, id: 2 }]; + + spyOn(component, 'dispatchStore'); + component.onScroll(); + expect(component.dispatchStore).toHaveBeenCalledWith(false); expect(component.dispatchStore).toHaveBeenCalledTimes(1); }); @@ -109,4 +143,39 @@ describe('NewsListComponent', () => { component.getFilterData(['News']); expect(component.getFilterData).toHaveBeenCalledWith(['News']); }); + + it('should cancel search correctly', () => { + component.searchNewsControl.setValue('test'); + component.cancelSearch(); + expect(component.searchNewsControl.value).toBe(''); + }); + + it('should toggle search state', () => { + component.isSearchVisible = false; + component.toggleSearch(); + expect(component.isSearchVisible).toBeTrue(); + }); + + it('should change favorite status when user is logged in', () => { + const event = new MouseEvent('click'); + const data = { ...newsMock, favorite: false }; + component.userId = 1; + + component.changeFavoriteStatus(event, data); + + const expectedAction = ChangeEcoNewsFavoriteStatusAction({ + id: data.id, + favorite: true, + isFavoritesPage: component.bookmarkSelected + }); + + expect(storeMock.dispatch).toHaveBeenCalledWith(expectedAction); + }); + + it('should toggle bookmarked news display', () => { + component.userId = 1; + component.bookmarkSelected = false; + component.showSelectedNews(); + expect(component.bookmarkSelected).toBeTrue(); + }); }); diff --git a/src/app/main/component/eco-news/components/news-list/news-list.component.ts b/src/app/main/component/eco-news/components/news-list/news-list.component.ts index 328b434ec7..b488a2190b 100644 --- a/src/app/main/component/eco-news/components/news-list/news-list.component.ts +++ b/src/app/main/component/eco-news/components/news-list/news-list.component.ts @@ -1,19 +1,21 @@ import { Breakpoints } from 'src/app/main/config/breakpoints.constants'; import { Component, OnInit, OnDestroy } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { EcoNewsService } from '@eco-news-service/eco-news.service'; +import { Observable, of, ReplaySubject } from 'rxjs'; +import { map, take, takeUntil } from 'rxjs/operators'; import { EcoNewsModel } from '@eco-news-models/eco-news-model'; import { FilterModel } from '@shared/components/tag-filter/tag-filter.model'; import { UserOwnAuthService } from '@auth-service/user-own-auth.service'; -import { MatSnackBarComponent } from '@global-errors/mat-snack-bar/mat-snack-bar.component'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { Store } from '@ngrx/store'; import { IAppState } from 'src/app/store/state/app.state'; import { IEcoNewsState } from 'src/app/store/state/ecoNews.state'; -import { GetEcoNewsByTagsAction, GetEcoNewsByPageAction } from 'src/app/store/actions/ecoNews.actions'; -import { Router } from '@angular/router'; +import { GetEcoNewsAction, ChangeEcoNewsFavoriteStatusAction } from 'src/app/store/actions/ecoNews.actions'; import { tagsListEcoNewsData } from '@eco-news-models/eco-news-consts'; +import { FormControl, Validators } from '@angular/forms'; +import { Patterns } from '@assets/patterns/patterns'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { AuthModalComponent } from '@global-auth/auth-modal/auth-modal.component'; +import { EcoNewsService } from '@eco-news-service/eco-news.service'; @Component({ selector: 'app-news-list', @@ -28,24 +30,31 @@ export class NewsListComponent implements OnInit, OnDestroy { remaining = 0; windowSize: number; isLoggedIn: boolean; - private currentPage: number; + userId: number; scroll: boolean; numberOfNews: number; + newsTotal: number; elementsArePresent = true; tagList: FilterModel[] = tagsListEcoNewsData; private destroyed$: ReplaySubject = new ReplaySubject(1); - + bookmarkSelected = false; hasNext = true; - + loading = false; + private page = 0; + noNewsMatch = false; + isSearchVisible = false; + searchNewsControl = new FormControl('', [Validators.maxLength(30), Validators.pattern(Patterns.NameInfoPattern)]); econews$ = this.store.select((state: IAppState): IEcoNewsState => state.ecoNewsState); + searchQuery = ''; + + private dialogRef: MatDialogRef; constructor( - private ecoNewsService: EcoNewsService, private userOwnAuthService: UserOwnAuthService, - private snackBar: MatSnackBarComponent, private localStorageService: LocalStorageService, private store: Store, - private router: Router + private readonly dialog: MatDialog, + private readonly ecoNewsService: EcoNewsService ) {} ngOnInit() { @@ -55,11 +64,10 @@ export class NewsListComponent implements OnInit, OnDestroy { this.userOwnAuthService.getDataFromLocalStorage(); this.scroll = false; this.setLocalizedTags(); - - this.dispatchStore(false); + this.localStorageService.setCurentPage('previousPage', '/news'); this.econews$.subscribe((value: IEcoNewsState) => { - this.currentPage = value.pageNumber; + this.page = value.pageNumber; if (value.ecoNews) { this.elements = [...value.pages]; const data = value.ecoNews; @@ -68,8 +76,13 @@ export class NewsListComponent implements OnInit, OnDestroy { Math.abs(data.totalElements - value.countOfEcoNews) > 0 && value.countOfEcoNews !== 0 ? value.countOfEcoNews : data.totalElements; this.elementsArePresent = this.elements.length < data.totalElements; } + this.loading = false; + }); + + this.searchNewsControl.valueChanges.subscribe((value) => { + this.searchQuery = value.trim(); + this.dispatchStore(true); }); - this.localStorageService.setCurentPage('previousPage', '/news'); } private setLocalizedTags(): void { @@ -91,7 +104,9 @@ export class NewsListComponent implements OnInit, OnDestroy { } onScroll(): void { - this.scroll = true; + if (!this.elements.length) { + return; + } this.dispatchStore(false); } @@ -103,25 +118,91 @@ export class NewsListComponent implements OnInit, OnDestroy { if (this.tagsList !== value) { this.tagsList = value; } - this.hasNext = true; - this.currentPage = 0; + this.dispatchStore(true); + } + + cancelSearch(): void { + if (!this.searchNewsControl.value.trim()) { + this.isSearchVisible = false; + } else { + this.searchNewsControl.setValue(''); + } + } + + toggleSearch(): void { + this.isSearchVisible = !this.isSearchVisible; + } + + private checkAuthentication(): Observable { + if (!this.userId) { + this.openAuthModalWindow('sign-in'); + return this.dialogRef.afterClosed().pipe( + take(1), + map((result) => !!result) + ); + } + return of(true); + } + + changeFavoriteStatus(event: Event, data: EcoNewsModel) { + event.preventDefault(); + event.stopPropagation(); + + this.checkAuthentication(); + + const action = ChangeEcoNewsFavoriteStatusAction({ id: data.id, favorite: !data.favorite, isFavoritesPage: this.bookmarkSelected }); + this.store.dispatch(action); + } + + openAuthModalWindow(page: string): void { + this.dialogRef = this.dialog.open(AuthModalComponent, { + hasBackdrop: true, + closeOnNavigation: true, + panelClass: ['custom-dialog-container'], + data: { + popUpName: page + } + }); + } + + showSelectedNews(): void { + this.checkAuthentication(); + this.bookmarkSelected = !this.bookmarkSelected; this.dispatchStore(true); } dispatchStore(res: boolean): void { - if (!this.hasNext || this.currentPage === undefined) { + if (res) { + this.hasNext = true; + this.page = 0; + this.elements = []; + this.noNewsMatch = false; + this.newsTotal = 0; + } + + if (!this.hasNext || this.loading) { return; } - const action = this.tagsList.length - ? GetEcoNewsByTagsAction({ currentPage: this.currentPage, numberOfNews: this.numberOfNews, tagsList: this.tagsList, reset: res }) - : GetEcoNewsByPageAction({ currentPage: this.currentPage, numberOfNews: this.numberOfNews, reset: res }); + this.loading = true; + const params = this.ecoNewsService.getNewsHttpParams({ + page: this.page, + size: this.numberOfNews, + title: this.searchQuery, + favorite: this.bookmarkSelected, + userId: this.userId, + tags: this.tagsList + }); + const action = GetEcoNewsAction({ params, reset: res }); this.store.dispatch(action); } private checkUserSingIn(): void { - this.userOwnAuthService.credentialDataSubject.subscribe((data) => (this.isLoggedIn = data?.userId)); + this.userOwnAuthService.credentialDataSubject.subscribe((data) => { + this.isLoggedIn = data?.userId; + this.userId = data.userId; + }); } private setDefaultNumberOfNews(quantity: number): void { diff --git a/src/app/main/component/eco-news/models/eco-news-model.ts b/src/app/main/component/eco-news/models/eco-news-model.ts index f28ee61ee1..bd31f19518 100644 --- a/src/app/main/component/eco-news/models/eco-news-model.ts +++ b/src/app/main/component/eco-news/models/eco-news-model.ts @@ -16,4 +16,5 @@ export interface EcoNewsModel { tagsUa: Array; title: string; countOfEcoNews?: number; + favorite?: boolean; } diff --git a/src/app/main/component/eco-news/services/eco-news.service.spec.ts b/src/app/main/component/eco-news/services/eco-news.service.spec.ts index 9c6a885b51..9bbafa3da6 100644 --- a/src/app/main/component/eco-news/services/eco-news.service.spec.ts +++ b/src/app/main/component/eco-news/services/eco-news.service.spec.ts @@ -4,6 +4,7 @@ import { LocalStorageService } from '@global-service/localstorage/local-storage. import { BehaviorSubject } from 'rxjs'; import { environment } from '@environment/environment'; import { EcoNewsService } from './eco-news.service'; +import { HttpParams } from '@angular/common/http'; describe('EcoNewsService', () => { let service: EcoNewsService; @@ -14,12 +15,25 @@ describe('EcoNewsService', () => { imagePath: null, title: '', text: '', - author: { id: 312, name: 'taqcTestName' }, + content: '', + shortInfo: '', tags: ['News', 'Events'], + tagsEn: ['News'], + tagsUa: ['Новини'], creationDate: '2021-11-25T22:32:30.555088+02:00', likes: 0, - source: '' + source: '', + author: { id: 312, name: 'taqcTestName' } }; + + const newsDtoMock = { + page: [newsMock], + totalElements: 5, + currentPage: 0, + totalPages: 1, + hasNext: true + }; + const localStorageServiceMock: LocalStorageService = jasmine.createSpyObj('LocalStorageService', ['languageBehaviourSubject']); localStorageServiceMock.languageBehaviourSubject = new BehaviorSubject('en'); @@ -41,34 +55,12 @@ describe('EcoNewsService', () => { it('should return eco news list by current page', () => { service.getEcoNewsListByPage(0, 5).subscribe((data) => { - expect(data).toBe(newsMock); + expect(data).toEqual(newsDtoMock); }); const req = httpTestingController.expectOne(`${environment.backendLink}eco-news?page=0&size=5`); expect(req.request.method).toEqual('GET'); - req.flush(newsMock); - }); - - it('should return eco news list by current tags', () => { - const tagMock = ['News']; - service.getNewsListByTags(0, 5, tagMock).subscribe((data) => { - expect(data).toBe(newsMock); - }); - - const req = httpTestingController.expectOne(`${environment.backendLink}eco-news?tags=${tagMock}&page=0&size=5`); - expect(req.request.method).toEqual('GET'); - req.flush(newsMock); - }); - - it('should return news list', () => { - const arr = [newsMock]; - service.getNewsList().subscribe((data) => { - expect(data).toBe(arr); - }); - - const req = httpTestingController.expectOne(`${environment.backendLink}eco-news`); - expect(req.request.method).toEqual('GET'); - req.flush(arr); + req.flush(newsDtoMock); }); it('should return news list by id', () => { @@ -111,4 +103,58 @@ describe('EcoNewsService', () => { expect(req.request.method).toEqual('GET'); req.flush(isLikedByUser); }); + + it('should get eco news list with custom parameters', () => { + const params = new HttpParams().set('page', '0').set('size', '5'); + service.getEcoNewsList(params).subscribe((data) => { + expect(data).toEqual(newsDtoMock); + }); + + const req = httpTestingController.expectOne(`${environment.backendLink}eco-news?page=0&size=5`); + expect(req.request.method).toEqual('GET'); + req.flush(newsDtoMock); + }); + + it('should get eco news list by author ID', () => { + const authorId = 123; + service.getEcoNewsListByAuthorId(authorId, 0, 5).subscribe((data) => { + expect(data).toBeDefined(); + }); + + const req = httpTestingController.expectOne(`${environment.backendLink}eco-news?author-id=${authorId}&page=0&size=5`); + expect(req.request.method).toEqual('GET'); + }); + + it('should delete a news item', () => { + const newsId = 13578; + service.deleteNews(newsId).subscribe((data) => { + expect(data).toBeDefined(); + }); + + const req = httpTestingController.expectOne(`${environment.backendLink}eco-news/${newsId}`); + expect(req.request.method).toEqual('DELETE'); + req.flush({}); + }); + + it('should add news to favorites', () => { + const newsId = 13578; + service.addNewsToFavorites(newsId).subscribe((data) => { + expect(data).toBeDefined(); + }); + + const req = httpTestingController.expectOne(`${environment.backendLink}eco-news/${newsId}/favorites`); + expect(req.request.method).toEqual('POST'); + req.flush({}); + }); + + it('should remove news from favorites', () => { + const newsId = 13578; + service.removeNewsFromFavorites(newsId).subscribe((data) => { + expect(data).toBeDefined(); + }); + + const req = httpTestingController.expectOne(`${environment.backendLink}eco-news/${newsId}/favorites`); + expect(req.request.method).toEqual('DELETE'); + req.flush({}); + }); }); diff --git a/src/app/main/component/eco-news/services/eco-news.service.ts b/src/app/main/component/eco-news/services/eco-news.service.ts index 6f04e382cd..7919eea8e7 100644 --- a/src/app/main/component/eco-news/services/eco-news.service.ts +++ b/src/app/main/component/eco-news/services/eco-news.service.ts @@ -1,7 +1,7 @@ import { Injectable, OnDestroy } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Observable, Observer, ReplaySubject } from 'rxjs'; -import { take, takeUntil } from 'rxjs/operators'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable, ReplaySubject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; import { EcoNewsModel } from '../models/eco-news-model'; import { environment } from '@environment/environment'; import { EcoNewsDto } from '../models/eco-news-dto'; @@ -22,29 +22,16 @@ export class EcoNewsService implements OnDestroy { this.localStorageService.languageBehaviourSubject.pipe(takeUntil(this.destroyed$)).subscribe((language) => (this.language = language)); } - getEcoNewsListByPage(page: number, quantity: number) { - return this.http.get(`${this.backEnd}eco-news?page=${page}&size=${quantity}`); + getEcoNewsListByPage(page: number, quantity: number): Observable { + return this.http.get(`${this.backEnd}eco-news?page=${page}&size=${quantity}`); } - getEcoNewsListByAutorId(authorId: number, page: number, quantity: number) { - return this.http.get(`${this.backEnd}eco-news?author-id=${authorId}&page=${page}&size=${quantity}`); + getEcoNewsList(params: HttpParams): Observable { + return this.http.get(`${this.backEnd}eco-news`, { params }); } - getNewsListByTags(page: number, quantity: number, tags: Array) { - return this.http.get(`${this.backEnd}eco-news?tags=${tags}&page=${page}&size=${quantity}`); - } - - getNewsList(): Observable { - const headers = new HttpHeaders(); - headers.set('Content-type', 'application/json'); - return new Observable((observer: Observer) => { - this.http - .get(`${this.backEnd}eco-news`) - .pipe(take(1)) - .subscribe((newsDto: EcoNewsDto) => { - observer.next(newsDto); - }); - }); + getEcoNewsListByAuthorId(authorId: number, page: number, quantity: number): Observable { + return this.http.get(`${this.backEnd}eco-news?author-id=${authorId}&page=${page}&size=${quantity}`); } getEcoNewsById(id: number): Observable { @@ -67,6 +54,48 @@ export class EcoNewsService implements OnDestroy { return this.http.delete(`${this.backEnd}eco-news/${id}`); } + addNewsToFavorites(id: number) { + return this.http.post(`${this.backEnd}eco-news/${id}/favorites`, {}); + } + + removeNewsFromFavorites(id: number) { + return this.http.delete(`${this.backEnd}eco-news/${id}/favorites`, {}); + } + + getNewsHttpParams(parameters: { + page: number; + size: number; + title?: string; + favorite: boolean; + userId: number; + authorId?: number; + tags: string[]; + }): HttpParams { + let params = new HttpParams().set('page', parameters.page.toString()).set('size', parameters.size.toString()); + + const optionalParams = [ + parameters.favorite && this.appendIfNotEmpty('user-id', parameters.userId.toString()), + !parameters.favorite && this.appendIfNotEmpty('author-id', parameters.authorId ? parameters?.authorId.toString() : null), + this.appendIfNotEmpty('title', parameters.title), + this.appendIfNotEmpty('tags', parameters.tags), + parameters.favorite && { key: 'favorite', value: parameters.favorite } + ]; + + optionalParams.forEach((param) => { + if (param) { + params = params.append(param.key, param.value); + } + }); + + const serializedParams = params.toString(); + return new HttpParams({ fromString: serializedParams }); + } + + private appendIfNotEmpty(key: string, value: string | string[]): { key: string; value: string } | null { + const formattedValue = Array.isArray(value) ? value.join(',') : value; + return formattedValue?.trim() ? { key, value: formattedValue.toUpperCase() } : null; + } + ngOnDestroy(): void { this.destroyed$.next(true); this.destroyed$.complete(); diff --git a/src/app/main/component/events/components/events-list/events-list.component.scss b/src/app/main/component/events/components/events-list/events-list.component.scss index 0395ed28b3..8002b582ef 100644 --- a/src/app/main/component/events/components/events-list/events-list.component.scss +++ b/src/app/main/component/events/components/events-list/events-list.component.scss @@ -108,7 +108,14 @@ width: 100%; box-shadow: 0 0 1px 0 var(--primary-grey); height: 40px; - padding-right: 25px; + padding: 0 14px; + + &:active, + &:focus, + &:hover { + border: 1px solid var(--primary-grey); + outline: none; + } } } diff --git a/src/app/main/component/events/components/events-list/events-list.component.ts b/src/app/main/component/events/components/events-list/events-list.component.ts index 9588be79bb..2ddf76d375 100644 --- a/src/app/main/component/events/components/events-list/events-list.component.ts +++ b/src/app/main/component/events/components/events-list/events-list.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Addresses, EventListResponse, FilterItem } from '../../models/events.interface'; import { UserOwnAuthService } from '@auth-service/user-own-auth.service'; -import { Observable, ReplaySubject, Subscription } from 'rxjs'; +import { Observable, ReplaySubject, Subscription, take } from 'rxjs'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { Store } from '@ngrx/store'; import { IAppState } from 'src/app/store/state/app.state'; @@ -9,7 +9,7 @@ import { IEcoEventsState } from 'src/app/store/state/ecoEvents.state'; import { statusFiltersData, timeStatusFiltersData, typeFiltersData } from '../../models/event-consts'; import { Router } from '@angular/router'; import { AuthModalComponent } from '@global-auth/auth-modal/auth-modal.component'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { FormControl, Validators } from '@angular/forms'; import { MatSelect } from '@angular/material/select'; import { Patterns } from 'src/assets/patterns/patterns'; @@ -35,7 +35,7 @@ export class EventsListComponent implements OnInit, OnDestroy { searchEventControl = new FormControl('', [Validators.maxLength(30), Validators.pattern(Patterns.NameInfoPattern)]); eventsList: EventListResponse[] = []; - isLoggedIn: string; + isLoggedIn: boolean; selectedEventTimeStatusFiltersList: string[] = []; selectedLocationFiltersList: string[] = []; selectedStatusFiltersList: string[] = []; @@ -59,6 +59,7 @@ export class EventsListComponent implements OnInit, OnDestroy { private eventsPerPage = 6; private searchResultSubscription: Subscription; private searchQuery: string; + private readonly dialogRef: MatDialogRef; constructor( private store: Store, @@ -115,16 +116,25 @@ export class EventsListComponent implements OnInit, OnDestroy { } showSelectedEvents(): void { - this.bookmarkSelected = !this.bookmarkSelected; - if (this.bookmarkSelected) { - this.cleanEventList(); - this.getUserFavoriteEvents(); + if (!this.isLoggedIn) { + this.openAuthModalWindow('sign-in'); + this.dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + this.isLoggedIn = !!result; + }); } else { - this.cleanEventList(); - this.getEvents(); + this.toggleEventList(); } } + private toggleEventList(): void { + this.bookmarkSelected = !this.bookmarkSelected; + this.cleanEventList(); + this.getEvents(); + } + getUniqueLocations(addresses: Array): FilterItem[] { const uniqueLocationsName = new Set(); const uniqueLocations: FilterItem[] = [{ type: 'location', nameEn: 'Online', nameUa: 'Онлайн' }]; @@ -351,7 +361,6 @@ export class EventsListComponent implements OnInit, OnDestroy { this.eventService.getEvents(this.getEventsHttpParams()).subscribe((res) => { this.isLoading = false; this.eventsList.push(...res.page); - this.page++; this.countOfEvents = res.totalElements; this.hasNextPage = res.hasNext; }); @@ -446,7 +455,7 @@ export class EventsListComponent implements OnInit, OnDestroy { private checkUserSingIn(): void { this.userOwnAuthService.credentialDataSubject.subscribe((data) => { - this.isLoggedIn = data?.userId; + this.isLoggedIn = !!data?.userId; this.userId = data.userId; this.statusFiltersList = this.userId ? statusFiltersData : statusFiltersData.slice(0, 2); }); diff --git a/src/app/main/component/shared/components/events-list-item/events-list-item.component.scss b/src/app/main/component/shared/components/events-list-item/events-list-item.component.scss index 06f5849e32..a46c8a80f9 100644 --- a/src/app/main/component/shared/components/events-list-item/events-list-item.component.scss +++ b/src/app/main/component/shared/components/events-list-item/events-list-item.component.scss @@ -146,17 +146,19 @@ span { display: inline-block; - width: 25px; - height: 25px; + width: 12px; + height: 16.5px; transition: all 0.3s; } .flag { background: url('/assets/img/places/bookmark-default.svg') no-repeat center; + background-size: contain; } .flag-active { background: url('/assets/img/places/bookmark-set.svg') no-repeat center; + background-size: contain; } } diff --git a/src/app/main/component/shared/components/input-google-autocomplete/input-google-autocomplete.component.ts b/src/app/main/component/shared/components/input-google-autocomplete/input-google-autocomplete.component.ts index e63f0ebe3d..bbc8e53c48 100644 --- a/src/app/main/component/shared/components/input-google-autocomplete/input-google-autocomplete.component.ts +++ b/src/app/main/component/shared/components/input-google-autocomplete/input-google-autocomplete.component.ts @@ -33,6 +33,7 @@ export class InputGoogleAutocompleteComponent implements OnInit, OnDestroy, Cont disabled = false; touched = false; + isCitySelected = false; predictionList: GooglePrediction[]; autocompleteService: GoogleAutoService; placeId: string; @@ -70,11 +71,15 @@ export class InputGoogleAutocompleteComponent implements OnInit, OnDestroy, Cont } onUserChange() { - this.inputValue.setValue(''); - this.onChange(''); - this.markAsTouched(); - this.predictionSelected.emit(null); - this.selectedPredictionCoordinates.emit({ longitude: null, latitude: null }); + setTimeout(() => { + if (!this.isCitySelected) { + this.inputValue.setValue(''); + this.onChange(''); + this.markAsTouched(); + this.predictionSelected.emit(null); + this.selectedPredictionCoordinates.emit({ longitude: null, latitude: null }); + } + }, 100); } writeValue(value: any): void { @@ -130,6 +135,7 @@ export class InputGoogleAutocompleteComponent implements OnInit, OnDestroy, Cont this.returnCoordinatesFromPrediction(prediction); } + this.isCitySelected = true; this.predictionSelected.emit(prediction); } diff --git a/src/app/main/component/shared/components/tag-filter/tag-filter.component.html b/src/app/main/component/shared/components/tag-filter/tag-filter.component.html index 6e085e522b..5c8bbb774f 100644 --- a/src/app/main/component/shared/components/tag-filter/tag-filter.component.html +++ b/src/app/main/component/shared/components/tag-filter/tag-filter.component.html @@ -1,5 +1,5 @@
- {{ header | translate }} + {{ header | translate }}
-
+
-
- +
+
diff --git a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.scss b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.scss index fc4c8ac58d..005028cd38 100644 --- a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.scss +++ b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.scss @@ -80,14 +80,14 @@ } } - .duration-shoping { + .duration-to-do { display: flex; width: 100%; justify-content: space-between; gap: 32px; .duration, - .shopping { + .to-do { width: calc(50% - 16px); background: var(--secondary-light-grey); border-radius: 8px; @@ -352,13 +352,13 @@ input[type='radio'] + img { padding: 0; gap: 24px; - .duration-shoping { + .duration-to-do { flex-direction: column; height: auto; gap: 24px; .duration, - .shopping { + .to-do { width: 100%; } } diff --git a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.spec.ts b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.spec.ts index 3ca72e68a5..a087776252 100644 --- a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.spec.ts +++ b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.spec.ts @@ -13,11 +13,11 @@ import { HabitAssignService } from '@global-service/habit-assign/habit-assign.se import { HabitService } from '@global-service/habit/habit.service'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { HabitDurationComponent } from '@global-user/components/habit/add-new-habit/habit-duration/habit-duration.component'; -import { HabitEditShoppingListComponent } from '@global-user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component'; +import { HabitEditToDoListComponent } from '@global-user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component'; import { HabitInviteFriendsComponent } from '@global-user/components/habit/add-new-habit/habit-invite-friends/habit-invite-friends.component'; import { HabitProgressComponent } from '@global-user/components/habit/add-new-habit/habit-progress/habit-progress.component'; import { CalendarWeekComponent } from '@global-user/components/profile/calendar/calendar-week/calendar-week.component'; -import { ShoppingList } from '@global-user/models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; @@ -82,7 +82,7 @@ describe('AddEditCustomHabitComponent', () => { HabitDurationComponent, CalendarWeekComponent, CalendarComponent, - HabitEditShoppingListComponent, + HabitEditToDoListComponent, HabitInviteFriendsComponent, HabitProgressComponent, SelectImagesComponent, @@ -152,7 +152,7 @@ describe('AddEditCustomHabitComponent', () => { }); it('should call changeCustomHabit() and goToAllHabits() on success', () => { - const habitFormValue = { title: 'Title', description: 'Description', complexity: 1, duration: 7, tagIds: [1], image: '', shopList: [] }; + const habitFormValue = { title: 'Title', description: 'Description', complexity: 1, duration: 7, tagIds: [1], image: '', toDoList: [] }; component.habitForm.setValue(habitFormValue); habitServiceMock.changeCustomHabit = jasmine.createSpy('changeCustomHabit').and.returnValue(of(null)); spyOn(component, 'goToAllHabits'); diff --git a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.ts b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.ts index 331092a0b6..93a34dd9a8 100644 --- a/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.ts +++ b/src/app/main/component/user/components/habit/add-edit-custom-habit/add-edit-custom-habit.component.ts @@ -14,7 +14,7 @@ import ImageResize from 'quill-image-resize-module'; import { HabitService } from '@global-service/habit/habit.service'; import { TagInterface } from '@shared/components/tag-filter/tag-filter.model'; import { quillConfig } from 'src/app/main/component/events/components/event-editor/quillEditorFunc'; -import { ShoppingList } from '@global-user/models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { FileHandle } from '@eco-news-models/create-news-interface'; import { UserFriendsService } from '@global-user/services/user-friends.service'; import { TodoStatus } from '../models/todo-status.enum'; @@ -35,8 +35,8 @@ export class AddEditCustomHabitComponent extends FormBaseComponent implements On habitImages = HABIT_IMAGES; stars = STAR_IMAGES; initialDuration = HABIT_DEFAULT_DURATION; - shopList: ShoppingList[] = []; - newList: ShoppingList[] = []; + toDoList: ToDoList[] = []; + newList: ToDoList[] = []; tagsList: TagInterface[]; tagMaxLength = HABIT_TAGS_MAXLENGTH; selectedTagsList: number[]; @@ -151,13 +151,13 @@ export class AddEditCustomHabitComponent extends FormBaseComponent implements On return value <= complexity ? this.stars.GREEN : this.stars.WHITE; } - getShopList(list: ShoppingList[]): void { + getToDoList(list: ToDoList[]): void { this.newList = list.map((item) => ({ id: item.id, status: item.status, text: item.text })); - this.habitForm.get('shopList').setValue(this.newList); + this.habitForm.get('toDoList').setValue(this.newList); } getTagsList(list: TagInterface[]): void { @@ -217,7 +217,7 @@ export class AddEditCustomHabitComponent extends FormBaseComponent implements On duration: new FormControl(this.initialDuration, [Validators.required, Validators.min(7), Validators.max(56)]), tagIds: new FormControl([], Validators.required), image: new FormControl(''), - shopList: new FormControl([]) + toDoList: new FormControl([]) }); } @@ -230,13 +230,13 @@ export class AddEditCustomHabitComponent extends FormBaseComponent implements On duration: this.habit.defaultDuration, tagIds: this.habit.tags, image: this.habit.image, - shopList: this.habit.customShoppingListItems + toDoList: this.habit.customToDoListItems }); this.habitId = this.habit.id; - this.shopList = this.habit.customShoppingListItems?.length - ? [...(this.habit.customShoppingListItems || [])] - : [...(this.habit.customShoppingListItems || []), ...this.habit.shoppingListItems]; - this.shopList = this.shopList.map((el) => ({ ...el, selected: el.status === TodoStatus.inprogress })); + this.toDoList = this.habit.customToDoListItems?.length + ? [...(this.habit.customToDoListItems || [])] + : [...(this.habit.customToDoListItems || []), ...this.habit.ToDoListItems]; + this.toDoList = this.toDoList.map((el) => ({ ...el, selected: el.status === TodoStatus.inprogress })); } private subscribeToLangChange(): void { diff --git a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.html b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.html index 2bedd7542c..f0437cb8e0 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.html +++ b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.html @@ -46,7 +46,7 @@

{{ 'user.habit.info' | translate }}

>
-
+
{{ 'user.habit.info' | translate }}
-
- + + >
diff --git a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.scss b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.scss index e21e28345a..5e08e97963 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.scss +++ b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.scss @@ -196,14 +196,14 @@ } } - .duration-shoping { + .duration-to-do { display: flex; width: 100%; justify-content: space-between; gap: 32px; .duration, - .shopping { + .to-do { width: calc(50% - 16px); background: var(--secondary-light-grey); border-radius: 8px; @@ -402,13 +402,13 @@ padding: 0; gap: 24px; - .duration-shoping { + .duration-to-do { flex-direction: column; height: auto; gap: 24px; .duration, - .shopping { + .to-do { width: 100%; } } diff --git a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.spec.ts b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.spec.ts index 6ba397933e..599717e5ca 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.spec.ts +++ b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.spec.ts @@ -6,7 +6,7 @@ import { AddNewHabitComponent } from './add-new-habit.component'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; -import { ShoppingListService } from './habit-edit-shopping-list/shopping-list.service'; +import { ToDoListService } from './habit-edit-to-do-list/to-do-list.service'; import { HabitService } from '@global-service/habit/habit.service'; import { HabitAssignService } from '@global-service/habit-assign/habit-assign.service'; import { of, Subject } from 'rxjs'; @@ -80,14 +80,14 @@ describe('AddNewHabitComponent', () => { const matSnackBarMock: MatSnackBarComponent = jasmine.createSpyObj('MatSnackBarComponent', ['openSnackBar']); - const fakeShoppingListService: ShoppingListService = jasmine.createSpyObj('fakeShoppingListService', [ - 'getHabitAllShopLists', - 'getHabitShopList', - 'updateHabitShopList' + const fakeToDoListService: ToDoListService = jasmine.createSpyObj('fakeToDoListService', [ + 'getHabitAllToDoLists', + 'getHabitToDoList', + 'updateHabitToDoList' ]); - fakeShoppingListService.getHabitAllShopLists = () => of(); - fakeShoppingListService.getHabitShopList = () => of(); - fakeShoppingListService.updateHabitShopList = () => of(); + fakeToDoListService.getHabitAllToDoLists = () => of(); + fakeToDoListService.getHabitToDoList = () => of(); + fakeToDoListService.updateHabitToDoList = () => of(); matSnackBarMock.openSnackBar = (type: string) => type; @@ -115,7 +115,7 @@ describe('AddNewHabitComponent', () => { { provide: HabitService, useValue: fakeHabitService }, { provide: HabitAssignService, useValue: fakeHabitAssignService }, { provide: EcoNewsService, useValue: ecoNewsServiceMock }, - { provide: ShoppingListService, useValue: fakeShoppingListService }, + { provide: ToDoListService, useValue: fakeToDoListService }, { provide: LocalStorageService, useValue: fakeLocalStorageService }, { provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: Location, useValue: locationMock }, @@ -144,8 +144,8 @@ describe('AddNewHabitComponent', () => { it('should navigate back on onGoBack without call dialog', () => { component.initialDuration = 1; component.newDuration = 1; - component.standardShopList = null; - component.customShopList = null; + component.standardToDoList = null; + component.customToDoList = null; const spy = spyOn(locationMock, 'back'); component.onGoBack(); expect(spy).toHaveBeenCalled(); @@ -169,22 +169,22 @@ describe('AddNewHabitComponent', () => { expect(component.canAcquire).toBeTruthy(); }); - it('should set standardShopList', () => { - component.getList(DEFAULTFULLINFOHABIT.shoppingListItems); - expect(component.standardShopList).toEqual([{ id: 6, status: TodoStatus.active, text: 'TEST' }]); - expect(component.customShopList).toEqual([]); + it('should set standardToDoList', () => { + component.getList(DEFAULTFULLINFOHABIT.toDoListItems); + expect(component.standardToDoList).toEqual([{ id: 6, status: TodoStatus.active, text: 'TEST' }]); + expect(component.customToDoList).toEqual([]); }); - it('should set and customShopList', () => { - component.getList(CUSTOMFULLINFOHABIT.shoppingListItems); - expect(component.customShopList).toEqual([{ id: 6, status: TodoStatus.active, text: 'TEST', custom: true }]); - expect(component.standardShopList).toEqual([]); + it('should set and customToDoList', () => { + component.getList(CUSTOMFULLINFOHABIT.toDoListItems); + expect(component.customToDoList).toEqual([{ id: 6, status: TodoStatus.active, text: 'TEST', custom: true }]); + expect(component.standardToDoList).toEqual([]); }); - it('should set standardShopList and customShopList', () => { - const shoppingListItems = DEFAULTFULLINFOHABIT.shoppingListItems.concat(CUSTOMFULLINFOHABIT.shoppingListItems); - component.getList(shoppingListItems); - expect(component.customShopList).toEqual(shoppingListItems.filter((item) => item.custom)); - expect(component.standardShopList).toEqual(shoppingListItems.filter((item) => !item.custom)); + it('should set standardToDoList and customToDoList', () => { + const toDoListItems = DEFAULTFULLINFOHABIT.toDoListItems.concat(CUSTOMFULLINFOHABIT.toDoListItems); + component.getList(toDoListItems); + expect(component.customToDoList).toEqual(toDoListItems.filter((item) => item.custom)); + expect(component.standardToDoList).toEqual(toDoListItems.filter((item) => !item.custom)); }); }); diff --git a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.ts b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.ts index 73c597b027..9442a5cf1b 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.ts +++ b/src/app/main/component/user/components/habit/add-new-habit/add-new-habit.component.ts @@ -7,7 +7,7 @@ import { HabitService } from '@global-service/habit/habit.service'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { TranslateService } from '@ngx-translate/core'; import { Subject } from 'rxjs'; -import { ShoppingListService } from './habit-edit-shopping-list/shopping-list.service'; +import { ToDoListService } from './habit-edit-to-do-list/to-do-list.service'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { WarningPopUpComponent } from '@shared/components'; import { Location } from '@angular/common'; @@ -20,7 +20,7 @@ import { HabitAcquireConfirm, HabitCongratulation, HabitGiveUp, HabitLeavePage } import { WarningDialog } from '@global-user/models/warning-dialog.inteface'; import { HabitAssignInterface } from '../models/interfaces/habit-assign.interface'; import { HabitInterface, HabitListInterface } from '../models/interfaces/habit.interface'; -import { AllShoppingLists, HabitUpdateShopList, ShoppingList } from '@user-models/shoppinglist.interface'; +import { AllToDoLists, HabitUpdateToDoList, ToDoList } from '@global-user/models/to-do-list.interface'; import { UserFriendsService } from '@global-user/services/user-friends.service'; import { HabitAssignCustomPropertiesDto, HabitAssignPropertiesDto } from '@global-models/goal/HabitAssignCustomPropertiesDto'; import { singleNewsImages } from 'src/app/main/image-pathes/single-news-images'; @@ -37,9 +37,9 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { habitResponse: HabitInterface; recommendedHabits: HabitInterface[]; recommendedNews: EcoNewsModel[]; - initialShoppingList: ShoppingList[] = []; - standardShopList: ShoppingList[] = []; - customShopList: ShoppingList[] = []; + initialToDoList: ToDoList[] = []; + standardToDoList: ToDoList[] = []; + customToDoList: ToDoList[] = []; friendsIdsList: number[] = []; newDuration = 7; initialDuration: number; @@ -73,7 +73,7 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { private snackBar: MatSnackBarComponent, private habitAssignService: HabitAssignService, private newsService: EcoNewsService, - private shopListService: ShoppingListService, + private toDoListService: ToDoListService, private localStorageService: LocalStorageService, private translate: TranslateService, private location: Location, @@ -124,7 +124,7 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { this.isAcquired = this.assignedHabit.status === HabitStatus.ACQUIRED; this.initialDuration = res.duration || res.habit.defaultDuration; this.initHabitData(res.habit, res.duration || res.habit.defaultDuration); - this.getCustomShopList(); + this.getCustomToDoList(); }); } else { this.getDefaultHabit(); @@ -169,10 +169,10 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { this.initHabitData(data); this.isCustomHabit = data.isCustomHabit; if (data.isCustomHabit) { - data.customShoppingListItems?.forEach((item) => (item.custom = true)); - this.initialShoppingList = data.customShoppingListItems; + data.customToDoListItems?.forEach((item) => (item.custom = true)); + this.initialToDoList = data.customToDoListItems; } else { - this.getStandardShopList(); + this.getStandardToDoList(); } }); } @@ -196,7 +196,7 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { } onGoBack(): void { - const isHabitWasEdited = this.initialDuration !== this.newDuration || this.standardShopList || this.customShopList; + const isHabitWasEdited = this.initialDuration !== this.newDuration || this.standardToDoList || this.customToDoList; if (isHabitWasEdited) { const dialogRef = this.getOpenDialog(HabitLeavePage, false); if (!dialogRef) { @@ -230,28 +230,28 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { } } - getList(list: ShoppingList[]): void { - this.standardShopList = list.filter((item) => !item.custom); - this.customShopList = list.filter((item) => item.custom); + getList(list: ToDoList[]): void { + this.standardToDoList = list.filter((item) => !item.custom); + this.customToDoList = list.filter((item) => item.custom); } - private getStandardShopList(): void { - this.shopListService - .getHabitShopList(this.habitId) + private getStandardToDoList(): void { + this.toDoListService + .getHabitToDoList(this.habitId) .pipe(take(1)) .subscribe((res) => { - this.initialShoppingList = res; + this.initialToDoList = res; }); } - private getCustomShopList(): void { - this.shopListService - .getHabitAllShopLists(this.habitAssignId, this.currentLang) + private getCustomToDoList(): void { + this.toDoListService + .getHabitAllToDoLists(this.habitAssignId, this.currentLang) .pipe(take(1)) - .subscribe((res: AllShoppingLists) => { - res.customShoppingListItemDto?.forEach((item) => (item.custom = true)); - this.initialShoppingList = [...res.customShoppingListItemDto, ...res.userShoppingListItemDto]; - this.getList(this.initialShoppingList); + .subscribe((res: AllToDoLists) => { + res.customToDoListItemDto?.forEach((item) => (item.custom = true)); + this.initialToDoList = [...res.customToDoListItemDto, ...res.userToDoListItemDto]; + this.getList(this.initialToDoList); }); } @@ -289,14 +289,14 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { assignCustomHabit(): void { this.friendsIdsList = this.userFriendsService.addedFriends?.map((friend) => friend.id); const habitAssignPropertiesDto: HabitAssignPropertiesDto = { - defaultShoppingListItems: this.standardShopList.filter((item) => item.selected === true).map((item) => item.id), + defaultToDoListItems: this.standardToDoList.filter((item) => item.selected === true).map((item) => item.id), duration: this.newDuration, isPrivate: this.isPrivate }; const body: HabitAssignCustomPropertiesDto = { friendsIdsList: this.friendsIdsList, habitAssignPropertiesDto, - customShoppingListItemList: this.customShopList.map((item) => ({ text: item.text })) + customToDoListItemList: this.customToDoList.map((item) => ({ text: item.text })) }; this.habitAssignService @@ -312,9 +312,9 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { .updateHabitDuration(this.habitAssignId, this.newDuration) .pipe(take(1)) .subscribe(() => { - if (this.customShopList || this.standardShopList) { - this.convertShopLists(); - this.shopListService.updateHabitShopList(this.setHabitListForUpdate()); + if (this.customToDoList || this.standardToDoList) { + this.convertToDoLists(); + this.toDoListService.updateHabitToDoList(this.setHabitListForUpdate()); } this.afterHabitWasChanged('habitUpdated'); }); @@ -334,12 +334,12 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { }); } - private convertShopLists(): void { - this.customShopList?.forEach((el) => { + private convertToDoLists(): void { + this.customToDoList?.forEach((el) => { delete el.custom; delete el.selected; }); - this.standardShopList?.forEach((el) => { + this.standardToDoList?.forEach((el) => { delete el.custom; delete el.selected; }); @@ -350,11 +350,11 @@ export class AddNewHabitComponent implements OnInit, OnDestroy { this.snackBar.openSnackBar(kindOfChanges); } - private setHabitListForUpdate(): HabitUpdateShopList { + private setHabitListForUpdate(): HabitUpdateToDoList { return { habitAssignId: this.habitAssignId, - customShopList: this.customShopList, - standardShopList: this.standardShopList, + customToDoList: this.customToDoList, + standardToDoList: this.standardToDoList, lang: this.currentLang }; } diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.spec.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.spec.ts deleted file mode 100644 index 4f073b5e63..0000000000 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; - -import { ShoppingListService } from './shopping-list.service'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { environment } from '@environment/environment'; -import { - ALLUSERSHOPLISTS, - CUSTOMSHOPITEM, - SHOPLIST, - SHOPLISTITEMONE, - SHOPLISTITEMTWO, - UPDATEHABITSHOPLIST -} from '../../mocks/shopping-list-mock'; - -describe('ShoppingListService', () => { - let service: ShoppingListService; - let httpMock: HttpTestingController; - - const mainLink = environment.backendLink; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, TranslateModule.forRoot()], - providers: [ShoppingListService, TranslateService] - }); - - service = TestBed.inject(ShoppingListService); - httpMock = TestBed.inject(HttpTestingController); - }); - - afterEach(() => { - httpMock.verify(); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should return allShopList by habitId on getHabitAllShopLists', () => { - service.getHabitAllShopLists(2, 'en').subscribe((data) => { - expect(data).toEqual(ALLUSERSHOPLISTS); - }); - - const req = httpMock.expectOne(`${mainLink}habit/assign/2/allUserAndCustomList?lang=en`); - expect(req.request.responseType).toEqual('json'); - expect(req.request.method).toBe('GET'); - req.flush(ALLUSERSHOPLISTS); - }); - - it('should return all user shopList by lang on getUserShoppingLists', () => { - service.getUserShoppingLists('ua').subscribe((data) => { - expect(data).toEqual([ALLUSERSHOPLISTS]); - }); - - const req = httpMock.expectOne(`${mainLink}habit/assign/allUserAndCustomShoppingListsInprogress?lang=ua`); - expect(req.request.responseType).toEqual('json'); - expect(req.request.method).toBe('GET'); - req.flush([ALLUSERSHOPLISTS]); - }); - - it('should update Standard Shop Item Status', () => { - service.updateStandardShopItemStatus(SHOPLISTITEMTWO, 'ua').subscribe((data) => { - expect(data).toEqual([SHOPLISTITEMTWO]); - }); - const req = httpMock.expectOne(`${mainLink}user/shopping-list-items/2/status/INPROGRESS?lang=ua`); - expect(req.request.method).toBe('PATCH'); - req.flush([SHOPLISTITEMTWO]); - }); - - it('should update Custom Shop Item Status', () => { - service.updateCustomShopItemStatus(1, SHOPLISTITEMTWO).subscribe((data) => { - expect(data).toEqual(SHOPLISTITEMTWO); - }); - const req = httpMock.expectOne(`${mainLink}custom/shopping-list-items/1/custom-shopping-list-items?itemId=2&status=INPROGRESS`); - expect(req.request.method).toBe('PATCH'); - req.flush(SHOPLISTITEMTWO); - }); - - it('should update Habit Shop List', () => { - service.updateHabitShopList(UPDATEHABITSHOPLIST).subscribe((data) => { - expect(data).toEqual(null); - }); - const req = httpMock.expectOne(`${mainLink}habit/assign/2/allUserAndCustomList?lang=ua`); - expect(req.request.method).toBe('PUT'); - req.flush(null); - }); -}); diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.ts deleted file mode 100644 index 119936cd04..0000000000 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; -import { mainLink } from 'src/app/main/links'; -import { AllShoppingLists, HabitUpdateShopList, ShoppingList } from '@user-models/shoppinglist.interface'; - -@Injectable({ - providedIn: 'root' -}) -export class ShoppingListService { - constructor(private http: HttpClient) {} - - getHabitShopList(habitId: number): Observable { - return this.http.get(`${mainLink}habit/${habitId}/shopping-list`); - } - - getHabitAllShopLists(habitAssignId: number, lang: string): Observable { - return this.http.get(`${mainLink}habit/assign/${habitAssignId}/allUserAndCustomList?lang=${lang}`); - } - - getUserShoppingLists(lang: string): Observable { - return this.http.get(`${mainLink}habit/assign/allUserAndCustomShoppingListsInprogress?lang=${lang}`); - } - - updateStandardShopItemStatus(item: ShoppingList, lang: string): Observable { - const body = {}; - return this.http.patch(`${mainLink}user/shopping-list-items/${item.id}/status/${item.status}?lang=${lang}`, body); - } - - updateCustomShopItemStatus(userId: number, item: ShoppingList): Observable { - const body = {}; - return this.http.patch( - `${mainLink}custom/shopping-list-items/${userId}/custom-shopping-list-items?itemId=${item.id}&status=${item.status}`, - body - ); - } - - updateHabitShopList(habitShopList: HabitUpdateShopList) { - const assignId = habitShopList.habitAssignId; - const body = { - customShoppingListItemDto: habitShopList.customShopList, - userShoppingListItemDto: habitShopList.standardShopList - }; - return this.http.put(`${mainLink}habit/assign/${assignId}/allUserAndCustomList?lang=${habitShopList.lang}`, body); - } -} diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.html b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.html similarity index 85% rename from src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.html rename to src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.html index cd39734542..62ae3bef0b 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.html +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.html @@ -1,10 +1,10 @@ -
+
{{ 'user.habit.to-do.to-do-list' | translate }}
-
-
    -
  • +
    +
      +
    -
    - {{ 'user.habit.to-do.no-shopping-items' | translate }} +
    + {{ 'user.habit.to-do.no-to-do-items' | translate }}
    diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.scss b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.scss similarity index 97% rename from src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.scss rename to src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.scss index dcc5be3e71..988d0769e3 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.scss +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.scss @@ -1,4 +1,4 @@ -.shop-list-container { +.to-do-list-container { position: relative; display: flex; flex-direction: column; @@ -40,7 +40,7 @@ flex-direction: column; height: 100%; - .shop-list { + .to-do-list { display: flex; flex-direction: column; height: 190px; @@ -83,7 +83,7 @@ } } - .shop-list-edit-mode { + .to-do-list-edit-mode { height: 112px; } @@ -183,7 +183,7 @@ form { } @media (max-width: 560px) { - .shop-list-container .title { + .to-do-list-container .title { font-size: 16px; margin-bottom: 20px; } diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.spec.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.spec.ts similarity index 68% rename from src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.spec.ts rename to src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.spec.ts index 5b958838b0..161a9a45b7 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.spec.ts +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.spec.ts @@ -2,27 +2,27 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { HabitService } from '@global-service/habit/habit.service'; -import { ShoppingList } from '@global-user/models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { TranslateModule } from '@ngx-translate/core'; import { of } from 'rxjs'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { TodoStatus } from '../../models/todo-status.enum'; -import { HabitEditShoppingListComponent } from './habit-edit-shopping-list.component'; -import { ShoppingListService } from './shopping-list.service'; +import { HabitEditToDoListComponent } from './habit-edit-to-do-list.component'; +import { ToDoListService } from './to-do-list.service'; import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { SimpleChange, SimpleChanges } from '@angular/core'; -describe('HabitEditShoppingListComponent', () => { - let component: HabitEditShoppingListComponent; - let fixture: ComponentFixture; +describe('HabitEditToDoListComponent', () => { + let component: HabitEditToDoListComponent; + let fixture: ComponentFixture; let snackBar: MatSnackBar; const mockActivatedRoute = { params: of({ habitId: 2 }) }; - const mockList: ShoppingList[] = [ + const mockList: ToDoList[] = [ { id: 1, status: TodoStatus.active, @@ -36,7 +36,7 @@ describe('HabitEditShoppingListComponent', () => { selected: false } ]; - const mockItem: ShoppingList = { + const mockItem: ToDoList = { id: 234, status: TodoStatus.active, text: 'Item 2', @@ -51,10 +51,10 @@ describe('HabitEditShoppingListComponent', () => { const matDialogRefMock = jasmine.createSpyObj(['open', 'afterClosed']); matDialogRefMock.open.and.returnValue({ afterClosed: () => of(true) }); TestBed.configureTestingModule({ - declarations: [HabitEditShoppingListComponent], + declarations: [HabitEditToDoListComponent], imports: [HttpClientTestingModule, TranslateModule.forRoot(), MatSnackBarModule], providers: [ - ShoppingListService, + ToDoListService, HabitService, MatSnackBar, { provide: ActivatedRoute, useValue: mockActivatedRoute }, @@ -64,18 +64,18 @@ describe('HabitEditShoppingListComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(HabitEditShoppingListComponent); + fixture = TestBed.createComponent(HabitEditToDoListComponent); component = fixture.componentInstance; snackBar = TestBed.inject(MatSnackBar); fixture.detectChanges(); - component.shopList = []; + component.toDoList = []; }); it('should create', () => { expect(component).toBeTruthy(); }); - it('should add item to shop list on additem', () => { + it('should add item to toDo list on additem', () => { const newList = [ { id: null, @@ -85,9 +85,9 @@ describe('HabitEditShoppingListComponent', () => { selected: true } ]; - component.shopList = []; + component.toDoList = []; component.addItem('test'); - expect(component.shopList).toEqual(newList); + expect(component.toDoList).toEqual(newList); }); it('should setValue empty string on add item', () => { @@ -98,49 +98,49 @@ describe('HabitEditShoppingListComponent', () => { it('should return disableCheck if isAcquired is true', () => { component.isAcquired = true; - const result = component.getCheckIcon({} as ShoppingList); + const result = component.getCheckIcon({} as ToDoList); expect(result).toBe(component.img.disableCheck); }); it('should return doneCheck if item status is done', () => { - const item: ShoppingList = { status: TodoStatus.done, text: mockText1, id: null }; + const item: ToDoList = { status: TodoStatus.done, text: mockText1, id: null }; const result = component.getCheckIcon(item); expect(result).toBe(component.img.doneCheck); }); it('should return minusCheck if item is selected', () => { - const item: ShoppingList = { selected: true, status: TodoStatus.inprogress, text: mockText2, id: null }; + const item: ToDoList = { selected: true, status: TodoStatus.inprogress, text: mockText2, id: null }; const result = component.getCheckIcon(item); expect(result).toBe(component.img.minusCheck); }); it('should return plusCheck if item is not selected and status is not done', () => { - const item: ShoppingList = { selected: false, status: TodoStatus.inprogress, text: mockText3, id: null }; + const item: ToDoList = { selected: false, status: TodoStatus.inprogress, text: mockText3, id: null }; const result = component.getCheckIcon(item); expect(result).toBe(component.img.plusCheck); }); it('should select item and change status to in progress if selected', () => { - const item: ShoppingList = mockList[0]; - component.shopList = [item]; + const item: ToDoList = mockList[0]; + component.toDoList = [item]; component.selectItem(item); - expect(component.shopList[0].selected).toBe(true); - expect(component.shopList[0].status).toBe(TodoStatus.inprogress); + expect(component.toDoList[0].selected).toBe(true); + expect(component.toDoList[0].status).toBe(TodoStatus.inprogress); }); it('should deselect item and change status to active if not selected', () => { - const item: ShoppingList = { id: null, status: TodoStatus.inprogress, text: 'item1', selected: true }; - component.shopList = [item]; + const item: ToDoList = { id: null, status: TodoStatus.inprogress, text: 'item1', selected: true }; + component.toDoList = [item]; component.selectItem(item); - expect(component.shopList[0].selected).toBe(false); - expect(component.shopList[0].status).toBe(TodoStatus.active); + expect(component.toDoList[0].selected).toBe(false); + expect(component.toDoList[0].status).toBe(TodoStatus.active); }); it('should move selected item to the top of the list', () => { - component.shopList = [...mockList]; + component.toDoList = [...mockList]; component.selectItem(mockList[1]); - expect(component.shopList[0]).toBe(mockList[1]); - expect(component.shopList[1]).toBe(mockList[0]); + expect(component.toDoList[0]).toBe(mockList[1]); + expect(component.toDoList[1]).toBe(mockList[0]); }); it('should not open snackbar if form is valid', () => { diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.ts similarity index 73% rename from src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.ts rename to src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.ts index 5ec4740775..a39788bf2d 100644 --- a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component.ts +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component.ts @@ -3,22 +3,22 @@ import { Subject, Subscription } from 'rxjs'; import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { TranslateService } from '@ngx-translate/core'; -import { ShoppingList } from 'src/app/main/component/user/models/shoppinglist.interface'; +import { ToDoList } from 'src/app/main/component/user/models/to-do-list.interface'; import { TodoStatus } from '../../models/todo-status.enum'; import { MatSnackBarComponent } from '@global-errors/mat-snack-bar/mat-snack-bar.component'; -import { FIELD_SYMBOLS_LIMIT, HABIT_SHOPPING_LIST_CHECK, SHOPPING_ITEM_NAME_LIMIT } from '../../const/data.const'; +import { FIELD_SYMBOLS_LIMIT, HABIT_TO_DO_LIST_CHECK, TO_DO_ITEM_NAME_LIMIT } from '../../const/data.const'; import { MatDialog } from '@angular/material/dialog'; import { WarningPopUpComponent } from '@shared/components'; import { take } from 'rxjs/operators'; @Component({ - selector: 'app-habit-edit-shopping-list', - templateUrl: './habit-edit-shopping-list.component.html', - styleUrls: ['./habit-edit-shopping-list.component.scss'], + selector: 'app-habit-edit-to-do-list', + templateUrl: './habit-edit-to-do-list.component.html', + styleUrls: ['./habit-edit-to-do-list.component.scss'], providers: [MatSnackBarComponent] }) -export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDestroy { - @Input() shopList: ShoppingList[] = []; +export class HabitEditToDoListComponent implements OnInit, OnChanges, OnDestroy { + @Input() toDoList: ToDoList[] = []; @Input() isAcquired = false; private fieldSymbolsLimit = FIELD_SYMBOLS_LIMIT; @@ -29,10 +29,10 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest userId: number; private destroySub: Subject = new Subject(); private langChangeSub: Subscription; - shoppingItemNameLimit = SHOPPING_ITEM_NAME_LIMIT; + toDoItemNameLimit = TO_DO_ITEM_NAME_LIMIT; todoStatus = TodoStatus; isEditMode = false; - private shopListBeforeEditing: ShoppingList[] = []; + private toDoListBeforeEditing: ToDoList[] = []; isListChanged: boolean; private confirmDialogConfig = { hasBackdrop: true, @@ -48,9 +48,9 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest private deleteItemTitle = `user.habit.to-do.item-delete-pop-up-title`; private cancelEditingTitle = `user.habit.to-do.cancel-pop-up-title`; - img = HABIT_SHOPPING_LIST_CHECK; + img = HABIT_TO_DO_LIST_CHECK; - @Output() newList = new EventEmitter(); + @Output() newList = new EventEmitter(); constructor( private snackBar: MatSnackBarComponent, @@ -62,12 +62,12 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest ngOnInit() { this.subscribeToLangChange(); this.userId = this.localStorageService.getUserId(); - this.newList.emit(this.shopList); + this.newList.emit(this.toDoList); } ngOnChanges(changes: SimpleChanges): void { - if (changes.shopList) { - this.shopList.forEach((el) => (el.selected = el.status === TodoStatus.inprogress || el.status === TodoStatus.done)); + if (changes.toDoList) { + this.toDoList.forEach((el) => (el.selected = el.status === TodoStatus.inprogress || el.status === TodoStatus.done)); this.placeItemInOrder(); } } @@ -86,7 +86,7 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest }); } - getCheckIcon(item: ShoppingList): string { + getCheckIcon(item: ToDoList): string { if (this.isAcquired) { return this.img.disableCheck; } @@ -106,34 +106,33 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest selected: true }; if (newItem.text) { - this.shopList = [newItem, ...this.shopList]; + this.toDoList = [newItem, ...this.toDoList]; } this.item.setValue(''); this.placeItemInOrder(); - this.newList.emit(this.shopList); + this.newList.emit(this.toDoList); } - selectItem(item: ShoppingList): void { + selectItem(item: ToDoList): void { this.isListChanged = true; - this.shopList.map((element) => { + this.toDoList.forEach((element) => { if (element.text === item.text) { element.selected = !item.selected; element.status = item.selected ? TodoStatus.inprogress : TodoStatus.active; } - return element; }); if (item.selected) { - const index = this.shopList.indexOf(item); - this.shopList.splice(index, 1); - this.shopList = [item, ...this.shopList]; + const index = this.toDoList.indexOf(item); + this.toDoList.splice(index, 1); + this.toDoList = [item, ...this.toDoList]; } this.placeItemInOrder(); - this.newList.emit(this.shopList); + this.newList.emit(this.toDoList); } private placeItemInOrder(): void { const statusOrder = { DONE: 1, INPROGRESS: 2, ACTIVE: 3 }; - this.shopList.sort((a, b) => { + this.toDoList.sort((a, b) => { const statusDifference = statusOrder[a.status] - statusOrder[b.status]; const orderCustom = a.custom && !b.custom ? -1 : 1; return statusDifference || orderCustom; @@ -149,8 +148,8 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest .pipe(take(1)) .subscribe((confirm) => { if (confirm) { - this.shopList = this.shopList.filter((elem) => elem.text !== text); - this.newList.emit(this.shopList); + this.toDoList = this.toDoList.filter((elem) => elem.text !== text); + this.newList.emit(this.toDoList); } }); } @@ -164,18 +163,18 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest changeEditMode(): void { if (!this.isEditMode) { this.isListChanged = false; - this.shopListBeforeEditing = []; - this.shopList.forEach((el) => this.shopListBeforeEditing.push({ ...el })); + this.toDoListBeforeEditing = []; + this.toDoList.forEach((el) => this.toDoListBeforeEditing.push({ ...el })); } this.isEditMode = !this.isEditMode; } private isListItemsChanged(): boolean { - const isItemsChanged = !this.shopList.every((el) => { - const itemBeforeEditing = this.shopListBeforeEditing.find((item) => item.id === el.id); + const isItemsChanged = !this.toDoList.every((el) => { + const itemBeforeEditing = this.toDoListBeforeEditing.find((item) => item.id === el.id); return itemBeforeEditing && Object.keys(el).every((key) => el[key] === itemBeforeEditing[key]); }); - const isLengthChanged = this.shopList.length !== this.shopListBeforeEditing.length; + const isLengthChanged = this.toDoList.length !== this.toDoListBeforeEditing.length; return isItemsChanged || isLengthChanged; } @@ -188,11 +187,11 @@ export class HabitEditShoppingListComponent implements OnInit, OnChanges, OnDest .pipe(take(1)) .subscribe((confirm) => { if (confirm) { - this.shopList = []; - this.shopListBeforeEditing.forEach((el) => { - this.shopList.push(el); + this.toDoList = []; + this.toDoListBeforeEditing.forEach((el) => { + this.toDoList.push(el); }); - this.newList.emit(this.shopList); + this.newList.emit(this.toDoList); this.isEditMode = false; } }); diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.spec.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.spec.ts new file mode 100644 index 0000000000..7bccc38b57 --- /dev/null +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.spec.ts @@ -0,0 +1,88 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ToDoListService } from './to-do-list.service'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { environment } from '@environment/environment'; +import { + ALLUSERTODOLISTS, + CUSTOMTODOITEM, + TODOLIST, + TODOLISTITEMONE, + TODOLISTITEMTWO, + UPDATEHABITTODOLIST +} from '../../mocks/to-do-list-mock'; + +describe('ToDoListService', () => { + let service: ToDoListService; + let httpMock: HttpTestingController; + + const mainLink = environment.backendLink; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, TranslateModule.forRoot()], + providers: [ToDoListService, TranslateService] + }); + + service = TestBed.inject(ToDoListService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should return allToDoList by habitId on getHabitAllToDoLists', () => { + service.getHabitAllToDoLists(2, 'en').subscribe((data) => { + expect(data).toEqual(ALLUSERTODOLISTS); + }); + + const req = httpMock.expectOne(`${mainLink}habit/assign/2/allUserAndCustomList?lang=en`); + expect(req.request.responseType).toEqual('json'); + expect(req.request.method).toBe('GET'); + req.flush(ALLUSERTODOLISTS); + }); + + it('should return all user toDoList by lang on getUserToDoLists', () => { + service.getUserToDoLists('ua').subscribe((data) => { + expect(data).toEqual([ALLUSERTODOLISTS]); + }); + + const req = httpMock.expectOne(`${mainLink}habit/assign/allUserAndCustomToDoListsInprogress?lang=ua`); + expect(req.request.responseType).toEqual('json'); + expect(req.request.method).toBe('GET'); + req.flush([ALLUSERTODOLISTS]); + }); + + it('should update Standard ToDo Item Status', () => { + service.updateStandardToDoItemStatus(TODOLISTITEMTWO, 'ua').subscribe((data) => { + expect(data).toEqual([TODOLISTITEMTWO]); + }); + const req = httpMock.expectOne(`${mainLink}user/to-do-list-items/2/status/INPROGRESS?lang=ua`); + expect(req.request.method).toBe('PATCH'); + req.flush([TODOLISTITEMTWO]); + }); + + xit('should update Custom ToDo Item Status', () => { + service.updateCustomToDoItemStatus(1, TODOLISTITEMTWO).subscribe((data) => { + expect(data).toEqual(TODOLISTITEMTWO); + }); + const req = httpMock.expectOne(`${mainLink}custom/to-do-list-items/1/custom-to-do-list-items?itemId=2&status=INPROGRESS`); + expect(req.request.method).toBe('PATCH'); + req.flush(TODOLISTITEMTWO); + }); + + it('should update Habit ToDo List', () => { + service.updateHabitToDoList(UPDATEHABITTODOLIST).subscribe((data) => { + expect(data).toEqual(null); + }); + const req = httpMock.expectOne(`${mainLink}habit/assign/2/allUserAndCustomList?lang=ua`); + expect(req.request.method).toBe('PUT'); + req.flush(null); + }); +}); diff --git a/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.ts b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.ts new file mode 100644 index 0000000000..5ff0618901 --- /dev/null +++ b/src/app/main/component/user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { mainLink } from '../../../../../../links'; +import { AllToDoLists, HabitUpdateToDoList, ToDoList } from '@global-user/models/to-do-list.interface'; + +@Injectable({ + providedIn: 'root' +}) +export class ToDoListService { + constructor(private http: HttpClient) {} + + getHabitToDoList(habitId: number): Observable { + return this.http.get(`${mainLink}habit/${habitId}/to-do-list`); + } + + getHabitAllToDoLists(habitAssignId: number, lang: string): Observable { + return this.http.get(`${mainLink}habit/assign/${habitAssignId}/allUserAndCustomList?lang=${lang}`); + } + + // + getUserToDoLists(lang: string): Observable { + return this.http.get(`${mainLink}habit/assign/allUserAndCustomToDoListsInprogress?lang=${lang}`); + } + + updateStandardToDoItemStatus(item: ToDoList, lang: string): Observable { + const body = {}; + return this.http.patch(`${mainLink}user/to-do-list-items/${item.id}/status/${item.status}?lang=${lang}`, body); + } + + // + updateCustomToDoItemStatus(userId: number, item: ToDoList): Observable { + const body = {}; + return this.http.patch( + `${mainLink}custom/to-do-list-items/${userId}/custom-to-do-ping-list-items?itemId=${item.id}&status=${item.status}`, + body + ); + } + + updateHabitToDoList(habitToDoList: HabitUpdateToDoList) { + const assignId = habitToDoList.habitAssignId; + const body = { + customToDoListItemDto: habitToDoList.customToDoList, + userToDoListItemDto: habitToDoList.standardToDoList + }; + return this.http.put(`${mainLink}habit/assign/${assignId}/allUserAndCustomList?lang=${habitToDoList.lang}`, body); + } +} diff --git a/src/app/main/component/user/components/habit/all-habits/all-habits.component.spec.ts b/src/app/main/component/user/components/habit/all-habits/all-habits.component.spec.ts index 6c8b0150c9..88651cf757 100644 --- a/src/app/main/component/user/components/habit/all-habits/all-habits.component.spec.ts +++ b/src/app/main/component/user/components/habit/all-habits/all-habits.component.spec.ts @@ -84,7 +84,7 @@ describe('AllHabitsComponent', () => { rating: null, showEcoPlace: true, showLocation: true, - showShoppingList: true, + showToDoList: true, socialNetworks: [{ id: 1, url: defaultImagePath }] } as EditProfileModel; diff --git a/src/app/main/component/user/components/habit/const/data.const.ts b/src/app/main/component/user/components/habit/const/data.const.ts index 4829c7890b..0007332634 100644 --- a/src/app/main/component/user/components/habit/const/data.const.ts +++ b/src/app/main/component/user/components/habit/const/data.const.ts @@ -4,7 +4,7 @@ export const HABIT_TAGS_MAXLENGTH = 3; export const FIELD_SYMBOLS_LIMIT = 2048; -export const SHOPPING_ITEM_NAME_LIMIT = 20; +export const TO_DO_ITEM_NAME_LIMIT = 20; export const HABIT_COMPLEXITY_LIST = [ { value: 1, name: 'user.habit.add-new-habit.difficulty.easy', alt: 'Easy difficulty' }, @@ -23,7 +23,7 @@ export const STAR_IMAGES = { GREEN: 'assets/img/icon/star-1.png' }; -export const HABIT_SHOPPING_LIST_CHECK = { +export const HABIT_TO_DO_LIST_CHECK = { doneCheck: 'assets/icons/habits/filled-check-circle.svg', inprogressCheck: 'assets/icons/habits/lined-green-circle.svg', plusCheck: 'assets/icons/habits/doted-plus-green-circle.svg', diff --git a/src/app/main/component/user/components/habit/mocks/habit-assigned-mock.ts b/src/app/main/component/user/components/habit/mocks/habit-assigned-mock.ts index 9262fb9e98..3c7b676910 100644 --- a/src/app/main/component/user/components/habit/mocks/habit-assigned-mock.ts +++ b/src/app/main/component/user/components/habit/mocks/habit-assigned-mock.ts @@ -26,7 +26,7 @@ export const DEFAULTHABIT: HabitInterface = { id: 1, usersIdWhoCreatedCustomHabit: 33, image: 'https://www.testgreencity.ga/assets/img/habits/man.svg', - shoppingListItems: [], + toDoListItems: [], tags: [], isCustomHabit: false, isAssigned: false @@ -49,7 +49,7 @@ export const CUSTOMHABIT: HabitInterface = { id: 2, usersIdWhoCreatedCustomHabit: 50, image: '', - shoppingListItems: [], + toDoListItems: [], tags: [], isCustomHabit: true, isAssigned: false @@ -70,7 +70,7 @@ export const DEFAULTFULLINFOHABIT: HabitAssignInterface = { { enrollDate: '2023-04-14', id: 2 }, { enrollDate: '2023-04-10', id: 3 } ], - shoppingListItems: [ + toDoListItems: [ { id: 6, status: TodoStatus.active, @@ -95,7 +95,7 @@ export const CUSTOMFULLINFOHABIT: HabitAssignInterface = { { enrollDate: 'monday', id: 2 }, { enrollDate: 'wednesday', id: 3 } ], - shoppingListItems: [ + toDoListItems: [ { id: 6, status: TodoStatus.active, @@ -172,7 +172,7 @@ export const DEFAULTFULLINFOHABIT_2: HabitAssignInterface = { { enrollDate: '2023-04-14', id: 2 }, { enrollDate: '2023-04-10', id: 3 } ], - shoppingListItems: [ + toDoListItems: [ { id: 6, status: TodoStatus.active, diff --git a/src/app/main/component/user/components/habit/mocks/habit-mock.ts b/src/app/main/component/user/components/habit/mocks/habit-mock.ts index 9ef5b6b8d9..afb5262710 100644 --- a/src/app/main/component/user/components/habit/mocks/habit-mock.ts +++ b/src/app/main/component/user/components/habit/mocks/habit-mock.ts @@ -82,7 +82,7 @@ export const MOCK_CUSTOM_HABIT: CustomHabit = { duration: 30, tagIds: [1, 2], image: 'testImage', - shopList: [ + toDoList: [ { id: 2, text: 'Collapsible Silicone Water Bottle', @@ -107,7 +107,7 @@ export const MOCK_CUSTOM_HABIT_RESPONSE: CustomHabitDtoRequest = { defaultDuration: MOCK_CUSTOM_HABIT.duration, image: MOCK_CUSTOM_HABIT.image, tagIds: MOCK_CUSTOM_HABIT.tagIds, - customShoppingListItemDto: MOCK_CUSTOM_HABIT.shopList + customToDoListItemDto: MOCK_CUSTOM_HABIT.toDoList }; export const NEW_HABIT_ARRAY_MOCK: NewHabitDto[] = [new NewHabitDto(1), new NewHabitDto(2), new NewHabitDto(3)]; diff --git a/src/app/main/component/user/components/habit/mocks/shopping-list-mock.ts b/src/app/main/component/user/components/habit/mocks/to-do-list-mock.ts similarity index 73% rename from src/app/main/component/user/components/habit/mocks/shopping-list-mock.ts rename to src/app/main/component/user/components/habit/mocks/to-do-list-mock.ts index 490c47b531..b280cef10f 100644 --- a/src/app/main/component/user/components/habit/mocks/shopping-list-mock.ts +++ b/src/app/main/component/user/components/habit/mocks/to-do-list-mock.ts @@ -1,36 +1,36 @@ -import { AllShoppingLists, CustomShoppingItem, HabitUpdateShopList, ShoppingList } from '@user-models/shoppinglist.interface'; +import { AllToDoLists, CustomToDoItem, HabitUpdateToDoList, ToDoList } from '@global-user/models/to-do-list.interface'; import { TodoStatus } from '../models/todo-status.enum'; import { HabitListInterface } from '@global-user/components/habit/models/interfaces/habit.interface'; import { CustomHabit, CustomHabitDtoRequest } from '@global-user/components/habit/models/interfaces/custom-habit.interface'; import { FriendProfilePicturesArrayModel } from '@user-models/friend.model'; -export const SHOPLISTITEMONE: ShoppingList = { +export const TODOLISTITEMONE: ToDoList = { id: 1, text: 'Reusable stainless steel water bottle', status: TodoStatus.active }; -export const SHOPLISTITEMTWO: ShoppingList = { +export const TODOLISTITEMTWO: ToDoList = { id: 2, text: 'Collapsible Silicone Water Bottle', status: TodoStatus.inprogress }; -export const SHOPLIST: ShoppingList[] = [SHOPLISTITEMONE, SHOPLISTITEMTWO]; +export const TODOLIST: ToDoList[] = [TODOLISTITEMONE, TODOLISTITEMTWO]; -export const ALLUSERSHOPLISTS: AllShoppingLists = { - userShoppingListItemDto: [SHOPLISTITEMONE], - customShoppingListItemDto: [SHOPLISTITEMTWO] +export const ALLUSERTODOLISTS: AllToDoLists = { + userToDoListItemDto: [TODOLISTITEMONE], + customToDoListItemDto: [TODOLISTITEMTWO] }; -export const UPDATEHABITSHOPLIST: HabitUpdateShopList = { +export const UPDATEHABITTODOLIST: HabitUpdateToDoList = { habitAssignId: 2, - standardShopList: [SHOPLISTITEMONE], - customShopList: [SHOPLISTITEMTWO], + standardToDoList: [TODOLISTITEMONE], + customToDoList: [TODOLISTITEMTWO], lang: 'ua' }; -export const CUSTOMSHOPITEM: CustomShoppingItem = { +export const CUSTOMTODOITEM: CustomToDoItem = { text: 'New item' }; @@ -57,8 +57,8 @@ export const MOCK_HABITS: HabitListInterface = { habitAssignStatus: 'testStatus', isCustomHabit: true, usersIdWhoCreatedCustomHabit: 1, - customShoppingListItems: [], - shoppingListItems: [], + customToDoListItems: [], + toDoListItems: [], tags: ['tag1', 'tag2'] } ], @@ -73,7 +73,7 @@ export const MOCK_CUSTOM_HABIT: CustomHabit = { duration: 30, tagIds: [1, 2], image: 'testImage', - shopList: ALLUSERSHOPLISTS.customShoppingListItemDto + toDoList: ALLUSERTODOLISTS.customToDoListItemDto }; export const MOCK_CUSTOM_HABIT_RESPONSE: CustomHabitDtoRequest = { @@ -92,7 +92,7 @@ export const MOCK_CUSTOM_HABIT_RESPONSE: CustomHabitDtoRequest = { defaultDuration: MOCK_CUSTOM_HABIT.duration, image: MOCK_CUSTOM_HABIT.image, tagIds: MOCK_CUSTOM_HABIT.tagIds, - customShoppingListItemDto: MOCK_CUSTOM_HABIT.shopList + customToDoListItemDto: MOCK_CUSTOM_HABIT.toDoList }; export const MOCK_FRIEND_PROFILE_PICTURES: FriendProfilePicturesArrayModel[] = [ diff --git a/src/app/main/component/user/components/habit/models/interfaces/custom-habit.interface.ts b/src/app/main/component/user/components/habit/models/interfaces/custom-habit.interface.ts index 45601b36b1..2cb1d0e92e 100644 --- a/src/app/main/component/user/components/habit/models/interfaces/custom-habit.interface.ts +++ b/src/app/main/component/user/components/habit/models/interfaces/custom-habit.interface.ts @@ -1,4 +1,4 @@ -import { ShoppingList } from '@user-models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { HabitTranslationInterface } from './habit.interface'; export interface CustomHabitDtoRequest { @@ -7,7 +7,7 @@ export interface CustomHabitDtoRequest { defaultDuration: number; image: string; tagIds: number[]; - customShoppingListItemDto: ShoppingList[]; + customToDoListItemDto: ToDoList[]; id?: number; userId?: number; } @@ -19,5 +19,5 @@ export interface CustomHabit { duration: number; tagIds: number[]; image: string; - shopList: ShoppingList[]; + toDoList: ToDoList[]; } diff --git a/src/app/main/component/user/components/habit/models/interfaces/habit-assign.interface.ts b/src/app/main/component/user/components/habit/models/interfaces/habit-assign.interface.ts index 11fadba3f2..e4e3f8037d 100644 --- a/src/app/main/component/user/components/habit/models/interfaces/habit-assign.interface.ts +++ b/src/app/main/component/user/components/habit/models/interfaces/habit-assign.interface.ts @@ -1,5 +1,5 @@ import { HabitStatus } from '@global-models/habit/HabitStatus.enum'; -import { ShoppingList } from '@user-models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { HabitInterface } from './habit.interface'; export interface HabitAssignInterface { @@ -16,7 +16,7 @@ export interface HabitAssignInterface { habitStreak: number; lastEnrollmentDate: Date; habitStatusCalendarDtoList: Array; - shoppingListItems: Array; + toDoListItems: Array; progressNotificationHasDisplayed?: boolean; } diff --git a/src/app/main/component/user/components/habit/models/interfaces/habit.interface.ts b/src/app/main/component/user/components/habit/models/interfaces/habit.interface.ts index 2ca88d2ffa..464574d58c 100644 --- a/src/app/main/component/user/components/habit/models/interfaces/habit.interface.ts +++ b/src/app/main/component/user/components/habit/models/interfaces/habit.interface.ts @@ -1,4 +1,4 @@ -import { ShoppingList } from '@user-models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; export interface HabitInterface { defaultDuration: number; @@ -12,8 +12,8 @@ export interface HabitInterface { habitAssignStatus?: string; isCustomHabit: boolean; usersIdWhoCreatedCustomHabit: number; - customShoppingListItems?: ShoppingList[]; - shoppingListItems?: ShoppingList[]; + customToDoListItems?: ToDoList[]; + toDoListItems?: ToDoList[]; tags: Array; duration?: number; } diff --git a/src/app/main/component/user/components/index.ts b/src/app/main/component/user/components/index.ts index d511b52fa1..ba84fa28a4 100644 --- a/src/app/main/component/user/components/index.ts +++ b/src/app/main/component/user/components/index.ts @@ -23,5 +23,5 @@ export { OneHabitComponent } from './profile/profile-dashboard/one-habit/one-hab export { ProfileWidgetComponent } from './profile/profile-widget/profile-widget.component'; export { ProfileHeaderComponent } from './profile/profile-widget/profile-header/profile-header.component'; export { ProfileProgressComponent } from './profile/profile-widget/profile-progress/profile-progress.component'; -export { ShoppingListComponent } from './profile/shopping-list/shopping-list.component'; +export { ToDoListComponent } from './profile/to-do-list/to-do-list.component'; export { UserSettingComponent } from './user-setting/user-setting.component'; diff --git a/src/app/main/component/user/components/profile/edit-profile/edit-profile-form-builder.ts b/src/app/main/component/user/components/profile/edit-profile/edit-profile-form-builder.ts index e31a5b7c77..9f52da7828 100644 --- a/src/app/main/component/user/components/profile/edit-profile/edit-profile-form-builder.ts +++ b/src/app/main/component/user/components/profile/edit-profile/edit-profile-form-builder.ts @@ -19,7 +19,7 @@ export class EditProfileFormBuilder { credo: ['', Validators.maxLength(170)], showLocation: [false], showEcoPlace: [false], - showShoppingList: [false], + showToDoList: [false], socialNetworks: [''], emailPreferences: this.createEmailPreferencesGroup(null) }); @@ -37,6 +37,8 @@ export class EditProfileFormBuilder { ) ); + this.setupEmailPreferenceListeners(emailPrefsGroup); + if (preferences) { this.initializeEmailPreferences(emailPrefsGroup, preferences); } @@ -44,6 +46,17 @@ export class EditProfileFormBuilder { return emailPrefsGroup; } + private setupEmailPreferenceListeners(group: FormGroup): void { + this.emailPreferences.forEach((pref) => { + const periodicityControl = group.get(`periodicity${this.capitalizeFirstLetter(pref)}`); + const checkboxControl = group.get(pref); + + periodicityControl?.valueChanges.subscribe((value) => { + checkboxControl?.setValue(value !== 'NEVER', { emitEvent: false }); + }); + }); + } + initializeEmailPreferences(group: FormGroup, preferences: NotificationPreference[]): void { preferences.forEach(({ emailPreference, periodicity }) => { const controlName = this.mapPreferenceToFormControl(emailPreference); @@ -94,7 +107,7 @@ export class EditProfileFormBuilder { credo: [editForm.userCredo, Validators.maxLength(170)], showLocation: [editForm.showLocation], showEcoPlace: [editForm.showEcoPlace], - showShoppingList: [editForm.showShoppingList], + showToDoList: [editForm.showToDoList], socialNetworks: [editForm.socialNetworks], emailPreferences: this.createEmailPreferencesGroup(editForm.notificationPreferences) }); diff --git a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.html b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.html index 6c12b1a315..4697dff660 100644 --- a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.html +++ b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.html @@ -67,9 +67,9 @@

    {{ 'user.edit-profile.edit-profile' | translate }}

diff --git a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.spec.ts b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.spec.ts index 20960589f4..adf2709231 100644 --- a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.spec.ts +++ b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.spec.ts @@ -1,7 +1,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarComponent } from '@global-errors/mat-snack-bar/mat-snack-bar.component'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { MatDialogModule } from '@angular/material/dialog'; @@ -186,7 +186,7 @@ describe('EditProfileComponent', () => { userCredo: '', showLocation: '', showEcoPlace: '', - showShoppingList: '', + showToDoList: '', socialNetworks: [] }; component.editProfileForm.value.city = ''; @@ -194,7 +194,7 @@ describe('EditProfileComponent', () => { component.editProfileForm.value.credo = ''; component.editProfileForm.value.showLocation = ''; component.editProfileForm.value.showEcoPlace = ''; - component.editProfileForm.value.showShoppingList = ''; + component.editProfileForm.value.showToDoList = ''; component.socialNetworksToServer = []; }); @@ -239,11 +239,12 @@ describe('EditProfileComponent', () => { describe('The formControl: city should be marked as valid if the value:', () => { for (let i = 0; i < validCity.length; i++) { - it(`${i + 1}-st - ${validCity[i]}.`, () => { + it(`${i + 1}-st - ${validCity[i]}.`, fakeAsync(() => { const control = component.editProfileForm.get('city'); control.setValue(validCity[i]); + tick(100); expect(control.valid).toBeTruthy(); - }); + })); } }); }); @@ -264,7 +265,7 @@ describe('EditProfileComponent', () => { rating: 658, showEcoPlace: true, showLocation: true, - showShoppingList: true, + showToDoList: true, socialNetworks: [{ id: 220, url: 'http://instagram.com/profile' }] } as EditProfileModel; }); diff --git a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.ts b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.ts index 04cdf06730..b1d891c3d5 100644 --- a/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.ts +++ b/src/app/main/component/user/components/profile/edit-profile/edit-profile.component.ts @@ -110,7 +110,7 @@ export class EditProfileComponent extends FormBaseComponent implements OnInit, O userCredo: this.editProfileForm.value.credo === null ? '' : this.editProfileForm.value.credo, showLocation: this.editProfileForm.value.showLocation, showEcoPlace: this.editProfileForm.value.showEcoPlace, - showShoppingList: this.editProfileForm.value.showShoppingList, + showToDoList: this.editProfileForm.value.showToDoList, socialNetworks: this.socialNetworksToServer }; } @@ -133,7 +133,7 @@ export class EditProfileComponent extends FormBaseComponent implements OnInit, O }, showLocation: data.showLocation, showEcoPlace: data.showEcoPlace, - showShoppingList: data.showShoppingList, + showToDoList: data.showToDoList, socialNetworks: data.socialNetworks.map((network) => network.url) }; this.editProfileForm.markAllAsTouched(); @@ -197,7 +197,7 @@ export class EditProfileComponent extends FormBaseComponent implements OnInit, O userCredo: form.value.credo, showLocation: !!form.value.showLocation, showEcoPlace: !!form.value.showEcoPlace, - showShoppingList: !!form.value.showShoppingList, + showToDoList: !!form.value.showToDoList, socialNetworks: this.socialNetworksToServer, emailPreferences: emailPreferences.length > 0 ? emailPreferences : null }; diff --git a/src/app/main/component/user/components/profile/edit-profile/social-networks/social-networks.component.spec.ts b/src/app/main/component/user/components/profile/edit-profile/social-networks/social-networks.component.spec.ts index 5a057fc64d..7e5fa06705 100644 --- a/src/app/main/component/user/components/profile/edit-profile/social-networks/social-networks.component.spec.ts +++ b/src/app/main/component/user/components/profile/edit-profile/social-networks/social-networks.component.spec.ts @@ -47,7 +47,6 @@ describe('SocialNetworksComponent', () => { facebook: './assets/img/icon/facebook-icon.svg', linkedin: './assets/img/icon/linked-icon.svg', instagram: './assets/img/icon/instagram-icon.svg', - twitter: './assets/img/icon/twitter-icon.svg', x: './assets/img/icon/twitter-icon.svg', youtube: './assets/img/icon/youtube-icon.svg' }; diff --git a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.html b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.html index a8c0365b9f..2493e80f55 100644 --- a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.html +++ b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.html @@ -65,24 +65,41 @@
-
-

- - {{ 'profile.dashboard.news-list' | translate }}
-
-

- -
- {{ 'profile.dashboard.add-news' | translate }} +
+ +
@@ -99,33 +116,36 @@
-
-
-
- -
- {{ 'profile.dashboard.add-event' | translate }} +
+
+

+ + {{ (isFavoriteBtnClicked ? 'profile.dashboard.saved-events' : 'profile.dashboard.my-events') | translate }}
+
+

+
-
-
- -
{{ 'create-event.back' | translate }}
-
-
-
-
- - {{ 'homepage.events.my-space.event-type-online' | translate }} - - {{ 'homepage.events.my-space.event-type-offline' | translate }} - +
+
+
+ + {{ 'homepage.events.my-space.event-type-online' | translate }} + + {{ 'homepage.events.my-space.event-type-offline' | translate }} + +
diff --git a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.scss b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.scss index 374f883acb..17c411b4dd 100644 --- a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.scss +++ b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.scss @@ -18,6 +18,7 @@ a { font-size: 16px; line-height: 24px; letter-spacing: 0.01em; + margin: 24px 0; p { color: var(--primary-dark-grey); @@ -29,9 +30,9 @@ a { align-items: center; .favourites { - margin: 0 10px; - width: 40px; - height: 40px; + margin-left: 10px; + width: 36px; + height: 36px; background-color: var(--primary-white); border-radius: 5px; display: flex; @@ -48,6 +49,10 @@ a { transition: all 0.3s; } + .flag { + background: url('/assets/events-icons/bookmark-set.png') no-repeat center; + } + .flag-active { background: url('/assets/events-icons/bookmark-active.png') no-repeat center; } @@ -57,10 +62,21 @@ a { .in-progress { display: flex; - justify-content: space-between; align-items: center; - margin: 24px 0; - height: 48px; + justify-content: space-between; + } + + .column { + flex-direction: column; + margin-bottom: 24px; + } + + .filters { + margin-top: 12px; + } + + .justify-end { + justify-content: end; } .secondary-global-button { diff --git a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.spec.ts b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.spec.ts index 2de149ae40..24062d0da9 100644 --- a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.spec.ts +++ b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.spec.ts @@ -10,11 +10,12 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { EventsService } from 'src/app/main/component/events/services/events.service'; import { NgxPaginationModule } from 'ngx-pagination'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; import { EventType } from 'src/app/ubs/ubs/services/event-type.enum'; -import { mockEvent, mockFavouriteEvents, mockHabitAssign } from '@assets/mocks/events/mock-events'; +import { mockEvent, mockFavouriteEvents } from '@assets/mocks/events/mock-events'; import { mockHabits } from '@assets/mocks/habit/mock-habit-calendar'; +import { newsMock } from '@assets/mocks/eco-news/mock-news-item'; describe('ProfileDashboardComponent', () => { let component: ProfileDashboardComponent; @@ -34,14 +35,7 @@ describe('ProfileDashboardComponent', () => { LocalStorageServiceMock.getCurrentLanguage = () => of('ua'); const storeMock = jasmine.createSpyObj('store', ['select', 'dispatch']); - storeMock.select = () => - of({ - ecoNews: {}, - authorNews: [{ newsId: 1 }], - pageNumber: 1, - error: 'error', - ecoNewsByAuthor: true - }); + storeMock.select = () => of({ ecoNews: {}, pages: [], pageNumber: 1, error: 'error' }); const eventsServiceMock = jasmine.createSpyObj('EventsService', ['getEvents', 'getUserFavoriteEvents']); eventsServiceMock.getEvents = () => of(mockEvent); @@ -93,8 +87,8 @@ describe('ProfileDashboardComponent', () => { it('onInit news should have expected result', waitForAsync(() => { component.ngOnInit(); - component.authorNews$.subscribe((item: any) => { - expect(component.news[0]).toEqual({ newsId: 1 } as any); + component.econews$.subscribe((item: any) => { + expect(component.news).toEqual([]); }); })); @@ -217,6 +211,7 @@ describe('ProfileDashboardComponent', () => { it('onScroll', () => { const spy = spyOn(component, 'dispatchNews'); + component.news = [newsMock, { ...newsMock, id: 2 }]; component.onScroll(); expect(spy).toHaveBeenCalledTimes(1); }); @@ -228,16 +223,18 @@ describe('ProfileDashboardComponent', () => { }); it('should toggle isFavoriteBtnClicked property on escapeFromFavorites method', () => { + component.isFavoriteBtnClicked = false; expect(component.isFavoriteBtnClicked).toBeFalse(); - component.escapeFromFavorites(); + component.toggleFavorites(); expect(component.isFavoriteBtnClicked).toBeTrue(); - component.escapeFromFavorites(); + component.toggleFavorites(); expect(component.isFavoriteBtnClicked).toBeFalse(); }); it('should set isFavoriteBtnClicked to true and call getUserEvents when goToFavorites is called', () => { spyOn(component, 'getUserEvents'); - component.goToFavorites(); + component.isFavoriteBtnClicked = false; + component.toggleFavorites(); expect(component.isFavoriteBtnClicked).toBeTrue(); expect(component.getUserEvents).toHaveBeenCalled(); }); diff --git a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.ts b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.ts index d6ad754d11..0954d01974 100644 --- a/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.ts +++ b/src/app/main/component/user/components/profile/profile-dashboard/profile-dashboard.component.ts @@ -8,7 +8,7 @@ import { MatTabChangeEvent } from '@angular/material/tabs'; import { Store } from '@ngrx/store'; import { IAppState } from 'src/app/store/state/app.state'; import { IEcoNewsState } from 'src/app/store/state/ecoNews.state'; -import { GetEcoNewsByAuthorAction } from 'src/app/store/actions/ecoNews.actions'; +import { GetEcoNewsAction } from 'src/app/store/actions/ecoNews.actions'; import { EcoNewsModel } from '@eco-news-models/eco-news-model'; import { EventResponse, EventResponseDto } from 'src/app/main/component/events/models/events.interface'; import { EventsService } from 'src/app/main/component/events/services/events.service'; @@ -18,6 +18,9 @@ import { EventType } from 'src/app/ubs/ubs/services/event-type.enum'; import { singleNewsImages } from 'src/app/main/image-pathes/single-news-images'; import { HttpParams } from '@angular/common/http'; import { TranslateService } from '@ngx-translate/core'; +import { FilterModel } from '@shared/components/tag-filter/tag-filter.model'; +import { tagsListEcoNewsData } from '@eco-news-models/eco-news-consts'; +import { EcoNewsService } from '@eco-news-service/eco-news.service'; @Component({ selector: 'app-profile-dashboard', @@ -39,26 +42,28 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { isActiveNewsScroll = false; isActiveEventsScroll = false; userId: number; - news: EcoNewsModel[]; + news: EcoNewsModel[] = []; isOnlineChecked = false; isOfflineChecked = false; eventsList: EventResponse[] = []; eventsPerPage = 6; eventsPage = 1; - favoriteEventsPage = 0; totalEvents = 0; totalNews = 0; + tagsList: Array = []; + tagList: FilterModel[] = tagsListEcoNewsData; loadingEvents = false; eventType = ''; isFavoriteBtnClicked = false; + isNewsFavoriteBtnClicked = false; userLatitude = 0; userLongitude = 0; images = singleNewsImages; - authorNews$ = this.store.select((state: IAppState): IEcoNewsState => state.ecoNewsState); + econews$ = this.store.select((state: IAppState): IEcoNewsState => state.ecoNewsState); private destroyed$: ReplaySubject = new ReplaySubject(1); private hasNext = true; private hasNextPageOfEvents = true; - private currentPage: number; + private page: number; private newsCount = 5; constructor( @@ -68,7 +73,8 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { private eventService: EventsService, private route: ActivatedRoute, private readonly cdr: ChangeDetectorRef, - private readonly translate: TranslateService + private readonly translate: TranslateService, + private readonly ecoNewsService: EcoNewsService ) {} ngOnInit() { @@ -76,12 +82,12 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { this.subscribeToLangChange(); this.getUserId(); - this.authorNews$.subscribe((val: IEcoNewsState) => { - this.currentPage = val.authorNewsPage; - if (val.ecoNewsByAuthor) { - this.totalNews = val.ecoNewsByAuthor.totalElements; - this.hasNext = val.ecoNewsByAuthor.hasNext; - this.news = val.authorNews; + this.econews$.subscribe((val: IEcoNewsState) => { + this.page = val.pageNumber; + if (val.ecoNews) { + this.totalNews = val.ecoNews.totalElements; + this.hasNext = val.ecoNews.hasNext; + this.news = [...val.pages]; } }); @@ -134,20 +140,13 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { this.hasNextPageOfEvents = true; } - escapeFromFavorites(): void { + toggleFavorites(): void { this.isFavoriteBtnClicked = !this.isFavoriteBtnClicked; this.cleanState(); this.getUserEvents(); } - goToFavorites(): void { - this.isFavoriteBtnClicked = true; - - this.cleanState(); - this.getUserEvents(); - } - initGetUserEvents(): void { this.eventService .getEvents(this.getHttpParams(0)) @@ -187,6 +186,7 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { next: (res: EventResponseDto) => { this.eventsList.push(...res.page); this.eventsPage++; + this.totalEvents = res.totalElements; this.hasNextPageOfEvents = res.hasNext; this.isActiveEventsScroll = res.hasNext; }, @@ -197,17 +197,45 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { } } - dispatchNews(res: boolean) { - if (this.currentPage !== undefined && this.hasNext) { - this.store.dispatch( - GetEcoNewsByAuthorAction({ - authorId: this.userId, - currentPage: this.currentPage, - numberOfNews: this.newsCount, - reset: res - }) - ); + toggleNewsFavorites() { + this.isNewsFavoriteBtnClicked = !this.isNewsFavoriteBtnClicked; + this.dispatchNews(true); + } + + getFilterData(value: Array): void { + if (this.tagsList !== value) { + this.tagsList = value; + } + this.dispatchNews(true); + } + + dispatchNews(res: boolean): void { + if (res) { + this.hasNext = true; + this.page = 0; + this.news = []; + this.totalNews = 0; } + + if (!this.hasNext || this.loading) { + return; + } + + this.loading = true; + const params = this.ecoNewsService.getNewsHttpParams({ + page: this.page, + size: this.newsCount, + authorId: this.userId, + favorite: this.isNewsFavoriteBtnClicked, + userId: this.userId, + tags: this.tagsList + }); + + const action = GetEcoNewsAction({ params, reset: res }); + this.store.dispatch(action); + + this.page++; + this.loading = false; } changeStatus(habit: HabitAssignInterface) { @@ -256,6 +284,9 @@ export class ProfileDashboardComponent implements OnInit, OnDestroy { } onScroll(): void { + if (!this.news.length) { + return; + } this.dispatchNews(false); } diff --git a/src/app/main/component/user/components/profile/profile-service/profile.service.spec.ts b/src/app/main/component/user/components/profile/profile-service/profile.service.spec.ts index 8ab5c5d87d..332b28b0a2 100644 --- a/src/app/main/component/user/components/profile/profile-service/profile.service.spec.ts +++ b/src/app/main/component/user/components/profile/profile-service/profile.service.spec.ts @@ -83,7 +83,7 @@ describe('ProfileService', () => { rating: 1999, showEcoPlace: true, showLocation: false, - showShoppingList: true, + showToDoList: true, socialNetworks: [] }; @@ -169,12 +169,9 @@ describe('ProfileService', () => { }); it('should return the twitter icon when the url belongs to twitter', () => { - expect(profileService.getSocialImage('https://twitter.com')).toBe(profileService.socialMedia.twitter); - expect(profileService.getSocialImage('https://twitter.com/facebook')).toBe(profileService.socialMedia.twitter); - expect(profileService.getSocialImage('https://twitter.com?test=youtube&val=linkedin')).toBe(profileService.socialMedia.twitter); - expect(profileService.getSocialImage('https://x.com')).toBe(profileService.socialMedia.twitter); - expect(profileService.getSocialImage('https://x.com/facebook')).toBe(profileService.socialMedia.twitter); - expect(profileService.getSocialImage('https://x.com?test=youtube&val=instagram')).toBe(profileService.socialMedia.twitter); + expect(profileService.getSocialImage('https://x.com')).toBe(profileService.socialMedia.x); + expect(profileService.getSocialImage('https://x.com/facebook')).toBe(profileService.socialMedia.x); + expect(profileService.getSocialImage('https://x.com?test=youtube&val=instagram')).toBe(profileService.socialMedia.x); }); it('should youtube the instagram icon when the url belongs to youtube', () => { diff --git a/src/app/main/component/user/components/profile/profile-service/profile.service.ts b/src/app/main/component/user/components/profile/profile-service/profile.service.ts index cd42f723dd..a658f8d359 100644 --- a/src/app/main/component/user/components/profile/profile-service/profile.service.ts +++ b/src/app/main/component/user/components/profile/profile-service/profile.service.ts @@ -26,7 +26,6 @@ export class ProfileService { facebook: './assets/img/icon/facebook-icon.svg', linkedin: './assets/img/icon/linkedin-icon.svg', instagram: './assets/img/icon/instagram-icon.svg', - twitter: './assets/img/icon/twitter-icon.svg', x: './assets/img/icon/twitter-icon.svg', youtube: './assets/img/icon/youtube-icon.svg' }; diff --git a/src/app/main/component/user/components/profile/profile-widget/profile-header/profile-header.component.spec.ts b/src/app/main/component/user/components/profile/profile-widget/profile-header/profile-header.component.spec.ts index eed028d681..9674a1ea93 100644 --- a/src/app/main/component/user/components/profile/profile-widget/profile-header/profile-header.component.spec.ts +++ b/src/app/main/component/user/components/profile/profile-widget/profile-header/profile-header.component.spec.ts @@ -58,7 +58,7 @@ describe('ProfileHeaderComponent', () => { rating: 2, showEcoPlace: false, showLocation: false, - showShoppingList: false, + showToDoList: false, socialNetworks: [{ id: 220, url: 'http://instagram' }] } as EditProfileModel; fixture.detectChanges(); @@ -73,7 +73,6 @@ describe('ProfileHeaderComponent', () => { facebook: './assets/img/icon/facebook-icon.svg', linkedin: './assets/img/icon/linked-icon.svg', instagram: './assets/img/icon/instagram-icon.svg', - twitter: './assets/img/icon/twitter-icon.svg', x: './assets/img/icon/twitter-icon.svg', youtube: './assets/img/icon/youtube-icon.svg' }; diff --git a/src/app/main/component/user/components/profile/profile.component.html b/src/app/main/component/user/components/profile/profile.component.html index 33b686a781..4ecd2c0692 100644 --- a/src/app/main/component/user/components/profile/profile.component.html +++ b/src/app/main/component/user/components/profile/profile.component.html @@ -6,7 +6,7 @@ - +
@@ -15,7 +15,7 @@ - +
diff --git a/src/app/main/component/user/components/profile/profile.component.scss b/src/app/main/component/user/components/profile/profile.component.scss index fffd7dc0d8..6c51056a42 100644 --- a/src/app/main/component/user/components/profile/profile.component.scss +++ b/src/app/main/component/user/components/profile/profile.component.scss @@ -12,6 +12,7 @@ .left-column { width: 100%; box-shadow: 4px 0 8px -4px var(--quintynary-dark-grey); + background-color: var(--primary-white); .app-users-achievements, .app-users-friends, @@ -41,6 +42,7 @@ order: 1; width: 100vw; min-height: 323px; + background-color: transparent; } .app-users-friends, @@ -154,7 +156,7 @@ .profile-wrapper { .right-column { - app-shopping-list { + app-to-do-list { margin: 0; display: none; } @@ -176,13 +178,13 @@ display: flex; } - .app-shopping-list { + .app-to-do-list { display: none; } } .right-column { - app-shopping-list { + app-to-do-list { margin-top: 50px; margin-bottom: 24px; } @@ -193,13 +195,13 @@ @include responsiveRange(md, xl) { .profile-wrapper { .left-column { - .app-shopping-list { + .app-to-do-list { display: block; } } .right-column { - .app-shopping-list { + .app-to-do-list { display: none; } } @@ -209,13 +211,13 @@ @include responsiveMobileFirst(xl) { .profile-wrapper { .left-column { - .app-shopping-list { + .app-to-do-list { display: none; } } .right-column { - .app-shopping-list { + .app-to-do-list { display: block; margin-top: 16px; } diff --git a/src/app/main/component/user/components/profile/profile.component.spec.ts b/src/app/main/component/user/components/profile/profile.component.spec.ts index da6c7d7bbb..36a74f5e63 100644 --- a/src/app/main/component/user/components/profile/profile.component.spec.ts +++ b/src/app/main/component/user/components/profile/profile.component.spec.ts @@ -14,7 +14,7 @@ describe('ProfileComponent', () => { const fakeItem = { city: 'fakeCity', - showShoppingList: false + showToDoList: false }; const liveAnnouncerMock = jasmine.createSpyObj('announcer', ['announce']); const localStorageServiceMock = jasmine.createSpyObj('localStorageService', ['getCurrentLanguage', 'setCurentPage']); diff --git a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.html b/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.html deleted file mode 100644 index 91cf2cba9b..0000000000 --- a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
-
-
- {{ 'profile.to-do-list' | translate }} -
-
{{ shoppingList?.length }} {{ 'profile.elements' | correctUnit: shoppingList?.length:currentLang | translate }} -
-
- {{ - (toggle ? 'user.habit.btn.see-less' : 'user.habit.btn.see-all') | translate - }} -
-
- -
-
-
- -
-
-
-

{{ 'profile.empty-list' | translate }}

-
-
-
diff --git a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.ts b/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.ts deleted file mode 100644 index 43f38758d4..0000000000 --- a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { catchError, takeUntil } from 'rxjs/operators'; -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; -import { of, Subject } from 'rxjs'; -import { Patterns } from 'src/assets/patterns/patterns'; -import { ShoppingListService } from '@global-user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service'; -import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; -import { Subscription } from 'stompjs'; -import { AllShoppingLists, ShoppingList } from '@global-user/models/shoppinglist.interface'; -import { TodoStatus } from '@global-user/components/habit/models/todo-status.enum'; - -@Component({ - selector: 'app-shopping-list', - templateUrl: './shopping-list.component.html', - styleUrls: ['./shopping-list.component.scss'] -}) -export class ShoppingListComponent implements OnInit { - shoppingList: ShoppingList[] = []; - toggle: boolean; - private userId: number; - currentLang: string; - profileSubscription: Subscription; - private shoppingListCache: AllShoppingLists[] | null = null; - private destroy$: Subject = new Subject(); - - constructor( - private localStorageService: LocalStorageService, - private shopListService: ShoppingListService, - private cdr: ChangeDetectorRef - ) {} - - ngOnInit(): void { - this.userId = this.localStorageService.getUserId(); - this.subscribeToLangChange(); - } - - private subscribeToLangChange(): void { - this.localStorageService.languageBehaviourSubject.subscribe((lang: string) => { - this.currentLang = lang; - if (this.localStorageService.getUserId()) { - this.getAllShopLists(); - } - }); - } - - private getAllShopLists(): void { - if (this.shoppingListCache) { - this.updateShoppingList(this.shoppingListCache); - return; - } - - this.shopListService - .getUserShoppingLists(this.currentLang) - .pipe( - takeUntil(this.destroy$), - catchError(() => of([])) - ) - .subscribe((list) => { - this.shoppingListCache = list; - this.updateShoppingList(list); - }); - } - - private updateShoppingList(list: AllShoppingLists[]): void { - const customShopList = this.convertShopList(list, 'custom'); - const standardShopList = this.convertShopList(list, 'standard'); - customShopList.forEach((el) => (el.custom = true)); - this.shoppingList = [...customShopList, ...standardShopList]; - } - - convertShopList(list: AllShoppingLists[], type: string): ShoppingList[] { - return list.reduce((acc, obj) => acc.concat(type === 'custom' ? obj.customShoppingListItemDto : obj.userShoppingListItemDto), []); - } - - isValidURL(url: string): boolean { - return Patterns.isValidURL.test(url); - } - - openCloseList(): void { - this.toggle = !this.toggle; - } - - toggleDone(item: ShoppingList): void { - item.status = item.status === TodoStatus.inprogress ? TodoStatus.done : TodoStatus.inprogress; - item.custom ? this.updateStatusCustomItem(item) : this.updateStatusItem(item); - } - - private updateStatusItem(item: ShoppingList): void { - this.shopListService - .updateStandardShopItemStatus(item, this.currentLang) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.updateShopList(item); - }); - } - - private updateStatusCustomItem(item: ShoppingList): void { - this.shopListService - .updateCustomShopItemStatus(this.userId, item) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.updateShopList(item); - }); - } - - private updateShopList(item: ShoppingList): void { - this.shoppingList = this.shoppingList.map((el) => (el.id === item.id ? { ...el, status: item.status } : el)); - this.cdr.detectChanges(); - } -} diff --git a/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.html b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.html new file mode 100644 index 0000000000..61a88fd3fa --- /dev/null +++ b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.html @@ -0,0 +1,41 @@ +
+
+
+
+ {{ 'profile.to-do-list' | translate }} +
+
{{ toDoList?.length }} {{ 'profile.elements' | correctUnit: toDoList?.length : currentLang | translate }} +
+
+ {{ + (toggle ? 'user.habit.btn.see-less' : 'user.habit.btn.see-all') | translate + }} +
+
+ +
+
+
+ +
+
+
+

{{ 'profile.empty-list' | translate }}

+
+
+
diff --git a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.scss b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.scss similarity index 93% rename from src/app/main/component/user/components/profile/shopping-list/shopping-list.component.scss rename to src/app/main/component/user/components/profile/to-do-list/to-do-list.component.scss index 3166262899..bed0c57202 100644 --- a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.scss +++ b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.scss @@ -7,12 +7,12 @@ box-shadow: 1px 4px 8px var(--quintynary-dark-grey); } -.shopping-list-block { +.to-do-list-block { width: 213px; margin: 0 auto; } -.shopping-list-content { +.to-do-list-content { padding-top: 24px; font-family: var(--tertiary-font); font-style: normal; @@ -86,15 +86,15 @@ h2 > span { text-decoration: line-through; } -.shopping-list-min { +.to-do-list-min { height: 90px; } -.shopping-list-max { +.to-do-list-max { height: max-content; } -.shopping-list { +.to-do-list { width: 100%; padding: 5px 0 0; overflow-x: hidden; @@ -138,7 +138,7 @@ h2 > span { } @include responsiveRange(md, 2xl) { - .shopping-list-block { + .to-do-list-block { border-top: 1px solid var(--quaternary-light-grey); width: 184px; } @@ -149,7 +149,7 @@ h2 > span { margin-top: 20px; } - .shopping-list-block { + .to-do-list-block { width: 100%; padding: 0 16px; } diff --git a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.spec.ts b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.spec.ts similarity index 55% rename from src/app/main/component/user/components/profile/shopping-list/shopping-list.component.spec.ts rename to src/app/main/component/user/components/profile/to-do-list/to-do-list.component.spec.ts index 3ff2b31661..4cbf3872db 100644 --- a/src/app/main/component/user/components/profile/shopping-list/shopping-list.component.spec.ts +++ b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.spec.ts @@ -1,24 +1,24 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { ShoppingListComponent } from '@global-user/components'; +import { ToDoListComponent } from '@global-user/components'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; import { of, BehaviorSubject } from 'rxjs'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { ShoppingListService } from '@global-user/components/habit/add-new-habit/habit-edit-shopping-list/shopping-list.service'; +import { ToDoListService } from '@global-user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service'; import { Language } from 'src/app/main/i18n/Language'; -import { SHOPLISTITEMONE, SHOPLISTITEMTWO } from '@global-user/components/habit/mocks/shopping-list-mock'; -import { SHOPLIST } from '@global-user/components/habit/mocks/shopping-list-mock'; -import { ALLUSERSHOPLISTS } from '@global-user/components/habit/mocks/shopping-list-mock'; +import { TODOLISTITEMONE, TODOLISTITEMTWO } from '@global-user/components/habit/mocks/to-do-list-mock'; +import { TODOLIST } from '@global-user/components/habit/mocks/to-do-list-mock'; +import { ALLUSERTODOLISTS } from '@global-user/components/habit/mocks/to-do-list-mock'; import { CorrectUnitPipe } from 'src/app/shared/correct-unit-pipe/correct-unit.pipe'; import { TodoStatus } from '@global-user/components/habit/models/todo-status.enum'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -describe('ShoppingListComponent', () => { - let component: ShoppingListComponent; - let fixture: ComponentFixture; +describe('ToDoListComponent', () => { + let component: ToDoListComponent; + let fixture: ComponentFixture; const localStorageServiceMock = jasmine.createSpyObj('localStorageService', [ 'languageBehaviourSubject', @@ -30,18 +30,18 @@ describe('ShoppingListComponent', () => { localStorageServiceMock.languageSubject = of('en'); localStorageServiceMock.getUserId = () => 1; - const shoppingListServiceMock: ShoppingListService = jasmine.createSpyObj('fakeShoppingListService', [ - 'getUserShoppingLists', - 'updateStandardShopItemStatus', - 'updateCustomShopItemStatus' + const toDoListServiceMock: ToDoListService = jasmine.createSpyObj('fakeToDoListService', [ + 'getUserToDoLists', + 'updateStandardToDoItemStatus', + 'updateCustomToDoItemStatus' ]); - shoppingListServiceMock.getUserShoppingLists = () => of([ALLUSERSHOPLISTS]); - shoppingListServiceMock.updateStandardShopItemStatus = () => of(); - shoppingListServiceMock.updateCustomShopItemStatus = () => of(); + toDoListServiceMock.getUserToDoLists = () => of([ALLUSERTODOLISTS]); + toDoListServiceMock.updateStandardToDoItemStatus = () => of(); + toDoListServiceMock.updateCustomToDoItemStatus = () => of(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ShoppingListComponent, CorrectUnitPipe], + declarations: [ToDoListComponent, CorrectUnitPipe], imports: [ RouterTestingModule, HttpClientTestingModule, @@ -51,7 +51,7 @@ describe('ShoppingListComponent', () => { NgbModule ], providers: [ - { provide: ShoppingListService, useValue: shoppingListServiceMock }, + { provide: ToDoListService, useValue: toDoListServiceMock }, { provide: LocalStorageService, useValue: localStorageServiceMock } ], schemas: [CUSTOM_ELEMENTS_SCHEMA] @@ -59,7 +59,7 @@ describe('ShoppingListComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(ShoppingListComponent); + fixture = TestBed.createComponent(ToDoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -68,20 +68,20 @@ describe('ShoppingListComponent', () => { expect(component).toBeTruthy(); }); - it('should set custom true after getShopLists', () => { + it('should set custom true after getToDoLists', () => { const result = [ - { ...SHOPLISTITEMONE, custom: true }, - { ...SHOPLISTITEMTWO, custom: true } + { ...TODOLISTITEMONE, custom: true }, + { ...TODOLISTITEMTWO, custom: true } ]; - SHOPLIST.forEach((el) => (el.custom = true)); - component.shoppingList = SHOPLIST; - expect(component.shoppingList).toEqual(result); + TODOLIST.forEach((el) => (el.custom = true)); + component.toDoList = TODOLIST; + expect(component.toDoList).toEqual(result); }); - it('should set shopList after getShopList', () => { - component.shoppingList = []; - component.shoppingList = [...component.shoppingList, ...SHOPLIST]; - expect(component.shoppingList).toEqual(SHOPLIST); + it('should set toDoList after getToDoList', () => { + component.toDoList = []; + component.toDoList = [...component.toDoList, ...TODOLIST]; + expect(component.toDoList).toEqual(TODOLIST); }); it('should change toogle from true on openCloseList', () => { @@ -97,8 +97,8 @@ describe('ShoppingListComponent', () => { }); it('should change item status on toggleDone', () => { - SHOPLISTITEMONE.status = TodoStatus.inprogress; - component.toggleDone(SHOPLISTITEMONE); - expect(SHOPLISTITEMONE.status).toBe('DONE'); + TODOLISTITEMONE.status = TodoStatus.inprogress; + component.toggleDone(TODOLISTITEMONE); + expect(TODOLISTITEMONE.status).toBe('DONE'); }); }); diff --git a/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.ts b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.ts new file mode 100644 index 0000000000..3659bd7848 --- /dev/null +++ b/src/app/main/component/user/components/profile/to-do-list/to-do-list.component.ts @@ -0,0 +1,109 @@ +import { catchError, takeUntil } from 'rxjs/operators'; +import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { of, Subject } from 'rxjs'; +import { Patterns } from 'src/assets/patterns/patterns'; +import { ToDoListService } from '@global-user/components/habit/add-new-habit/habit-edit-to-do-list/to-do-list.service'; +import { LocalStorageService } from '@global-service/localstorage/local-storage.service'; +import { Subscription } from 'stompjs'; +import { AllToDoLists, ToDoList } from '@global-user/models/to-do-list.interface'; +import { TodoStatus } from '@global-user/components/habit/models/todo-status.enum'; + +@Component({ + selector: 'app-to-do-list', + templateUrl: './to-do-list.component.html', + styleUrls: ['./to-do-list.component.scss'] +}) +export class ToDoListComponent implements OnInit { + toDoList: ToDoList[] = []; + toggle: boolean; + private userId: number; + currentLang: string; + profileSubscription: Subscription; + private toDoListCache: AllToDoLists[] | null = null; + private destroy$: Subject = new Subject(); + + constructor( + private localStorageService: LocalStorageService, + private toDoListService: ToDoListService, + private cdr: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.userId = this.localStorageService.getUserId(); + this.subscribeToLangChange(); + } + + private subscribeToLangChange(): void { + this.localStorageService.languageBehaviourSubject.subscribe((lang: string) => { + this.currentLang = lang; + if (this.localStorageService.getUserId()) { + this.getAllToDoLists(); + } + }); + } + + private getAllToDoLists(): void { + if (this.toDoListCache) { + this.updateAllToDoList(this.toDoListCache); + return; + } + + this.toDoListService + .getUserToDoLists(this.currentLang) + .pipe( + takeUntil(this.destroy$), + catchError(() => of([])) + ) + .subscribe((list) => { + this.toDoListCache = list; + this.updateAllToDoList(list); + }); + } + + private updateAllToDoList(list: AllToDoLists[]): void { + const customToDoList = this.convertToDoList(list, 'custom'); + const standardToDoList = this.convertToDoList(list, 'standard'); + customToDoList.forEach((el) => (el.custom = true)); + this.toDoList = [...customToDoList, ...standardToDoList]; + } + + convertToDoList(list: AllToDoLists[], type: string): ToDoList[] { + return list.reduce((acc, obj) => acc.concat(type === 'custom' ? obj.customToDoListItemDto : obj.userToDoListItemDto), []); + } + + isValidURL(url: string): boolean { + return Patterns.isValidURL.test(url); + } + + openCloseList(): void { + this.toggle = !this.toggle; + } + + toggleDone(item: ToDoList): void { + item.status = item.status === TodoStatus.inprogress ? TodoStatus.done : TodoStatus.inprogress; + item.custom ? this.updateStatusCustomItem(item) : this.updateStatusItem(item); + } + + private updateStatusItem(item: ToDoList): void { + this.toDoListService + .updateStandardToDoItemStatus(item, this.currentLang) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.updateToDoList(item); + }); + } + + private updateStatusCustomItem(item: ToDoList): void { + this.toDoListService + .updateCustomToDoItemStatus(this.userId, item) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.updateToDoList(item); + }); + } + + private updateToDoList(item: ToDoList): void { + this.toDoList = this.toDoList.map((el) => (el.id === item.id ? { ...el, status: item.status } : el)); + this.cdr.detectChanges(); + } +} diff --git a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.html b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.html index 01ec463266..9b720e4567 100644 --- a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.html +++ b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.html @@ -110,10 +110,10 @@

{{ 'homepage.notifications.title' | translate }}

(keydown)="navigate($event)" >

- -
diff --git a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.spec.ts b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.spec.ts index fe7466520b..a0c7fc8dda 100644 --- a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.spec.ts +++ b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.spec.ts @@ -30,8 +30,8 @@ describe('UserNotificationsComponent', () => { const notifications = [ { - actionUserId: 2, - actionUserText: 'testUser', + actionUserId: [2], + actionUserText: ['testUser'], bodyText: 'test texts', message: 'test message', notificationId: 5, @@ -45,8 +45,8 @@ describe('UserNotificationsComponent', () => { viewed: false }, { - actionUserId: 1, - actionUserText: 'testUser1', + actionUserId: [1], + actionUserText: ['testUser1'], bodyText: 'test texts1', message: 'test message1', notificationId: 2, @@ -140,6 +140,50 @@ describe('UserNotificationsComponent', () => { expect(component.filterCriteriaOptions.find((el) => el.name === FilterCriteria.ORIGIN).isSelected).toBeTrue(); }); + it('should navigate to news page when notification type is ECONEWS', fakeAsync(() => { + const event = new MouseEvent('click'); + const target = document.createElement('div'); + target.setAttribute('data-notificationType', 'ECONEWS'); + target.setAttribute('data-targetid', '5'); + + const customEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + composed: true + }); + + Object.defineProperty(customEvent, 'target', { value: target }); + + spyOn(component, 'navigate').and.callThrough(); + + component.navigate(customEvent); + tick(); + + expect(routerMock.navigate).toHaveBeenCalledWith(['news', 5]); + })); + + it('should navigate to habit editing page when notification type is HABIT', fakeAsync(() => { + const event = new MouseEvent('click'); + const target = document.createElement('div'); + target.setAttribute('data-notificationType', 'HABIT'); + target.setAttribute('data-targetid', '3'); + + const customEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + composed: true + }); + + Object.defineProperty(customEvent, 'target', { value: target }); + + spyOn(component, 'navigate').and.callThrough(); + + component.navigate(customEvent); + tick(); + + expect(routerMock.navigate).toHaveBeenCalledWith(['profile', 1, 'allhabits', 'edithabit', 3]); + })); + it('should return checkSelectedFilter', () => { component.filterCriteriaOptions = filterCriteriaOptions; expect(component.checkSelectedFilter(FilterCriteria.TYPE)).toBeFalsy(); diff --git a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.ts b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.ts index 49a7aff636..38e5c3a6e1 100644 --- a/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.ts +++ b/src/app/main/component/user/components/profile/user-notifications/user-notifications.component.ts @@ -190,8 +190,8 @@ export class UserNotificationsComponent implements OnInit, OnDestroy { const parsedDate = new Date(body.notificationTime); const isValidDate = !isNaN(parsedDate.getTime()); return { - actionUserId: 0, - actionUserText: '', + actionUserId: [], + actionUserText: [], bodyText: body.body || '', message: body.body || '', notificationId: body.id, @@ -293,8 +293,32 @@ export class UserNotificationsComponent implements OnInit, OnDestroy { const userId = this.userService.userId; const targetTextContent = target.textContent?.trim() || ''; const targetUserId = target.getAttribute('data-userid')?.toString(); - if ((event instanceof MouseEvent || (event instanceof KeyboardEvent && event.key === 'Enter')) && targetUserId) { + const notificationType = target.getAttribute('data-notificationType'); + const targetId = Number(target.getAttribute('data-targetid')); + + const isClickOrEnter = event instanceof MouseEvent || (event instanceof KeyboardEvent && event.key === 'Enter'); + if (!isClickOrEnter) { + return; + } + + if (targetUserId) { this.router.navigate(['profile', userId, 'users', targetTextContent, targetUserId]); + return; + } + + if (targetId && notificationType) { + const routes = { + EVENT: ['events', targetId], + ECONEWS: ['news', targetId], + HABIT: ['profile', userId, 'allhabits', 'edithabit', targetId] + }; + + for (const type in routes) { + if (notificationType.includes(type)) { + this.router.navigate(routes[type]); + return; + } + } } } diff --git a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.html b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.html index e6ab00aef0..2ea3c9ee0a 100644 --- a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.html +++ b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.html @@ -1,7 +1,7 @@
-
+
absent-friends

{{ 'user.habit.all-habits.title' | translate }}

@@ -12,7 +12,7 @@

{{ 'user.habit.all-habits.title' | translate }}

-
+
absent-friends

{{ 'user.habit.all-habits.mutual' | translate }}

@@ -24,7 +24,7 @@

{{ 'user.habit.all-habits.mutual' | translate }}

-
+
absent-friends

{{ 'profile.dashboard.my-habits' | translate }}

diff --git a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.scss b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.scss index 358f2d9664..f9fce7a920 100644 --- a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.scss +++ b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-dashboard/friend-profile-dashboard.component.scss @@ -1,3 +1,5 @@ +@import 'src/typography/_resp.scss'; + .dashboard { font-family: var(--primary-font); margin-bottom: 30px; @@ -80,34 +82,56 @@ max-width: 100%; } -@media (min-width: 768px) { +@include responsiveMobileFirst(md) { .friends-item { width: 469px; } } -@media (min-width: 1200px) { +@include responsiveMobileFirst(xl) { + .dashboard-container { + max-width: 900px; + overflow-x: auto; + } + .friends-list { display: grid; grid-template-columns: 417px 417px; } } +@include responsiveMobileFirst(3xl) { + .dashboard-container { + max-width: 1220px; + overflow-x: auto; + } +} + +@include responsivePCFirst(md) { + .dashboard-container { + padding: 0; + } +} + .gallery { display: grid; - grid-template-columns: 250px; + grid-template-columns: minmax(280px, 350px); grid-gap: 30px; justify-content: center; - @media (min-width: 576px) { - grid-template-columns: repeat(2, 50%); + @include responsiveMobileFirst(md) { + grid-template-columns: repeat(1, minmax(200px, 380px)); + } + + @include responsiveMobileFirst(lg) { + grid-template-columns: repeat(2, minmax(250px, 350px)); } - @media (min-width: 992px) { - grid-template-columns: repeat(3, 199px); + @include responsiveMobileFirst(2xl) { + grid-template-columns: repeat(2, 350px); } - @media (min-width: 1200px) { + @include responsiveMobileFirst(3xl) { grid-template-columns: repeat(3, 350px); } } diff --git a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.html b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.html index 7faa61b377..88c1a4ff0a 100644 --- a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.html +++ b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.scss b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.scss index 6e33e98830..9002de6533 100644 --- a/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.scss +++ b/src/app/main/component/user/components/profile/users-friends/friend-dashboard/friend-profile-page/friend-profile-page.component.scss @@ -76,7 +76,7 @@ @include responsiveMobileFirst(xl) { .profile-wrapper { .left-column { - .app-shopping-list { + .app-to-do-list { display: none; } } diff --git a/src/app/main/component/user/directives/notific-content-replace.directive.spec.ts b/src/app/main/component/user/directives/notific-content-replace.directive.spec.ts index c93601227b..0998c51f63 100644 --- a/src/app/main/component/user/directives/notific-content-replace.directive.spec.ts +++ b/src/app/main/component/user/directives/notific-content-replace.directive.spec.ts @@ -15,16 +15,16 @@ describe('NotificContentReplaceDirective', () => { let component: TestComponent; const notification = { - actionUserId: null, - actionUserText: 'testUser', + actionUserId: [2, 3], + actionUserText: ['testUser1', 'testUser2'], bodyText: 'test texts', message: 'test message', notificationId: 5, notificationType: 'Eco_NEWS', - projectName: 'GreeCity', + projectName: 'GreenCity', secondMessage: 'secondMessageTest', secondMessageId: 'secondMessageId', - targetId: null, + targetId: 10, time: '', titleText: 'test title', viewed: false @@ -41,6 +41,29 @@ describe('NotificContentReplaceDirective', () => { paragrEl = fixture.nativeElement.querySelector('p'); }); + it('should display multiple user replacements', () => { + component.notification = { ...notification, ...{ bodyText: '{user1} and {user2} liked your post' } }; + fixture.detectChanges(); + expect(paragrEl.textContent).toBe('testUser1 and testUser2 liked your post'); + expect(paragrEl.innerHTML).toBe(`testUser1 and testUser2 liked your post`); + }); + + it('should leave placeholders as is if replacements are missing', () => { + component.notification = { ...notification, ...{ bodyText: 'Hello {missingKey}' } }; + fixture.detectChanges(); + expect(paragrEl.textContent).toBe('Hello {missingKey}'); + }); + + it('should handle multiple users in a single string replacement', () => { + component.notification = { + ...notification, + ...{ bodyText: '{user1},{user2} interacted with your post', actionUserId: [2, 3], actionUserText: ['testUser1', 'testUser2'] } + }; + fixture.detectChanges(); + expect(paragrEl.textContent).toBe('testUser1,testUser2 interacted with your post'); + expect(paragrEl.innerHTML).toBe('testUser1,testUser2 interacted with your post'); + }); + it('should use body text when there are no property to set', () => { component.notification = notification; fixture.detectChanges(); @@ -54,10 +77,13 @@ describe('NotificContentReplaceDirective', () => { expect(paragrEl.innerHTML).toBe('commented event test message'); }); - it('should add property value to the content and ancor tag', () => { - component.notification = { ...notification, ...{ bodyText: '{user} commented event {message}', actionUserId: 2 } }; + it('should add property value to the content and anchor tag', () => { + component.notification = { + ...notification, + ...{ bodyText: '{user1},{user2} commented event {message}', actionUserId: [2, 3], actionUserText: ['testUser1', 'testUser2'] } + }; fixture.detectChanges(); - expect(paragrEl.textContent).toBe('testUser commented event test message'); - expect(paragrEl.innerHTML).toBe(`testUser commented event test message`); + expect(paragrEl.textContent).toBe('testUser1,testUser2 commented event test message'); + expect(paragrEl.innerHTML).toBe('testUser1,testUser2 commented event test message'); }); }); diff --git a/src/app/main/component/user/directives/notific-content-replace.directive.ts b/src/app/main/component/user/directives/notific-content-replace.directive.ts index cb79498197..598a7ed336 100644 --- a/src/app/main/component/user/directives/notific-content-replace.directive.ts +++ b/src/app/main/component/user/directives/notific-content-replace.directive.ts @@ -9,6 +9,8 @@ export class NotificContentReplaceDirective implements OnChanges { @Input() replacements: NotificationModel; replacementKeys = [ { contentKey: 'user', replacementKey: 'actionUserText', idToNavigate: 'actionUserId' }, + { contentKey: 'user1', replacementKey: 'actionUserText', idToNavigate: 'actionUserId' }, + { contentKey: 'user2', replacementKey: 'actionUserText', idToNavigate: 'actionUserId' }, { contentKey: 'message', replacementKey: 'message' }, { contentKey: 'secondMessage', replacementKey: 'secondMessage' } ]; @@ -26,15 +28,46 @@ export class NotificContentReplaceDirective implements OnChanges { } } - private replaceContent(content: string, replacements): string { + private replaceContent(content: string, replacements: NotificationModel): string { let result = content; - this.replacementKeys.forEach((el) => { - if (replacements.hasOwnProperty(el.replacementKey)) { - result = el.idToNavigate - ? result.replace(`{${el.contentKey}}`, `${replacements[el.replacementKey]}`) - : result.replace(`{${el.contentKey}}`, replacements[el.replacementKey]); + + this.replacementKeys.forEach(({ contentKey, replacementKey, idToNavigate }) => { + if (contentKey === 'secondMessage' && replacements.notificationType) { + result = this.buildReplacementString(result, contentKey, replacements[replacementKey], { + targetId: replacements.targetId, + notificationId: replacements.notificationId, + notificationType: replacements.notificationType + }); + } else if (['user1', 'user2'].includes(contentKey)) { + const index = parseInt(contentKey.slice(-1)) - 1; + if (replacements.actionUserText?.[index] && replacements.actionUserId?.[index]) { + result = this.buildReplacementString(result, contentKey, replacements.actionUserText[index], { + userId: replacements.actionUserId[index] + }); + } + } else if (replacements.hasOwnProperty(replacementKey)) { + const linkAttributes = idToNavigate ? { userId: replacements[idToNavigate] } : null; + result = this.buildReplacementString(result, contentKey, replacements[replacementKey], linkAttributes); } }); + return result; } + + private buildReplacementString( + content: string, + placeholder: string, + text: string, + attributes: Record | null + ): string { + if (!attributes) { + return content.replace(`{${placeholder}}`, text); + } + + const attrString = Object.entries(attributes) + .map(([key, value]) => `data-${key}="${value}"`) + .join(' '); + + return content.replace(`{${placeholder}}`, `${text}`); + } } diff --git a/src/app/main/component/user/models/edit-profile.model.ts b/src/app/main/component/user/models/edit-profile.model.ts index cf8654c222..f84b8bcd3e 100644 --- a/src/app/main/component/user/models/edit-profile.model.ts +++ b/src/app/main/component/user/models/edit-profile.model.ts @@ -6,7 +6,7 @@ export class EditProfileModel { rating: number | null; showEcoPlace: boolean; showLocation: boolean; - showShoppingList: boolean; + showToDoList: boolean; socialNetworks: Array<{ id: number; url: string }>; notificationPreferences: NotificationPreference[]; } @@ -17,7 +17,7 @@ export class EditProfileDto { userCredo: string; showEcoPlace: boolean; showLocation: boolean; - showShoppingList: boolean; + showToDoList: boolean; socialNetworks: Array; emailPreferences: NotificationPreference[]; } diff --git a/src/app/main/component/user/models/notification.model.ts b/src/app/main/component/user/models/notification.model.ts index d812cc0444..0f64000cab 100644 --- a/src/app/main/component/user/models/notification.model.ts +++ b/src/app/main/component/user/models/notification.model.ts @@ -11,8 +11,8 @@ export interface NotificationArrayModel { } export interface NotificationModel { - actionUserId: number; - actionUserText: string; + actionUserId: number[]; + actionUserText: string[]; bodyText: string; message: string; notificationId: number; diff --git a/src/app/main/component/user/models/shoppinglist.interface.ts b/src/app/main/component/user/models/shoppinglist.interface.ts deleted file mode 100644 index c65e0db816..0000000000 --- a/src/app/main/component/user/models/shoppinglist.interface.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TodoStatus } from '@global-user/components/habit/models/todo-status.enum'; - -export interface ShoppingList { - id: null | number; - status: TodoStatus; - text: string; - selected?: boolean; - custom?: boolean; -} - -export interface AllShoppingLists { - userShoppingListItemDto: ShoppingList[]; - customShoppingListItemDto: ShoppingList[]; -} - -export interface CustomShoppingItem { - text: string; -} - -export interface HabitUpdateShopList { - habitAssignId: number; - standardShopList: ShoppingList[]; - customShopList: ShoppingList[]; - lang: string; -} diff --git a/src/app/main/component/user/models/to-do-list.interface.ts b/src/app/main/component/user/models/to-do-list.interface.ts new file mode 100644 index 0000000000..a99e67747e --- /dev/null +++ b/src/app/main/component/user/models/to-do-list.interface.ts @@ -0,0 +1,25 @@ +import { TodoStatus } from '@global-user/components/habit/models/todo-status.enum'; + +export interface ToDoList { + id: null | number; + status: TodoStatus; + text: string; + selected?: boolean; + custom?: boolean; +} + +export interface AllToDoLists { + userToDoListItemDto: ToDoList[]; + customToDoListItemDto: ToDoList[]; +} + +export interface CustomToDoItem { + text: string; +} + +export interface HabitUpdateToDoList { + habitAssignId: number; + standardToDoList: ToDoList[]; + customToDoList: ToDoList[]; + lang: string; +} diff --git a/src/app/main/component/user/user.module.ts b/src/app/main/component/user/user.module.ts index b458ae9ac9..af9ff5ecd0 100644 --- a/src/app/main/component/user/user.module.ts +++ b/src/app/main/component/user/user.module.ts @@ -31,11 +31,11 @@ import { ProfileWidgetComponent, ProfileHeaderComponent, ProfileProgressComponent, - ShoppingListComponent, UserSettingComponent, EditProfileComponent, PersonalPhotoComponent, - SocialNetworksComponent + SocialNetworksComponent, + ToDoListComponent } from './components'; import { ShowFirstNLettersPipe } from '@pipe/show-first-n-letters/show-first-n-letters.pipe'; import { ShowFirstNPipe } from '@pipe/show-first-n-pipe/show-first-n.pipe'; @@ -55,7 +55,7 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { HabitProgressComponent } from './components/habit/add-new-habit/habit-progress/habit-progress.component'; import { HabitInviteFriendsComponent } from './components/habit/add-new-habit/habit-invite-friends/habit-invite-friends.component'; import { HabitDurationComponent } from './components/habit/add-new-habit/habit-duration/habit-duration.component'; -import { HabitEditShoppingListComponent } from './components/habit/add-new-habit/habit-edit-shopping-list/habit-edit-shopping-list.component'; +import { HabitEditToDoListComponent } from './components/habit/add-new-habit/habit-edit-to-do-list/habit-edit-to-do-list.component'; import { AddNewHabitComponent } from './components/habit/add-new-habit/add-new-habit.component'; import { GradientDirective } from './components/habit/add-new-habit/habit-duration/gradient.directive'; import { FriendDashboardComponent } from './components/profile/users-friends/friend-dashboard/friend-dashboard.component'; @@ -119,7 +119,7 @@ import { AchievementItemComponent } from './components/profile/users-achievement ProfileProgressComponent, ProfileComponent, EcoPlacesComponent, - ShoppingListComponent, + ToDoListComponent, CalendarComponent, EditProfileComponent, PersonalPhotoComponent, @@ -133,7 +133,7 @@ import { AchievementItemComponent } from './components/profile/users-achievement HabitProgressComponent, HabitInviteFriendsComponent, HabitDurationComponent, - HabitEditShoppingListComponent, + HabitEditToDoListComponent, GradientDirective, FriendDashboardComponent, AllFriendsComponent, diff --git a/src/app/main/model/goal/HabitAssignCustomPropertiesDto.ts b/src/app/main/model/goal/HabitAssignCustomPropertiesDto.ts index 6e7d069730..1f3af908dd 100644 --- a/src/app/main/model/goal/HabitAssignCustomPropertiesDto.ts +++ b/src/app/main/model/goal/HabitAssignCustomPropertiesDto.ts @@ -1,13 +1,13 @@ -import { CustomShoppingItem } from '@global-user/models/shoppinglist.interface'; +import { CustomToDoItem } from '@global-user/models/to-do-list.interface'; export interface HabitAssignCustomPropertiesDto { friendsIdsList: Array; habitAssignPropertiesDto: HabitAssignPropertiesDto; - customShoppingListItemList: Array; + customToDoListItemList: Array; } export interface HabitAssignPropertiesDto { - defaultShoppingListItems: Array; + defaultToDoListItems: Array; duration: number; isPrivate: boolean; } diff --git a/src/app/main/model/search/eventsSearch.model.ts b/src/app/main/model/search/eventsSearch.model.ts index b5ad483536..abecaaa015 100644 --- a/src/app/main/model/search/eventsSearch.model.ts +++ b/src/app/main/model/search/eventsSearch.model.ts @@ -1,6 +1,5 @@ export interface EventsSearchModel { id: number; title: string; - creationDate: string; - tags: Array; + tags: string[]; } diff --git a/src/app/main/model/search/newsSearch.model.ts b/src/app/main/model/search/newsSearch.model.ts index 71bbd8bd91..8b1b81e898 100644 --- a/src/app/main/model/search/newsSearch.model.ts +++ b/src/app/main/model/search/newsSearch.model.ts @@ -1,10 +1,5 @@ export interface NewsSearchModel { id: number; title: string; - author: { - id: number; - name: string; - }; - creationDate: string; - tags: Array; + tags: string[]; } diff --git a/src/app/main/model/search/placesSearch.model.ts b/src/app/main/model/search/placesSearch.model.ts new file mode 100644 index 0000000000..8fdf08d754 --- /dev/null +++ b/src/app/main/model/search/placesSearch.model.ts @@ -0,0 +1,5 @@ +export interface PlacesSearchModel { + id: number; + name: string; + category: string; +} diff --git a/src/app/main/model/search/search.model.ts b/src/app/main/model/search/search.model.ts index ae64286a46..8fe72c8e6e 100644 --- a/src/app/main/model/search/search.model.ts +++ b/src/app/main/model/search/search.model.ts @@ -1,17 +1,6 @@ -import { NewsSearchModel } from './newsSearch.model'; -import { TipsSearchModel } from './tipsSearch.model'; -import { EventsSearchModel } from './eventsSearch.model'; - -export interface SearchModel { - countOfResults: number; - ecoNews: Array; - events: Array; - tipsAndTricks: Array; -} - -export interface SearchDataModel { +export interface SearchDataModel { currentPage: number; - page: Array; + page: Array; totalElements: number; totalPages: number; } diff --git a/src/app/main/service/habit-assign/habit-assign.service.spec.ts b/src/app/main/service/habit-assign/habit-assign.service.spec.ts index ffb68b18bd..2ad6a7f794 100644 --- a/src/app/main/service/habit-assign/habit-assign.service.spec.ts +++ b/src/app/main/service/habit-assign/habit-assign.service.spec.ts @@ -109,11 +109,11 @@ describe('HabitService', () => { const HABIT_ASSIGN_CUSTOM: HabitAssignCustomPropertiesDto = { friendsIdsList: [2, 3, 4], habitAssignPropertiesDto: { - defaultShoppingListItems: [], + defaultToDoListItems: [], duration: 15, isPrivate: true }, - customShoppingListItemList: [{ text: '1234567890' }] + customToDoListItemList: [{ text: '1234567890' }] }; const spy = spyOn(service, 'assignCustomHabit'); diff --git a/src/app/main/service/habit/habit.service.spec.ts b/src/app/main/service/habit/habit.service.spec.ts index 28e82f35b8..3d3fab39b8 100644 --- a/src/app/main/service/habit/habit.service.spec.ts +++ b/src/app/main/service/habit/habit.service.spec.ts @@ -20,8 +20,8 @@ import { MOCK_CUSTOM_HABIT_RESPONSE, MOCK_FRIEND_PROFILE_PICTURES, MOCK_HABITS, - SHOPLIST -} from '@global-user/components/habit/mocks/shopping-list-mock'; + TODOLIST +} from '@global-user/components/habit/mocks/to-do-list-mock'; import { TAGLIST } from '@global-user/components/habit/mocks/tags-list-mock'; import { HttpParams, HttpResponse } from '@angular/common/http'; @@ -96,16 +96,16 @@ describe('HabitService', () => { req.flush(CUSTOMHABIT); }); - it('should return habit shopping list', () => { - habitService.getHabitShoppingList(2).subscribe((data) => { - expect(data).toBe(SHOPLIST); + it('should return habit toDo list', () => { + habitService.getHabitToDoList(2).subscribe((data) => { + expect(data).toBe(TODOLIST); }); - const req = httpMock.expectOne(`${habitLink}/2/shopping-list?lang=en`); + const req = httpMock.expectOne(`${habitLink}/2/to-do-list?lang=en`); expect(req.cancelled).toBeFalsy(); expect(req.request.responseType).toEqual('json'); expect(req.request.method).toBe('GET'); - req.flush(SHOPLIST); + req.flush(TODOLIST); }); it('should return habit tags', () => { diff --git a/src/app/main/service/habit/habit.service.ts b/src/app/main/service/habit/habit.service.ts index 084c3de7d5..abae933d90 100644 --- a/src/app/main/service/habit/habit.service.ts +++ b/src/app/main/service/habit/habit.service.ts @@ -7,7 +7,7 @@ import { habitLink } from '../../links'; import { TagInterface } from '@shared/components/tag-filter/tag-filter.model'; import { environment } from '@environment/environment'; import { HabitInterface, HabitListInterface } from '@global-user/components/habit/models/interfaces/habit.interface'; -import { ShoppingList } from '@global-user/models/shoppinglist.interface'; +import { ToDoList } from '@global-user/models/to-do-list.interface'; import { CustomHabitDtoRequest, CustomHabit } from '@global-user/components/habit/models/interfaces/custom-habit.interface'; import { FriendProfilePicturesArrayModel } from '@global-user/models/friend.model'; import { FileHandle } from '@eco-news-models/create-news-interface'; @@ -54,8 +54,8 @@ export class HabitService { return this.http.get(`${habitLink}/${id}?lang=${this.language}`); } - getHabitShoppingList(id: number): Observable> { - return this.http.get>(`${habitLink}/${id}/shopping-list?lang=${this.language}`); + getHabitToDoList(id: number): Observable> { + return this.http.get>(`${habitLink}/${id}/to-do-list?lang=${this.language}`); } getAllTags(): Observable> { @@ -97,7 +97,7 @@ export class HabitService { complexity: habit.complexity, defaultDuration: habit.duration, tagIds: habit.tagIds, - customShoppingListItemDto: habit.shopList + customToDoListItemDto: habit.toDoList }; const formData = new FormData(); diff --git a/src/app/main/service/search/search.service.ts b/src/app/main/service/search/search.service.ts index 81a011c20c..7a6b81087d 100644 --- a/src/app/main/service/search/search.service.ts +++ b/src/app/main/service/search/search.service.ts @@ -2,8 +2,9 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '@environment/environment'; import { Observable, Subject } from 'rxjs'; -import { SearchDataModel, SearchModel } from '../../model/search/search.model'; +import { SearchDataModel } from '../../model/search/search.model'; import { SearchDto } from 'src/app/main/component/layout/components/models/search-dto'; +import { SearchCategory } from 'src/app/shared/search-popup/search-consts'; @Injectable({ providedIn: 'root' @@ -15,8 +16,8 @@ export class SearchService { allSearchSubject = new Subject(); allElements: SearchDto; - getAllResults(searchQuery: string, category, lang: string): Observable { - return this.http.get(`${this.backEndLink}search/${category}?lang=${lang}&searchQuery=${encodeURI(searchQuery)}`); + getAllResults(searchQuery: string, category: SearchCategory, lang: string): Observable { + return this.http.get(`${this.backEndLink}search/${category}?lang=${lang}&searchQuery=${encodeURI(searchQuery)}`); } getAllResultsByCat( diff --git a/src/app/shared/news-list-gallery-view/news-list-gallery-view.component.html b/src/app/shared/news-list-gallery-view/news-list-gallery-view.component.html index 6a1049a21f..b7161825a4 100644 --- a/src/app/shared/news-list-gallery-view/news-list-gallery-view.component.html +++ b/src/app/shared/news-list-gallery-view/news-list-gallery-view.component.html @@ -2,8 +2,14 @@ user added image