Skip to content

Commit d78553a

Browse files
authored
Merge pull request #2079 from bcgov/feature/ALCS-2226
ALCS-2226: Add flagging to apps/NOI's
2 parents cef6116 + 245272f commit d78553a

File tree

37 files changed

+1040
-138
lines changed

37 files changed

+1040
-138
lines changed

alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.html

+39-1
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,24 @@ <h5>Res #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }}</h5>
8383
<app-application-type-pill [type]="releasedDecisionLabel"></app-application-type-pill>
8484
</ng-container>
8585
</div>
86+
87+
<div class="flag-button-container">
88+
<button
89+
type="button"
90+
(click)="decision.isFlagged ? unflag(decision) : flag(decision, false)"
91+
class="flag-button"
92+
[class.flagged]="decision.isFlagged"
93+
[matTooltip]="decision.isFlagged ? 'Click to unflag' : ''"
94+
>
95+
<mat-icon svgIcon="personal_places"></mat-icon>
96+
Flag{{ decision.isFlagged ? 'ged' : '' }} for Condition Follow Up
97+
</button>
98+
</div>
99+
86100
<ng-container *ngIf="decision.isDraft">
87-
<button mat-flat-button color="primary" (click)="onEdit(decision)">Edit Draft</button>
101+
<button class="edit-decision-button" mat-flat-button color="primary" (click)="onEdit(decision)">
102+
Edit Draft
103+
</button>
88104
</ng-container>
89105

90106
<div
@@ -183,6 +199,28 @@ <h5>Res #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }}</h5>
183199
</button>
184200
</div>
185201
</div>
202+
203+
<ng-container *ngIf="decision.isFlagged">
204+
<section class="flag-details">
205+
<div class="flag-details-header">
206+
<div class="flag-details-flagger">
207+
<mat-icon svgIcon="personal_places"></mat-icon><strong>{{ decision.flaggedBy?.prettyName }}</strong>
208+
</div>
209+
<div><strong>Follow-Up Date:</strong> {{ formatDate(decision.followUpAt) || 'No Data' }}</div>
210+
</div>
211+
212+
<div class="flag-details-body">
213+
<strong>Flagged for condition follow-up because:</strong> {{ decision.reasonFlagged }}
214+
</div>
215+
216+
<div class="flag-details-footer">
217+
<div class="flag-details-edited-details">
218+
{{ formatDate(decision.flagEditedAt, true) }} (Last Edited by {{ decision.flagEditedBy?.prettyName }})
219+
</div>
220+
<button mat-button type="button" (click)="flag(decision, true)">Edit</button>
221+
</div>
222+
</section>
223+
</ng-container>
186224
</section>
187225
<ng-container *ngIf="isSummary">
188226
<!-- Components -->

alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.scss

+94-2
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,20 @@ hr {
5353
}
5454

5555
.header {
56-
display: flex;
57-
justify-content: space-between;
56+
display: grid;
57+
grid-template-columns: auto auto auto;
58+
grid-template-rows: auto auto;
59+
row-gap: 10px;
60+
column-gap: 28px;
5861
margin-bottom: 36px;
5962

6063
.title {
6164
display: flex;
6265
align-items: center;
6366
justify-content: space-between;
6467
gap: 28px;
68+
grid-row: 1/2;
69+
grid-column: 1/2;
6570

6671
.days {
6772
display: inline-block;
@@ -78,6 +83,11 @@ hr {
7883
}
7984
}
8085

86+
.edit-decision-button {
87+
grid-row: 1/2;
88+
grid-column: 3/4;
89+
}
90+
8191
.loading-overlay {
8292
position: absolute;
8393
z-index: 2;
@@ -187,3 +197,85 @@ hr {
187197
color: colors.$link-color;
188198
}
189199
}
200+
201+
.flag-button-container {
202+
justify-self: end;
203+
grid-row: 2/3;
204+
grid-column: 1/4;
205+
206+
@media screen and (min-width: 1440px) {
207+
grid-row: 1/2;
208+
grid-column: 2/3;
209+
}
210+
}
211+
212+
.flag-button {
213+
display: flex;
214+
align-items: center;
215+
column-gap: 5px;
216+
217+
text-align: left;
218+
font-weight: normal;
219+
text-wrap: nowrap;
220+
221+
background-color: transparent;
222+
padding: 5px;
223+
border: none;
224+
border-radius: 5px;
225+
margin: 0;
226+
227+
&:hover {
228+
background-color: colors.$grey-light;
229+
}
230+
231+
mat-icon {
232+
flex-shrink: 0;
233+
}
234+
235+
&.flagged {
236+
mat-icon {
237+
color: blue;
238+
}
239+
}
240+
}
241+
242+
.flag-details {
243+
background-color: white;
244+
display: flex;
245+
flex-direction: column;
246+
gap: 16px;
247+
248+
padding: 16px;
249+
border: 1px solid colors.$grey;
250+
border-radius: 4px;
251+
}
252+
253+
.flag-details-header {
254+
display: flex;
255+
justify-content: space-between;
256+
align-items: center;
257+
}
258+
259+
.flag-details-flagger {
260+
display: flex;
261+
gap: 5px;
262+
263+
mat-icon {
264+
color: blue;
265+
}
266+
}
267+
268+
.flag-details-body {
269+
line-height: 1.5;
270+
}
271+
272+
.flag-details-footer {
273+
display: flex;
274+
justify-content: space-between;
275+
align-items: flex-end;
276+
}
277+
278+
.flag-details-edited-details {
279+
font-size: 12px;
280+
color: colors.$grey;
281+
}

alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ import {
1616
import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service';
1717
import { ToastService } from '../../../../services/toast/toast.service';
1818
import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service';
19-
2019
import { DecisionV2Component } from './decision-v2.component';
20+
import { HttpClient } from '@angular/common/http';
2121

2222
describe('DecisionV2Component', () => {
2323
let component: DecisionV2Component;
2424
let fixture: ComponentFixture<DecisionV2Component>;
2525
let mockApplicationDecisionService: DeepMocked<ApplicationDecisionV2Service>;
2626
let mockAppDetailService: DeepMocked<ApplicationDetailService>;
2727
let mockApplicationDecisionComponentService: DeepMocked<ApplicationDecisionComponentService>;
28+
let mockHttpClient: DeepMocked<HttpClient>;
2829

2930
beforeEach(async () => {
3031
mockApplicationDecisionService = createMock();
@@ -36,6 +37,8 @@ describe('DecisionV2Component', () => {
3637

3738
mockApplicationDecisionComponentService = createMock();
3839

40+
mockHttpClient = createMock();
41+
3942
await TestBed.configureTestingModule({
4043
imports: [MatSnackBarModule, MatMenuModule],
4144
declarations: [DecisionV2Component],
@@ -72,6 +75,10 @@ describe('DecisionV2Component', () => {
7275
provide: ActivatedRoute,
7376
useValue: {},
7477
},
78+
{
79+
provide: HttpClient,
80+
useValue: mockHttpClient,
81+
},
7582
],
7683
schemas: [NO_ERRORS_SCHEMA],
7784
}).compileComponents();

alcs-frontend/src/app/features/application/decision/decision-v2/decision-v2.component.ts

+88
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ import { formatDateForApi } from '../../../../shared/utils/api-date-formatter';
3131
import { RevertToDraftDialogComponent } from './revert-to-draft-dialog/revert-to-draft-dialog.component';
3232
import { ApplicationConditionWithStatus, getEndDate } from '../../../../shared/utils/decision-methods';
3333
import { openFileInline } from '../../../../shared/utils/file';
34+
import { UserService } from '../../../../services/user/user.service';
35+
import { UserDto } from '../../../../services/user/user.dto';
36+
import { FlagDialogComponent, FlagDialogIO } from '../../../../shared/flag-dialog/flag-dialog.component';
37+
import { UpdateApplicationDecisionDto } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.dto';
38+
import moment from 'moment';
3439

3540
type LoadingDecision = ApplicationDecisionWithLinkedResolutionDto & {
3641
loading: boolean;
@@ -67,6 +72,7 @@ export class DecisionV2Component implements OnInit, OnDestroy {
6772
isSummary = false;
6873

6974
conditions: Record<string, ApplicationConditionWithStatus[]> = {};
75+
profile: UserDto | undefined;
7076

7177
constructor(
7278
public dialog: MatDialog,
@@ -78,6 +84,7 @@ export class DecisionV2Component implements OnInit, OnDestroy {
7884
private router: Router,
7985
private activatedRouter: ActivatedRoute,
8086
private elementRef: ElementRef,
87+
private userService: UserService,
8188
) {}
8289

8390
ngOnInit(): void {
@@ -90,6 +97,10 @@ export class DecisionV2Component implements OnInit, OnDestroy {
9097
this.application = application;
9198
}
9299
});
100+
101+
this.userService.$userProfile.pipe(takeUntil(this.$destroy)).subscribe((profile) => {
102+
this.profile = profile;
103+
});
93104
}
94105

95106
async loadDecisions(fileNumber: string) {
@@ -324,4 +335,81 @@ export class DecisionV2Component implements OnInit, OnDestroy {
324335
return DECISION_CONDITION_ONGOING_LABEL;
325336
}
326337
}
338+
339+
async flag(decision: ApplicationDecisionWithLinkedResolutionDto, isEditing: boolean) {
340+
this.dialog
341+
.open(FlagDialogComponent, {
342+
minWidth: '800px',
343+
maxWidth: '800px',
344+
maxHeight: '80vh',
345+
width: '90%',
346+
autoFocus: false,
347+
data: {
348+
isEditing,
349+
decisionNumber: decision.index,
350+
reasonFlagged: decision.reasonFlagged,
351+
followUpAt: decision.followUpAt,
352+
},
353+
})
354+
.beforeClosed()
355+
.subscribe(async ({ isEditing, reasonFlagged, followUpAt, isSaving }: FlagDialogIO) => {
356+
if (isSaving) {
357+
const updateDto: UpdateApplicationDecisionDto = {
358+
isDraft: decision.isDraft,
359+
isFlagged: true,
360+
reasonFlagged,
361+
flagEditedByUuid: this.profile?.uuid,
362+
flagEditedAt: moment().toDate().getTime(),
363+
};
364+
365+
if (!isEditing) {
366+
updateDto.flaggedByUuid = this.profile?.uuid;
367+
}
368+
369+
if (followUpAt !== undefined) {
370+
updateDto.followUpAt = followUpAt;
371+
}
372+
373+
await this.decisionService.update(decision.uuid, updateDto);
374+
await this.loadDecisions(this.fileNumber);
375+
}
376+
});
377+
}
378+
379+
async unflag(decision: ApplicationDecisionWithLinkedResolutionDto) {
380+
this.confirmationDialogService
381+
.openDialog({
382+
title: `Unflag Decision #${decision.index}`,
383+
body: `<strong>Warning:</strong> Only remove if flagged in error.
384+
<br>
385+
<br>
386+
This action will also remove the follow-up date and explanatory text
387+
associated with the flag and cannot be undone.
388+
<br>
389+
<br>
390+
Are you sure you want to remove the flag?`,
391+
})
392+
.subscribe(async (confirmed) => {
393+
if (confirmed) {
394+
await this.decisionService.update(decision.uuid, {
395+
isDraft: decision.isDraft,
396+
isFlagged: false,
397+
reasonFlagged: null,
398+
followUpAt: null,
399+
flaggedByUuid: null,
400+
flagEditedByUuid: null,
401+
flagEditedAt: null,
402+
});
403+
await this.loadDecisions(this.fileNumber);
404+
}
405+
});
406+
}
407+
408+
formatDate(timestamp?: number | null, includeTime = false): string {
409+
if (timestamp === undefined || timestamp === null) {
410+
return '';
411+
}
412+
413+
return moment(new Date(timestamp)).format(`YYYY-MMM-DD ${includeTime ? 'hh:mm:ss A' : ''}`);
414+
}
327415
}

alcs-frontend/src/app/features/board/board.component.ts

+1
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ export class BoardComponent implements OnInit, OnDestroy {
537537
dateReceived: 0,
538538
isExpired,
539539
isPastDue,
540+
decisionIsFlagged: applicationDecisionCondition.decisionIsFlagged,
540541
};
541542
}
542543

alcs-frontend/src/app/features/board/dialogs/application-decision-condition-dialog/application-decision-condition-dialog.component.html

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ <h3 class="card-title">
3434
[type]="getStatusPill('RECONSIDERATION')"
3535
></app-application-type-pill>
3636
<app-application-type-pill [type]="getStatusPill('CONDITION')"></app-application-type-pill>
37+
38+
<mat-icon *ngIf="decision.isFlagged" svgIcon="personal_places" class="flag-icon"></mat-icon>
3739
</div>
3840
<div class="header-row">
3941
<div class="left">

alcs-frontend/src/app/features/board/dialogs/application-decision-condition-dialog/application-decision-condition-dialog.component.scss

+5
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,15 @@
104104
.pill-row {
105105
display: flex;
106106
flex-direction: row;
107+
align-items: flex-end;
107108
gap: 1px;
108109
}
109110

110111
.no-data {
111112
color: colors.$grey;
112113
font-weight: 400;
113114
}
115+
116+
.flag-icon {
117+
color: blue;
118+
}

0 commit comments

Comments
 (0)