From 905890ed451362ef451ae59e2ef0cb742e44efc3 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 4 Oct 2023 15:18:35 -0700 Subject: [PATCH] View Public App Submissions * Viewing of Public App Submissions and Documents * New Module and Pages Created --- .../application-details.component.html | 3 - .../naru-details.component.spec.ts | 2 +- .../tur-details/tur-details.component.spec.ts | 2 +- .../alc-review/alc-review.component.html | 45 +++ .../alc-review/alc-review.component.scss | 8 + .../alc-review/alc-review.component.spec.ts | 33 ++ .../alc-review/alc-review.component.ts | 47 +++ .../decisions/decisions.component.html | 26 ++ .../decisions/decisions.component.scss | 20 ++ .../decisions/decisions.component.spec.ts | 33 ++ .../decisions/decisions.component.ts | 39 +++ .../submission-documents.component.html | 43 +++ .../submission-documents.component.scss | 25 ++ .../submission-documents.component.spec.ts | 35 ++ .../submission-documents.component.ts | 60 ++++ .../lfng-review/lfng-review.component.html | 285 ++++++++++++++++ .../lfng-review/lfng-review.component.scss | 74 ++++ .../lfng-review/lfng-review.component.spec.ts | 57 ++++ .../lfng-review/lfng-review.component.ts | 131 ++++++++ .../public-application.component.html | 60 ++++ .../public-application.component.scss | 105 ++++++ .../public-application.component.spec.ts | 49 +++ .../public-application.component.ts | 50 +++ .../excl-details/excl-details.component.html | 52 +++ .../excl-details/excl-details.component.scss | 0 .../excl-details.component.spec.ts | 30 ++ .../excl-details/excl-details.component.ts | 35 ++ .../incl-details/incl-details.component.html | 58 ++++ .../incl-details/incl-details.component.scss | 0 .../incl-details.component.spec.ts | 30 ++ .../incl-details/incl-details.component.ts | 35 ++ .../naru-details/naru-details.component.html | 209 ++++++++++++ .../naru-details/naru-details.component.scss | 9 + .../naru-details.component.spec.ts | 33 ++ .../naru-details/naru-details.component.ts | 30 ++ .../nfu-details/nfu-details.component.html | 96 ++++++ .../nfu-details/nfu-details.component.scss | 7 + .../nfu-details/nfu-details.component.spec.ts | 33 ++ .../nfu-details/nfu-details.component.ts | 31 ++ .../submission/parcel/parcel.component.html | 79 +++++ .../submission/parcel/parcel.component.scss | 39 +++ .../parcel/parcel.component.spec.ts | 40 +++ .../submission/parcel/parcel.component.ts | 37 ++ .../pfrs-details/pfrs-details.component.html | 213 ++++++++++++ .../pfrs-details/pfrs-details.component.scss | 14 + .../pfrs-details.component.spec.ts | 33 ++ .../pfrs-details/pfrs-details.component.ts | 34 ++ .../pofo-details/pofo-details.component.html | 129 +++++++ .../pofo-details/pofo-details.component.scss | 9 + .../pofo-details.component.spec.ts | 31 ++ .../pofo-details/pofo-details.component.ts | 33 ++ .../roso-details/roso-details.component.html | 120 +++++++ .../roso-details/roso-details.component.scss | 9 + .../roso-details.component.spec.ts | 33 ++ .../roso-details/roso-details.component.ts | 33 ++ .../subd-details/subd-details.component.html | 53 +++ .../subd-details/subd-details.component.scss | 9 + .../subd-details.component.spec.ts | 33 ++ .../subd-details/subd-details.component.ts | 31 ++ .../submission-details.component.html | 160 +++++++++ .../submission-details.component.scss | 143 ++++++++ .../submission-details.component.spec.ts | 42 +++ .../submission-details.component.ts | 66 ++++ .../submission/submission-details.module.ts | 34 ++ .../tur-details/tur-details.component.html | 41 +++ .../tur-details/tur-details.component.scss | 0 .../tur-details/tur-details.component.spec.ts | 31 ++ .../tur-details/tur-details.component.ts | 29 ++ .../src/app/features/public/public.module.ts | 8 + .../search-list/search-list.component.ts | 2 +- .../src/app/services/public/public.dto.ts | 133 ++++++++ .../services/public/public.service.spec.ts | 53 +++ .../src/app/services/public/public.service.ts | 39 +++ .../automapper/public.automapper.profile.ts | 39 +++ .../application-submission.entity.ts | 1 + .../portal/public/public.controller.spec.ts | 138 ++++++++ .../src/portal/public/public.controller.ts | 110 ++++++ .../apps/alcs/src/portal/public/public.dto.ts | 317 ++++++++++++++++++ .../alcs/src/portal/public/public.module.ts | 11 +- 79 files changed, 4321 insertions(+), 8 deletions(-) create mode 100644 portal-frontend/src/app/features/public/application/alc-review/alc-review.component.html create mode 100644 portal-frontend/src/app/features/public/application/alc-review/alc-review.component.scss create mode 100644 portal-frontend/src/app/features/public/application/alc-review/alc-review.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/alc-review/alc-review.component.ts create mode 100644 portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html create mode 100644 portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.scss create mode 100644 portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts create mode 100644 portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html create mode 100644 portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.scss create mode 100644 portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts create mode 100644 portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html create mode 100644 portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.scss create mode 100644 portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts create mode 100644 portal-frontend/src/app/features/public/application/public-application.component.html create mode 100644 portal-frontend/src/app/features/public/application/public-application.component.scss create mode 100644 portal-frontend/src/app/features/public/application/public-application.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/public-application.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/submission-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/submission-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/submission-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/submission-details.component.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/submission-details.module.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html create mode 100644 portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.scss create mode 100644 portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.spec.ts create mode 100644 portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts create mode 100644 portal-frontend/src/app/services/public/public.dto.ts create mode 100644 portal-frontend/src/app/services/public/public.service.spec.ts create mode 100644 portal-frontend/src/app/services/public/public.service.ts create mode 100644 services/apps/alcs/src/common/automapper/public.automapper.profile.ts create mode 100644 services/apps/alcs/src/portal/public/public.controller.spec.ts create mode 100644 services/apps/alcs/src/portal/public/public.controller.ts create mode 100644 services/apps/alcs/src/portal/public/public.dto.ts diff --git a/portal-frontend/src/app/features/applications/application-details/application-details.component.html b/portal-frontend/src/app/features/applications/application-details/application-details.component.html index 31226669e8..b4c631960b 100644 --- a/portal-frontend/src/app/features/applications/application-details/application-details.component.html +++ b/portal-frontend/src/app/features/applications/application-details/application-details.component.html @@ -17,9 +17,6 @@

3. Primary Contact

- - Changes made to this section will not be flagged as -
Type
diff --git a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.spec.ts b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.spec.ts index b954864e63..d42ffa1ad7 100644 --- a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.spec.ts +++ b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.spec.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentService } from '../../../../services/application-doc import { NaruDetailsComponent } from './naru-details.component'; -describe('PofoDetailsComponent', () => { +describe('NaruDetailsComponent', () => { let component: NaruDetailsComponent; let fixture: ComponentFixture; let mockAppDocumentService: DeepMocked; diff --git a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.spec.ts b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.spec.ts index f3125dd612..26d6c4cf71 100644 --- a/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.spec.ts +++ b/portal-frontend/src/app/features/applications/application-details/tur-details/tur-details.component.spec.ts @@ -4,7 +4,7 @@ import { ApplicationDocumentService } from '../../../../services/application-doc import { TurDetailsComponent } from './tur-details.component'; -describe('NfuDetailsComponent', () => { +describe('TurDetailsComponent', () => { let component: TurDetailsComponent; let fixture: ComponentFixture; let mockAppDocumentService: DeepMocked; diff --git a/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.html b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.html new file mode 100644 index 0000000000..4ea1d813f6 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.html @@ -0,0 +1,45 @@ +
+
+

ALC Review and Decision

+
+ + +
+
+
+ This section will update after the application is submitted to the ALC. +
+
+ Application not subject to Agricultural Land Commission review. +
+
+ + +
+
diff --git a/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.scss b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.scss new file mode 100644 index 0000000000..7ca8d1c637 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.scss @@ -0,0 +1,8 @@ +@use '../../../../../styles/functions' as *; +@use '../../../../../styles/colors'; + +.warning { + background-color: rgba(colors.$accent-color-light, 0.5); + padding: rem(16); + margin-bottom: rem(24); +} diff --git a/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.spec.ts b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.spec.ts new file mode 100644 index 0000000000..62a72d72d0 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ApplicationSubmissionReviewService } from '../../../../services/application-submission-review/application-submission-review.service'; + +import { AlcReviewComponent } from './alc-review.component'; + +describe('AlcsReviewComponent', () => { + let component: AlcReviewComponent; + let fixture: ComponentFixture; + let mockAppReviewService: DeepMocked; + + beforeEach(async () => { + mockAppReviewService = createMock(); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: ApplicationSubmissionReviewService, + useValue: mockAppReviewService, + }, + ], + declarations: [AlcReviewComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AlcReviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.ts b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.ts new file mode 100644 index 0000000000..ced2da29b9 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/alc-review.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; +import { ApplicationSubmissionReviewService } from '../../../../services/application-submission-review/application-submission-review.service'; +import { + SUBMISSION_STATUS, + ApplicationSubmissionDetailedDto, +} from '../../../../services/application-submission/application-submission.dto'; + +@Component({ + selector: 'app-alc-review', + templateUrl: './alc-review.component.html', + styleUrls: ['./alc-review.component.scss'], +}) +export class AlcReviewComponent implements OnInit, OnDestroy { + private $destroy = new Subject(); + + @Input() $application = new BehaviorSubject(undefined); + @Input() $applicationDocuments = new BehaviorSubject([]); + + application: ApplicationSubmissionDetailedDto | undefined; + SUBMISSION_STATUS = SUBMISSION_STATUS; + + constructor(private applicationReviewService: ApplicationSubmissionReviewService, private router: Router) {} + + ngOnInit(): void { + this.$application.pipe(takeUntil(this.$destroy)).subscribe((application) => { + this.application = application; + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + async onReview(fileId: string) { + if (this.application?.status.code === SUBMISSION_STATUS.SUBMITTED_TO_LG) { + const review = await this.applicationReviewService.startReview(fileId); + if (!review) { + return; + } + } + await this.router.navigateByUrl(`application/${fileId}/review`); + } +} diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html new file mode 100644 index 0000000000..ee4d949dba --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.html @@ -0,0 +1,26 @@ +
+

Decision #{{ decisions.length - index }}

+
+
+
Decision Date
+ {{ decision.date | momentFormat }} +
+ +
+
Resolution Number
+ #{{ decision.resolutionNumber }}/{{ decision.resolutionYear }} +
+ +
+
Decision Outcome
+ {{ decision.outcome.label }} {{ decision.isSubjectToConditions ? '- Subject to Conditions' : '' }} +
+ +
+
Decision Document
+ +
+
+
diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.scss b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.scss new file mode 100644 index 0000000000..c92c6fb6a8 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.scss @@ -0,0 +1,20 @@ +@use '../../../../../../styles/functions' as *; +@use '../../../../../../styles/colors'; + +.decision-table { + padding: rem(8); + margin: rem(12) 0 rem(20) 0; + background-color: colors.$grey-light; + display: grid; + grid-row-gap: rem(24); + grid-column-gap: rem(16); + grid-template-columns: 100%; + word-wrap: break-word; + hyphens: auto; + + @media screen and (min-width: $tabletBreakpoint) { + padding: rem(16); + margin: rem(24) 0 rem(40) 0; + grid-template-columns: 50% 50%; + } +} diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.spec.ts b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.spec.ts new file mode 100644 index 0000000000..f56a7194b9 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock } from '@golevelup/ts-jest'; +import { ApplicationDecisionService } from '../../../../../services/application-decision/application-decision.service'; + +import { DecisionsComponent } from './decisions.component'; + +describe('DecisionsComponent', () => { + let component: DecisionsComponent; + let fixture: ComponentFixture; + let mockDecisionService: ApplicationDecisionService; + + beforeEach(async () => { + mockDecisionService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [DecisionsComponent], + providers: [ + { + provide: ApplicationDecisionService, + useValue: mockDecisionService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DecisionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts new file mode 100644 index 0000000000..1280cc0118 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/decisions/decisions.component.ts @@ -0,0 +1,39 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { ApplicationPortalDecisionDto } from '../../../../../services/application-decision/application-decision.dto'; +import { ApplicationDecisionService } from '../../../../../services/application-decision/application-decision.service'; + +@Component({ + selector: 'app-decisions[fileNumber]', + templateUrl: './decisions.component.html', + styleUrls: ['./decisions.component.scss'], +}) +export class DecisionsComponent implements OnInit, OnChanges { + @Input() fileNumber = ''; + decisions: ApplicationPortalDecisionDto[] = []; + + constructor(private decisionService: ApplicationDecisionService) {} + + ngOnInit(): void { + this.loadDecisions(); + } + + ngOnChanges(changes: SimpleChanges): void { + this.loadDecisions(); + } + + async openFile(uuid: string) { + const res = await this.decisionService.openFile(uuid); + if (res) { + window.open(res.url, '_blank'); + } + } + + private async loadDecisions() { + if (this.fileNumber) { + const decisions = await this.decisionService.getByFileId(this.fileNumber); + if (decisions) { + this.decisions = decisions; + } + } + } +} diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html new file mode 100644 index 0000000000..8dc409f48a --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.html @@ -0,0 +1,43 @@ +

Application Documents

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type + {{ element.type?.label }} + Document Name + {{ element.fileName }} + Source{{ element.source }}Upload Date{{ element.uploadedAt | date }}Actions + +
Documents will be visible here once provided by ALC
+
diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.scss b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.scss new file mode 100644 index 0000000000..0027146765 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.scss @@ -0,0 +1,25 @@ +@use '../../../../../../styles/colors'; +@use '../../../../../../styles/functions' as *; + +.header { + display: flex; + justify-content: space-between; +} + +.table-container { + margin: rem(4); + overflow-x: auto; +} + +.documents { + margin-top: rem(12); +} + +.mat-mdc-no-data-row { + height: rem(56); + color: colors.$grey-dark; +} + +a { + word-break: break-all; +} diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.spec.ts b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.spec.ts new file mode 100644 index 0000000000..09eb1292d3 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.spec.ts @@ -0,0 +1,35 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; + +import { SubmissionDocumentsComponent } from './submission-documents.component'; + +describe('SubmissionDocumentsComponent', () => { + let component: SubmissionDocumentsComponent; + let fixture: ComponentFixture; + let mockAppDocService: DeepMocked; + + beforeEach(async () => { + mockAppDocService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [SubmissionDocumentsComponent], + providers: [ + { + provide: ApplicationDocumentService, + useValue: mockAppDocService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(SubmissionDocumentsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts new file mode 100644 index 0000000000..027fbdbfea --- /dev/null +++ b/portal-frontend/src/app/features/public/application/alc-review/submission-documents/submission-documents.component.ts @@ -0,0 +1,60 @@ +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; + +@Component({ + selector: 'app-submission-documents', + templateUrl: './submission-documents.component.html', + styleUrls: ['./submission-documents.component.scss'], +}) +export class SubmissionDocumentsComponent implements OnInit, OnDestroy { + private $destroy = new Subject(); + + displayedColumns: string[] = ['type', 'fileName', 'source', 'uploadedAt', 'actions']; + documents: ApplicationDocumentDto[] = []; + + @Input() $applicationDocuments = new BehaviorSubject([]); + + @ViewChild(MatSort) sort!: MatSort; + dataSource: MatTableDataSource = new MatTableDataSource(); + + constructor(private applicationDocumentService: ApplicationDocumentService) {} + + ngOnInit(): void { + this.$applicationDocuments.pipe(takeUntil(this.$destroy)).subscribe((documents) => { + this.dataSource = new MatTableDataSource(documents); + }); + } + + async openFile(uuid: string) { + const res = await this.applicationDocumentService.openFile(uuid); + if (res) { + window.open(res.url, '_blank'); + } + } + + async downloadFile(uuid: string) { + const res = await this.applicationDocumentService.downloadFile(uuid); + if (res) { + const downloadLink = document.createElement('a'); + downloadLink.href = res.url; + downloadLink.download = res.url.split('/').pop()!; + if (window.webkitURL == null) { + downloadLink.onclick = (event: MouseEvent) => document.body.removeChild(event.target); + downloadLink.style.display = 'none'; + document.body.appendChild(downloadLink); + } + downloadLink.click(); + } + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + protected readonly open = open; +} diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html new file mode 100644 index 0000000000..2ae5a57c31 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.html @@ -0,0 +1,285 @@ +
+
+

Local/First Nation Gov Review

+
+ + + + +
+
+
+ infoThis section will update after the application is submitted. +
+
+ infoApplication not subject to Local/First Nation Government review. +
+
+ infoPending Local/First Nation Government review. +
+
+
Comment for Applicant
+ {{ application.returnedComment }} + No comment added +
+
+
+

Contact Information

+
+
+
Local Government File Number
+
+ {{ applicationReview.localGovernmentFileNumber }} + +
+
+
+
First Name
+
+ {{ applicationReview.firstName }} + +
+
+
+
Last Name
+
+ {{ applicationReview.lastName }} + +
+
+
+
Position
+
+ {{ applicationReview.position }} + +
+
+
+
Department
+
+ {{ applicationReview.department }} + +
+
+
+
Phone Number
+
+ {{ applicationReview.phoneNumber }} + +
+
+
+
Email
+
+ {{ applicationReview.email }} + +
+
+
+
+
+

Plans & Bylaws: OCP

+
+
+
Is the application parcel(s) subject to a Local Government OCP designation?
+
+ {{ + applicationReview.isOCPDesignation ? 'Yes' : 'No' + }} + +
+
+
+
OCP Bylaw Name
+
+ {{ applicationReview.OCPBylawName }} + +
+
+
+
OCP Designation
+
+ {{ applicationReview.OCPDesignation }} + +
+
+
+
Is this proposal consistent with the current OCP designation?
+
+ {{ + applicationReview.OCPConsistent ? 'Yes' : 'No' + }} + +
+
+
+
+
+

Plans & Bylaws: Zoning

+
+
+
Is the application parcel(s) subject to a Local Government zoning designation?
+
+ {{ + applicationReview.isSubjectToZoning ? 'Yes' : 'No' + }} + +
+
+
+
Zoning Bylaw Name
+
+ {{ applicationReview.zoningBylawName }} + +
+
+
+
Zoning Designation
+
+ {{ applicationReview.zoningDesignation }} + +
+
+
+
Minimum Lot Size (hectares)
+
+ {{ applicationReview.zoningMinimumLotSize }} + +
+
+
+
Is this proposal consistent with the current zoning designation?
+
+ {{ + applicationReview.isZoningConsistent ? 'Yes' : 'No' + }} + +
+
+
+
+
+

Resolution

+
+
+ Please complete both Step 2 Plans & Bylaws: OCP and Step 3 Plans & Bylaws: Zoning to continue with this step. +
+
+ By indicating that the parcel(s) is not subject to Local Government OCP or Zoning, + no authorizing resolution is required as per S. 25 (3) or S. 29 (4) of the ALC Act. + The only option available is to forward this application on to the ALC. +
+
+
Resolution for Application to Proceed to the ALC
+
+ {{ + applicationReview.isAuthorized ? 'Authorized' : 'Refuse to Authorize' + }} + +
+
+
+
+
+

Attachments

+
+
+ Please complete Step 2 Plans & Bylaws: OCP, Step 3 Plans & Bylaws: Zoning and Step 4 Resolution to continue + with this step. +
+
+
+
+
Resolution Document
+ + +
+
+
+ Staff Report (optional) +
+ + +
+
+
Other Attachments (optional):
+ +
+
+
+
+
diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.scss b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.scss new file mode 100644 index 0000000000..8af4733989 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.scss @@ -0,0 +1,74 @@ +@use '../../../../../styles/functions' as *; +@use '../../../../../styles/colors'; + +.warning { + align-items: center; + background-color: rgba(colors.$accent-color-light, 0.5); + border-radius: rem(4); + display: flex; + padding: rem(16); + margin-bottom: rem(24); + + mat-icon { + margin-right: 12px; + } +} + +.comment-container { + align-items: flex-start; + background-color: rgba(colors.$accent-color-light, 0.5); + border-radius: rem(4); + display: flex; + flex-direction: column; + margin-bottom: rem(24); + padding: rem(16); + + div { + margin-bottom: rem(4); + } +} + +.no-comment { + color: colors.$grey-dark; + font-style: italic; +} + +.link { + color: colors.$link-color; + cursor: pointer; +} + +.review-table { + padding: rem(8); + margin: rem(12) 0 rem(20) 0; + background-color: colors.$grey-light; + display: grid; + grid-row-gap: rem(24); + grid-column-gap: rem(16); + grid-template-columns: 1fr; + word-wrap: break-word; + hyphens: auto; + + .edit-button { + display: flex; + justify-content: center; + } + + .subheading2 { + margin-bottom: rem(4) !important; + } + + @media screen and (min-width: $tabletBreakpoint) { + padding: rem(16); + margin: rem(24) 0 rem(40) 0; + grid-template-columns: 1fr 1fr; + + .full-width { + grid-column: 1/3; + } + + .edit-button { + grid-column: 1/3; + } + } +} diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.spec.ts b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.spec.ts new file mode 100644 index 0000000000..d1556cc200 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.spec.ts @@ -0,0 +1,57 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; +import { ApplicationSubmissionReviewDto } from '../../../../services/application-submission-review/application-submission-review.dto'; +import { ApplicationSubmissionReviewService } from '../../../../services/application-submission-review/application-submission-review.service'; +import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto'; +import { PdfGenerationService } from '../../../../services/pdf-generation/pdf-generation.service'; + +import { LfngReviewComponent } from './lfng-review.component'; + +describe('LfngReviewComponent', () => { + let component: LfngReviewComponent; + let fixture: ComponentFixture; + + let mockAppSubReviewService: DeepMocked; + let mockPdfGenerationService: DeepMocked; + let mockAppDocumentService: DeepMocked; + + beforeEach(async () => { + mockAppSubReviewService = createMock(); + mockPdfGenerationService = createMock(); + mockAppDocumentService = createMock(); + + mockAppSubReviewService.$applicationReview = new BehaviorSubject( + undefined + ); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: ApplicationSubmissionReviewService, + useValue: mockAppSubReviewService, + }, + { + provide: PdfGenerationService, + useValue: mockPdfGenerationService, + }, + { + provide: ApplicationDocumentService, + useValue: mockAppDocumentService, + }, + ], + declarations: [LfngReviewComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(LfngReviewComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts new file mode 100644 index 0000000000..e80935380e --- /dev/null +++ b/portal-frontend/src/app/features/public/application/lfng-review/lfng-review.component.ts @@ -0,0 +1,131 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { Router } from '@angular/router'; +import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; +import { ApplicationSubmissionReviewDto } from '../../../../services/application-submission-review/application-submission-review.dto'; +import { ApplicationSubmissionReviewService } from '../../../../services/application-submission-review/application-submission-review.service'; +import { + ApplicationSubmissionDetailedDto, + SUBMISSION_STATUS, +} from '../../../../services/application-submission/application-submission.dto'; +import { PdfGenerationService } from '../../../../services/pdf-generation/pdf-generation.service'; +import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-lfng-review', + templateUrl: './lfng-review.component.html', + styleUrls: ['./lfng-review.component.scss'], +}) +export class LfngReviewComponent implements OnInit, OnDestroy { + private $destroy = new Subject(); + + @Input() $application = new BehaviorSubject(undefined); + @Input() $applicationDocuments = new BehaviorSubject([]); + + @Output() onCancel = new EventEmitter(); + + application: ApplicationSubmissionDetailedDto | undefined; + applicationReview: ApplicationSubmissionReviewDto | undefined; + SUBMISSION_STATUS = SUBMISSION_STATUS; + staffReport: ApplicationDocumentDto[] = []; + resolutionDocument: ApplicationDocumentDto[] = []; + governmentOtherAttachments: ApplicationDocumentDto[] = []; + hasCompletedStepsBeforeResolution = false; + hasCompletedStepsBeforeDocuments = false; + submittedToAlcStatus = false; + + constructor( + private applicationReviewService: ApplicationSubmissionReviewService, + private pdfGenerationService: PdfGenerationService, + private applicationDocumentService: ApplicationDocumentService, + private router: Router + ) {} + + ngOnInit(): void { + this.applicationReviewService.$applicationReview.pipe(takeUntil(this.$destroy)).subscribe((appReview) => { + if (appReview) { + this.applicationReview = appReview; + + this.hasCompletedStepsBeforeResolution = + appReview.isFirstNationGovernment || + (!appReview.isFirstNationGovernment && + appReview.isOCPDesignation !== null && + appReview.isSubjectToZoning !== null && + (appReview.isOCPDesignation === true || appReview.isSubjectToZoning === true)); + + this.hasCompletedStepsBeforeDocuments = + appReview.isFirstNationGovernment || + (appReview.isAuthorized !== null && + appReview.isOCPDesignation !== null && + appReview.isSubjectToZoning !== null) || + (appReview.isAuthorized === null && + appReview.isOCPDesignation === false && + appReview.isSubjectToZoning === false); + } + }); + + this.$application.pipe(takeUntil(this.$destroy)).subscribe((application) => { + this.application = application; + this.submittedToAlcStatus = !!this.application?.submissionStatuses.find( + (s) => s.statusTypeCode === SUBMISSION_STATUS.SUBMITTED_TO_ALC && !!s.effectiveDate + ); + this.loadReview(); + }); + + this.$applicationDocuments.subscribe((documents) => { + this.staffReport = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.STAFF_REPORT); + this.resolutionDocument = documents.filter( + (document) => document.type?.code === DOCUMENT_TYPE.RESOLUTION_DOCUMENT + ); + this.governmentOtherAttachments = documents.filter( + (document) => document.type?.code === DOCUMENT_TYPE.OTHER && document.source === DOCUMENT_SOURCE.LFNG + ); + }); + } + + async onDownloadReviewPdf(fileNumber: string | undefined) { + if (fileNumber) { + await this.pdfGenerationService.generateReview(fileNumber); + } + } + + async loadReview() { + if ( + this.application && + this.application.typeCode !== 'TURP' && + (this.submittedToAlcStatus || + (this.application.status.code === SUBMISSION_STATUS.IN_REVIEW_BY_LG && this.application.canReview)) + ) { + await this.applicationReviewService.getByFileId(this.application.fileNumber); + } else { + this.applicationReview = undefined; + } + } + + async openFile(uuid: string) { + const res = await this.applicationDocumentService.openFile(uuid); + if (res) { + window.open(res.url, '_blank'); + } + } + + async onReview(fileId: string) { + if (this.application?.status.code === SUBMISSION_STATUS.SUBMITTED_TO_LG) { + const review = await this.applicationReviewService.startReview(fileId); + if (!review) { + return; + } + } + await this.router.navigateByUrl(`application/${fileId}/review`); + } + + onCancelClicked(fileNumber: string) { + this.onCancel.emit(fileNumber); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } +} diff --git a/portal-frontend/src/app/features/public/application/public-application.component.html b/portal-frontend/src/app/features/public/application/public-application.component.html new file mode 100644 index 0000000000..8bf92b73ce --- /dev/null +++ b/portal-frontend/src/app/features/public/application/public-application.component.html @@ -0,0 +1,60 @@ + + + +
+ +
diff --git a/portal-frontend/src/app/features/public/application/public-application.component.scss b/portal-frontend/src/app/features/public/application/public-application.component.scss new file mode 100644 index 0000000000..0e74e9ca5b --- /dev/null +++ b/portal-frontend/src/app/features/public/application/public-application.component.scss @@ -0,0 +1,105 @@ +@use '../../../../styles/functions' as *; +@use '../../../../styles/colors'; + +.navigation { + ::ng-deep .mdc-tab__text-label { + font-weight: bold; + } +} + +.content { + margin: rem(24) 0; +} + +:host::ng-deep { + .header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: rem(24); + flex-direction: column; + + @media screen and (min-width: $tabletBreakpoint) { + flex-direction: row; + } + + h3 { + margin-top: rem(8) !important; + } + + .btns-wrapper { + display: flex; + flex-direction: column-reverse; + width: 100%; + + button { + margin-top: rem(16) !important; + } + + @media screen and (min-width: $tabletBreakpoint) { + display: inline-block; + width: unset; + + button { + margin-right: rem(16) !important; + margin-top: rem(8) !important; + } + + button:last-child { + margin-right: 0 !important; + } + } + } + } +} + +.absolute { + position: absolute; + left: 0; + right: 0; +} + +.banner { + background-color: colors.$secondary-color; + width: 100%; + color: #fff; + padding: rem(16) rem(24); + margin-bottom: rem(32); + + @media screen and (min-width: $tabletBreakpoint) { + padding: rem(12) rem(36) !important; + } + + // TODO: this is just a placeholder and will be addressed later + @media screen and (min-width: $desktopBreakpoint) { + padding: rem(18) rem(80) !important; + } + + .banner-status { + margin: rem(16) 0; + display: grid; + grid-template-columns: 1fr; + + div { + margin-top: rem(16); + } + + @media screen and (min-width: $tabletBreakpoint) { + grid-template-columns: 1fr 1fr; + margin-bottom: 0; + } + } +} + +.no-comment { + color: colors.$grey-dark; + font-style: italic; +} + +section { + margin-bottom: rem(24); +} + +.document { + margin-bottom: rem(8); +} diff --git a/portal-frontend/src/app/features/public/application/public-application.component.spec.ts b/portal-frontend/src/app/features/public/application/public-application.component.spec.ts new file mode 100644 index 0000000000..1c02e7c4dd --- /dev/null +++ b/portal-frontend/src/app/features/public/application/public-application.component.spec.ts @@ -0,0 +1,49 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { PublicService } from '../../../services/public/public.service'; + +import { PublicApplicationComponent } from './public-application.component'; + +describe('PublicApplicationComponent', () => { + let component: PublicApplicationComponent; + let fixture: ComponentFixture; + + let mockRoute; + let mockPublicService: DeepMocked; + + let routeParamMap: BehaviorSubject>; + + beforeEach(async () => { + mockRoute = createMock(); + mockPublicService = createMock(); + + routeParamMap = new BehaviorSubject(new Map()); + mockRoute.paramMap = routeParamMap; + + await TestBed.configureTestingModule({ + providers: [ + { + provide: ActivatedRoute, + useValue: mockRoute, + }, + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + declarations: [PublicApplicationComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(PublicApplicationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/public-application.component.ts b/portal-frontend/src/app/features/public/application/public-application.component.ts new file mode 100644 index 0000000000..f3700a1941 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/public-application.component.ts @@ -0,0 +1,50 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, takeUntil } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../services/application-document/application-document.dto'; +import { ApplicationParcelDto } from '../../../services/application-parcel/application-parcel.dto'; +import { SUBMISSION_STATUS } from '../../../services/application-submission/application-submission.dto'; +import { PublicApplicationSubmissionDto } from '../../../services/public/public.dto'; +import { PublicService } from '../../../services/public/public.service'; + +@Component({ + selector: 'app-public-application', + templateUrl: './public-application.component.html', + styleUrls: ['./public-application.component.scss'], +}) +export class PublicApplicationComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + + SUBMISSION_STATUS = SUBMISSION_STATUS; + + submission: PublicApplicationSubmissionDto | undefined; + documents: ApplicationDocumentDto[] = []; + parcels: ApplicationParcelDto[] = []; + + constructor(private publicService: PublicService, private route: ActivatedRoute) {} + + ngOnInit(): void { + this.route.paramMap.pipe(takeUntil(this.$destroy)).subscribe((routeParams) => { + const fileId = routeParams.get('fileId'); + if (fileId) { + this.loadApplication(fileId); + } + }); + } + + private async loadApplication(fileId: string) { + const res = await this.publicService.getApplication(fileId); + if (res) { + const { submission, documents, parcels } = res; + + this.submission = submission; + this.documents = documents; + this.parcels = parcels; + } + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html new file mode 100644 index 0000000000..f05b0ed16f --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.html @@ -0,0 +1,52 @@ +
+
The governmental or prescribed public body that is applying to exclude land
+
+ {{ applicationSubmission.prescribedBody }} +
+ +
How many hectares are you proposing to exclude? (hectares)
+
+ {{ applicationSubmission.inclExclHectares }} + +
+ +
+ Does any land under application share a common property line with land in another Local or First Nation Government? +
+
+ + {{ applicationSubmission.exclShareGovernmentBorders ? 'Yes' : 'No' }} + + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ +
Explain why you believe that the parcel(s) should be excluded from the ALR
+
+ {{ applicationSubmission.exclWhyLand }} + +
+ +
Proposal Map / Site Plan
+ + +
Report of Public Hearing
+ +
diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.scss b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.spec.ts new file mode 100644 index 0000000000..dce18eaba5 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; +import { ExclDetailsComponent } from './excl-details.component'; + +describe('ExclDetailsComponent', () => { + let component: ExclDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExclDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ExclDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts new file mode 100644 index 0000000000..cc7b23fc89 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/excl-details/excl-details.component.ts @@ -0,0 +1,35 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-excl-details', + templateUrl: './excl-details.component.html', + styleUrls: ['./excl-details.component.scss'], +}) +export class ExclDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + this.reportOfPublicHearing = documents.filter( + (document) => document.type?.code === DOCUMENT_TYPE.REPORT_OF_PUBLIC_HEARING + ); + } + + proposalMap: ApplicationDocumentDto[] = []; + reportOfPublicHearing: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html new file mode 100644 index 0000000000..557efad2b4 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.html @@ -0,0 +1,58 @@ +
+
How many hectares are you proposing to include? (hectares)
+
+ {{ applicationSubmission.inclExclHectares }} + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ +
Does the proposal support agriculture in the short or long term?
+
+ {{ applicationSubmission.inclAgricultureSupport }} + +
+ +
+ Describe any improvements that have been made to, or are planned for the parcel proposed for inclusion. +
+
+ {{ applicationSubmission.inclImprovements }} + +
+ +
Proposal Map / Site Plan
+ + + +
+ Is the applying government the registered land owner of all parcels under this inclusion application? +
+
+ + {{ applicationSubmission.inclGovernmentOwnsAllParcels ? 'Yes' : 'No' }} + +
+
+ + +
Report of Public Hearing
+ +
+
diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.scss b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.spec.ts new file mode 100644 index 0000000000..339ae1b64a --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; +import { InclDetailsComponent } from './incl-details.component'; + +describe('InclDetailsComponent', () => { + let component: InclDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [InclDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(InclDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts new file mode 100644 index 0000000000..53e325f10f --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/incl-details/incl-details.component.ts @@ -0,0 +1,35 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-incl-details', + templateUrl: './incl-details.component.html', + styleUrls: ['./incl-details.component.scss'], +}) +export class InclDetailsComponent { + proposalMap: ApplicationDocumentDto[] = []; + reportOfPublicHearing: ApplicationDocumentDto[] = []; + + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + this.reportOfPublicHearing = documents.filter( + (document) => document.type?.code === DOCUMENT_TYPE.REPORT_OF_PUBLIC_HEARING + ); + } + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html new file mode 100644 index 0000000000..552d817696 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.html @@ -0,0 +1,209 @@ +
+
Selected Subtype:
+
+ + {{ applicationSubmission.naruSubtype.label }} + + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ + +
+ What is the total floor area (m2) of the proposed additional residence? +
+
+ {{ applicationSubmission.naruFloorArea }} + +
+ +
+ Describe the necessity for an additional residence for farm use and how it will support agriculture in the short + or long term. +
+
+ {{ applicationSubmission.naruResidenceNecessity }} + +
+ +
Describe the rationale for the proposed location of the additional residence.
+
+ {{ applicationSubmission.naruLocationRationale }} + +
+ +
+ Provide the total area (m2) and a description of infrastructure necessary to support the additional + residence. +
+
+ {{ applicationSubmission.naruInfrastructure }} + +
+
+ + +
+ What is the total floor area (m2) of the proposed principal residence? +
+
+ {{ applicationSubmission.naruFloorArea }} + +
+ +
+ Describe how the proposal for a principal residence more than 500 m2 will support agriculture in the + short or long term. +
+
+ {{ applicationSubmission.naruResidenceNecessity }} + +
+ +
Describe the rationale for the proposed location of the principal residence.
+
+ {{ applicationSubmission.naruLocationRationale }} + +
+ +
+ Provide the total area (m2) and a description of infrastructure necessary to support the additional + residence. +
+
+ {{ applicationSubmission.naruInfrastructure }} + +
+
+ + +
What is the total floor area (m2) of the proposed accommodation?
+
+ {{ applicationSubmission.naruFloorArea }} + +
+ +
How many "sleeping units" in total are proposed?
+
+ {{ applicationSubmission.naruSleepingUnits }} + +
+ +
+ Describe how the proposal for tourism accommodation will support agriculture in the short or long term. +
+
+ {{ applicationSubmission.naruResidenceNecessity }} + +
+ +
Describe the rationale for the proposed location of the tourism accommodation.
+
+ {{ applicationSubmission.naruLocationRationale }} + +
+ +
+ Provide the total area (m2) and a description of infrastructure necessary to support the tourism + accommodation. +
+
+ {{ applicationSubmission.naruInfrastructure }} + +
+ +
Describe any agri-tourism that is currently taking place on the property.
+
+ {{ applicationSubmission.naruAgriTourism }} + +
+
+ +
+ Describe the total floor area (m2), type, number, and occupancy of all residential structures currently + located on the property. +
+
+ {{ applicationSubmission.naruExistingStructures }} + +
+ +
Proposal Map / Site Plan
+ + +
+ Do you need to import any fill to construct or conduct the proposed non-adhering residential use? +
+
+ {{ + applicationSubmission.naruWillImportFill ? 'Yes' : 'No' + }} + +
+ + +
Describe the type and amount of fill proposed to be placed.
+
+ {{ applicationSubmission.naruFillType }} + +
+ +
Briefly describe the origin and quality of fill.
+
+ {{ applicationSubmission.naruFillOrigin }} + +
+ +
Fill to be Placed
+
+
Volume
+
+ {{ applicationSubmission.naruToPlaceVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.naruToPlaceArea }} + m2 + +
+
Maximum Depth
+
+ {{ applicationSubmission.naruToPlaceMaximumDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.naruToPlaceAverageDepth }} + m + +
+
+ +
+ Project Duration +
+
Duration
+
+ {{ applicationSubmission.naruProjectDurationUnit }} + +
+
Length
+
+ {{ applicationSubmission.naruProjectDurationAmount }} + +
+
+
diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.scss b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.scss new file mode 100644 index 0000000000..75917bf09c --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.scss @@ -0,0 +1,9 @@ +@use '../../../../../../styles/functions' as *; + +.soil-table { + display: grid; + grid-template-columns: max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); +} diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.spec.ts new file mode 100644 index 0000000000..3fa010cfa6 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { NaruDetailsComponent } from './naru-details.component'; + +describe('NaruDetailsComponent', () => { + let component: NaruDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [NaruDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(NaruDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts new file mode 100644 index 0000000000..e593aff636 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/naru-details/naru-details.component.ts @@ -0,0 +1,30 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-naru-details[applicationSubmission]', + templateUrl: './naru-details.component.html', + styleUrls: ['./naru-details.component.scss'], +}) +export class NaruDetailsComponent { + proposalMap: ApplicationDocumentDto[] = []; + + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html new file mode 100644 index 0000000000..504b5a76c5 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.html @@ -0,0 +1,96 @@ +
+
How many hectares are proposed for non-farm use? (hectares)
+
+ {{ applicationSubmission.nfuHectares }} +
+
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + + +
+
Could this proposal be accommodated on lands outside of the ALR?
+
+ {{ applicationSubmission.nfuOutsideLands }} + +
+
Does the proposal support agriculture in the short or long term?
+
+ {{ applicationSubmission.nfuAgricultureSupport }} + +
+
Proposal Map / Site Plan
+ +
+ Do you need to import any fill to construct or conduct the proposed non-farm use? +
+
+ {{ + applicationSubmission.nfuWillImportFill ? 'Yes' : 'No' + }} + +
+ +
+
+
+
+

Soil and Fill Components

+
+
Describe the type and amount of fill proposed to be placed.
+
+ {{ applicationSubmission.nfuFillTypeDescription }} + +
+
Briefly describe the origin and quality of fill.
+
+ {{ applicationSubmission.nfuFillOriginDescription }} + +
+
Fill to be Placed
+
+
Volume
+
+ {{ applicationSubmission.nfuFillVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.nfuTotalFillArea }} + m2 + +
+
Maximum Depth
+
+ {{ applicationSubmission.nfuMaxFillDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.nfuAverageFillDepth }} + m + +
+
+
+ Project Duration +
+
Duration
+
+ {{ applicationSubmission.nfuProjectDurationUnit }} + +
+
Length
+
+ {{ applicationSubmission.nfuProjectDurationAmount }} + +
+
+
diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.scss b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.scss new file mode 100644 index 0000000000..b2b6b90127 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.scss @@ -0,0 +1,7 @@ +.soil-table { + display: grid; + grid-template-columns: max-content max-content; + overflow-x: auto; + grid-column-gap: 36px; + grid-row-gap: 12px; +} diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.spec.ts new file mode 100644 index 0000000000..0dcc20a8ae --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { NfuDetailsComponent } from './nfu-details.component'; + +describe('NfuDetailsComponent', () => { + let component: NfuDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [NfuDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(NfuDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts new file mode 100644 index 0000000000..d0642d7090 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/nfu-details/nfu-details.component.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-nfu-details[applicationSubmission]', + templateUrl: './nfu-details.component.html', + styleUrls: ['./nfu-details.component.scss'], +}) +export class NfuDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.html b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.html new file mode 100644 index 0000000000..782f41bf2a --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.html @@ -0,0 +1,79 @@ +

+ {{ pageTitle }} +

+
+ +
+

Parcel {{ parcelInd + 1 }}: Parcel and Owner Information

+
+
Parcel Information
+
Ownership Type
+
+ {{ parcel.ownershipType?.label }} + +
+
Legal Description
+
+ {{ parcel.legalDescription }} +
+
Area (Hectares)
+
+ {{ parcel.mapAreaHectares }} +
+
+ PID {{ parcel.ownershipType?.code === PARCEL_OWNERSHIP_TYPES.CROWN ? '(optional)' : '' }} +
+
+ {{ parcel.pid | mask : '000-000-000' }} + +
+ +
PIN (optional)
+
+ {{ parcel.pin }} + +
+
+ +
Purchase Date
+
+ {{ parcel.purchasedDate | date }} + +
+
+
Farm Classification
+
+ {{ parcel.isFarm ? 'Yes' : 'No' }} + +
+
Civic Address
+
+ {{ parcel.civicAddress }} + +
+ +
Crown Selection
+
+ {{ parcel.crownLandOwnerType }} + +
+
+
+
Land Owner(s)
+
Organization
+
+ Ministry/ Department +
+ +
{{ owner.displayName }}
+
+ {{ owner.organizationName }} + +
+
+
+ +
+
+
+
diff --git a/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.scss b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.scss new file mode 100644 index 0000000000..a706f903ba --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.scss @@ -0,0 +1,39 @@ +@use '../../../../../../styles/functions' as *; + +.owner-information { + display: grid; + grid-template-columns: max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); + + .full-width { + grid-column: 1/3; + } +} + +.review-table { + grid-template-columns: 1fr 1fr !important; + + .full-width { + grid-column: 1/3; + } + + .edit-button { + grid-column: 1/3; + } + + @media screen and (min-width: $tabletBreakpoint) { + .full-width { + grid-column: 1/5; + } + + .edit-button { + grid-column: 1/5; + } + } +} + +.crown-land { + text-transform: capitalize; +} diff --git a/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.spec.ts new file mode 100644 index 0000000000..16768f44d8 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.spec.ts @@ -0,0 +1,40 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; +import { ParcelComponent } from './parcel.component'; + +describe('ParcelComponent', () => { + let component: ParcelComponent; + let fixture: ComponentFixture; + + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + await TestBed.configureTestingModule({ + declarations: [ParcelComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + { + provides: Router, + useValue: {}, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(ParcelComponent); + component = fixture.componentInstance; + component.applicationSubmission = {} as any; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.ts b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.ts new file mode 100644 index 0000000000..0a4a1d2da5 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/parcel/parcel.component.ts @@ -0,0 +1,37 @@ +import { Component, Input } from '@angular/core'; +import { Subject } from 'rxjs'; +import { + ApplicationParcelDto, + PARCEL_OWNERSHIP_TYPE, +} from '../../../../../services/application-parcel/application-parcel.dto'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; + +@Component({ + selector: 'app-parcel', + templateUrl: './parcel.component.html', + styleUrls: ['./parcel.component.scss'], +}) +export class ParcelComponent { + $destroy = new Subject(); + + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + @Input() parcels: ApplicationParcelDto[] = []; + + PARCEL_OWNERSHIP_TYPES = PARCEL_OWNERSHIP_TYPE; + pageTitle: string = 'Identify Parcel(s) Under Application'; + + fileId = ''; + submissionUuid = ''; + + constructor() {} + + ngOnInit(): void { + this.fileId = this.applicationSubmission.fileNumber; + this.submissionUuid = this.applicationSubmission.uuid; + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html new file mode 100644 index 0000000000..71c70b13b9 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.html @@ -0,0 +1,213 @@ +
+
+ Has the ALC previously received an application or Notice of Intent for this proposal? +
+
+ + {{ applicationSubmission.soilIsFollowUp ? 'Yes' : 'No' }} + + +
+ +
Application or NOI ID
+
+ {{ applicationSubmission.soilFollowUpIDs }} + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ +
+
+
Soil to be Removed
+
Fill to be Placed
+ +
Volume
+
+ {{ applicationSubmission.soilToRemoveVolume }} + m3 + +
+
+ {{ applicationSubmission.soilToPlaceVolume }} + m3 + +
+ +
Area
+
+ {{ applicationSubmission.soilToRemoveArea }} + m2 + +
+
+ {{ applicationSubmission.soilToPlaceArea }} + m2 + +
+ +
Maximum Depth
+
+ {{ applicationSubmission.soilToRemoveMaximumDepth }} + m + +
+
+ {{ applicationSubmission.soilToPlaceMaximumDepth }} + m + +
+ +
Average Depth
+
+ {{ applicationSubmission.soilToRemoveAverageDepth }} + m + +
+
+ {{ applicationSubmission.soilToPlaceAverageDepth }} + m + +
+ +
+ +
+
Soil already Removed
+
Fill already Placed
+ +
Volume
+
+ {{ applicationSubmission.soilAlreadyRemovedVolume }} + m3 + +
+
+ {{ applicationSubmission.soilAlreadyPlacedVolume }} + m3 + +
+ +
Area
+
+ {{ applicationSubmission.soilAlreadyRemovedArea }} + m2 + +
+
+ {{ applicationSubmission.soilAlreadyPlacedArea }} + m2 + +
+ +
Maximum Depth
+
+ {{ applicationSubmission.soilAlreadyRemovedMaximumDepth }} + m + +
+
+ {{ applicationSubmission.soilAlreadyPlacedMaximumDepth }} + m + +
+ +
Average Depth
+
+ {{ applicationSubmission.soilAlreadyRemovedAverageDepth }} + m + +
+
+ {{ applicationSubmission.soilAlreadyPlacedAverageDepth }} + m + +
+
+ +
+ Project Duration +
+
Duration
+
+ {{ applicationSubmission.soilProjectDurationUnit }} + +
+
Length
+
+ {{ applicationSubmission.soilProjectDurationAmount }} + +
+ +
Describe the type, origin and quality of fill proposed to be placed.
+
+ {{ applicationSubmission.soilFillTypeToPlace }} + +
+ +
Describe the type of soil proposed to be removed.
+
+ {{ applicationSubmission.soilTypeRemoved }} + +
+ +
+ What alternative measures have you considered or attempted before proposing to place fill? +
+
+ {{ applicationSubmission.soilAlternativeMeasures }} + +
+ +
What steps will be taken to reduce impacts to surrounding agricultural land?
+
+ {{ applicationSubmission.soilReduceNegativeImpacts }} + +
+ +
Proposal Map / Site Plan
+ + +
Cross Sections
+ + +
Reclamation Plan
+ + +
Is your proposal for aggregate extraction or placer mining?
+
+ + {{ applicationSubmission.soilIsExtractionOrMining ? 'Yes' : 'No' }} + + +
+ +
+ Have you submitted a Notice of Work to the Ministry of Energy, Mines and Low Carbon Innovation (EMLI)? +
+
+ + {{ applicationSubmission.soilHasSubmittedNotice ? 'Yes' : 'No' }} + + +
+
diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.scss b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.scss new file mode 100644 index 0000000000..7fa4e7417f --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.scss @@ -0,0 +1,14 @@ +@use '../../../../../../styles/functions' as *; + +.soil-table { + display: grid; + grid-template-columns: max-content max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); + + .spacer-row { + grid-column: 1/4; + height: rem(16); + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.spec.ts new file mode 100644 index 0000000000..a4aebe95c0 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { PfrsDetailsComponent } from './pfrs-details.component'; + +describe('PfrsDetailsComponent', () => { + let component: PfrsDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [PfrsDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(PfrsDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts new file mode 100644 index 0000000000..da1bd6a17b --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pfrs-details/pfrs-details.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; +import { PublicApplicationComponent } from '../../public-application.component'; + +@Component({ + selector: 'app-pfrs-details[applicationSubmission]', + templateUrl: './pfrs-details.component.html', + styleUrls: ['./pfrs-details.component.scss'], +}) +export class PfrsDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS); + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + crossSections: ApplicationDocumentDto[] = []; + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html new file mode 100644 index 0000000000..37b284d75b --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.html @@ -0,0 +1,129 @@ +
+
+ Has the ALC previously received an application or Notice of Intent for this proposal? +
+
+ + {{ applicationSubmission.soilIsFollowUp ? 'Yes' : 'No' }} + + +
+ +
Application or NOI ID
+
+ {{ applicationSubmission.soilFollowUpIDs }} + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ +
Fill to be Placed
+
+
Volume
+
+ {{ applicationSubmission.soilToPlaceVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.soilToPlaceArea }} + m2 + +
+
Maximum Depth
+
+ {{ applicationSubmission.soilToPlaceMaximumDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.soilToPlaceAverageDepth }} + m + +
+
+ +
Fill already Placed
+
+
Volume
+
+ {{ applicationSubmission.soilAlreadyPlacedVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.soilAlreadyPlacedArea }} + m2 + +
+
Maximum Depth
+
+ {{ applicationSubmission.soilAlreadyPlacedMaximumDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.soilAlreadyPlacedAverageDepth }} + m + +
+
+ +
+ Project Duration +
+
Duration
+
+ {{ applicationSubmission.soilProjectDurationUnit }} + +
+
Length
+
+ {{ applicationSubmission.soilProjectDurationAmount }} + +
+ +
+ What alternative measures have you considered or attempted before proposing to place fill? +
+
+ {{ applicationSubmission.soilAlternativeMeasures }} + +
+ +
Describe the type, origin and quality of fill proposed to be placed.
+
+ {{ applicationSubmission.soilFillTypeToPlace }} + +
+ +
What steps will be taken to reduce impacts to surrounding agricultural land?
+
+ {{ applicationSubmission.soilReduceNegativeImpacts }} + +
+ +
Proposal Map / Site Plan
+ + +
Cross Sections
+ +
diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.scss b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.scss new file mode 100644 index 0000000000..75917bf09c --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.scss @@ -0,0 +1,9 @@ +@use '../../../../../../styles/functions' as *; + +.soil-table { + display: grid; + grid-template-columns: max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); +} diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.spec.ts new file mode 100644 index 0000000000..853fa7eaa4 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { PofoDetailsComponent } from './pofo-details.component'; + +describe('PofoDetailsComponent', () => { + let component: PofoDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PofoDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(PofoDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts new file mode 100644 index 0000000000..11c3f8cef4 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/pofo-details/pofo-details.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-pofo-details[applicationSubmission]', + templateUrl: './pofo-details.component.html', + styleUrls: ['./pofo-details.component.scss'], +}) +export class PofoDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS); + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + crossSections: ApplicationDocumentDto[] = []; + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html new file mode 100644 index 0000000000..2856ebcfb2 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.html @@ -0,0 +1,120 @@ +
+
+ Has the ALC previously received an application or Notice of Intent for this proposal? +
+
+ + {{ applicationSubmission.soilIsFollowUp ? 'Yes' : 'No' }} + + +
+ +
Application or NOI ID
+
+ {{ applicationSubmission.soilFollowUpIDs }} + +
+ +
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+ +
Soil to be Removed
+
+
Volume
+
+ {{ applicationSubmission.soilToRemoveVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.soilToRemoveArea }} ha + +
+
Maximum Depth
+
+ {{ applicationSubmission.soilToRemoveMaximumDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.soilToRemoveAverageDepth }} + m + +
+
+ +
Soil already Removed
+
+
Volume
+
+ {{ applicationSubmission.soilAlreadyRemovedVolume }} + m3 + +
+
Area
+
+ {{ applicationSubmission.soilAlreadyRemovedArea }} + ha + +
+
Maximum Depth
+
+ {{ applicationSubmission.soilAlreadyRemovedMaximumDepth }} + m + +
+
Average Depth
+
+ {{ applicationSubmission.soilAlreadyRemovedAverageDepth }} + m + +
+
+ +
+ Project Duration +
+
Duration
+
+ {{ applicationSubmission.soilProjectDurationUnit }} + +
+
Length
+
+ {{ applicationSubmission.soilProjectDurationAmount }} + +
+ +
Describe the type of soil proposed to be removed.
+
+ {{ applicationSubmission.soilTypeRemoved }} + +
+ +
What steps will be taken to reduce impacts to surrounding agricultural land?
+
+ {{ applicationSubmission.soilReduceNegativeImpacts }} + +
+ +
Proposal Map / Site Plan
+ + +
Cross Sections
+ +
diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.scss b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.scss new file mode 100644 index 0000000000..75917bf09c --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.scss @@ -0,0 +1,9 @@ +@use '../../../../../../styles/functions' as *; + +.soil-table { + display: grid; + grid-template-columns: max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); +} diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.spec.ts new file mode 100644 index 0000000000..1a78d7acee --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { RosoDetailsComponent } from './roso-details.component'; + +describe('RosoDetailsComponent', () => { + let component: RosoDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [RosoDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(RosoDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts new file mode 100644 index 0000000000..9ab2fc15d9 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/roso-details/roso-details.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-roso-details[applicationSubmission]', + templateUrl: './roso-details.component.html', + styleUrls: ['./roso-details.component.scss'], +}) +export class RosoDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS); + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + crossSections: ApplicationDocumentDto[] = []; + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html new file mode 100644 index 0000000000..2bcdcaaf9f --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.html @@ -0,0 +1,53 @@ +
+
Proposed Lot Areas
+
+
#
+
Type
+
Size
+ +
+ {{ i + 1 }} +
+
+ {{ lot.type }} + +
+
+ {{ lot.size }} + +
+
+
+ +
+
+
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+
Why do you believe this parcel is suitable for subdivision?
+
+ {{ applicationSubmission.subdSuitability }} + +
+
Does the proposal support agriculture in the short or long term?
+
+ {{ applicationSubmission.subdAgricultureSupport }} + +
+
Proposal Map / Site Plan
+ +
Are you applying for subdivision pursuant to the ALC Homesite Severance Policy?
+
+ + {{ applicationSubmission.subdIsHomeSiteSeverance ? 'Yes' : 'No' }} + + +
+
diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.scss b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.scss new file mode 100644 index 0000000000..e50cc03d80 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.scss @@ -0,0 +1,9 @@ +@use '../../../../../../styles/functions' as *; + +.parcel-table { + display: grid; + grid-template-columns: max-content max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); +} diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.spec.ts new file mode 100644 index 0000000000..030389b2d3 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { SubdDetailsComponent } from './subd-details.component'; + +describe('SubdDetailsComponent', () => { + let component: SubdDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + mockPublicService = createMock(); + + await TestBed.configureTestingModule({ + declarations: [SubdDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SubdDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts new file mode 100644 index 0000000000..a2f5720ac4 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/subd-details/subd-details.component.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../../services/application-document/application-document.service'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-subd-details[applicationSubmission]', + templateUrl: './subd-details.component.html', + styleUrls: ['./subd-details.component.scss'], +}) +export class SubdDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/submission-details.component.html b/portal-frontend/src/app/features/public/application/submission/submission-details.component.html new file mode 100644 index 0000000000..979dd4889e --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/submission-details.component.html @@ -0,0 +1,160 @@ +
+ +
+
+

Primary Contact

+
+
Type
+
+ {{ primaryContact?.type?.label }} + +
+
First Name
+
+ {{ primaryContact?.firstName }} + +
+
Last Name
+
+ {{ primaryContact?.lastName }} + +
+
+ Organization (optional) + Ministry/Department Responsible + Department +
+
+ {{ primaryContact?.organizationName }} + +
+
+
+
+

Government

+
+
Local or First Nation Government
+
+ {{ localGovernment?.name }} + +
+
+
+
+

Land Use

+
+
+

Land Use of Parcel(s) under Application

+
+
Describe all agriculture that currently takes place on the parcel(s).
+
+ {{ applicationSubmission.parcelsAgricultureDescription }} + +
+
Describe all agricultural improvements made to the parcel(s).
+
+ {{ applicationSubmission.parcelsAgricultureImprovementDescription }} + +
+
Describe all other uses that currently take place on the parcel(s).
+
+ {{ applicationSubmission.parcelsNonAgricultureUseDescription }} + +
+
+

Land Use of Adjacent Parcels

+
+
+
+
Main Land Use Type
+
Specific Activity
+
North
+
+ {{ applicationSubmission.northLandUseType }} + +
+
+ {{ applicationSubmission.northLandUseTypeDescription }} + +
+
East
+
+ {{ applicationSubmission.eastLandUseType }} + +
+
+ {{ applicationSubmission.eastLandUseTypeDescription }} + +
+
South
+
+ {{ applicationSubmission.southLandUseType }} + +
+
+ {{ applicationSubmission.southLandUseTypeDescription }} + +
+
West
+
+ {{ applicationSubmission.westLandUseType }} + +
+
+ {{ applicationSubmission.westLandUseTypeDescription }} + +
+
+
+
+
+

Proposal

+ + + + + + + + + +
diff --git a/portal-frontend/src/app/features/public/application/submission/submission-details.component.scss b/portal-frontend/src/app/features/public/application/submission/submission-details.component.scss new file mode 100644 index 0000000000..8aae88078a --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/submission-details.component.scss @@ -0,0 +1,143 @@ +@use '../../../../../styles/functions' as *; +@use '../../../../../styles/colors'; + +:host::ng-deep { + .view-grid-item { + display: grid; + grid-template-columns: minmax(rem(100), 0.5fr) 1fr; + column-gap: rem(16); + margin-bottom: rem(12); + } + + .details-wrapper { + margin-top: rem(24); + margin-bottom: rem(24); + + .title { + margin-bottom: rem(16) !important; + } + } + + h3 .subtext { + margin: 0.5rem 0 !important; + } + + label { + font-weight: 600; + } + + .no-data-text { + text-align: center; + color: colors.$grey; + padding-top: rem(12); + + .error { + justify-content: center; + } + } + + .custom-mat-expansion-panel-header { + height: fit-content; + } + + .table-wrapper { + overflow-x: auto; + width: 100%; + } + + @media screen and (min-width: $tabletBreakpoint) { + .flex-item { + display: flex; + gap: rem(16); + } + } +} + +:host::ng-deep { + .scrollable { + overflow-x: auto; + } + + .other-attachments { + display: grid; + grid-template-columns: max-content max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); + + .full-width { + grid-column: 1/3; + } + } + + .adjacent-parcels { + display: grid; + grid-template-columns: max-content max-content max-content; + overflow-x: auto; + grid-column-gap: rem(36); + grid-row-gap: rem(12); + + .full-width { + grid-column: 1/4; + } + } + + .review-table { + padding: rem(8); + margin: rem(12) 0 rem(20) 0; + background-color: colors.$grey-light; + display: grid; + grid-row-gap: rem(24); + grid-column-gap: rem(16); + grid-template-columns: 1fr; + word-wrap: break-word; + hyphens: auto; + + .edit-button { + display: flex; + justify-content: center; + + button { + width: 100%; + + @media screen and (min-width: $tabletBreakpoint) { + width: unset; + } + } + } + + .subheading2 { + margin-bottom: rem(4) !important; + } + + @media screen and (min-width: $tabletBreakpoint) { + padding: rem(16); + margin: rem(24) 0 rem(40) 0; + grid-template-columns: minmax(rem(60), 1fr) minmax(rem(60), 1fr) minmax(rem(60), 1fr) minmax(rem(60), 1fr); + + .full-width { + grid-column: 1/5; + } + + .grid-double { + grid-column: 2/5; + } + + .grid-1 { + grid-column: 1/2; + } + + .grid-2 { + grid-column: 2/3; + } + + .grid-3 { + grid-column: 3/5; + } + + .edit-button { + grid-column: 1/5; + } + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/submission-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/submission-details.component.spec.ts new file mode 100644 index 0000000000..a06f7a8d15 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/submission-details.component.spec.ts @@ -0,0 +1,42 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; +import { CodeService } from '../../../../services/code/code.service'; + +import { SubmissionDetailsComponent } from './submission-details.component'; + +describe('SubmissionDetailsComponent', () => { + let component: SubmissionDetailsComponent; + let fixture: ComponentFixture; + let mockCodeService: DeepMocked; + let mockAppDocumentService: DeepMocked; + + beforeEach(async () => { + mockCodeService = createMock(); + mockAppDocumentService = createMock(); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: CodeService, + useValue: mockCodeService, + }, + { + provide: ApplicationDocumentService, + useValue: mockAppDocumentService, + }, + ], + declarations: [SubmissionDetailsComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(SubmissionDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/submission-details.component.ts b/portal-frontend/src/app/features/public/application/submission/submission-details.component.ts new file mode 100644 index 0000000000..c98f76888a --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/submission-details.component.ts @@ -0,0 +1,66 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { ApplicationDocumentDto } from '../../../../services/application-document/application-document.dto'; +import { ApplicationDocumentService } from '../../../../services/application-document/application-document.service'; +import { ApplicationParcelDto, PARCEL_TYPE } from '../../../../services/application-parcel/application-parcel.dto'; +import { LocalGovernmentDto } from '../../../../services/code/code.dto'; +import { CodeService } from '../../../../services/code/code.service'; +import { PublicApplicationSubmissionDto, PublicOwnerDto } from '../../../../services/public/public.dto'; +import { OWNER_TYPE } from '../../../../shared/dto/owner.dto'; + +@Component({ + selector: 'app-public-app-submission-details', + templateUrl: './submission-details.component.html', + styleUrls: ['./submission-details.component.scss'], +}) +export class SubmissionDetailsComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + @Input() applicationDocuments: ApplicationDocumentDto[] = []; + @Input() applicationParcels: ApplicationParcelDto[] = []; + + parcelType = PARCEL_TYPE; + primaryContact: PublicOwnerDto | undefined; + localGovernment: LocalGovernmentDto | undefined; + OWNER_TYPE = OWNER_TYPE; + + private localGovernments: LocalGovernmentDto[] = []; + + constructor(private codeService: CodeService, private applicationDocumentService: ApplicationDocumentService) {} + + ngOnInit(): void { + this.loadGovernments(); + if (this.applicationSubmission) { + this.primaryContact = this.applicationSubmission.owners.find( + (owner) => owner.uuid === this.applicationSubmission.primaryContactOwnerUuid + ); + this.populateLocalGovernment(this.applicationSubmission.localGovernmentUuid); + } + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + async openFile(uuid: string) { + const res = await this.applicationDocumentService.openFile(uuid); + window.open(res?.url, '_blank'); + } + + private async loadGovernments() { + const codes = await this.codeService.loadCodes(); + this.localGovernments = codes.localGovernments.sort((a, b) => (a.name > b.name ? 1 : -1)); + if (this.applicationSubmission?.localGovernmentUuid) { + this.populateLocalGovernment(this.applicationSubmission?.localGovernmentUuid); + } + } + + private populateLocalGovernment(governmentUuid: string) { + const lg = this.localGovernments.find((lg) => lg.uuid === governmentUuid); + if (lg) { + this.localGovernment = lg; + } + } +} diff --git a/portal-frontend/src/app/features/public/application/submission/submission-details.module.ts b/portal-frontend/src/app/features/public/application/submission/submission-details.module.ts new file mode 100644 index 0000000000..e02adcc592 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/submission-details.module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { NgxMaskPipe } from 'ngx-mask'; +import { SharedModule } from '../../../../shared/shared.module'; +import { SubmissionDetailsComponent } from './submission-details.component'; +import { InclDetailsComponent } from './incl-details/incl-details.component'; +import { NaruDetailsComponent } from './naru-details/naru-details.component'; +import { NfuDetailsComponent } from './nfu-details/nfu-details.component'; +import { ParcelComponent } from './parcel/parcel.component'; +import { PfrsDetailsComponent } from './pfrs-details/pfrs-details.component'; +import { PofoDetailsComponent } from './pofo-details/pofo-details.component'; +import { RosoDetailsComponent } from './roso-details/roso-details.component'; +import { SubdDetailsComponent } from './subd-details/subd-details.component'; +import { TurDetailsComponent } from './tur-details/tur-details.component'; +import { ExclDetailsComponent } from './excl-details/excl-details.component'; + +@NgModule({ + declarations: [ + ParcelComponent, + SubmissionDetailsComponent, + NfuDetailsComponent, + TurDetailsComponent, + SubdDetailsComponent, + RosoDetailsComponent, + PofoDetailsComponent, + PfrsDetailsComponent, + NaruDetailsComponent, + ExclDetailsComponent, + InclDetailsComponent, + ], + imports: [CommonModule, SharedModule, NgxMaskPipe], + exports: [SubmissionDetailsComponent], +}) +export class SubmissionDetailsModule {} diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html new file mode 100644 index 0000000000..5eb61b005b --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.html @@ -0,0 +1,41 @@ +
+
What is the purpose of the proposal?
+
+ {{ applicationSubmission.purpose }} + +
+
+ Specify any agricultural activities such as livestock operations, greenhouses or horticultural activities in + proximity to the proposal. +
+
+ {{ applicationSubmission.turAgriculturalActivities }} + +
+
+ What steps will you take to reduce potential negative impacts on surrounding agricultural lands? +
+
+ {{ applicationSubmission.turReduceNegativeImpacts }} + +
+
Could this proposal be accommodated on lands outside of the ALR?
+
+ {{ applicationSubmission.turOutsideLands }} + +
+
Total area of corridor
+
+ {{ + applicationSubmission.turTotalCorridorArea + }} + +
+
Proposal Map / Sketch Plan
+ +
diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.scss b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.spec.ts b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.spec.ts new file mode 100644 index 0000000000..2886c9d962 --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeepMocked } from '@golevelup/ts-jest'; +import { PublicService } from '../../../../../services/public/public.service'; + +import { TurDetailsComponent } from './tur-details.component'; + +describe('TurDetailsComponent', () => { + let component: TurDetailsComponent; + let fixture: ComponentFixture; + let mockPublicService: DeepMocked; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [TurDetailsComponent], + providers: [ + { + provide: PublicService, + useValue: mockPublicService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(TurDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts new file mode 100644 index 0000000000..a448a06fae --- /dev/null +++ b/portal-frontend/src/app/features/public/application/submission/tur-details/tur-details.component.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApplicationDocumentDto } from '../../../../../services/application-document/application-document.dto'; +import { PublicApplicationSubmissionDto } from '../../../../../services/public/public.dto'; +import { PublicService } from '../../../../../services/public/public.service'; +import { DOCUMENT_TYPE } from '../../../../../shared/dto/document.dto'; + +@Component({ + selector: 'app-tur-details[applicationSubmission]', + templateUrl: './tur-details.component.html', + styleUrls: ['./tur-details.component.scss'], +}) +export class TurDetailsComponent { + @Input() applicationSubmission!: PublicApplicationSubmissionDto; + @Input() set applicationDocuments(documents: ApplicationDocumentDto[]) { + this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP); + } + + proposalMap: ApplicationDocumentDto[] = []; + + constructor(private router: Router, private publicService: PublicService) {} + + async openFile(uuid: string) { + const res = await this.publicService.getApplicationFileUrl(this.applicationSubmission.fileNumber, uuid); + if (res) { + window.open(res?.url, '_blank'); + } + } +} diff --git a/portal-frontend/src/app/features/public/public.module.ts b/portal-frontend/src/app/features/public/public.module.ts index 90ef0d4953..57b0fa785e 100644 --- a/portal-frontend/src/app/features/public/public.module.ts +++ b/portal-frontend/src/app/features/public/public.module.ts @@ -6,6 +6,8 @@ import { MatSortModule } from '@angular/material/sort'; import { MatTreeModule } from '@angular/material/tree'; import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from '../../shared/shared.module'; +import { PublicApplicationComponent } from './application/public-application.component'; +import { SubmissionDetailsModule } from './application/submission/submission-details.module'; import { ApplicationSearchTableComponent } from './search/application-search-table/application-search-table.component'; import { FileTypeFilterDropDownComponent } from './search/file-type-filter-drop-down/file-type-filter-drop-down.component'; import { NoticeOfIntentSearchTableComponent } from './search/notice-of-intent-search-table/notice-of-intent-search-table.component'; @@ -18,6 +20,10 @@ const routes: Routes = [ path: '', component: PublicSearchComponent, }, + { + path: 'application/:fileId', + component: PublicApplicationComponent, + }, ]; @NgModule({ @@ -28,6 +34,7 @@ const routes: Routes = [ ApplicationSearchTableComponent, FileTypeFilterDropDownComponent, SearchListComponent, + PublicApplicationComponent, ], imports: [ CommonModule, @@ -37,6 +44,7 @@ const routes: Routes = [ RouterModule.forChild(routes), MatAutocompleteModule, MatTreeModule, + SubmissionDetailsModule, ], }) export class PublicModule {} diff --git a/portal-frontend/src/app/features/public/search/search-list/search-list.component.ts b/portal-frontend/src/app/features/public/search/search-list/search-list.component.ts index 5f3dc1879a..2271f9fc92 100644 --- a/portal-frontend/src/app/features/public/search/search-list/search-list.component.ts +++ b/portal-frontend/src/app/features/public/search/search-list/search-list.component.ts @@ -40,7 +40,7 @@ export class SearchListComponent implements OnDestroy { async onSelectRecord(record: SearchResult) { const targetUrl = CLASS_TO_URL_MAP[record.class]; - await this.router.navigateByUrl(`/${targetUrl}/${record.referenceId}`); + await this.router.navigateByUrl(`/public/${targetUrl}/${record.referenceId}`); } private mapResults(applications: ApplicationSearchResultDto[]): SearchResult[] { diff --git a/portal-frontend/src/app/services/public/public.dto.ts b/portal-frontend/src/app/services/public/public.dto.ts new file mode 100644 index 0000000000..5c49d34add --- /dev/null +++ b/portal-frontend/src/app/services/public/public.dto.ts @@ -0,0 +1,133 @@ +import { OwnerTypeDto } from '../../shared/dto/owner.dto'; +import { ApplicationDocumentDto } from '../application-document/application-document.dto'; +import { ApplicationParcelDto } from '../application-parcel/application-parcel.dto'; +import { + ApplicationStatusDto, + NaruSubtypeDto, + ProposedLot, +} from '../application-submission/application-submission.dto'; + +export interface GetPublicApplicationResponseDto { + submission: PublicApplicationSubmissionDto; + parcels: ApplicationParcelDto[]; + documents: ApplicationDocumentDto[]; +} + +export interface PublicOwnerDto { + uuid: string; + displayName: string; + firstName?: string | null; + lastName?: string | null; + organizationName?: string | null; + type: OwnerTypeDto; +} + +export interface PublicApplicationSubmissionDto { + fileNumber: string; + uuid: string; + createdAt: number; + updatedAt: number; + applicant: string; + localGovernmentUuid: string; + lastStatusUpdate: number; + status: ApplicationStatusDto; + owners: PublicOwnerDto[]; + type: string; + typeCode: string; + purpose: string | null; + parcelsAgricultureDescription: string; + parcelsAgricultureImprovementDescription: string; + parcelsNonAgricultureUseDescription: string; + northLandUseType: string; + northLandUseTypeDescription: string; + eastLandUseType: string; + eastLandUseTypeDescription: string; + southLandUseType: string; + southLandUseTypeDescription: string; + westLandUseType: string; + westLandUseTypeDescription: string; + primaryContactOwnerUuid?: string | null; + + //NFU Specific Fields + nfuHectares?: number | null; + nfuOutsideLands?: string | null; + nfuAgricultureSupport?: string | null; + nfuWillImportFill?: boolean | null; + nfuTotalFillArea?: number | null; + nfuMaxFillDepth?: number | null; + nfuAverageFillDepth?: number | null; + nfuFillVolume?: number | null; + nfuProjectDurationAmount?: number | null; + nfuProjectDurationUnit?: string | null; + nfuFillTypeDescription?: string | null; + nfuFillOriginDescription?: string | null; + + //TUR Fields + turAgriculturalActivities?: string | null; + turReduceNegativeImpacts?: string | null; + turOutsideLands?: string | null; + turTotalCorridorArea?: number | null; + turAllOwnersNotified?: boolean | null; + + //Subdivision Fields + subdSuitability?: string | null; + subdAgricultureSupport?: string | null; + subdIsHomeSiteSeverance?: boolean | null; + subdProposedLots: ProposedLot[]; + + //Soil Fields + soilIsFollowUp: boolean | null; + soilFollowUpIDs: string | null; + soilTypeRemoved: string | null; + soilReduceNegativeImpacts: string | null; + soilToRemoveVolume: number | null; + soilToRemoveArea: number | null; + soilToRemoveMaximumDepth: number | null; + soilToRemoveAverageDepth: number | null; + soilAlreadyRemovedVolume: number | null; + soilAlreadyRemovedArea: number | null; + soilAlreadyRemovedMaximumDepth: number | null; + soilAlreadyRemovedAverageDepth: number | null; + soilToPlaceVolume: number | null; + soilToPlaceArea: number | null; + soilToPlaceMaximumDepth: number | null; + soilToPlaceAverageDepth: number | null; + soilAlreadyPlacedVolume: number | null; + soilAlreadyPlacedArea: number | null; + soilAlreadyPlacedMaximumDepth: number | null; + soilAlreadyPlacedAverageDepth: number | null; + soilProjectDurationAmount: number | null; + soilProjectDurationUnit?: string | null; + soilFillTypeToPlace?: string | null; + soilAlternativeMeasures?: string | null; + soilIsExtractionOrMining?: boolean; + soilHasSubmittedNotice?: boolean; + + //NARU Fields + naruSubtype: NaruSubtypeDto | null; + naruFloorArea: number | null; + naruResidenceNecessity: string | null; + naruLocationRationale: string | null; + naruInfrastructure: string | null; + naruExistingStructures: string | null; + naruWillImportFill: boolean | null; + naruFillType: string | null; + naruFillOrigin: string | null; + naruProjectDurationAmount: number | null; + naruProjectDurationUnit: string | null; + naruToPlaceVolume: number | null; + naruToPlaceArea: number | null; + naruToPlaceMaximumDepth: number | null; + naruToPlaceAverageDepth: number | null; + naruSleepingUnits: number | null; + naruAgriTourism: string | null; + + //Inclusion / Exclusion Fields + prescribedBody: string | null; + inclExclHectares: number | null; + exclWhyLand: string | null; + inclAgricultureSupport: string | null; + inclImprovements: string | null; + exclShareGovernmentBorders: boolean | null; + inclGovernmentOwnsAllParcels?: boolean | null; +} diff --git a/portal-frontend/src/app/services/public/public.service.spec.ts b/portal-frontend/src/app/services/public/public.service.spec.ts new file mode 100644 index 0000000000..3ecc7eed9c --- /dev/null +++ b/portal-frontend/src/app/services/public/public.service.spec.ts @@ -0,0 +1,53 @@ +import { HttpClient } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { of } from 'rxjs'; +import { ToastService } from '../toast/toast.service'; +import { PublicService } from './public.service'; + +describe('PublicService', () => { + let service: PublicService; + let mockHttpClient: DeepMocked; + let mockToastService: DeepMocked; + + beforeEach(() => { + mockHttpClient = createMock(); + mockToastService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: HttpClient, + useValue: mockHttpClient, + }, + { + provide: ToastService, + useValue: mockToastService, + }, + ], + }); + service = TestBed.inject(PublicService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call get for loading applications', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.getApplication('fileId'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); + + it('should call get for loading application files', async () => { + mockHttpClient.get.mockReturnValue(of({})); + + const res = await service.getApplicationFileUrl('fileId', 'documentUuid'); + + expect(mockHttpClient.get).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + }); +}); diff --git a/portal-frontend/src/app/services/public/public.service.ts b/portal-frontend/src/app/services/public/public.service.ts new file mode 100644 index 0000000000..b7021b2c5e --- /dev/null +++ b/portal-frontend/src/app/services/public/public.service.ts @@ -0,0 +1,39 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { firstValueFrom } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { ToastService } from '../toast/toast.service'; +import { GetPublicApplicationResponseDto } from './public.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class PublicService { + private serviceUrl = `${environment.authUrl}/public`; + + constructor(private httpClient: HttpClient, private toastService: ToastService) {} + + async getApplication(fileId: string) { + try { + return await firstValueFrom( + this.httpClient.get(`${this.serviceUrl}/application/${fileId}`) + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast('Failed to load Application, please try again later'); + return undefined; + } + } + + async getApplicationFileUrl(fileId: string, uuid: string) { + try { + return await firstValueFrom( + this.httpClient.get<{ url: string }>(`${this.serviceUrl}/application/${fileId}/${uuid}/open`) + ); + } catch (e) { + console.error(e); + this.toastService.showErrorToast('Failed to load Application File, please try again later'); + return undefined; + } + } +} diff --git a/services/apps/alcs/src/common/automapper/public.automapper.profile.ts b/services/apps/alcs/src/common/automapper/public.automapper.profile.ts new file mode 100644 index 0000000000..c463e68068 --- /dev/null +++ b/services/apps/alcs/src/common/automapper/public.automapper.profile.ts @@ -0,0 +1,39 @@ +import { createMap, forMember, mapFrom, Mapper } from '@automapper/core'; +import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { Injectable } from '@nestjs/common'; +import { ApplicationOwner } from '../../portal/application-submission/application-owner/application-owner.entity'; +import { ApplicationSubmission } from '../../portal/application-submission/application-submission.entity'; +import { + PublicApplicationSubmissionDto, + PublicOwnerDto, +} from '../../portal/public/public.dto'; + +@Injectable() +export class PublicAutomapperProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper) => { + createMap( + mapper, + ApplicationSubmission, + PublicApplicationSubmissionDto, + forMember( + (a) => a.lastStatusUpdate, + mapFrom((ad) => { + return ad.status.effectiveDate?.getTime(); + }), + ), + forMember( + (a) => a.status, + mapFrom((ad) => { + return ad.status.statusType; + }), + ), + ); + createMap(mapper, ApplicationOwner, PublicOwnerDto); + }; + } +} diff --git a/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts b/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts index 81bc6ac891..e53d765a9a 100644 --- a/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts +++ b/services/apps/alcs/src/portal/application-submission/application-submission.entity.ts @@ -190,6 +190,7 @@ export class ApplicationSubmission extends Base { }) typeCode: string; + @AutoMap(() => [ApplicationOwner]) @OneToMany(() => ApplicationOwner, (owner) => owner.applicationSubmission) owners: ApplicationOwner[]; diff --git a/services/apps/alcs/src/portal/public/public.controller.spec.ts b/services/apps/alcs/src/portal/public/public.controller.spec.ts new file mode 100644 index 0000000000..734f96eae1 --- /dev/null +++ b/services/apps/alcs/src/portal/public/public.controller.spec.ts @@ -0,0 +1,138 @@ +import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; +import { classes } from '@automapper/classes'; +import { AutomapperModule } from '@automapper/nestjs'; +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ClsService } from 'nestjs-cls'; +import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; +import { + ApplicationDocument, + VISIBILITY_FLAG, +} from '../../alcs/application/application-document/application-document.entity'; +import { ApplicationDocumentService } from '../../alcs/application/application-document/application-document.service'; +import { ApplicationSubmissionToSubmissionStatus } from '../../alcs/application/application-submission-status/submission-status.entity'; +import { Application } from '../../alcs/application/application.entity'; +import { ApplicationService } from '../../alcs/application/application.service'; +import { PublicAutomapperProfile } from '../../common/automapper/public.automapper.profile'; +import { ApplicationParcelService } from '../application-submission/application-parcel/application-parcel.service'; +import { ApplicationSubmission } from '../application-submission/application-submission.entity'; +import { ApplicationSubmissionService } from '../application-submission/application-submission.service'; +import { PublicController } from './public.controller'; + +describe('PublicSearchController', () => { + let controller: PublicController; + let mockAppService: DeepMocked; + let mockAppSubService: DeepMocked; + let mockAppParcelService: DeepMocked; + let mockAppDocService: DeepMocked; + + beforeEach(async () => { + mockAppService = createMock(); + mockAppSubService = createMock(); + mockAppParcelService = createMock(); + mockAppDocService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + providers: [ + PublicAutomapperProfile, + { + provide: ApplicationService, + useValue: mockAppService, + }, + { + provide: ApplicationSubmissionService, + useValue: mockAppSubService, + }, + { + provide: ApplicationParcelService, + useValue: mockAppParcelService, + }, + { + provide: ApplicationDocumentService, + useValue: mockAppDocService, + }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + controllers: [PublicController], + }).compile(); + + controller = module.get(PublicController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('load an Application and its related data for get application', async () => { + mockAppService.get.mockResolvedValue( + new Application({ + dateReceivedAllItems: new Date(), + }), + ); + mockAppSubService.getOrFailByFileNumber.mockResolvedValue( + new ApplicationSubmission({ + get status(): ApplicationSubmissionToSubmissionStatus { + return new ApplicationSubmissionToSubmissionStatus(); + }, + }), + ); + mockAppParcelService.fetchByApplicationFileId.mockResolvedValue([]); + mockAppDocService.list.mockResolvedValue([]); + + const fileId = 'file-id'; + await controller.getApplication(fileId); + + expect(mockAppService.get).toHaveBeenCalledTimes(1); + expect(mockAppSubService.getOrFailByFileNumber).toHaveBeenCalledTimes(1); + expect(mockAppParcelService.fetchByApplicationFileId).toHaveBeenCalledTimes( + 1, + ); + expect(mockAppDocService.list).toHaveBeenCalledTimes(1); + expect(mockAppDocService.list).toHaveBeenCalledWith(fileId, [ + VISIBILITY_FLAG.PUBLIC, + ]); + }); + + it('should call through to document service for getting files', async () => { + const mockDoc = new ApplicationDocument({ + visibilityFlags: [VISIBILITY_FLAG.PUBLIC], + }); + mockAppDocService.get.mockResolvedValue(mockDoc); + mockAppDocService.getInlineUrl.mockResolvedValue(''); + + const fileId = 'file-id'; + const documentUuid = 'document-uuid'; + await controller.getApplicationDocumentOpen(fileId, documentUuid); + + expect(mockAppDocService.get).toHaveBeenCalledTimes(1); + expect(mockAppDocService.getInlineUrl).toHaveBeenCalledTimes(1); + expect(mockAppDocService.getInlineUrl).toHaveBeenCalledWith(mockDoc); + }); + + it('should throw an exception when the document is not public', async () => { + const mockDoc = new ApplicationDocument({ + visibilityFlags: [VISIBILITY_FLAG.APPLICANT], + }); + mockAppDocService.get.mockResolvedValue(mockDoc); + + const fileId = 'file-id'; + const documentUuid = 'document-uuid'; + const promise = controller.getApplicationDocumentOpen(fileId, documentUuid); + + await expect(promise).rejects.toMatchObject( + new ServiceNotFoundException('Failed to find document'), + ); + + expect(mockAppDocService.get).toHaveBeenCalledTimes(1); + expect(mockAppDocService.getInlineUrl).toHaveBeenCalledTimes(0); + }); +}); diff --git a/services/apps/alcs/src/portal/public/public.controller.ts b/services/apps/alcs/src/portal/public/public.controller.ts new file mode 100644 index 0000000000..264d0393b5 --- /dev/null +++ b/services/apps/alcs/src/portal/public/public.controller.ts @@ -0,0 +1,110 @@ +import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; +import { Mapper } from '@automapper/core'; +import { InjectMapper } from '@automapper/nestjs'; +import { Controller, Get, Param } from '@nestjs/common'; +import { Public } from 'nest-keycloak-connect'; +import { ApplicationDocumentDto } from '../../alcs/application/application-document/application-document.dto'; +import { + ApplicationDocument, + VISIBILITY_FLAG, +} from '../../alcs/application/application-document/application-document.entity'; +import { ApplicationDocumentService } from '../../alcs/application/application-document/application-document.service'; +import { ApplicationService } from '../../alcs/application/application.service'; +import { ApplicationParcelDto } from '../application-submission/application-parcel/application-parcel.dto'; +import { ApplicationParcel } from '../application-submission/application-parcel/application-parcel.entity'; +import { ApplicationParcelService } from '../application-submission/application-parcel/application-parcel.service'; +import { ApplicationSubmission } from '../application-submission/application-submission.entity'; +import { ApplicationSubmissionService } from '../application-submission/application-submission.service'; +import { PublicApplicationSubmissionDto } from './public.dto'; + +@Public() +@Controller('/public') +export class PublicController { + constructor( + @InjectMapper() private mapper: Mapper, + private applicationService: ApplicationService, + private applicationSubmissionService: ApplicationSubmissionService, + private applicationParcelService: ApplicationParcelService, + private applicationDocumentService: ApplicationDocumentService, + ) {} + + @Get('/application/:fileId') + async getApplication(@Param('fileId') fileId: string) { + const application = await this.applicationService.get(fileId); + if (!application?.dateReceivedAllItems) { + throw new ServiceNotFoundException( + `Failed to find application with File ID ${fileId}`, + ); + } + + const submission = + await this.applicationSubmissionService.getOrFailByFileNumber(fileId); + + const parcels = + await this.applicationParcelService.fetchByApplicationFileId(fileId); + + const mappedParcels = this.mapper.mapArray( + parcels, + ApplicationParcel, + ApplicationParcelDto, + ); + + const mappedSubmission = this.mapper.map( + submission, + ApplicationSubmission, + PublicApplicationSubmissionDto, + ); + + const documents = await this.applicationDocumentService.list(fileId, [ + VISIBILITY_FLAG.PUBLIC, + ]); + + const mappedDocuments = this.mapper.mapArray( + documents, + ApplicationDocument, + ApplicationDocumentDto, + ); + + return { + submission: mappedSubmission, + parcels: mappedParcels, + documents: mappedDocuments, + }; + } + + @Get('/application/:fileId/:uuid/download') + async getApplicationDocumentDownload( + @Param('fileId') fileId: string, + @Param('uuid') documentUuid: string, + ) { + const document = await this.applicationDocumentService.get(documentUuid); + + if (!document.visibilityFlags.includes(VISIBILITY_FLAG.PUBLIC)) { + throw new ServiceNotFoundException('Failed to find document'); + } + + const url = await this.applicationDocumentService.getDownloadUrl(document); + + return { + url, + }; + } + + @Get('/application/:fileId/:uuid/open') + async getApplicationDocumentOpen( + @Param('fileId') fileId: string, + @Param('uuid') documentUuid: string, + ) { + const document = await this.applicationDocumentService.get(documentUuid); + + if (!document.visibilityFlags.includes(VISIBILITY_FLAG.PUBLIC)) { + throw new ServiceNotFoundException('Failed to find document'); + } + + const url = await this.applicationDocumentService.getInlineUrl(document); + + return { + url, + }; + } +} diff --git a/services/apps/alcs/src/portal/public/public.dto.ts b/services/apps/alcs/src/portal/public/public.dto.ts new file mode 100644 index 0000000000..e502cf8720 --- /dev/null +++ b/services/apps/alcs/src/portal/public/public.dto.ts @@ -0,0 +1,317 @@ +import { AutoMap } from '@automapper/classes'; +import { ApplicationStatusDto } from '../../alcs/application/application-submission-status/submission-status.dto'; +import { OwnerTypeDto } from '../../common/owner-type/owner-type.entity'; +import { NaruSubtypeDto } from '../application-submission/application-submission.dto'; +import { ProposedLot } from '../application-submission/application-submission.entity'; + +export class PublicOwnerDto { + @AutoMap() + uuid: string; + + displayName: string; + + @AutoMap(() => String) + firstName?: string | null; + + @AutoMap(() => String) + lastName?: string | null; + + @AutoMap(() => String) + organizationName?: string | null; + + @AutoMap() + type: OwnerTypeDto; +} + +export class PublicApplicationSubmissionDto { + @AutoMap() + fileNumber: string; + + @AutoMap() + uuid: string; + + @AutoMap() + createdAt: number; + + @AutoMap() + updatedAt: number; + + @AutoMap() + applicant: string; + + @AutoMap() + localGovernmentUuid: string; + + @AutoMap(() => Boolean) + hasOtherParcelsInCommunity?: boolean | null; + + lastStatusUpdate: number; + + status: ApplicationStatusDto; + + @AutoMap(() => [PublicOwnerDto]) + owners: PublicOwnerDto[]; + + type: string; + + @AutoMap() + typeCode: string; + + @AutoMap(() => String) + purpose: string | null; + + @AutoMap() + parcelsAgricultureDescription: string; + + @AutoMap() + parcelsAgricultureImprovementDescription: string; + + @AutoMap() + parcelsNonAgricultureUseDescription: string; + + @AutoMap() + northLandUseType: string; + + @AutoMap() + northLandUseTypeDescription: string; + + @AutoMap() + eastLandUseType: string; + + @AutoMap() + eastLandUseTypeDescription: string; + + @AutoMap() + southLandUseType: string; + + @AutoMap() + southLandUseTypeDescription: string; + + @AutoMap() + westLandUseType: string; + + @AutoMap() + westLandUseTypeDescription: string; + + @AutoMap(() => String) + primaryContactOwnerUuid?: string | null; + + //NFU Specific Fields + @AutoMap(() => Number) + nfuHectares?: number | null; + + @AutoMap(() => String) + nfuOutsideLands?: string | null; + + @AutoMap(() => String) + nfuAgricultureSupport?: string | null; + + @AutoMap(() => String) + nfuWillImportFill?: boolean | null; + + @AutoMap(() => Number) + nfuTotalFillArea?: number | null; + + @AutoMap(() => Number) + nfuMaxFillDepth?: number | null; + + @AutoMap(() => Number) + nfuAverageFillDepth?: number | null; + + @AutoMap(() => Number) + nfuFillVolume?: number | null; + + @AutoMap(() => Number) + nfuProjectDurationAmount?: number | null; + + @AutoMap(() => String) + nfuProjectDurationUnit?: string | null; + + @AutoMap(() => String) + nfuFillTypeDescription?: string | null; + + @AutoMap(() => String) + nfuFillOriginDescription?: string | null; + + //TUR Fields + @AutoMap(() => String) + turAgriculturalActivities?: string | null; + + @AutoMap(() => String) + turReduceNegativeImpacts?: string | null; + + @AutoMap(() => String) + turOutsideLands?: string | null; + + @AutoMap(() => Number) + turTotalCorridorArea?: number | null; + + @AutoMap(() => Boolean) + turAllOwnersNotified?: boolean | null; + + //Subdivision Fields + @AutoMap(() => String) + subdSuitability?: string | null; + + @AutoMap(() => String) + subdAgricultureSupport?: string | null; + + @AutoMap(() => Boolean) + subdIsHomeSiteSeverance?: boolean | null; + + @AutoMap(() => [ProposedLot]) + subdProposedLots?: ProposedLot[]; + + //Soil Fields + @AutoMap(() => Boolean) + soilIsFollowUp: boolean | null; + + @AutoMap(() => String) + soilFollowUpIDs: string | null; + + @AutoMap(() => String) + soilTypeRemoved: string | null; + + @AutoMap(() => String) + soilReduceNegativeImpacts: string | null; + + @AutoMap(() => Number) + soilToRemoveVolume: number | null; + + @AutoMap(() => Number) + soilToRemoveArea: number | null; + + @AutoMap(() => Number) + soilToRemoveMaximumDepth: number | null; + + @AutoMap(() => Number) + soilToRemoveAverageDepth: number | null; + + @AutoMap(() => Number) + soilAlreadyRemovedVolume: number | null; + + @AutoMap(() => Number) + soilAlreadyRemovedArea: number | null; + + @AutoMap(() => Number) + soilAlreadyRemovedMaximumDepth: number | null; + + @AutoMap(() => Number) + soilAlreadyRemovedAverageDepth: number | null; + + @AutoMap(() => Number) + soilToPlaceVolume: number | null; + + @AutoMap(() => Number) + soilToPlaceArea: number | null; + + @AutoMap(() => Number) + soilToPlaceMaximumDepth: number | null; + + @AutoMap(() => Number) + soilToPlaceAverageDepth: number | null; + + @AutoMap(() => Number) + soilAlreadyPlacedVolume: number | null; + + @AutoMap(() => Number) + soilAlreadyPlacedArea: number | null; + + @AutoMap(() => Number) + soilAlreadyPlacedMaximumDepth: number | null; + + @AutoMap(() => Number) + soilAlreadyPlacedAverageDepth: number | null; + + @AutoMap(() => Number) + soilProjectDurationAmount: number | null; + + @AutoMap(() => String) + soilProjectDurationUnit?: string | null; + + @AutoMap(() => String) + soilFillTypeToPlace?: string | null; + + @AutoMap(() => String) + soilAlternativeMeasures?: string | null; + + @AutoMap(() => Boolean) + soilIsExtractionOrMining?: boolean; + + @AutoMap(() => Boolean) + soilHasSubmittedNotice?: boolean; + + //NARU Fields + @AutoMap(() => [NaruSubtypeDto]) + naruSubtype: NaruSubtypeDto | null; + + @AutoMap(() => Number) + naruFloorArea: number | null; + + @AutoMap(() => String) + naruResidenceNecessity: string | null; + + @AutoMap(() => String) + naruLocationRationale: string | null; + + @AutoMap(() => String) + naruInfrastructure: string | null; + + @AutoMap(() => String) + naruExistingStructures: string | null; + + @AutoMap(() => Boolean) + naruWillImportFill: boolean | null; + + @AutoMap(() => String) + naruFillType: string | null; + + @AutoMap(() => String) + naruFillOrigin: string | null; + + @AutoMap(() => Number) + naruProjectDurationAmount: number | null; + + @AutoMap(() => String) + naruProjectDurationUnit: string | null; + + @AutoMap(() => Number) + naruToPlaceVolume: number | null; + + @AutoMap(() => Number) + naruToPlaceArea: number | null; + + @AutoMap(() => Number) + naruToPlaceMaximumDepth: number | null; + + @AutoMap(() => Number) + naruToPlaceAverageDepth: number | null; + + @AutoMap(() => Number) + naruSleepingUnits: number | null; + + @AutoMap(() => String) + naruAgriTourism: string | null; + + //Inclusion / Exclusion Fields + @AutoMap(() => String) + prescribedBody: string | null; + + @AutoMap(() => Number) + inclExclHectares: number | null; + + @AutoMap(() => String) + exclWhyLand: string | null; + + @AutoMap(() => String) + inclAgricultureSupport: string | null; + + @AutoMap(() => String) + inclImprovements: string | null; + + @AutoMap(() => Boolean) + exclShareGovernmentBorders: boolean | null; + + @AutoMap(() => Boolean) + inclGovernmentOwnsAllParcels?: boolean | null; +} diff --git a/services/apps/alcs/src/portal/public/public.module.ts b/services/apps/alcs/src/portal/public/public.module.ts index a63b142147..c0e86ea8d4 100644 --- a/services/apps/alcs/src/portal/public/public.module.ts +++ b/services/apps/alcs/src/portal/public/public.module.ts @@ -1,7 +1,12 @@ import { Module } from '@nestjs/common'; import { RouterModule } from '@nestjs/core'; import { ApplicationSubmissionStatusModule } from '../../alcs/application/application-submission-status/application-submission-status.module'; +import { ApplicationModule } from '../../alcs/application/application.module'; import { NotificationSubmissionStatusModule } from '../../alcs/notification/notification-submission-status/notification-submission-status.module'; +import { ApplicationSubmissionProfile } from '../../common/automapper/application-submission.automapper.profile'; +import { PublicAutomapperProfile } from '../../common/automapper/public.automapper.profile'; +import { ApplicationSubmissionModule } from '../application-submission/application-submission.module'; +import { PublicController } from './public.controller'; import { PublicSearchModule } from './search/public-search.module'; import { PublicStatusController } from './status/public-status.controller'; @@ -10,10 +15,12 @@ import { PublicStatusController } from './status/public-status.controller'; PublicSearchModule, ApplicationSubmissionStatusModule, NotificationSubmissionStatusModule, + ApplicationModule, + ApplicationSubmissionModule, RouterModule.register([{ path: 'public', module: PublicSearchModule }]), ], - controllers: [PublicStatusController], - providers: [], + controllers: [PublicStatusController, PublicController], + providers: [PublicAutomapperProfile], exports: [], }) export class PublicModule {}