Skip to content

Commit 39b1c05

Browse files
authored
Merge branch 'develop' into backport/2025-01-28
2 parents 0450582 + a86055d commit 39b1c05

File tree

240 files changed

+7752
-6042
lines changed

Some content is hidden

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

240 files changed

+7752
-6042
lines changed

alcs-frontend/src/app/features/admin/unarchive/unarchive.component.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
UnarchiveCardService,
66
} from '../../../services/unarchive-card/unarchive-card.service';
77
import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service';
8+
import { CardType } from '../../../shared/card/card.component';
89

910
@Component({
1011
selector: 'app-unarchive',
@@ -18,7 +19,7 @@ export class UnarchiveComponent {
1819

1920
constructor(
2021
private unarchiveCardService: UnarchiveCardService,
21-
private confirmationDialogService: ConfirmationDialogService
22+
private confirmationDialogService: ConfirmationDialogService,
2223
) {}
2324

2425
onUnarchive(uuid: string) {
@@ -36,6 +37,11 @@ export class UnarchiveComponent {
3637
async onSearch() {
3738
const results = await this.unarchiveCardService.search(this.search);
3839
if (results) {
40+
results.forEach((result) => {
41+
if (result.type === CardType.APP_CON) {
42+
result.type = 'Condition';
43+
}
44+
});
3945
this.cards = results;
4046
}
4147
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[application]="application"
55
[modifications]="modifications"
66
[reconsiderations]="reconsiderations"
7+
[conditionCards]="decisionConditionCards"
78
[showStatus]="true"
89
[submissionStatusService]="applicationStatusService"
910
[applicationDetailService]="applicationDetailService"

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

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ApplicationSubmissionService } from '../../services/application/applica
1515
import { ApplicationSubmissionStatusService } from '../../services/application/application-submission-status/application-submission-status.service';
1616

1717
import { ApplicationComponent } from './application.component';
18+
import { ApplicationDecisionConditionCardService } from '../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition-card/application-decision-condition-card.service';
1819

1920
describe('ApplicationComponent', () => {
2021
let component: ApplicationComponent;
@@ -25,6 +26,7 @@ describe('ApplicationComponent', () => {
2526
let mockReviewService: DeepMocked<ApplicationReviewService>;
2627
let mockAppSubmissionService: DeepMocked<ApplicationSubmissionService>;
2728
let mockAppStatusService: DeepMocked<ApplicationSubmissionStatusService>;
29+
let mockApplicationDecisionConditionCardService: DeepMocked<ApplicationDecisionConditionCardService>;
2830

2931
beforeEach(async () => {
3032
mockAppDetailService = createMock();
@@ -40,6 +42,8 @@ describe('ApplicationComponent', () => {
4042
mockAppSubmissionService = createMock();
4143
mockAppStatusService = createMock();
4244

45+
mockApplicationDecisionConditionCardService = createMock();
46+
4347
await TestBed.configureTestingModule({
4448
providers: [
4549
{
@@ -70,6 +74,10 @@ describe('ApplicationComponent', () => {
7074
provide: ApplicationSubmissionStatusService,
7175
useValue: mockAppStatusService,
7276
},
77+
{
78+
provide: ApplicationDecisionConditionCardService,
79+
useValue: mockApplicationDecisionConditionCardService,
80+
},
7381
{
7482
provide: ActivatedRoute,
7583
useValue: {

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

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import { ReviewComponent } from './review/review.component';
3030
import { ApplicationSubmissionStatusService } from '../../services/application/application-submission-status/application-submission-status.service';
3131
import { ApplicationTagService } from '../../services/application/application-tag/application-tag.service';
3232
import { FileTagService } from '../../services/common/file-tag.service';
33+
import { ApplicationDecisionV2Service } from '../../services/application/decision/application-decision-v2/application-decision-v2.service';
34+
import { ApplicationDecisionConditionCardDto } from '../../services/application/decision/application-decision-v2/application-decision-v2.dto';
35+
import { ApplicationDecisionConditionCardService } from '../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition-card/application-decision-condition-card.service';
3336

3437
export const unsubmittedRoutes = [
3538
{
@@ -184,6 +187,7 @@ export class ApplicationComponent implements OnInit, OnDestroy {
184187
application: ApplicationDto | undefined;
185188
reconsiderations: ApplicationReconsiderationDto[] = [];
186189
modifications: ApplicationModificationDto[] = [];
190+
decisionConditionCards: ApplicationDecisionConditionCardDto[] = [];
187191
submission?: ApplicationSubmissionDto;
188192

189193
isApplicantSubmission = false;
@@ -195,6 +199,7 @@ export class ApplicationComponent implements OnInit, OnDestroy {
195199
public applicationSubmissionService: ApplicationSubmissionService,
196200
private reconsiderationService: ApplicationReconsiderationService,
197201
private modificationService: ApplicationModificationService,
202+
private decisionConditionCardService: ApplicationDecisionConditionCardService,
198203
private route: ActivatedRoute,
199204
private titleService: Title,
200205
public applicationStatusService: ApplicationSubmissionStatusService,
@@ -214,6 +219,9 @@ export class ApplicationComponent implements OnInit, OnDestroy {
214219
this.reconsiderationService.fetchByApplication(application.fileNumber);
215220
this.modificationService.fetchByApplication(application.fileNumber);
216221

222+
this.decisionConditionCards =
223+
(await this.decisionConditionCardService.fetchByApplicationFileNumber(application.fileNumber)) || [];
224+
217225
this.isApplicantSubmission = application.source !== SYSTEM_SOURCE_TYPES.ALCS;
218226
let wasSubmittedToLfng = false;
219227

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

-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { appChildRoutes, ApplicationComponent } from './application.component';
1515
import { BoundaryAmendmentComponent } from './boundary-amendment/boundary-amendment.component';
1616
import { EditBoundaryAmendmentDialogComponent } from './boundary-amendment/edit-boundary-amendment-dialog/edit-boundary-amendment-dialog.component';
1717
import { DecisionModule } from './decision/decision.module';
18-
import { DocumentUploadDialogComponent } from './documents/document-upload-dialog/document-upload-dialog.component';
1918
import { DocumentsComponent } from './documents/documents.component';
2019
import { InfoRequestsComponent } from './info-requests/info-requests.component';
2120
import { InfoRequestDialogComponent } from './info-requests/info-request-dialog/info-request-dialog.component';
@@ -73,7 +72,6 @@ const routes: Routes = [
7372
ApplicantInfoComponent,
7473
LfngInfoComponent,
7574
DocumentsComponent,
76-
DocumentUploadDialogComponent,
7775
ProposalComponent,
7876
NfuProposalComponent,
7977
SubdProposalComponent,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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-z3" [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+
matTooltip="Condition is already used by another card"
57+
[matTooltipDisabled]="!isConditionCardNotNull(row)"
58+
></tr>
59+
</table>
60+
</div>
61+
</div>
62+
<div class="section">
63+
<div class="button-row">
64+
<button type="button" mat-stroked-button color="primary" (click)="onCancel()">Cancel</button>
65+
<button type="button" mat-flat-button color="primary" [disabled]="isSaveDisabled()" (click)="onSave()">
66+
Save
67+
</button>
68+
</div>
69+
</div>
70+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
padding: 2px;
47+
}
48+
49+
.disabled-row {
50+
background-color: #f0f0f0;
51+
opacity: 0.6;
52+
cursor: default;
53+
}
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,90 @@
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+
import { CardType } from '../../../../../shared/card/card.component';
15+
16+
@Component({
17+
selector: 'app-condition-card-dialog',
18+
templateUrl: './condition-card-dialog.component.html',
19+
styleUrl: './condition-card-dialog.component.scss',
20+
})
21+
export class ConditionCardDialogComponent implements OnInit {
22+
displayColumns: string[] = ['select', 'index', 'type', 'description'];
23+
conditionBoard: BoardDto | undefined;
24+
selectedStatus = '';
25+
26+
@ViewChild(MatSort) sort!: MatSort;
27+
dataSource: MatTableDataSource<{ condition: ApplicationDecisionConditionDto; index: number; selected: boolean }> =
28+
new MatTableDataSource();
29+
30+
constructor(
31+
@Inject(MAT_DIALOG_DATA)
32+
public data: { conditions: { condition: ApplicationDecisionConditionDto; index: number }[]; decision: string },
33+
private dialogRef: MatDialogRef<ConditionCardDialogComponent>,
34+
private decisionConditionCardService: ApplicationDecisionConditionCardService,
35+
private boardService: BoardService,
36+
private toastService: ToastService,
37+
) {}
38+
39+
async ngOnInit() {
40+
this.dataSource.data = this.data.conditions.map((item) => ({
41+
condition: item.condition,
42+
selected: false,
43+
index: item.index + 1,
44+
}));
45+
46+
this.conditionBoard = await this.boardService.fetchBoardDetail(BOARD_TYPE_CODES.APPCON);
47+
}
48+
49+
onStatusSelected(applicationStatus: BoardStatusDto) {
50+
this.selectedStatus = applicationStatus.statusCode;
51+
}
52+
53+
isConditionCardNotNull(element: any): boolean {
54+
return element.condition.conditionCard !== null;
55+
}
56+
57+
isSaveDisabled(): boolean {
58+
const isStatusSelected = !!this.selectedStatus;
59+
const isAnyRowSelected = this.dataSource.data.some((item) => item.selected);
60+
return !(isStatusSelected && isAnyRowSelected);
61+
}
62+
63+
onCancel(): void {
64+
this.dialogRef.close({ action: 'cancel' });
65+
}
66+
67+
async onSave() {
68+
const selectedStatusCode = this.conditionBoard?.statuses.find(
69+
(status) => status.label === this.selectedStatus,
70+
)?.statusCode;
71+
const selectedConditions = this.dataSource.data.filter((item) => item.selected).map((item) => item.condition.uuid);
72+
const createDto: CreateApplicationDecisionConditionCardDto = {
73+
conditionsUuids: selectedConditions,
74+
decisionUuid: this.data.decision,
75+
cardStatusCode: this.selectedStatus,
76+
};
77+
const res = await this.decisionConditionCardService.create(createDto);
78+
if (res) {
79+
this.toastService.showSuccessToastWithLink(
80+
'Condition card created successfully',
81+
'GO TO BOARD',
82+
`/board/appcon?card=${res.cardUuid}&type=${CardType.APP_CON}`,
83+
);
84+
this.dialogRef.close({ action: 'save', result: true });
85+
} else {
86+
this.toastService.showErrorToast('Failed to create condition card');
87+
this.dialogRef.close({ action: 'save', result: false });
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)