From fd87ef4cddedb422b3842446b5dd3a5a17399068 Mon Sep 17 00:00:00 2001 From: Oleksandr Bohuslavskyi Date: Thu, 21 Nov 2024 16:28:47 -0600 Subject: [PATCH] DSS-918: FE: Adding icon multi-host listings + click through to results --- .../src/app/common/models/listing-details.ts | 1 + .../app/common/models/listing-table-row.ts | 1 + .../common/services/listing-data.service.ts | 38 ++++++------- .../aggregated-listings-table.component.html | 10 +++- .../aggregated-listings-table.component.scss | 17 ++++++ .../aggregated-listings-table.component.ts | 33 ++++++++++-- .../listing-details.component.html | 19 ++++++- .../listing-details.component.scss | 15 ++++++ .../listing-details.component.ts | 54 +++++++++++++++---- frontend/src/assets/images/house-blue.svg | 10 ++++ 10 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 frontend/src/assets/images/house-blue.svg diff --git a/frontend/src/app/common/models/listing-details.ts b/frontend/src/app/common/models/listing-details.ts index 38fb283c..6a4b0cbb 100644 --- a/frontend/src/app/common/models/listing-details.ts +++ b/frontend/src/app/common/models/listing-details.ts @@ -48,6 +48,7 @@ export interface ListingDetails { hasAtLeastOneValidHostEmail: boolean; hostsInfo: Array; bizLicenceInfo: BusinessLicence; + hasMultipleProperties: boolean; } export interface ListingDetailsWithHostCheckboxExtension extends ListingDetails { diff --git a/frontend/src/app/common/models/listing-table-row.ts b/frontend/src/app/common/models/listing-table-row.ts index 715ef21d..b2db5813 100644 --- a/frontend/src/app/common/models/listing-table-row.ts +++ b/frontend/src/app/common/models/listing-table-row.ts @@ -58,6 +58,7 @@ export interface AggregatedListingTableRow { listingCount: number; listings: Array; + hasMultipleProperties: boolean; selected?: boolean; id?: string; } diff --git a/frontend/src/app/common/services/listing-data.service.ts b/frontend/src/app/common/services/listing-data.service.ts index 5eeaecb4..b054ccb8 100644 --- a/frontend/src/app/common/services/listing-data.service.ts +++ b/frontend/src/app/common/services/listing-data.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { forkJoin, Observable } from 'rxjs'; +import { forkJoin, map, Observable } from 'rxjs'; import { environment } from '../../../environments/environment'; import { PagingResponse } from '../models/paging-response'; import { ListingUploadHistoryRecord } from '../models/listing-upload-history-record'; @@ -22,7 +22,7 @@ export class ListingDataService { textHeaders = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8'); - constructor(private httpClient: HttpClient) {} + constructor(private httpClient: HttpClient) { } uploadListings(reportPeriod: string, organizationId: number, file: any): Observable { const formData = new FormData(); @@ -108,14 +108,12 @@ export class ListingDataService { if (filter) { if (filter.byLocation) { if (!!filter.byLocation?.isPrincipalResidenceRequired) { - endpointUrl += `&prRequirement=${ - filter.byLocation.isPrincipalResidenceRequired == 'Yes' - }`; + endpointUrl += `&prRequirement=${filter.byLocation.isPrincipalResidenceRequired == 'Yes' + }`; } if (!!filter.byLocation?.isBusinessLicenceRequired) { - endpointUrl += `&blRequirement=${ - filter.byLocation.isBusinessLicenceRequired == 'Yes' - }`; + endpointUrl += `&blRequirement=${filter.byLocation.isBusinessLicenceRequired == 'Yes' + }`; } } if (filter.byStatus) { @@ -149,6 +147,11 @@ export class ListingDataService { return this.httpClient.get>(endpointUrl); } + getHostListingsCount(primaryHostNm: string): Observable<{ primaryHostNm: string, hasMultipleProperties: boolean }> { + return this.httpClient.get(`${environment.API_HOST}/rentallistings/grouped/count?hostName=${primaryHostNm}`) + .pipe(map(count => ({ primaryHostNm, hasMultipleProperties: count > 1 }))); + } + getAggregatedListings( pageNumber: number = 1, pageSize: number = 10, @@ -185,14 +188,12 @@ export class ListingDataService { if (filter) { if (filter.byLocation) { if (!!filter.byLocation?.isPrincipalResidenceRequired) { - listingsEndpointUrl += `&prRequirement=${ - filter.byLocation.isPrincipalResidenceRequired == 'Yes' - }`; + listingsEndpointUrl += `&prRequirement=${filter.byLocation.isPrincipalResidenceRequired == 'Yes' + }`; } if (!!filter.byLocation?.isBusinessLicenceRequired) { - listingsEndpointUrl += `&blRequirement=${ - filter.byLocation.isBusinessLicenceRequired == 'Yes' - }`; + listingsEndpointUrl += `&blRequirement=${filter.byLocation.isBusinessLicenceRequired == 'Yes' + }`; } } if (filter.byStatus) { @@ -224,11 +225,10 @@ export class ListingDataService { } } - const listingsCountEndpointUrl = `${ - environment.API_HOST - }/rentallistings/grouped/count${listingsEndpointUrl.substring( - listingsEndpointUrl.indexOf('?'), - )}`; + const listingsCountEndpointUrl = `${environment.API_HOST + }/rentallistings/grouped/count${listingsEndpointUrl.substring( + listingsEndpointUrl.indexOf('?'), + )}`; //return this.httpClient.get>(listingsEndpointUrl); return forkJoin({ diff --git a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.html b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.html index 9be46352..22260e5b 100644 --- a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.html +++ b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.html @@ -107,7 +107,13 @@

Aggregated Listings

- {{ row.primaryHostNm }} + +
+ {{ row.primaryHostNm }} + +
+ {{ row.matchAddressTxt }} @@ -386,4 +392,4 @@

Aggregated Listings

(click)="onCancelFilters()"> - + \ No newline at end of file diff --git a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.scss b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.scss index d69eb746..c531142b 100644 --- a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.scss +++ b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.scss @@ -171,6 +171,23 @@ font-size: 12px; word-break: break-word; + .host-name { + width: 100%; + display: flex; + justify-content: start; + align-items: center; + gap: 12px; + + .multihost-icon { + width: 24px; + height: 24px; + background-image: url(../../../../../assets/images/house-blue.svg); + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + } + } + &.listings-count { font-size: smaller; color: #afadad; diff --git a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.ts b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.ts index 0e43739a..b8f35d3c 100644 --- a/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.ts +++ b/frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.ts @@ -14,7 +14,6 @@ import { SidebarModule } from 'primeng/sidebar'; import { TableModule } from 'primeng/table'; import { TagModule } from 'primeng/tag'; import { TooltipModule } from 'primeng/tooltip'; -import { ListingDetailsComponent } from '../listing-details/listing-details.component'; import { ListingFilter } from '../../../../common/models/listing-filter'; import { environment } from '../../../../../environments/environment'; import { PagingResponsePageInfo } from '../../../../common/models/paging-response'; @@ -36,6 +35,7 @@ import { ListingSearchRequest } from '../../../../common/models/listing-search-r import { ListingDetails } from '../../../../common/models/listing-details'; import { OrganizationService } from '../../../../common/services/organization.service'; import { UrlProtocolPipe } from '../../../../common/pipes/url-protocol.pipe'; +import { forkJoin, tap } from 'rxjs'; @Component({ selector: 'app-aggregated-listings-table', @@ -52,7 +52,6 @@ import { UrlProtocolPipe } from '../../../../common/pipes/url-protocol.pipe'; PanelModule, RouterModule, TooltipModule, - ListingDetailsComponent, TagModule, SidebarModule, AccordionModule, @@ -74,6 +73,7 @@ export class AggregatedListingsTableComponent implements OnInit { searchColumns = new Array(); communities = new Array(); groupedCommunities = new Array(); + hosts = new Array<{ primaryHostNm: string, hasMultipleProperties: boolean }>(); isCEU = false; isLegendShown = false; @@ -137,6 +137,11 @@ export class AggregatedListingsTableComponent implements OnInit { if (prms['searchTerm']) { this.searchTerm = prms['searchTerm']; } + if (prms['hostName']) { + this.searchColumn = 'hostName'; + this.searchTerm = prms['hostName']; + } + this.cd.detectChanges(); this.cloakParams(); this.userService.getCurrentUser().subscribe({ @@ -212,7 +217,9 @@ export class AggregatedListingsTableComponent implements OnInit { } onDetailsOpen(row: ListingTableRow): void { - this.router.navigateByUrl(`/listings/${row.rentalListingId}`); + this.router.navigate([`/listings/${row.rentalListingId}`], { + queryParams: { returnUrl: this.getUrlFromState() }, + }); } onNoticeOpen(): void { @@ -377,6 +384,8 @@ export class AggregatedListingsTableComponent implements OnInit { this.sort?.dir || 'asc', searchReq, this.currentFilter, + ).pipe( + tap(res => { this.calculateIfHostsHaveMoreThanOneProperty(res.listings.sourceList); }) ) .subscribe({ next: (res) => { @@ -443,6 +452,24 @@ export class AggregatedListingsTableComponent implements OnInit { this.cd.detectChanges(); } + private calculateIfHostsHaveMoreThanOneProperty(listings: Array): void { + const uniqueObjects = new Map(); + + listings.forEach(obj => { + uniqueObjects.set(obj.primaryHostNm, obj); + }); + + const hosts = Array.from(uniqueObjects.values()).map(x => ({ primaryHostNm: x.primaryHostNm, hasMultipleProperties: false })); + + forkJoin(hosts.map(h => this.listingService.getHostListingsCount(h.primaryHostNm))) + .subscribe(hostCount => { + this.aggregatedListings.forEach(al => { + al.hasMultipleProperties = hostCount.find(x => x.primaryHostNm === al.primaryHostNm)?.hasMultipleProperties ?? false; + this.cd.detectChanges(); + }) + }); + } + private getUrlFromState(): string { const state = { pageNumber: this.currentPage?.pageNumber || 0, diff --git a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.html b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.html index c386cd80..45d788a2 100644 --- a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.html +++ b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.html @@ -135,7 +135,24 @@

Detailed Listing Information for

{{host.isPropertyOwner?'Property':'STR'}} Host (Platform Listing): - {{host.fullNm}} + + @if (host.isPropertyOwner) { + + @if(listing.hasMultipleProperties){ + + + } + @else{ + {{host.fullNm}} + } + + } + @else { + {{host.fullNm}} + } +
{{host.isPropertyOwner?'Property':'STR'}} Host Address: diff --git a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.scss b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.scss index 2e8b0ddd..87d4c34d 100644 --- a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.scss +++ b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.scss @@ -451,4 +451,19 @@ } } } + + .has-multiple-properties { + display: flex; + gap: 8px; + align-items: center; + + .multihost-icon { + width: 24px; + height: 24px; + background-image: url(../../../../../assets/images/house-blue.svg); + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + } + } } \ No newline at end of file diff --git a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.ts b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.ts index 089200e8..5c6170de 100644 --- a/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.ts +++ b/frontend/src/app/features/components/listings-table/listing-details/listing-details.component.ts @@ -3,7 +3,7 @@ import { PanelModule } from 'primeng/panel'; import { ButtonModule } from 'primeng/button'; import { TableModule } from 'primeng/table'; import { CommonModule } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ListingDataService } from '../../../../common/services/listing-data.service'; import { ListingAddressCandidate, ListingDetails } from '../../../../common/models/listing-details'; import { DialogModule } from 'primeng/dialog'; @@ -24,6 +24,7 @@ import { BusinessLicenceService } from '../../../../common/services/business-lic import { BLSearchResultRow } from '../../../../common/models/bl-search-result-row'; import { UrlProtocolPipe } from '../../../../common/pipes/url-protocol.pipe'; import { TextCleanupPipe } from '../../../../common/pipes/text-cleanup.pipe'; +import { tap } from 'rxjs'; @Component({ selector: 'app-listing-details', @@ -42,6 +43,7 @@ import { TextCleanupPipe } from '../../../../common/pipes/text-cleanup.pipe'; TagModule, UrlProtocolPipe, TextCleanupPipe, + RouterModule, ], templateUrl: './listing-details.component.html', styleUrl: './listing-details.component.scss' @@ -67,6 +69,7 @@ export class ListingDetailsComponent implements OnInit { selectedBl!: BLSearchResultRow | null; searchBlText = ''; noBlsFound = false; + returnUrl!: string; constructor( private route: ActivatedRoute, @@ -84,6 +87,9 @@ export class ListingDetailsComponent implements OnInit { this.loaderService.loadingStart(); this.id = this.route.snapshot.params['id']; + this.route.queryParams.subscribe( + (param) => { this.returnUrl = param['returnUrl']; }); + this.userDataService.getCurrentUser().subscribe({ next: (user) => { this.isCEU = user.organizationType === 'BCGov'; @@ -223,6 +229,17 @@ export class ListingDetailsComponent implements OnInit { this.addressChangeCandidates = []; } + navigateToListingsByHost(): void { + const owner = this.listing.hosts.find(x => x.isPropertyOwner); + if (owner) { + const url = this.router.serializeUrl(this.router.createUrlTree([`/aggregated-listings`], { queryParams: { hostName: owner.fullNm } })); + window.open(url, '_blank'); + } + else { + this.errorService.showError(`Unable to retrieve the host's name. Neither host is the owner`); + } + } + onSubmitAddressChange(): void { let observableRef; this.loaderService.loadingStart(); @@ -262,15 +279,30 @@ export class ListingDetailsComponent implements OnInit { private getListingDetailsById(id: number): void { this.loaderService.loadingStart(); - this.listingService.getListingDetailsById(id).subscribe({ - next: (response: ListingDetails) => { - this.listing = response; - this.blInfo = response.bizLicenceInfo; - }, - complete: () => { - this.loaderService.loadingEnd(); - this.cd.detectChanges(); - } - }); + this.listingService.getListingDetailsById(id) + .pipe( + tap((listing) => { + if (listing.hosts.length) { + const owner = listing.hosts.find(x => x.isPropertyOwner); + + if (owner) { + this.listingService.getHostListingsCount(owner.fullNm) + .subscribe(x => { + this.listing.hasMultipleProperties = x.hasMultipleProperties; + this.cd.detectChanges(); + }); + } + } + }) + ).subscribe({ + next: (response: ListingDetails) => { + this.listing = response; + this.blInfo = response.bizLicenceInfo; + }, + complete: () => { + this.loaderService.loadingEnd(); + this.cd.detectChanges(); + } + }); } } diff --git a/frontend/src/assets/images/house-blue.svg b/frontend/src/assets/images/house-blue.svg new file mode 100644 index 00000000..eeced544 --- /dev/null +++ b/frontend/src/assets/images/house-blue.svg @@ -0,0 +1,10 @@ + + + + + + + + + +