Skip to content

Commit 535543b

Browse files
Merge pull request #785 from bcgov/oleks
DSS-1003 FE: Send Compliance Order
2 parents 0c342cb + a7eb426 commit 535543b

14 files changed

+686
-4
lines changed

frontend/src/app/app.routes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PageNotFoundComponent } from './common/components/page-not-found/page-n
99
import { approvedUserGuard } from './common/guards/approved-user.guard';
1010
import { activeUserGuard } from './common/guards/active-user.guard';
1111
import { accessRequestTokenGuard } from './common/guards/access-request-token.guard';
12-
import { jurisdiction_read, jurisdiction_write, licence_file_upload, listing_file_upload, listing_read, platform_read, platform_write, role_read, role_write, takedown_action, upload_history_read, user_read, user_write } from './common/consts/permissions.const';
12+
import { ceu_action, jurisdiction_read, jurisdiction_write, licence_file_upload, listing_file_upload, listing_read, platform_read, platform_write, role_read, role_write, takedown_action, upload_history_read, user_read, user_write } from './common/consts/permissions.const';
1313
import { hasPermissionsGuard } from './common/guards/has-permissions.guard';
1414
import { TermsAndConditionsComponent } from './common/components/terms-and-conditions/terms-and-conditions.component';
1515
import { areTermsAceptedGuard } from './common/guards/are-terms-acepted.guard';
@@ -35,6 +35,7 @@ import { EditSubPlatformComponent } from './features/components/platform-managem
3535
import { ManageJurisdictionsComponent } from './features/components/manage-jurisdictions/manage-jurisdictions.component';
3636
import { UpdateJurisdictionInformationComponent } from './features/components/manage-jurisdictions/update-jurisdiction-information/update-jurisdiction-information.component';
3737
import { UpdateLocalGovernmentInformationComponent } from './features/components/manage-jurisdictions/update-local-gvernment-information/update-local-gvernment-information.component';
38+
import { SendComplianceOrderComponent } from './features/components/send-compliance-order/send-compliance-order.component';
3839

3940
export const routes: Routes = [
4041
{
@@ -89,6 +90,12 @@ export const routes: Routes = [
8990
component: BulkComplianceNoticeComponent,
9091
data: { permissions: [takedown_action] }
9192
},
93+
{
94+
path: 'send-compliance-order',
95+
canActivate: [approvedUserGuard, activeUserGuard, hasPermissionsGuard, areTermsAceptedGuard],
96+
component: SendComplianceOrderComponent,
97+
data: { permissions: [ceu_action] }
98+
},
9299
{
93100
path: 'listings',
94101
component: ListingsTableComponent,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface ComplianceOrder {
2+
rentalListingId: number;
3+
bccList: Array<string>;
4+
comment: string;
5+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { ComplianceService } from './compliance.service';
4+
5+
xdescribe('ComplianceService', () => {
6+
let service: ComplianceService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(ComplianceService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { HttpClient } from '@angular/common/http';
2+
import { Injectable } from '@angular/core';
3+
import { Observable } from 'rxjs';
4+
import { environment } from '../../../environments/environment';
5+
import { ComplianceOrder } from '../models/compliance-order';
6+
7+
@Injectable({
8+
providedIn: 'root'
9+
})
10+
export class ComplianceService {
11+
constructor(private httpClient: HttpClient) { }
12+
13+
sendComplianceOrdersPreview(complianceOrder: Array<ComplianceOrder>): Observable<{ content: string }> {
14+
return this.httpClient.post<{ content: string }>(`${environment.API_HOST}/delisting/complianceorders/preview`, complianceOrder);
15+
}
16+
sendComplianceOrdersConfirm(complianceOrder: Array<ComplianceOrder>): Observable<unknown> {
17+
return this.httpClient.post<unknown>(`${environment.API_HOST}/delisting/complianceorders`, complianceOrder);
18+
}
19+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ <h2 class="title">Aggregated Listings</h2>
3636
Send Takedown Request
3737
</button>
3838
</div>
39+
<div class="actions left-alignment" *ngIf="isCEU">
40+
<button pButton icon="pi pi-envelope" [disabled]="!listingsSelected" id="send_compliance_order_btn"
41+
(click)="onContactHost()">
42+
&nbsp;Contact Host
43+
</button>
44+
</div>
3945
<div class="content">
4046
<p-panel class="listing-table-panel">
4147
<ng-template pTemplate="header">

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,14 @@
112112
}
113113

114114
.actions {
115+
display: flex;
116+
width: 100%;
115117
padding-bottom: 12px;
116118

119+
&.left-alignment {
120+
justify-content: end;
121+
}
122+
117123
button {
118124
margin-right: 6px;
119125
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ export class AggregatedListingsTableComponent implements OnInit {
233233
});
234234
}
235235

236+
onContactHost(): void {
237+
this.searchStateService.selectedListings = Object.values(
238+
this.selectedListings,
239+
) as unknown as Array<ListingDetails>;
240+
this.router.navigate(['/send-compliance-order'], {
241+
queryParams: { returnUrl: this.getUrlFromState() },
242+
});
243+
}
244+
236245
onPageChange(value: any): void {
237246
this.currentPage.pageSize = value.rows;
238247
this.currentPage.pageNumber = value.page + 1;

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ <h2>Detailed Listing Information for</h2>
2525
<span class="nonc-icon"></span> Send Notice Of Non-Compliance </button>
2626
<button *ngIf="!isCEU" pButton (click)="sendTakedownRequest()" class="p-button-outlined do-not-print-it"><span
2727
class="tdr-icon"></span> Send Takedown Request</button>
28+
29+
<button pButton *ngIf="isCEU" icon="pi pi-envelope" id="send_compliance_order_btn" (click)="onContactHost()">
30+
&nbsp;Contact Host
31+
</button>
32+
2833
</div>
2934
</div>
3035
<div class="content" *ngIf="listing">
@@ -34,8 +39,8 @@ <h2>Detailed Listing Information for</h2>
3439
<div class="property-info-container">
3540
<strong class="panel-header-text">Platform Information</strong>
3641
<p-tag class="tag-blue">
37-
<strong class="tag-text">Platform Report Month:</strong>{{listing.latestReportPeriodYm
38-
|date:'YYYY-MM' || '-'}}
42+
<strong class="tag-text">Platform Report Month:</strong>{{(listing.latestReportPeriodYm
43+
|date:'YYYY-MM') || '-'}}
3944
</p-tag>
4045
</div>
4146
</ng-template>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ export class ListingDetailsComponent implements OnInit {
181181
this.router.navigate(['/bulk-compliance-notice'], { queryParams: { returnUrl: this.getUrlFromState() } });
182182
}
183183

184+
onContactHost(): void {
185+
this.searchStateService.selectedListings = [this.listing];
186+
this.router.navigate(['/send-compliance-order'], { queryParams: { returnUrl: this.getUrlFromState() } });
187+
}
188+
184189
onAddressChangeClicked(): void {
185190
this.isEditAddressShown = true;
186191
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<h2>Contact Host</h2>
2+
<h4>Send a message to all hosts of the listings selected below. All fields are required except where stated.</h4>
3+
4+
<p-panel class="table-panel">
5+
<ng-template pTemplate="header">
6+
<div class="header-panel">
7+
<strong>Included Listings</strong>
8+
</div>
9+
</ng-template>
10+
11+
<p-table [value]="extendedListings" [scrollable]="true" scrollDirection="vertical" scrollHeight="425px"
12+
styleClass="p-datatable-sm" [(selection)]="selectedListings">
13+
<ng-template pTemplate="header">
14+
<tr>
15+
<th style="width: 4rem">
16+
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
17+
</th>
18+
<th class="sortable-header" id="offeringOrganizationNm_header"
19+
[class.sorted]="this.sort && this.sort.prop === 'offeringOrganizationNm'"
20+
(click)="onSort('offeringOrganizationNm')">
21+
Platform
22+
<i class="pi pi-angle-down"
23+
*ngIf="this.sort && this.sort.prop === 'offeringOrganizationNm' && this.sort.dir === 'desc'"></i>
24+
<i class="pi pi-angle-up"
25+
*ngIf="this.sort && this.sort.prop === 'offeringOrganizationNm' && this.sort.dir === 'asc'"></i>
26+
</th>
27+
<th class="sortable-header" id="platformListingNo_header"
28+
[class.sorted]="this.sort && this.sort.prop === 'platformListingNo'"
29+
(click)="onSort('platformListingNo')">
30+
Listing ID
31+
<i class="pi pi-angle-down"
32+
*ngIf="this.sort && this.sort.prop === 'platformListingNo' && this.sort.dir === 'desc'"></i>
33+
<i class="pi pi-angle-up"
34+
*ngIf="this.sort && this.sort.prop === 'platformListingNo' && this.sort.dir === 'asc'"></i>
35+
</th>
36+
<th class="sortable-header" id="matchAddressTxt_header"
37+
[class.sorted]="this.sort && this.sort.prop === 'matchAddressTxt'"
38+
(click)="onSort('matchAddressTxt')">
39+
Address (Best Match)
40+
<i class="pi pi-angle-down"
41+
*ngIf="this.sort && this.sort.prop === 'matchAddressTxt' && this.sort.dir === 'desc'"></i>
42+
<i class="pi pi-angle-up"
43+
*ngIf="this.sort && this.sort.prop === 'matchAddressTxt' && this.sort.dir === 'asc'"></i>
44+
</th>
45+
46+
<th class="limit-width">
47+
Supplier Host
48+
</th>
49+
<th class="limit-width">
50+
Host Name & Emails
51+
</th>
52+
</tr>
53+
</ng-template>
54+
55+
<ng-template pTemplate="body" let-listing>
56+
<tr class="small-text">
57+
<td>
58+
<p-tableCheckbox [value]="listing"
59+
[disabled]="!listing.hasAtLeastOneValidHostEmail"></p-tableCheckbox>
60+
</td>
61+
<td>{{ listing.offeringOrganizationNm }}</td>
62+
<td>
63+
<a target="_blank" [href]="listing.platformListingUrl">{{ listing.platformListingNo }}</a>
64+
</td>
65+
<td>{{ listing.matchAddressTxt }}
66+
</td>
67+
<td>{{ listing.listingContactNamesTxt }}
68+
</td>
69+
70+
<td class="host-info limit-width">
71+
<span class="tip" (click)="op.toggle($event)">
72+
<i class="pi pi-info-circle"></i>
73+
</span>
74+
<p-overlayPanel #op>
75+
<span class="host-data" *ngFor="let hostData of listing.hosts"
76+
[class.invalid-email]="!hostData.hasValidEmail">{{hostData.fullNm}}
77+
{{hostData.emailAddressDsc}}<br></span>
78+
</p-overlayPanel>
79+
<span class="host-data" *ngFor="let hostData of listing.hosts">
80+
{{hostData.emailAddressDsc}};</span>
81+
</td>
82+
</tr>
83+
</ng-template>
84+
</p-table>
85+
</p-panel>
86+
87+
<form *ngIf="myForm" [formGroup]="myForm">
88+
<p-panel class="message-panel">
89+
<ng-template pTemplate="header">
90+
<div class="header-panel">
91+
<strong>Message to Host</strong>
92+
</div>
93+
</ng-template>
94+
95+
<div class="form-group-row">
96+
<div class="form-group-row-col">
97+
<label for="comment">Compose a Message That Will Be Sent to All Hosts</label>
98+
</div>
99+
<div class="form-group-row-col">
100+
<textarea #textarea rows="5" cols="30" maxlength="3800" class="full-width-text-field custom-details"
101+
formControlName="comment" placeholder="Enter Messages here..." pInputTextarea id="comment"
102+
name="comment"></textarea>
103+
<span class="limit-text" [class.almost-limit-text]="textarea.value.length > 3750"
104+
[class.limited-text]="textarea.value.length > 3799">{{textarea.value.length}}/3800</span>
105+
</div>
106+
<div class="form-group-row-col validation-errors" *ngIf="!commentControl.pristine && commentControl.errors">
107+
<small id="invalidcomment" *ngIf="commentControl.errors?.['required']">
108+
Please make sure the email format you have entered is correct
109+
</small>
110+
</div>
111+
</div>
112+
</p-panel>
113+
114+
<p-panel class="extras-panel">
115+
<ng-template pTemplate="header">
116+
<div class="header-panel">
117+
<strong>Add Additional Information</strong>
118+
</div>
119+
</ng-template>
120+
121+
<div class="form-group-row">
122+
<div class="form-group-row-col">
123+
<span class="info-tooltip" [tooltipOptions]="tooltipOptions" pTooltip="Enter additional email addresses in the BCC field
124+
separated by commas—this will be copied on all
125+
emails to selected listings" tooltipPosition="top" placeholder="Top"></span>
126+
<label for="ccList">Add BCCs</label>
127+
</div>
128+
<div class="form-group-row-col">
129+
<textarea formControlName="ccList" placeholder="Enter Emails Here..." rows="1" cols="20" pInputTextarea
130+
id="ccList" name="ccList" class="full-width-text-field"></textarea>
131+
</div>
132+
<div class="form-group-row-col validation-errors" *ngIf="!ccListControl.pristine && ccListControl.errors">
133+
<small id="invalidCcList" *ngIf="ccListControl.errors?.['invalidEmailList']">
134+
Please make sure the email format you have entered is correct
135+
</small>
136+
</div>
137+
</div>
138+
</p-panel>
139+
</form>
140+
<div class="actions" *ngIf="selectedListings">
141+
<button pButton id="submit-btn" [disabled]="!extendedListings.length || !myForm.valid" class=""
142+
(click)="submitPreview()">Review</button>
143+
<button pButton id="cancel-btn" class="outline-btn" (click)="cancel()">Cancel</button>
144+
</div>
145+
146+
<p-dialog class="preview-dialog" header="Message to Hosts" [(visible)]="showPreviewDialog" [modal]="true"
147+
[style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
148+
149+
<div class="note">
150+
<div class="title">
151+
<i class="pi pi-info-circle"></i>
152+
<strong>Please Note</strong>
153+
</div>
154+
<div class="content">
155+
When you click submit, this email will be personalized with the relevant listing URL and sent to all hosts
156+
for each selected listing.
157+
</div>
158+
</div>
159+
160+
<div class="preview-html" [innerHTML]="previewText"></div>
161+
<ng-template pTemplate="footer">
162+
<div class="actions popup-actions">
163+
<button pButton id="submit-preview-btn" class="" (click)="submitAfterPreview()">Submit</button>
164+
<button pButton id="cancel-preview-btn" class="outline-btn" (click)="cancelPreview()">Cancel</button>
165+
</div>
166+
</ng-template>
167+
</p-dialog>

0 commit comments

Comments
 (0)