Skip to content

Commit

Permalink
Merge pull request #3447 from ita-social-projects/search-eco-places
Browse files Browse the repository at this point in the history
feat: Show places results in search popup
  • Loading branch information
hnativlyubomyr authored Nov 11, 2024
2 parents 4e9843e + e6762dc commit bcbbd6b
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 83 deletions.
3 changes: 1 addition & 2 deletions src/app/main/model/search/eventsSearch.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export interface EventsSearchModel {
id: number;
title: string;
creationDate: string;
tags: Array<string>;
tags: string[];
}
7 changes: 1 addition & 6 deletions src/app/main/model/search/newsSearch.model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
export interface NewsSearchModel {
id: number;
title: string;
author: {
id: number;
name: string;
};
creationDate: string;
tags: Array<string>;
tags: string[];
}
5 changes: 5 additions & 0 deletions src/app/main/model/search/placesSearch.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PlacesSearchModel {
id: number;
name: string;
category: string;
}
15 changes: 2 additions & 13 deletions src/app/main/model/search/search.model.ts
Original file line number Diff line number Diff line change
@@ -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<NewsSearchModel>;
events: Array<EventsSearchModel>;
tipsAndTricks: Array<TipsSearchModel>;
}

export interface SearchDataModel {
export interface SearchDataModel<T = any> {
currentPage: number;
page: Array<TipsSearchModel>;
page: Array<T>;
totalElements: number;
totalPages: number;
}
7 changes: 4 additions & 3 deletions src/app/main/service/search/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -15,8 +16,8 @@ export class SearchService {
allSearchSubject = new Subject<boolean>();
allElements: SearchDto;

getAllResults(searchQuery: string, category, lang: string): Observable<SearchModel> {
return this.http.get<SearchModel>(`${this.backEndLink}search/${category}?lang=${lang}&searchQuery=${encodeURI(searchQuery)}`);
getAllResults(searchQuery: string, category: SearchCategory, lang: string): Observable<SearchDataModel> {
return this.http.get<SearchDataModel>(`${this.backEndLink}search/${category}?lang=${lang}&searchQuery=${encodeURI(searchQuery)}`);
}

getAllResultsByCat(
Expand Down
10 changes: 4 additions & 6 deletions src/app/shared/search-item/search-item.component.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<div class="search_item-wrp">
<div class="search_item-tag" *ngFor="let tag of searchModel.tags">
{{ tag }}
<div class="search_item-tag" *ngFor="let item of commonModel.tagsOrCategory">
{{ item }}
</div>
<div class="search_item-title">
<a [routerLink]="['/' + type, searchModel.id]">
<p>
{{ searchModel.title | slice: 0 : 130 }}
</p>
<a [routerLink]="itemLink">
<p>{{ commonModel.title | slice: 0 : 130 }}</p>
</a>
</div>
</div>
35 changes: 32 additions & 3 deletions src/app/shared/search-item/search-item.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import { userAssignedCardsIcons } from '../../main/image-pathes/profile-icons';
import { NewsSearchModel } from '@global-models/search/newsSearch.model';
import { EventsSearchModel } from '@global-models/search/eventsSearch.model';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { PlacesSearchModel } from '@global-models/search/placesSearch.model';
import { CommonSearchModel } from './search-item.model';

@Component({
selector: 'app-search-item',
templateUrl: './search-item.component.html',
styleUrls: ['./search-item.component.scss']
})
export class SearchItemComponent {
@Input() searchModel: NewsSearchModel | EventsSearchModel;
export class SearchItemComponent implements OnInit {
@Input() searchModel: NewsSearchModel | EventsSearchModel | PlacesSearchModel;
@Input() type: string;
@Output() closeSearch: EventEmitter<boolean> = new EventEmitter();
profileIcons = userAssignedCardsIcons;

commonModel: CommonSearchModel;
itemLink: (string | number)[];

ngOnInit(): void {
this.commonModel = this.convertToCommonModel(this.searchModel);
this.itemLink = ['/' + this.type];
if (this.type !== 'places') {
this.itemLink.push(this.searchModel.id);
}
}

private convertToCommonModel(model: NewsSearchModel | EventsSearchModel | PlacesSearchModel): CommonSearchModel {
if ('tags' in model) {
return {
id: model.id,
title: model.title,
tagsOrCategory: model.tags
};
} else {
return {
id: model.id,
title: model.name,
tagsOrCategory: [model.category]
};
}
}
}
5 changes: 5 additions & 0 deletions src/app/shared/search-item/search-item.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CommonSearchModel {
id: number;
title: string;
tagsOrCategory: string[];
}
5 changes: 3 additions & 2 deletions src/app/shared/search-popup/search-consts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum SearchCategory {
NEWS = 'econews',
EVENTS = 'events'
NEWS = 'eco-news',
EVENTS = 'events',
PLACES = 'places'
}
35 changes: 30 additions & 5 deletions src/app/shared/search-popup/search-popup.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
<div class="search_body-wrapper">
<div class="search-content-wrapper container">
<div class="search_remaining-items" *ngIf="!isLoading && searchInput.value">
{{ itemsFound }} {{ 'search.search-popup.items-found' | translate }}
{{ resultsCounters.total }} {{ 'search.search-popup.items-found' | translate }}
</div>
<div *ngIf="newsElements && newsElements.length > 0">
<a [routerLink]="['/news']" (click)="closeSearch()">
<h3 class="search-title">{{ 'search.search-popup.news' | translate }}</h3>
<h3 class="search-title">
{{ 'search.search-popup.news' | translate }} <span class="counter">({{ resultsCounters.news }})</span>
</h3>
</a>
<div class="list-search-items">
<app-search-item
Expand All @@ -42,7 +44,9 @@ <h3 class="search-title">{{ 'search.search-popup.news' | translate }}</h3>
</div>
<div *ngIf="eventsElements && eventsElements.length > 0">
<a [routerLink]="['/events']" (click)="closeSearch()">
<h3 class="search-title">{{ 'search.search-popup.events' | translate }}</h3>
<h3 class="search-title">
{{ 'search.search-popup.events' | translate }} <span class="counter">({{ resultsCounters.events }})</span>
</h3>
</a>
<div class="list-search-items">
<app-search-item
Expand All @@ -55,7 +59,24 @@ <h3 class="search-title">{{ 'search.search-popup.events' | translate }}</h3>
</app-search-item>
</div>
</div>
<div class="search_see-all" *ngIf="itemsFound">
<div *ngIf="placesElements && placesElements.length > 0">
<a [routerLink]="['/places']" (click)="closeSearch()">
<h3 class="search-title">
{{ 'search.search-popup.places' | translate }} <span class="counter">({{ resultsCounters.places }})</span>
</h3>
</a>
<div class="list-search-items">
<app-search-item
*ngFor="let element of placesElements | slice: 0 : 3"
[searchModel]="element"
[type]="'places'"
(closeSearch)="closeSearch()"
class="search-item"
>
</app-search-item>
</div>
</div>
<div class="search_see-all" *ngIf="resultsCounters.total">
<a
[routerLink]="['/search']"
[queryParams]="{ query: searchInput.value, category: newsElements && newsElements.length > 0 ? 'econews' : 'events' }"
Expand All @@ -65,7 +86,11 @@ <h3 class="search-title">{{ 'search.search-popup.events' | translate }}</h3>
</a>
</div>

<app-search-not-found *ngIf="itemsFound === 0" [inputValue]="searchInput.value" (closeUnsuccessfulSearchResults)="closeSearch()">
<app-search-not-found
*ngIf="resultsCounters.total === 0"
[inputValue]="searchInput.value"
(closeUnsuccessfulSearchResults)="closeSearch()"
>
</app-search-not-found>
</div>
<app-spinner class="search_spinner" *ngIf="isLoading"></app-spinner>
Expand Down
4 changes: 4 additions & 0 deletions src/app/shared/search-popup/search-popup.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ div.search-content-wrapper {
display: block;
}

span.counter {
opacity: 0.5;
}

@media (min-width: 576px) {
.list-search-items {
grid-template-columns: repeat(2, 254px);
Expand Down
41 changes: 11 additions & 30 deletions src/app/shared/search-popup/search-popup.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { LocalStorageService } from '@global-service/localstorage/local-storage.
import { SharedModule } from 'src/app/shared/shared.module';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { EventsSearchModel } from '@global-models/search/eventsSearch.model';
import { SearchDataModel } from '@global-models/search/search.model';
import { SearchCategory } from './search-consts';

describe('SearchPopupComponent', () => {
let component: SearchPopupComponent;
Expand All @@ -26,45 +29,23 @@ describe('SearchPopupComponent', () => {
const localStorageServiceMock: LocalStorageService = jasmine.createSpyObj('LocalStorageService', ['getCurrentLanguage']);
localStorageServiceMock.getCurrentLanguage = () => 'ua' as Language;

const mockTipData = {
id: 1,
title: 'test',
author: {
id: 1,
name: 'test'
},
creationDate: '0101',
tags: ['test']
};

const mockNewsData = {
id: 1,
title: 'test',
author: {
id: 1,
name: 'test'
},
creationDate: '0101',
tags: ['test']
};

const mockEventsData = {
id: 1,
title: 'test',
creationDate: '0101',
tags: ['test']
};

const searchModelMock = {
countOfResults: 2,
ecoNews: [mockNewsData],
events: [mockEventsData],
tipsAndTricks: [mockTipData]
const eventsSearchModelMock: SearchDataModel<EventsSearchModel> = {
currentPage: 1,
page: [mockEventsData],
totalElements: 1,
totalPages: 1
};

const searchMock: SearchService = jasmine.createSpyObj('SearchService', ['getAllResults']);
searchMock.searchSubject = new Subject();
searchMock.getAllResults = () => of(searchModelMock);
searchMock.getAllResults = () => of(eventsSearchModelMock);
searchMock.closeSearchSignal = () => true;

beforeEach(waitForAsync(() => {
Expand Down Expand Up @@ -113,12 +94,12 @@ describe('SearchPopupComponent', () => {

describe('Testing services:', () => {
it('should handle search value changes', fakeAsync(() => {
const getSearchSpy = spyOn(component.searchService, 'getAllResults').and.returnValue(of(searchModelMock));
const getSearchSpy = spyOn(component.searchService, 'getAllResults').and.returnValue(of(eventsSearchModelMock));
component.ngOnInit();

component.searchInput.setValue('test');
tick(300);
expect(getSearchSpy).toHaveBeenCalledWith('test', 'econews', 'ua');
expect(getSearchSpy).toHaveBeenCalledWith('test', SearchCategory.EVENTS, 'ua');
}));

it('closeSearch should open SearchService/closeSearchSignal', () => {
Expand Down
40 changes: 28 additions & 12 deletions src/app/shared/search-popup/search-popup.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,27 @@ import { MatSnackBarComponent } from '@global-errors/mat-snack-bar/mat-snack-bar
import { LocalStorageService } from '@global-service/localstorage/local-storage.service';
import { MatDialog } from '@angular/material/dialog';
import { SearchCategory } from './search-consts';
import { PlacesSearchModel } from '@global-models/search/placesSearch.model';
import { PopupSearchResults } from './search-popup.model';

@Component({
selector: 'app-search-popup',
templateUrl: './search-popup.component.html',
styleUrls: ['./search-popup.component.scss']
})
export class SearchPopupComponent implements OnInit, OnDestroy {
resultsCounters: {
news: number;
events: number;
places: number;
total: number;
} = { events: null, news: null, places: null, total: null };

newsElements: NewsSearchModel[] = [];
eventsElements: EventsSearchModel[] = [];
placesElements: PlacesSearchModel[] = [];

isSearchClicked = false;
itemsFound: number = null;
searchModalSubscription: Subscription;
searchInput = new FormControl('');
isLoading = false;
Expand Down Expand Up @@ -53,15 +63,16 @@ export class SearchPopupComponent implements OnInit, OnDestroy {
this.resetData();
this.isLoading = true;
}),
switchMap((val: string) => {
switchMap((query: string) => {
this.currentLanguage = this.localStorageService.getCurrentLanguage();
return forkJoin([
this.searchService.getAllResults(val, SearchCategory.NEWS, this.currentLanguage),
this.searchService.getAllResults(val, SearchCategory.EVENTS, this.currentLanguage)
]);
return forkJoin({
news: this.searchService.getAllResults(query, SearchCategory.NEWS, this.currentLanguage),
events: this.searchService.getAllResults(query, SearchCategory.EVENTS, this.currentLanguage),
places: this.searchService.getAllResults(query, SearchCategory.PLACES, this.currentLanguage)
});
})
)
.subscribe((data: SearchDataModel[]) => {
.subscribe((data: PopupSearchResults) => {
this.setData(data);
});

Expand All @@ -80,12 +91,17 @@ export class SearchPopupComponent implements OnInit, OnDestroy {
this.snackBar.openSnackBar('error');
}

private setData(data: SearchDataModel[]): void {
private setData(data: PopupSearchResults): void {
this.isLoading = false;

this.newsElements = data[0].page;
this.eventsElements = data[1].page;
this.itemsFound = data[0].totalElements + data[1].totalElements;
this.newsElements = data.news.page;
this.eventsElements = data.events.page;
this.placesElements = data.places.page;

this.resultsCounters.events = data.events.totalElements;
this.resultsCounters.news = data.news.totalElements;
this.resultsCounters.places = data.places.totalElements;
this.resultsCounters.total = data.events.totalElements + data.news.totalElements + data.places.totalElements;
}

private subscribeToSignal(signal: boolean): void {
Expand All @@ -104,7 +120,7 @@ export class SearchPopupComponent implements OnInit, OnDestroy {
private resetData(): void {
this.newsElements = [];
this.eventsElements = [];
this.itemsFound = null;
this.resultsCounters = { events: null, news: null, places: null, total: null };
}

ngOnDestroy() {
Expand Down
10 changes: 10 additions & 0 deletions src/app/shared/search-popup/search-popup.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EventsSearchModel } from '@global-models/search/eventsSearch.model';
import { NewsSearchModel } from '@global-models/search/newsSearch.model';
import { PlacesSearchModel } from '@global-models/search/placesSearch.model';
import { SearchDataModel } from '@global-models/search/search.model';

export interface PopupSearchResults {
news: SearchDataModel<NewsSearchModel>;
events: SearchDataModel<EventsSearchModel>;
places: SearchDataModel<PlacesSearchModel>;
}
Loading

0 comments on commit bcbbd6b

Please sign in to comment.