Skip to content

Commit eecaf91

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents c7ae3f3 + fd1bf92 commit eecaf91

17 files changed

+600
-203
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* Sprint R15 - Indexes for GetRentalListings / count query performance
2+
Targets the base-table listings query and count used by the rental listings table.
3+
Partial index on current listings (including_rental_listing_report_id IS NULL) reduces
4+
work for the main table and count endpoints. */
5+
6+
-- Partial index on current listings: supports base filter and common join/filter columns
7+
CREATE INDEX IF NOT EXISTS dss_rental_listing_i12
8+
ON dss_rental_listing (offering_organization_id, locating_physical_address_id, listing_status_type)
9+
WHERE including_rental_listing_report_id IS NULL;
10+
11+
-- Partial index for status / reassigned / takedown filters
12+
CREATE INDEX IF NOT EXISTS dss_rental_listing_i13
13+
ON dss_rental_listing (listing_status_type, is_taken_down, is_lg_transferred)
14+
WHERE including_rental_listing_report_id IS NULL;
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
import { PagingResponse } from './paging-response';
21
import { AggregatedListingTableRow } from './listing-table-row';
32

4-
export interface ListingResponseWithCounts<T> extends PagingResponse<T> {
5-
recentCount: number;
6-
allCount: number;
7-
}
8-
9-
export interface AggregatedListingResponseWithCounts {
3+
export interface AggregatedListingResponse {
104
data: AggregatedListingTableRow[];
11-
recentCount: number;
12-
allCount: number;
135
}
14-

frontend/src/app/common/models/listing-table-row.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export interface ListingTableRow {
1818
isActive?: boolean;
1919
isNew?: boolean;
2020
isTakenDown?: boolean;
21+
isLgTransferred?: boolean;
22+
isMatchVerified?: boolean;
23+
isMatchCorrected?: boolean;
24+
isChangedAddress?: boolean;
2125
takeDownReason?: string;
2226
platformListingUrl?: string;
2327
matchScoreAmt?: number;

frontend/src/app/common/services/listing-data.service.ts

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { map, Observable } from 'rxjs';
44
import { environment } from '../../../environments/environment';
55
import { PagingResponse } from '../models/paging-response';
66
import { ListingUploadHistoryRecord } from '../models/listing-upload-history-record';
7-
import { ListingTableRow } from '../models/listing-table-row';
7+
import { AggregatedListingTableRow, ListingTableRow } from '../models/listing-table-row';
88
import { ListingSearchRequest } from '../models/listing-search-request';
99
import { ListingAddressCandidate, ListingDetails } from '../models/listing-details';
1010
import { ExportJurisdiction } from '../models/export-listing';
1111
import { ListingFilter } from '../models/listing-filter';
12-
import { ListingResponseWithCounts, AggregatedListingResponseWithCounts } from '../models/listing-response-with-counts';
1312

1413
@Injectable({
1514
providedIn: 'root',
@@ -73,90 +72,114 @@ export class ListingDataService {
7372
);
7473
}
7574

76-
getListings(
77-
pageNumber: number = 1,
78-
pageSize: number = 10,
79-
orderBy: string = '',
80-
direction: 'asc' | 'desc' = 'asc',
81-
searchReq: ListingSearchRequest = {},
75+
/**
76+
* Builds query string for listings filter params (searchReq, filter, recent).
77+
* Used by both getListings and getListingsCount.
78+
*/
79+
private buildListingsFilterParams(
80+
searchReq: ListingSearchRequest,
8281
filter?: ListingFilter,
8382
recent: boolean = false,
84-
): Observable<ListingResponseWithCounts<ListingTableRow>> {
85-
let endpointUrl = `${environment.API_HOST}/rentallistings?pageSize=${pageSize}&pageNumber=${pageNumber}`;
86-
87-
if (orderBy) {
88-
endpointUrl += `&orderBy=${orderBy}&direction=${direction}`;
89-
}
90-
83+
): string {
84+
const params: string[] = [];
9185
if (recent) {
92-
endpointUrl += `&recent=${recent}`;
86+
params.push(`recent=${recent}`);
9387
}
94-
9588
if (searchReq.all) {
96-
endpointUrl += `&all=${searchReq.all}`;
89+
params.push(`all=${encodeURIComponent(searchReq.all)}`);
9790
}
9891
if (searchReq.address) {
99-
endpointUrl += `&address=${searchReq.address}`;
92+
params.push(`address=${encodeURIComponent(searchReq.address)}`);
10093
}
10194
if (searchReq.url) {
102-
endpointUrl += `&url=${searchReq.url}`;
95+
params.push(`url=${encodeURIComponent(searchReq.url)}`);
10396
}
10497
if (searchReq.listingId) {
105-
endpointUrl += `&listingId=${searchReq.listingId}`;
98+
params.push(`listingId=${encodeURIComponent(searchReq.listingId)}`);
10699
}
107100
if (searchReq.hostName) {
108-
endpointUrl += `&hostName=${searchReq.hostName}`;
101+
params.push(`hostName=${encodeURIComponent(searchReq.hostName)}`);
109102
}
110103
if (searchReq.businessLicence) {
111-
endpointUrl += `&businessLicence=${searchReq.businessLicence}`;
104+
params.push(`businessLicence=${encodeURIComponent(searchReq.businessLicence)}`);
112105
}
113106
if (searchReq.registrationNumber) {
114-
endpointUrl += `&registrationNumber=${searchReq.registrationNumber}`;
107+
params.push(`registrationNumber=${encodeURIComponent(searchReq.registrationNumber)}`);
115108
}
116-
117109
if (filter) {
118110
if (filter.byLocation) {
119111
if (!!filter.byLocation?.isPrincipalResidenceRequired) {
120-
endpointUrl += `&prRequirement=${filter.byLocation.isPrincipalResidenceRequired == 'Yes'
121-
}`;
112+
params.push(`prRequirement=${filter.byLocation.isPrincipalResidenceRequired == 'Yes'}`);
122113
}
123114
if (!!filter.byLocation?.isBusinessLicenceRequired) {
124-
endpointUrl += `&blRequirement=${filter.byLocation.isBusinessLicenceRequired == 'Yes'
125-
}`;
115+
params.push(`blRequirement=${filter.byLocation.isBusinessLicenceRequired == 'Yes'}`);
126116
}
127117
}
128118
if (filter.byStatus) {
129119
if (
130120
filter.byStatus.reassigned !== null &&
131121
filter.byStatus.reassigned !== undefined
132122
) {
133-
endpointUrl += `&reassigned=${!!filter.byStatus.reassigned}`;
123+
params.push(`reassigned=${!!filter.byStatus.reassigned}`);
134124
}
135125
if (
136126
filter.byStatus.takedownComplete !== null &&
137127
filter.byStatus.takedownComplete !== undefined
138128
) {
139-
endpointUrl += `&takedownComplete=${!!filter.byStatus.takedownComplete}`;
129+
params.push(`takedownComplete=${!!filter.byStatus.takedownComplete}`);
140130
}
141-
142131
const statuses = new Array<string>();
143132
if (filter.byStatus.active) {
144133
statuses.push('A');
145-
statuses.push('U'); // Include Update when Active is selected
134+
statuses.push('U');
146135
}
147136
if (filter.byStatus.inactive) statuses.push('I');
148137
if (filter.byStatus.new) statuses.push('N');
149-
150138
if (statuses.length) {
151-
endpointUrl += `&statuses=${statuses.join(',')}`;
139+
params.push(`statuses=${statuses.join(',')}`);
152140
}
153141
}
154142
if (!!filter.community) {
155-
endpointUrl += `&lgId=${filter.community}`;
143+
params.push(`lgId=${filter.community}`);
156144
}
157145
}
146+
return params.length ? '&' + params.join('&') : '';
147+
}
148+
149+
getListingsCount(
150+
searchReq: ListingSearchRequest = {},
151+
filter?: ListingFilter,
152+
recent: boolean = false,
153+
): Observable<number> {
154+
const baseUrl = `${environment.API_HOST}/rentallistings/count`;
155+
const filterParams = this.buildListingsFilterParams(searchReq, filter, recent);
156+
const url = filterParams ? `${baseUrl}?${filterParams.slice(1)}` : baseUrl;
157+
return this.httpClient.get<number>(url);
158+
}
159+
160+
getListings(
161+
pageNumber: number = 1,
162+
pageSize: number = 10,
163+
orderBy: string = '',
164+
direction: 'asc' | 'desc' = 'asc',
165+
searchReq: ListingSearchRequest = {},
166+
filter?: ListingFilter,
167+
recent: boolean = false,
168+
includeTotalCount: boolean = true,
169+
): Observable<PagingResponse<ListingTableRow>> {
170+
let endpointUrl = `${environment.API_HOST}/rentallistings?pageSize=${pageSize}&pageNumber=${pageNumber}`;
171+
172+
if (orderBy) {
173+
endpointUrl += `&orderBy=${orderBy}&direction=${direction}`;
174+
}
175+
176+
if (!includeTotalCount) {
177+
endpointUrl += '&includeTotalCount=false';
178+
}
179+
180+
endpointUrl += this.buildListingsFilterParams(searchReq, filter, recent);
158181

159-
return this.httpClient.get<ListingResponseWithCounts<ListingTableRow>>(endpointUrl);
182+
return this.httpClient.get<PagingResponse<ListingTableRow>>(endpointUrl);
160183
}
161184

162185
getHostListingsCount(primaryHostNm: string): Observable<{ primaryHostNm: string, hasMultipleProperties: boolean }> {
@@ -168,7 +191,7 @@ export class ListingDataService {
168191
searchReq: ListingSearchRequest = {},
169192
filter?: ListingFilter,
170193
recent: boolean = false,
171-
): Observable<AggregatedListingResponseWithCounts> {
194+
): Observable<AggregatedListingTableRow[]> {
172195
let listingsEndpointUrl = `${environment.API_HOST}/rentallistings/grouped`;
173196
const params: string[] = [];
174197

@@ -242,8 +265,7 @@ export class ListingDataService {
242265
listingsEndpointUrl += `?${params.join('&')}`;
243266
}
244267

245-
// API now returns object with data, recentCount, and allCount
246-
return this.httpClient.get<AggregatedListingResponseWithCounts>(listingsEndpointUrl);
268+
return this.httpClient.get<AggregatedListingTableRow[]>(listingsEndpointUrl);
247269
}
248270

249271
getListingDetailsById(id: number): Observable<ListingDetails> {

frontend/src/app/common/services/year-month-gen.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class YearMonthGenService {
1111

1212
for (let i = 0; i < numberOfMonths; i++) {
1313
const prevMonth = today.getMonth() - i - 1;
14-
const year = prevMonth < 0 ? today.getFullYear() - 1 : today.getFullYear();
14+
const year = today.getFullYear() + Math.floor(prevMonth / 12);
1515
const month = (prevMonth % 12 + 12) % 12 + 1;
1616
const monthString = month.toString().padStart(2, '0');
1717

frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ <h2 class="title">Aggregated Listings</h2>
4848
<div class="rows-info-wrapper">
4949
<div class="panel-header-left">
5050
<div class="toggle-container">
51-
<span class="toggle-label" [class.active]="showRecentOnly">Recently Reported ({{recentCount}})</span>
51+
<span class="toggle-label" [class.active]="showRecentOnly">Recently Reported</span>
5252
<p-toggleSwitch [ngModel]="!showRecentOnly" (ngModelChange)="showRecentOnly = !$event; onToggleChange()"
5353
[inputId]="'recent-toggle-aggregated'"></p-toggleSwitch>
54-
<span class="toggle-label" [class.active]="!showRecentOnly">All Listings ({{allCount}})</span>
54+
<span class="toggle-label" [class.active]="!showRecentOnly">All Listings</span>
5555
</div>
5656
<span class="panel-header-small" [class.hidden]="!listingsSelected">Selected
5757
{{listingsSelected}} items. <button pButton class="p-button-link zero-padding small-text"

frontend/src/app/features/components/listings-table/aggregated-listings-table/aggregated-listings-table.component.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
ListingTableRow,
2828
} from '../../../../common/models/listing-table-row';
2929
import { ListingDataService } from '../../../../common/services/listing-data.service';
30-
import { AggregatedListingResponseWithCounts } from '../../../../common/models/listing-response-with-counts';
3130
import { FilterPersistenceService } from '../../../../common/services/filter-persistence.service';
3231
import { GlobalLoaderService } from '../../../../common/services/global-loader.service';
3332
import { SelectedListingsStateService } from '../../../../common/services/selected-listings-state.service';
@@ -88,8 +87,6 @@ export class AggregatedListingsTableComponent implements OnInit {
8887
currentFilter!: ListingFilter;
8988
cancelableFilter!: ListingFilter;
9089
showRecentOnly = true; // Default to "Recently Reported"
91-
recentCount = 0;
92-
allCount = 0;
9390
readonly addressLowScore = Number.parseInt(environment.ADDRESS_SCORE);
9491

9592
// Track which rows are currently loading/expanding to prevent multiple clicks
@@ -526,13 +523,8 @@ export class AggregatedListingsTableComponent implements OnInit {
526523
this.showRecentOnly,
527524
)
528525
.subscribe({
529-
next: (response: AggregatedListingResponseWithCounts) => {
530-
// Store counts from API response
531-
this.recentCount = response.recentCount;
532-
this.allCount = response.allCount;
533-
534-
// Store all data - API now returns object with data array
535-
this.aggregatedListings = response.data.map(
526+
next: (response: AggregatedListingTableRow[]) => {
527+
this.aggregatedListings = response.map(
536528
(l: AggregatedListingTableRow, index: number) => {
537529
return {
538530
...l,
@@ -546,7 +538,7 @@ export class AggregatedListingsTableComponent implements OnInit {
546538
);
547539

548540
// Calculate host properties after data is loaded
549-
this.calculateIfHostsHaveMoreThanOneProperty(response.data);
541+
this.calculateIfHostsHaveMoreThanOneProperty(response);
550542

551543
// Initialize currentPage if not exists
552544
if (!this.currentPage) {

frontend/src/app/features/components/listings-table/listings-table.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ <h2 class="title">Individual Listings</h2>
4848
<div class="rows-info-wrapper">
4949
<div class="panel-header-left">
5050
<div class="toggle-container">
51-
<span class="toggle-label" [class.active]="showRecentOnly">Recently Reported ({{recentCount}})</span>
51+
<span class="toggle-label" [class.active]="showRecentOnly">Recently Reported</span>
5252
<p-toggleSwitch [ngModel]="!showRecentOnly" (ngModelChange)="showRecentOnly = !$event; onToggleChange()"
5353
[inputId]="'recent-toggle'"></p-toggleSwitch>
54-
<span class="toggle-label" [class.active]="!showRecentOnly">All Listings ({{allCount}})</span>
54+
<span class="toggle-label" [class.active]="!showRecentOnly">All Listings</span>
5555
</div>
5656
<span class="panel-header-small"
5757
[class.hidden]="!selectedListings.length">Selected

frontend/src/app/features/components/listings-table/listings-table.component.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
22
import { ListingDataService } from '../../../common/services/listing-data.service';
3-
import { PagingResponsePageInfo } from '../../../common/models/paging-response';
4-
import { ListingResponseWithCounts } from '../../../common/models/listing-response-with-counts';
3+
import { PagingResponse, PagingResponsePageInfo } from '../../../common/models/paging-response';
54
import { ListingTableRow } from '../../../common/models/listing-table-row';
65
import { CommonModule } from '@angular/common';
76
import { TableModule } from 'primeng/table';
@@ -32,6 +31,7 @@ import { UrlProtocolPipe } from '../../../common/pipes/url-protocol.pipe';
3231
import { ListingDetails } from '../../../common/models/listing-details';
3332
import { FormsModule } from '@angular/forms';
3433
import { ToggleSwitchModule } from 'primeng/toggleswitch';
34+
import { forkJoin } from 'rxjs';
3535

3636
@Component({
3737
selector: 'app-listings-table',
@@ -78,8 +78,6 @@ export class ListingsTableComponent implements OnInit {
7878
currentFilter!: ListingFilter;
7979
cancelableFilter!: ListingFilter;
8080
showRecentOnly = true; // Default to "Recently Reported"
81-
recentCount = 0;
82-
allCount = 0;
8381

8482
readonly addressLowScore = Number.parseInt(environment.ADDRESS_SCORE);
8583

@@ -354,8 +352,6 @@ export class ListingsTableComponent implements OnInit {
354352

355353
onToggleChange(): void {
356354
this.selectedListings = [];
357-
this.skipNextPageChange = true;
358-
this.paginator.changePage(0);
359355
this.getListings(1);
360356
}
361357

@@ -365,25 +361,44 @@ export class ListingsTableComponent implements OnInit {
365361
const searchReq = {} as ListingSearchRequest;
366362
searchReq[this.searchColumn] = this.searchTerm;
367363

368-
this.listingService.getListings(
369-
selectedPageNumber ?? (this.currentPage?.pageNumber || 0),
370-
this.currentPage?.pageSize || 25,
371-
this.sort?.prop || '',
372-
this.sort?.dir || 'asc',
373-
searchReq,
374-
this.currentFilter,
375-
this.showRecentOnly,
376-
).subscribe({
377-
next: (res: ListingResponseWithCounts<ListingTableRow>) => {
378-
this.currentPage = res.pageInfo;
379-
this.listings = res.sourceList;
380-
this.recentCount = res.recentCount;
381-
this.allCount = res.allCount;
364+
const pageNumber = Math.max(1, selectedPageNumber ?? this.currentPage?.pageNumber ?? 1);
365+
const pageSize = this.currentPage?.pageSize || 25;
366+
const orderBy = this.sort?.prop || '';
367+
const direction = this.sort?.dir || 'asc';
368+
369+
forkJoin({
370+
count: this.listingService.getListingsCount(searchReq, this.currentFilter, this.showRecentOnly),
371+
data: this.listingService.getListings(
372+
pageNumber,
373+
pageSize,
374+
orderBy,
375+
direction,
376+
searchReq,
377+
this.currentFilter,
378+
this.showRecentOnly,
379+
false,
380+
),
381+
}).subscribe({
382+
next: ({ count, data }: { count: number; data: PagingResponse<ListingTableRow> }) => {
383+
this.currentPage = data.pageInfo;
384+
this.currentPage.totalCount = count;
385+
this.listings = data.sourceList;
386+
// Sync paginator to first page after load so subsequent paging works (e.g. after toggle Recent/All)
387+
if (this.paginator && data.pageInfo.pageNumber === 1) {
388+
this.skipNextPageChange = true;
389+
this.paginator.changePage(0);
390+
// If paginator was already on page 0 it may not emit; clear flag so next page click works
391+
setTimeout(() => { this.skipNextPageChange = false; }, 0);
392+
}
393+
},
394+
error: () => {
395+
this.loaderService.loadingEnd();
396+
this.cd.detectChanges();
382397
},
383398
complete: () => {
384399
this.loaderService.loadingEnd();
385400
this.cd.detectChanges();
386-
}
401+
},
387402
});
388403
}
389404

0 commit comments

Comments
 (0)