Skip to content

Commit 50bcb05

Browse files
authored
Merge pull request #2061 from bcgov/feature/ALCS-1871
Application Conditions Card
2 parents 0d14a89 + ff9e4cd commit 50bcb05

File tree

48 files changed

+2332
-336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2332
-336
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<div class="container">
2+
<div class="section">
3+
<h3>Create New Condition Card</h3>
4+
</div>
5+
<div class="section">
6+
<ng-select
7+
class="card-type"
8+
appearance="outline"
9+
[items]="conditionBoard?.statuses!"
10+
placeholder="Workflow Stage*"
11+
bindLabel="label"
12+
bindValue="statusCode"
13+
[clearable]="false"
14+
[(ngModel)]="selectedStatus"
15+
(change)="onStatusSelected($event)"
16+
>
17+
<ng-template ng-option-tmp let-item="item">
18+
<span [innerHTML]="item.label"> </span>
19+
</ng-template>
20+
<ng-template ng-label-tmp let-item="item">
21+
<span [innerHTML]="item.label"> </span>
22+
</ng-template>
23+
</ng-select>
24+
</div>
25+
<div class="section">
26+
<span>Add one or more conditions*</span>
27+
<div class="table-container">
28+
<table mat-table class="conditions-table mat-elevation-z2" [dataSource]="dataSource" style="width: 100%">
29+
<ng-container matColumnDef="select">
30+
<th mat-header-cell *matHeaderCellDef class="column-select"></th>
31+
<td mat-cell *matCellDef="let element" class="column-select">
32+
<mat-checkbox [(ngModel)]="element.selected" [disabled]="isConditionCardNotNull(element)"></mat-checkbox>
33+
</td>
34+
</ng-container>
35+
36+
<ng-container matColumnDef="index">
37+
<th mat-header-cell *matHeaderCellDef class="column-index">#</th>
38+
<td mat-cell *matCellDef="let element" class="column-index">{{ element.index }}</td>
39+
</ng-container>
40+
41+
<ng-container matColumnDef="type">
42+
<th mat-header-cell *matHeaderCellDef class="column-type">Type</th>
43+
<td mat-cell *matCellDef="let element" class="column-type">{{ element.condition.type.label }}</td>
44+
</ng-container>
45+
46+
<ng-container matColumnDef="description">
47+
<th mat-header-cell *matHeaderCellDef class="column-description">Description</th>
48+
<td mat-cell *matCellDef="let element" class="column-description">{{ element.condition.description }}</td>
49+
</ng-container>
50+
51+
<tr mat-header-row *matHeaderRowDef="displayColumns"></tr>
52+
<tr
53+
mat-row
54+
*matRowDef="let row; columns: displayColumns"
55+
[class.disabled-row]="isConditionCardNotNull(row)"
56+
></tr>
57+
</table>
58+
</div>
59+
</div>
60+
<div class="section">
61+
<div class="button-row">
62+
<button type="button" mat-stroked-button color="primary" (click)="onCancel()">Cancel</button>
63+
<button type="button" mat-flat-button color="primary" [disabled]="isSaveDisabled()" (click)="onSave()">
64+
Save
65+
</button>
66+
</div>
67+
</div>
68+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@use '../../../../../../styles/colors.scss' as *;
2+
3+
.container {
4+
display: flex;
5+
flex-direction: column;
6+
width: 100%;
7+
padding: 12px 8px;
8+
overflow-y: hidden;
9+
}
10+
11+
.section {
12+
width: 100%;
13+
padding: 16px;
14+
}
15+
16+
.button-row {
17+
display: flex;
18+
justify-content: flex-end;
19+
gap: 8px;
20+
}
21+
22+
.column-select {
23+
width: 10%;
24+
}
25+
26+
.column-index {
27+
width: 10%;
28+
}
29+
30+
.column-type {
31+
width: 30%;
32+
}
33+
34+
.column-description {
35+
width: 50%;
36+
}
37+
38+
.conditions-table {
39+
margin-top: 16px;
40+
width: 100%;
41+
}
42+
43+
.table-container {
44+
max-height: 300px;
45+
overflow-y: auto;
46+
}
47+
48+
.disabled-row {
49+
background-color: #f0f0f0; // Light gray background
50+
pointer-events: none; // Disable interactions
51+
opacity: 0.6; // Make it look disabled
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NO_ERRORS_SCHEMA } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4+
import { createMock, DeepMocked } from '@golevelup/ts-jest';
5+
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
6+
import { MatTableModule } from '@angular/material/table';
7+
import { MatSortModule } from '@angular/material/sort';
8+
import { HttpClientTestingModule } from '@angular/common/http/testing';
9+
import { RouterTestingModule } from '@angular/router/testing';
10+
import { ApplicationDecisionConditionCardService } from '../../../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition-card/application-decision-condition-card.service';
11+
import { BoardService } from '../../../../../services/board/board.service';
12+
import { ToastService } from '../../../../../services/toast/toast.service';
13+
import { ConditionCardDialogComponent } from './condition-card-dialog.component';
14+
15+
describe('ConditionCardDialogComponent', () => {
16+
let component: ConditionCardDialogComponent;
17+
let fixture: ComponentFixture<ConditionCardDialogComponent>;
18+
let mockDecisionConditionCardService: DeepMocked<ApplicationDecisionConditionCardService>;
19+
let mockBoardService: DeepMocked<BoardService>;
20+
let mockToastService: DeepMocked<ToastService>;
21+
22+
beforeEach(async () => {
23+
mockDecisionConditionCardService = createMock();
24+
mockBoardService = createMock();
25+
mockToastService = createMock();
26+
27+
await TestBed.configureTestingModule({
28+
declarations: [ConditionCardDialogComponent],
29+
imports: [
30+
MatDialogModule,
31+
BrowserAnimationsModule,
32+
MatTableModule,
33+
MatSortModule,
34+
HttpClientTestingModule,
35+
RouterTestingModule,
36+
],
37+
providers: [
38+
{ provide: MAT_DIALOG_DATA, useValue: { conditions: [], decision: 'decision-uuid' } },
39+
{ provide: MatDialogRef, useValue: {} },
40+
{ provide: ApplicationDecisionConditionCardService, useValue: mockDecisionConditionCardService },
41+
{ provide: BoardService, useValue: mockBoardService },
42+
{ provide: ToastService, useValue: mockToastService },
43+
],
44+
schemas: [NO_ERRORS_SCHEMA],
45+
}).compileComponents();
46+
47+
fixture = TestBed.createComponent(ConditionCardDialogComponent);
48+
component = fixture.componentInstance;
49+
fixture.detectChanges();
50+
});
51+
52+
it('should create', () => {
53+
expect(component).toBeTruthy();
54+
});
55+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
2+
import { MatTableDataSource } from '@angular/material/table';
3+
import { MatSort } from '@angular/material/sort';
4+
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
5+
import {
6+
ApplicationDecisionConditionDto,
7+
CreateApplicationDecisionConditionCardDto,
8+
} from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto';
9+
import { ApplicationDecisionConditionDto as OriginalApplicationDecisionConditionDto } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto';
10+
import { ApplicationDecisionConditionCardService } from '../../../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition-card/application-decision-condition-card.service';
11+
import { BOARD_TYPE_CODES, BoardService } from '../../../../../services/board/board.service';
12+
import { BoardDto, BoardStatusDto } from '../../../../../services/board/board.dto';
13+
import { ToastService } from '../../../../../services/toast/toast.service';
14+
15+
@Component({
16+
selector: 'app-condition-card-dialog',
17+
templateUrl: './condition-card-dialog.component.html',
18+
styleUrl: './condition-card-dialog.component.scss',
19+
})
20+
export class ConditionCardDialogComponent implements OnInit {
21+
displayColumns: string[] = ['select', 'index', 'type', 'description'];
22+
conditionBoard: BoardDto | undefined;
23+
selectedStatus = '';
24+
25+
@ViewChild(MatSort) sort!: MatSort;
26+
dataSource: MatTableDataSource<{ condition: ApplicationDecisionConditionDto; index: number; selected: boolean }> =
27+
new MatTableDataSource();
28+
29+
constructor(
30+
@Inject(MAT_DIALOG_DATA)
31+
public data: { conditions: { condition: ApplicationDecisionConditionDto; index: number }[]; decision: string },
32+
private dialogRef: MatDialogRef<ConditionCardDialogComponent>,
33+
private decisionConditionCardService: ApplicationDecisionConditionCardService,
34+
private boardService: BoardService,
35+
private toastService: ToastService,
36+
) {}
37+
38+
async ngOnInit() {
39+
this.dataSource.data = this.data.conditions.map((item) => ({
40+
condition: item.condition,
41+
selected: false,
42+
index: item.index + 1,
43+
}));
44+
45+
this.conditionBoard = await this.boardService.fetchBoardDetail(BOARD_TYPE_CODES.APPCON);
46+
}
47+
48+
onStatusSelected(applicationStatus: BoardStatusDto) {
49+
this.selectedStatus = applicationStatus.statusCode;
50+
}
51+
52+
isConditionCardNotNull(element: any): boolean {
53+
return element.condition.conditionCard !== null;
54+
}
55+
56+
isSaveDisabled(): boolean {
57+
const isStatusSelected = !!this.selectedStatus;
58+
const isAnyRowSelected = this.dataSource.data.some((item) => item.selected);
59+
return !(isStatusSelected && isAnyRowSelected);
60+
}
61+
62+
onCancel(): void {
63+
this.dialogRef.close({ action: 'cancel' });
64+
}
65+
66+
async onSave() {
67+
const selectedStatusCode = this.conditionBoard?.statuses.find(
68+
(status) => status.label === this.selectedStatus,
69+
)?.statusCode;
70+
const selectedConditions = this.dataSource.data.filter((item) => item.selected).map((item) => item.condition.uuid);
71+
const createDto: CreateApplicationDecisionConditionCardDto = {
72+
conditionsUuids: selectedConditions,
73+
decisionUuid: this.data.decision,
74+
cardStatusCode: this.selectedStatus,
75+
};
76+
const res = await this.decisionConditionCardService.create(createDto);
77+
if (res) {
78+
this.toastService.showSuccessToast('Condition card created successfully');
79+
this.dialogRef.close({ action: 'save', result: true });
80+
} else {
81+
this.toastService.showErrorToast('Failed to create condition card');
82+
this.dialogRef.close({ action: 'save', result: false });
83+
}
84+
}
85+
}

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

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
<section>
22
<div class="header">
33
<h3>View Conditions</h3>
4-
<button
5-
*ngIf="decision"
6-
type="button"
7-
mat-stroked-button
8-
color="primary"
9-
[routerLink]="['../../']"
10-
[queryParams]="{ uuid: decision.uuid }"
11-
>
12-
back to decision #{{ decision.index }}
13-
</button>
4+
<div class="header-buttons">
5+
<button *ngIf="decision" type="button" mat-flat-button color="primary" (click)="openConditionCardDialog()">
6+
+ Condition Card
7+
</button>
8+
<button
9+
*ngIf="decision"
10+
type="button"
11+
mat-stroked-button
12+
color="primary"
13+
[routerLink]="['../../']"
14+
[queryParams]="{ uuid: decision.uuid }"
15+
>
16+
back to decision #{{ decision.index }}
17+
</button>
18+
</div>
1419
</div>
1520
<section class="body" *ngIf="decision">
1621
<div class="decision-container">
@@ -35,7 +40,7 @@ <h3>View Conditions</h3>
3540
[condition]="condition"
3641
[isDraftDecision]="decision.isDraft"
3742
[fileNumber]="fileNumber"
38-
[index]="j+1"
43+
[index]="j + 1"
3944
></app-condition>
4045
</div>
4146
</div>

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

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ p {
2121
justify-content: flex-end;
2222
}
2323

24+
.header-buttons {
25+
display: flex;
26+
gap: 8px;
27+
}
28+
2429
:host ::ng-deep {
2530
.display-none {
2631
display: none !important;

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

+26
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
RELEASED_DECISION_TYPE_LABEL,
2020
} from '../../../../shared/application-type-pill/application-type-pill.constants';
2121
import { ApplicationDecisionConditionService } from '../../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service';
22+
import { MatDialog } from '@angular/material/dialog';
23+
import { ConditionCardDialogComponent } from './condition-card-dialog/condition-card-dialog.component';
2224

2325
export type ConditionComponentLabels = {
2426
label: string[];
@@ -70,6 +72,7 @@ export class ConditionsComponent implements OnInit {
7072
private decisionService: ApplicationDecisionV2Service,
7173
private conditionService: ApplicationDecisionConditionService,
7274
private activatedRouter: ActivatedRoute,
75+
private dialog: MatDialog,
7376
) {
7477
this.today = moment().startOf('day').toDate().getTime();
7578
}
@@ -187,4 +190,27 @@ export class ConditionsComponent implements OnInit {
187190
}),
188191
);
189192
}
193+
194+
openConditionCardDialog(): void {
195+
const dialogRef = this.dialog.open(ConditionCardDialogComponent, {
196+
minWidth: '800px',
197+
maxWidth: '1100px',
198+
maxHeight: '80vh',
199+
data: {
200+
conditions: this.decision.conditions.map((condition, index) => ({
201+
condition: condition,
202+
index: index,
203+
})),
204+
decision: this.decision.uuid,
205+
},
206+
});
207+
208+
dialogRef.afterClosed().subscribe((result) => {
209+
if (result) {
210+
if (result.action === 'save' && result.result === true) {
211+
this.loadDecisions(this.fileNumber);
212+
}
213+
}
214+
});
215+
}
190216
}

alcs-frontend/src/app/features/application/decision/decision.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ReleaseDialogComponent } from './decision-v2/release-dialog/release-dia
3434
import { RevertToDraftDialogComponent } from './decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component';
3535
import { DecisionComponent } from './decision.component';
3636
import { DecisionConditionDateDialogComponent } from './decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component';
37+
import { ConditionCardDialogComponent } from './conditions/condition-card-dialog/condition-card-dialog.component';
3738

3839
export const decisionChildRoutes = [
3940
{
@@ -94,6 +95,7 @@ export const decisionChildRoutes = [
9495
ConditionsComponent,
9596
ConditionComponent,
9697
BasicComponent,
98+
ConditionCardDialogComponent,
9799
],
98100
imports: [SharedModule, RouterModule.forChild(decisionChildRoutes), MatTabsModule, MatOptionModule, MatChipsModule],
99101
})

0 commit comments

Comments
 (0)