+
-
+
();
@@ -110,7 +113,7 @@ export class NoticeOfIntentComponent implements OnInit, OnDestroy {
private noticeOfIntentModificationService: NoticeOfIntentModificationService,
private route: ActivatedRoute,
private titleService: Title,
- public noticeOfIntentStatusService: NoticeOfIntentSubmissionStatusService
+ public noticeOfIntentStatusService: NoticeOfIntentSubmissionStatusService,
) {}
ngOnInit(): void {
@@ -123,7 +126,7 @@ export class NoticeOfIntentComponent implements OnInit, OnDestroy {
this.noticeOfIntentDetailService.$noticeOfIntent.pipe(takeUntil(this.destroy)).subscribe(async (noticeOfIntent) => {
if (noticeOfIntent) {
this.titleService.setTitle(
- `${environment.siteName} | ${noticeOfIntent.fileNumber} (${noticeOfIntent.applicant})`
+ `${environment.siteName} | ${noticeOfIntent.fileNumber} (${noticeOfIntent.applicant})`,
);
this.isApplicantSubmission = noticeOfIntent.source === SYSTEM_SOURCE_TYPES.APPLICANT;
diff --git a/alcs-frontend/src/app/features/notification/notification.component.html b/alcs-frontend/src/app/features/notification/notification.component.html
index 1d09f7568c..17016c98ff 100644
--- a/alcs-frontend/src/app/features/notification/notification.component.html
+++ b/alcs-frontend/src/app/features/notification/notification.component.html
@@ -6,6 +6,7 @@
days="Calendar Days"
[showStatus]="true"
[submissionStatusService]="notificationSubmissionStatusService"
+ [isTagSectionHidden]="true"
>
1">
+
diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.html b/alcs-frontend/src/app/features/planning-review/header/header.component.html
index ad734f2444..104bd2e30a 100644
--- a/alcs-frontend/src/app/features/planning-review/header/header.component.html
+++ b/alcs-frontend/src/app/features/planning-review/header/header.component.html
@@ -26,6 +26,18 @@ arrow_right_alt
+
{{ planningReview.fileNumber }} ({{ planningReview.documentName }})
+
+
+
+
+ Tags
+
+
+
+
+
+
+
+ {{ tag.name }} : {{ tag.category.name }}
+ Inactive
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
diff --git a/alcs-frontend/src/app/features/search/search.component.scss b/alcs-frontend/src/app/features/search/search.component.scss
index aa2450c797..a5cc047535 100644
--- a/alcs-frontend/src/app/features/search/search.component.scss
+++ b/alcs-frontend/src/app/features/search/search.component.scss
@@ -9,6 +9,19 @@ h4 {
margin-top: 48px !important;
}
+.category-label {
+ color: colors.$grey;
+}
+
+.inactive-label {
+ float: right;
+}
+
+.tag-field {
+ width: 100%;
+ margin-top: 2px;
+}
+
.info-banner {
margin-top: -16px;
background-color: rgba(#F7F7F7, 0.5);
@@ -36,6 +49,10 @@ h4 {
}
}
+::ng-deep .mat-mdc-option .mdc-list-item__primary-text {
+ width: 100%;
+}
+
:host::ng-deep {
h3,
div,
diff --git a/alcs-frontend/src/app/features/search/search.component.spec.ts b/alcs-frontend/src/app/features/search/search.component.spec.ts
index e56cbb65cf..f72db7885b 100644
--- a/alcs-frontend/src/app/features/search/search.component.spec.ts
+++ b/alcs-frontend/src/app/features/search/search.component.spec.ts
@@ -14,6 +14,7 @@ import { ToastService } from '../../services/toast/toast.service';
import { SearchComponent } from './search.component';
import { AuthenticationService, ICurrentUser } from '../../services/authentication/authentication.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('SearchComponent', () => {
let component: SearchComponent;
@@ -76,7 +77,7 @@ describe('SearchComponent', () => {
},
],
declarations: [SearchComponent],
- imports: [MatAutocompleteModule],
+ imports: [MatAutocompleteModule, HttpClientTestingModule],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
diff --git a/alcs-frontend/src/app/features/search/search.component.ts b/alcs-frontend/src/app/features/search/search.component.ts
index 965c3e1523..1e017e0e17 100644
--- a/alcs-frontend/src/app/features/search/search.component.ts
+++ b/alcs-frontend/src/app/features/search/search.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, SortDirection } from '@angular/material/sort';
@@ -6,7 +6,7 @@ import { MatTabGroup } from '@angular/material/tabs';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import moment from 'moment';
-import { Observable, Subject, combineLatestWith, map, startWith, takeUntil } from 'rxjs';
+import { Observable, Subject, combineLatestWith, map, of, startWith, takeUntil } from 'rxjs';
import { ApplicationRegionDto } from '../../services/application/application-code.dto';
import { ApplicationLocalGovernmentDto } from '../../services/application/application-local-government/application-local-government.dto';
import { ApplicationLocalGovernmentService } from '../../services/application/application-local-government/application-local-government.service';
@@ -33,6 +33,12 @@ import { formatDateForApi } from '../../shared/utils/api-date-formatter';
import { FileTypeFilterDropDownComponent } from './file-type-filter-drop-down/file-type-filter-drop-down.component';
import { TableChange } from './search.interface';
import { AuthenticationService, ROLES } from '../../services/authentication/authentication.service';
+import { TagCategoryService } from '../../services/tag/tag-category/tag-category.service';
+import { TagCategoryDto } from '../../services/tag/tag-category/tag-category.dto';
+import { TagDto } from '../../services/tag/tag.dto';
+import { TagService } from '../../services/tag/tag.service';
+import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
+import { COMMA, ENTER } from '@angular/cdk/keycodes';
export const defaultStatusBackgroundColour = '#ffffff';
export const defaultStatusColour = '#313132';
@@ -50,6 +56,15 @@ export class SearchComponent implements OnInit, OnDestroy {
@ViewChild('searchResultTabs') tabGroup!: MatTabGroup;
@ViewChild('fileTypeDropDown') fileTypeFilterDropDownComponent!: FileTypeFilterDropDownComponent;
@ViewChild('statusTypeDropDown') portalStatusFilterDropDownComponent!: FileTypeFilterDropDownComponent;
+ @ViewChild(MatAutocompleteTrigger) autoCompleteTrigger!: MatAutocompleteTrigger;
+ @ViewChild('tagInput') tagInput: ElementRef | undefined;
+
+ hovered = false;
+ clicked = false;
+ firstClicked = false;
+ showPlaceholder = false;
+ separatorKeysCodes: number[] = [ENTER, COMMA];
+ filteredTags: Observable = of([]);
applications: ApplicationSearchResultDto[] = [];
applicationTotal = 0;
@@ -70,7 +85,7 @@ export class SearchComponent implements OnInit, OnDestroy {
itemsPerPage = 20;
sortDirection: SortDirection = 'desc';
sortField = 'dateSubmitted';
-
+ tagControl = new FormControl();
localGovernmentControl = new FormControl(undefined);
portalStatusControl = new FormControl([]);
componentTypeControl = new FormControl(undefined);
@@ -93,11 +108,16 @@ export class SearchComponent implements OnInit, OnDestroy {
dateSubmittedTo: new FormControl(undefined),
dateDecidedFrom: new FormControl(undefined),
dateDecidedTo: new FormControl(undefined),
+ tagCategory: new FormControl(undefined),
+ tag: new FormControl([]),
});
resolutionYears: number[] = [];
localGovernments: ApplicationLocalGovernmentDto[] = [];
filteredLocalGovernments!: Observable;
regions: ApplicationRegionDto[] = [];
+ tags: TagDto[] = [];
+ allTags: TagDto[] = [];
+ tagCategories: TagCategoryDto[] = [];
applicationStatuses: ApplicationStatusDto[] = [];
allStatuses: (ApplicationStatusDto | NoticeOfIntentStatusDto | NotificationSubmissionStatusDto)[] = [];
@@ -123,6 +143,8 @@ export class SearchComponent implements OnInit, OnDestroy {
private authService: AuthenticationService,
public fileTypeService: FileTypeDataSourceService,
public portalStatusDataService: PortalStatusDataSourceService,
+ public tagCategoryService: TagCategoryService,
+ public tagService: TagService,
) {
this.titleService.setTitle('ALCS | Search');
}
@@ -130,6 +152,8 @@ export class SearchComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.setup();
+ this.updateFilteredTags();
+
this.applicationService.$applicationRegions
.pipe(takeUntil(this.$destroy))
.pipe(combineLatestWith(this.applicationService.$applicationStatuses, this.activatedRoute.queryParamMap))
@@ -146,6 +170,18 @@ export class SearchComponent implements OnInit, OnDestroy {
}
});
+ this.tagCategoryService.$categories
+ .pipe(takeUntil(this.$destroy))
+ .subscribe((result: { data: TagCategoryDto[]; total: number }) => {
+ this.tagCategories = result.data;
+ });
+
+ this.tagService.$tags
+ .pipe(takeUntil(this.$destroy))
+ .subscribe((result: { data: TagDto[]; total: number }) => {
+ this.allTags = result.data;
+ });
+
this.searchForm.valueChanges.pipe(takeUntil(this.$destroy)).subscribe(() => {
let isEmpty = true;
for (let key in this.searchForm.controls) {
@@ -189,6 +225,9 @@ export class SearchComponent implements OnInit, OnDestroy {
this.applicationService.setup();
this.loadStatuses();
+ this.tagCategoryService.fetch(0, 0);
+ this.tagService.fetch(0, 0);
+
this.filteredLocalGovernments = this.localGovernmentControl.valueChanges.pipe(
startWith(''),
map((value) => this.filterLocalGovernment(value || '')),
@@ -296,6 +335,8 @@ export class SearchComponent implements OnInit, OnDestroy {
? formatDateForApi(this.searchForm.controls.dateDecidedTo.value)
: undefined,
fileTypes: fileTypes,
+ tagCategoryId: this.searchForm.controls.tagCategory.value ?? undefined,
+ tagIds: this.tags.map((t) => t.uuid),
};
}
@@ -374,6 +415,62 @@ export class SearchComponent implements OnInit, OnDestroy {
this.portalStatusControl.setValue(statusCodes);
}
+ onClick(): void {
+ this.clicked = true;
+ if (!this.firstClicked) {
+ this.firstClicked = true;
+ this.tagControl.setValue('');
+ }
+ }
+
+ removeTag(tag: TagDto): void {
+ this.tags = this.tags.filter((t) => t.uuid !== tag.uuid)
+ this.updateFilteredTags();
+ }
+
+ clearTags() {
+ this.tags = [];
+ this.updateFilteredTags();
+ }
+
+ clearSearch() {
+ this.tagInput!.nativeElement.value = '';
+ }
+
+ checkDirty() {
+ this.tagInput!.nativeElement.value = this.tags.length > 0 ? ' ' : '';
+ }
+
+ selectTag(event: MatAutocompleteSelectedEvent) {
+ const selectedTag = event.option.value as TagDto;
+
+ if (!this.tags.find((tag) => tag.uuid === selectedTag.uuid)) {
+ this.tags.push(selectedTag);
+ this.tagInput!.nativeElement.value = '';
+ this.tagControl.setValue('');
+ this.searchForm.controls.tag.setValue(this.tags.map((t) => t.uuid));
+ }
+ }
+
+ private filterTags(value: string): TagDto[] {
+ const filterValue = value.toLowerCase();
+ return this.allTags.filter((tag) => {
+ if (filterValue) {
+ return !this.tags.includes(tag) && tag.name.toLowerCase().includes(filterValue);
+ } else {
+ return !this.tags.includes(tag);
+ }
+ });
+ }
+
+ private updateFilteredTags(): void {
+ this.filteredTags = this.tagControl.valueChanges.pipe(
+ startWith(''),
+ map((value) => (typeof value === 'string' ? value : value?.name || '')),
+ map((name) => this.filterTags(name || '')),
+ );
+ }
+
private async loadGovernments() {
const governments = await this.localGovernmentService.list();
this.localGovernments = governments.sort((a, b) => (a.name > b.name ? 1 : -1));
diff --git a/alcs-frontend/src/app/features/search/search.module.ts b/alcs-frontend/src/app/features/search/search.module.ts
index ca8a3a27c4..5484fb5684 100644
--- a/alcs-frontend/src/app/features/search/search.module.ts
+++ b/alcs-frontend/src/app/features/search/search.module.ts
@@ -12,6 +12,7 @@ import { NoticeOfIntentSearchTableComponent } from './notice-of-intent-search-ta
import { NotificationSearchTableComponent } from './notification-search-table/notification-search-table.component';
import { SearchComponent } from './search.component';
import { InquirySearchTableComponent } from './inquiry-search-table/inquiry-search-table.component';
+import { MatChipsModule } from '@angular/material/chips';
const routes: Routes = [
{
@@ -37,6 +38,7 @@ const routes: Routes = [
MatTabsModule,
MatPaginatorModule,
MatTreeModule,
+ MatChipsModule,
],
})
export class SearchModule {}
diff --git a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts
index e26b0d4ad1..9568259950 100644
--- a/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts
+++ b/alcs-frontend/src/app/services/application/application-submission/application-submission.service.spec.ts
@@ -91,6 +91,13 @@ describe('ApplicationSubmissionService', () => {
soilAlternativeMeasures: null,
soilHasSubmittedNotice: null,
soilIsExtractionOrMining: null,
+ soilIsNewStructure: null,
+ soilStructureFarmUseReason: null,
+ soilStructureResidentialUseReason: null,
+ soilAgriParcelActivity: null,
+ soilStructureResidentialAccessoryUseReason: null,
+ soilStructureOtherUseReason: null,
+ soilProposedStructures: [],
soilIsFollowUp: null,
soilFollowUpIDs: null,
soilProjectDuration: null,
diff --git a/alcs-frontend/src/app/services/application/application-tag/application-tag.dto.ts b/alcs-frontend/src/app/services/application/application-tag/application-tag.dto.ts
new file mode 100644
index 0000000000..9d35e9659e
--- /dev/null
+++ b/alcs-frontend/src/app/services/application/application-tag/application-tag.dto.ts
@@ -0,0 +1,3 @@
+import { FileTagDto } from '../../common/file-tag.dto';
+
+export interface ApplicationTagDto extends FileTagDto {}
diff --git a/alcs-frontend/src/app/services/application/application-tag/application-tag.service.spec.ts b/alcs-frontend/src/app/services/application/application-tag/application-tag.service.spec.ts
new file mode 100644
index 0000000000..7e5d633dd0
--- /dev/null
+++ b/alcs-frontend/src/app/services/application/application-tag/application-tag.service.spec.ts
@@ -0,0 +1,32 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ApplicationTagService } from './application-tag.service';
+import { createMock, DeepMocked } from '@golevelup/ts-jest';
+import { HttpClient } from '@angular/common/http';
+import { ToastService } from '../../toast/toast.service';
+
+describe('ApplicationTagService', () => {
+ let service: ApplicationTagService;
+ let httpClient: DeepMocked;
+ let toastService: DeepMocked;
+
+ beforeEach(() => {
+ httpClient = createMock();
+ toastService = createMock();
+
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: HttpClient, useValue: httpClient },
+ {
+ provide: ToastService,
+ useValue: toastService,
+ },
+ ],
+ });
+ service = TestBed.inject(ApplicationTagService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/alcs-frontend/src/app/services/application/application-tag/application-tag.service.ts b/alcs-frontend/src/app/services/application/application-tag/application-tag.service.ts
new file mode 100644
index 0000000000..9ae2ccfbbf
--- /dev/null
+++ b/alcs-frontend/src/app/services/application/application-tag/application-tag.service.ts
@@ -0,0 +1,64 @@
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { ToastService } from '../../toast/toast.service';
+import { environment } from '../../../../environments/environment';
+import { firstValueFrom } from 'rxjs';
+import { TagDto } from '../../tag/tag.dto';
+import { ApplicationTagDto } from './application-tag.dto';
+import { FileTagService } from '../../common/file-tag.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ApplicationTagService extends FileTagService {
+ private baseUrl = `${environment.apiUrl}/application`;
+ private tagUrl = 'tag';
+
+ constructor(http: HttpClient, toastService: ToastService) {
+ super(http, toastService);
+ }
+
+ async getTags(fileNumber: string) {
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}`;
+ try {
+ return await firstValueFrom(this.http.get(requestUrl));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && e.status === 404) {
+ this.toastService.showErrorToast(`Application with File ID ${fileNumber} was not found!`);
+ } else {
+ this.toastService.showErrorToast('Failed to retrieve the application');
+ }
+ }
+ return;
+ }
+
+ async addTag(fileNumber: string, applicationTagDto: ApplicationTagDto) {
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}`;
+ try {
+ return await firstValueFrom(this.http.post(requestUrl, applicationTagDto));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && (e.status === 404 || e.status === 400)) {
+ this.toastService.showErrorToast(e.error.message);
+ } else {
+ this.toastService.showErrorToast('Failed to add tag to the application');
+ }
+ }
+
+ return;
+ }
+
+ async deleteTag(fileNumber: string, tagName: string) {
+ const encodedTagName = encodeURIComponent(tagName);
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}/${encodedTagName}`;
+ try {
+ return await firstValueFrom(this.http.delete(requestUrl));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && (e.status === 404 || e.status === 400)) {
+ this.toastService.showErrorToast(e.error.message);
+ } else {
+ this.toastService.showErrorToast('Failed to remove tag to the application');
+ }
+ }
+ return;
+ }
+}
diff --git a/alcs-frontend/src/app/services/application/application.dto.ts b/alcs-frontend/src/app/services/application/application.dto.ts
index 9fe3f94b7e..22d8c38738 100644
--- a/alcs-frontend/src/app/services/application/application.dto.ts
+++ b/alcs-frontend/src/app/services/application/application.dto.ts
@@ -1,6 +1,8 @@
import { BaseCodeDto } from '../../shared/dto/base.dto';
import { SYSTEM_SOURCE_TYPES } from '../../shared/dto/system-source.types.dto';
import { CardDto } from '../card/card.dto';
+import { TagDto } from '../tag/tag.dto';
+import { ProposedStructure } from '../notice-of-intent/notice-of-intent.dto';
import { UserDto } from '../user/user.dto';
import { ApplicationRegionDto, ApplicationTypeDto } from './application-code.dto';
import { ApplicationLocalGovernmentDto } from './application-local-government/application-local-government.dto';
@@ -171,6 +173,7 @@ export interface ApplicationSubmissionDto {
subdProposedLots: ProposedLot[];
//Soil Fields
+ soilIsNewStructure: boolean | null;
soilIsFollowUp: boolean | null;
soilFollowUpIDs: string | null;
soilTypeRemoved: string | null;
@@ -197,6 +200,12 @@ export interface ApplicationSubmissionDto {
soilAlternativeMeasures: string | null;
soilIsExtractionOrMining: boolean | null;
soilHasSubmittedNotice: boolean | null;
+ soilStructureFarmUseReason: string | null;
+ soilStructureResidentialUseReason: string | null;
+ soilAgriParcelActivity: string | null;
+ soilStructureResidentialAccessoryUseReason: string | null;
+ soilStructureOtherUseReason: string | null;
+ soilProposedStructures: ProposedStructure[];
//NARU Fields
naruWillBeOverFiveHundredM2: boolean | null;
@@ -281,6 +290,7 @@ export interface ApplicationDto {
proposalEndDate2?: number;
proposalExpiryDate?: number;
legacyId?: string;
+ tags?: TagDto[];
}
export interface UpdateApplicationDto {
diff --git a/alcs-frontend/src/app/services/common/file-tag.dto.ts b/alcs-frontend/src/app/services/common/file-tag.dto.ts
new file mode 100644
index 0000000000..e937312f83
--- /dev/null
+++ b/alcs-frontend/src/app/services/common/file-tag.dto.ts
@@ -0,0 +1,3 @@
+export interface FileTagDto {
+ tagName: string;
+}
diff --git a/alcs-frontend/src/app/services/common/file-tag.service.spec.ts b/alcs-frontend/src/app/services/common/file-tag.service.spec.ts
new file mode 100644
index 0000000000..7e5f9aaf40
--- /dev/null
+++ b/alcs-frontend/src/app/services/common/file-tag.service.spec.ts
@@ -0,0 +1,32 @@
+import { TestBed } from '@angular/core/testing';
+
+import { FileTagService } from './file-tag.service';
+import { createMock, DeepMocked } from '@golevelup/ts-jest';
+import { HttpClient } from '@angular/common/http';
+import { ToastService } from '../toast/toast.service';
+
+describe('FileTagService', () => {
+ let service: FileTagService;
+ let httpClient: DeepMocked;
+ let toastService: DeepMocked;
+
+ beforeEach(() => {
+ httpClient = createMock();
+ toastService = createMock();
+
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: HttpClient, useValue: httpClient },
+ {
+ provide: ToastService,
+ useValue: toastService,
+ },
+ ],
+ });
+ service = TestBed.inject(FileTagService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/alcs-frontend/src/app/services/common/file-tag.service.ts b/alcs-frontend/src/app/services/common/file-tag.service.ts
new file mode 100644
index 0000000000..2e09df195f
--- /dev/null
+++ b/alcs-frontend/src/app/services/common/file-tag.service.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { ApplicationTagDto } from '../application/application-tag/application-tag.dto';
+import { TagDto } from '../tag/tag.dto';
+import { HttpClient } from '@angular/common/http';
+import { ToastService } from '../toast/toast.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export abstract class FileTagService {
+ constructor(
+ protected http: HttpClient,
+ protected toastService: ToastService,
+ ) {}
+
+ abstract getTags(filenumber: string): Promise;
+ abstract addTag(fileNumber: string, applicationTagDto: ApplicationTagDto): Promise;
+ abstract deleteTag(fileNymber: string, tagName: string): Promise;
+}
diff --git a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts
index 8ec4c659c1..4592da9c37 100644
--- a/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts
+++ b/alcs-frontend/src/app/services/decision-meeting/decision-meeting.dto.ts
@@ -8,6 +8,7 @@ export type UpcomingMeetingDto = {
boardCode: string;
assignee: AssigneeDto;
type: CardType;
+ isPaused: boolean;
};
export type UpcomingMeetingBoardMapDto = Record;
diff --git a/alcs-frontend/src/app/services/incoming-file/incoming-file.dto.ts b/alcs-frontend/src/app/services/incoming-file/incoming-file.dto.ts
index 5c13e0479c..b4d91073be 100644
--- a/alcs-frontend/src/app/services/incoming-file/incoming-file.dto.ts
+++ b/alcs-frontend/src/app/services/incoming-file/incoming-file.dto.ts
@@ -9,6 +9,7 @@ export type IncomingFileDto = {
type: CardType;
highPriority: boolean;
activeDays: number;
+ isPaused: boolean;
};
export type IncomingFileBoardMapDto = Record;
diff --git a/alcs-frontend/src/app/services/incoming-file/incoming-file.service.spec.ts b/alcs-frontend/src/app/services/incoming-file/incoming-file.service.spec.ts
index d1f5fa23a3..bd52a167cb 100644
--- a/alcs-frontend/src/app/services/incoming-file/incoming-file.service.spec.ts
+++ b/alcs-frontend/src/app/services/incoming-file/incoming-file.service.spec.ts
@@ -38,6 +38,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: false,
activeDays: 10,
+ isPaused: true,
},
{
fileNumber: '2',
@@ -47,6 +48,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: true,
activeDays: 12,
+ isPaused: true,
},
{
fileNumber: '3',
@@ -56,6 +58,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: true,
activeDays: 30,
+ isPaused: false,
},
],
};
@@ -70,6 +73,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: true,
activeDays: 30,
+ isPaused: false,
},
{
fileNumber: '2',
@@ -79,6 +83,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: true,
activeDays: 12,
+ isPaused: true,
},
{
fileNumber: '1',
@@ -88,6 +93,7 @@ describe('ApplicationIncomingFileService', () => {
assignee: null,
highPriority: false,
activeDays: 10,
+ isPaused: true,
},
],
};
diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.dto.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.dto.ts
new file mode 100644
index 0000000000..008b1fbc03
--- /dev/null
+++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.dto.ts
@@ -0,0 +1,3 @@
+import { FileTagDto } from '../../common/file-tag.dto';
+
+export interface NoticeOfIntentTagDto extends FileTagDto {}
diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.spec.ts
new file mode 100644
index 0000000000..e0ac667cb6
--- /dev/null
+++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.spec.ts
@@ -0,0 +1,32 @@
+import { TestBed } from '@angular/core/testing';
+
+import { NoticeOfIntentTagService } from './notice-of-intent-tag.service';
+import { createMock, DeepMocked } from '@golevelup/ts-jest';
+import { HttpClient } from '@angular/common/http';
+import { ToastService } from '../../toast/toast.service';
+
+describe('NoticeOfIntentTagService', () => {
+ let service: NoticeOfIntentTagService;
+ let httpClient: DeepMocked;
+ let toastService: DeepMocked;
+
+ beforeEach(() => {
+ httpClient = createMock();
+ toastService = createMock();
+
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: HttpClient, useValue: httpClient },
+ {
+ provide: ToastService,
+ useValue: toastService,
+ },
+ ],
+ });
+ service = TestBed.inject(NoticeOfIntentTagService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.ts
new file mode 100644
index 0000000000..b77c99c370
--- /dev/null
+++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-tag/notice-of-intent-tag.service.ts
@@ -0,0 +1,64 @@
+import { Injectable } from '@angular/core';
+import { FileTagService } from '../../common/file-tag.service';
+import { environment } from '../../../../environments/environment';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+import { ToastService } from '../../toast/toast.service';
+import { firstValueFrom } from 'rxjs';
+import { TagDto } from '../../tag/tag.dto';
+import { NoticeOfIntentTagDto } from './notice-of-intent-tag.dto';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class NoticeOfIntentTagService extends FileTagService {
+ private baseUrl = `${environment.apiUrl}/notice-of-intent`;
+ private tagUrl = 'tag';
+
+ constructor(http: HttpClient, toastService: ToastService) {
+ super(http, toastService);
+ }
+
+ async getTags(fileNumber: string) {
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}`;
+ try {
+ return await firstValueFrom(this.http.get(requestUrl));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && e.status === 404) {
+ this.toastService.showErrorToast(`Notice of Intent with File ID ${fileNumber} was not found!`);
+ } else {
+ this.toastService.showErrorToast('Failed to retrieve the Notice of Intent');
+ }
+ }
+ return;
+ }
+
+ async addTag(fileNumber: string, noiTagDto: NoticeOfIntentTagDto) {
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}`;
+ try {
+ return await firstValueFrom(this.http.post(requestUrl, noiTagDto));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && (e.status === 404 || e.status === 400)) {
+ this.toastService.showErrorToast(e.error.message);
+ } else {
+ this.toastService.showErrorToast('Failed to add tag to the Notice of Intent');
+ }
+ }
+
+ return;
+ }
+
+ async deleteTag(fileNumber: string, tagName: string) {
+ const encodedTagName = encodeURIComponent(tagName);
+ const requestUrl = `${this.baseUrl}/${fileNumber}/${this.tagUrl}/${encodedTagName}`;
+ try {
+ return await firstValueFrom(this.http.delete(requestUrl));
+ } catch (e) {
+ if (e instanceof HttpErrorResponse && (e.status === 404 || e.status === 400)) {
+ this.toastService.showErrorToast(e.error.message);
+ } else {
+ this.toastService.showErrorToast('Failed to remove tag to the Notice of Intent');
+ }
+ }
+ return;
+ }
+}
diff --git a/alcs-frontend/src/app/services/search/search.dto.ts b/alcs-frontend/src/app/services/search/search.dto.ts
index 3c72c2c4c4..c5f2d6ea8a 100644
--- a/alcs-frontend/src/app/services/search/search.dto.ts
+++ b/alcs-frontend/src/app/services/search/search.dto.ts
@@ -80,6 +80,8 @@ export interface SearchRequestDto extends PagingRequestDto {
dateDecidedFrom?: number;
dateDecidedTo?: number;
fileTypes: string[];
+ tagIds?: string[];
+ tagCategoryId?: string;
}
export interface SearchResultDto {
diff --git a/alcs-frontend/src/app/services/tag/tag-category/tag-category.dto.ts b/alcs-frontend/src/app/services/tag/tag-category/tag-category.dto.ts
new file mode 100644
index 0000000000..2a950f5cec
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag-category/tag-category.dto.ts
@@ -0,0 +1,4 @@
+export interface TagCategoryDto {
+ uuid: string;
+ name: string;
+}
diff --git a/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.spec.ts b/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.spec.ts
new file mode 100644
index 0000000000..891ed8c89b
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.spec.ts
@@ -0,0 +1,91 @@
+import { HttpClient } from '@angular/common/http';
+import { TestBed } from '@angular/core/testing';
+import { createMock, DeepMocked } from '@golevelup/ts-jest';
+import { of, throwError } from 'rxjs';
+import { ToastService } from '../../toast/toast.service';
+import { TagCategoryService } from './tag-category.service';
+
+describe('TagCategoryService', () => {
+ let service: TagCategoryService;
+ 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(TagCategoryService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should call post on create', async () => {
+ mockHttpClient.post.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.create({
+ uuid: '',
+ name: '',
+ });
+
+ expect(mockHttpClient.post).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+
+ it('should call patch on update', async () => {
+ mockHttpClient.patch.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.update('fake', {
+ uuid: '',
+ name: '',
+ });
+
+ expect(mockHttpClient.patch).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+
+ it('should call get on fetch', async () => {
+ mockHttpClient.get.mockReturnValue(of([]));
+
+ await service.fetch(0, 0);
+
+ expect(mockHttpClient.get).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call delete on delete', async () => {
+ mockHttpClient.delete.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.delete('fake');
+
+ expect(mockHttpClient.delete).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+});
diff --git a/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.ts b/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.ts
new file mode 100644
index 0000000000..c9eab99cbd
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag-category/tag-category.service.ts
@@ -0,0 +1,99 @@
+import { HttpClient, HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, firstValueFrom } from 'rxjs';
+import { environment } from '../../../../environments/environment';
+import { TagCategoryDto } from './tag-category.dto';
+import { ToastService } from '../../toast/toast.service';
+
+export interface PaginatedTagCategoryResponse {
+ data: TagCategoryDto[];
+ total: number;
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class TagCategoryService {
+ private url = `${environment.apiUrl}/tag-category`;
+
+ constructor(
+ private http: HttpClient,
+ private toastService: ToastService,
+ ) {}
+
+ public $categories = new BehaviorSubject({ data: [], total: 0 });
+
+ async fetch(pageIndex: number, itemsPerPage: number, search?: string) {
+ const result = await this.search(pageIndex, itemsPerPage, search);
+ if (result) {
+ this.$categories.next(result);
+ }
+ }
+
+ async search(pageIndex: number, itemsPerPage: number, search?: string) {
+ const pageQuery = `?pageIndex=${pageIndex}`;
+ const itemsQuery = `&itemsPerPage=${itemsPerPage}`;
+ const searchQuery = search ? `&search=${search}` : '';
+ try {
+ return await firstValueFrom(
+ this.http.get(`${this.url}${pageQuery}${itemsQuery}${searchQuery}`),
+ );
+ } catch (err) {
+ console.error(err);
+ this.toastService.showErrorToast('Failed to fetch tag categories');
+ }
+ return;
+ }
+
+ async create(createDto: TagCategoryDto) {
+ try {
+ return await firstValueFrom(this.http.post(`${this.url}`, createDto));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+ if (res.error.statusCode === HttpStatusCode.Conflict) {
+ throw res;
+ } else {
+ console.error(e);
+ this.toastService.showErrorToast('Failed to create tag category');
+ }
+ }
+ return;
+ }
+
+ async update(uuid: string, updateDto: TagCategoryDto) {
+ try {
+ return await firstValueFrom(this.http.patch(`${this.url}/${uuid}`, updateDto));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+ if (res.error.statusCode === HttpStatusCode.Conflict) {
+ throw res;
+ } else {
+ console.error(e);
+ this.toastService.showErrorToast('Failed to update tag category');
+ }
+ }
+ return;
+ }
+
+ async delete(uuid: string) {
+ try {
+ return await firstValueFrom(this.http.delete(`${this.url}/${uuid}`));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+
+ // TODO: FIX THE ERROR
+ // if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('update or delete on table')) {
+ // this.toastService.showErrorToast('weird');
+ // } else if (
+ if (
+ res.error.statusCode === HttpStatusCode.Conflict &&
+ res.error.message === 'Category is associated with tags. Unable to delete.'
+ ) {
+ this.toastService.showErrorToast('Category is associated with tags. Unable to delete.');
+ } else {
+ this.toastService.showErrorToast('Failed to delete tag category');
+ }
+ }
+ return;
+ }
+}
diff --git a/alcs-frontend/src/app/services/tag/tag.dto.ts b/alcs-frontend/src/app/services/tag/tag.dto.ts
new file mode 100644
index 0000000000..1a1115677e
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag.dto.ts
@@ -0,0 +1,8 @@
+import { TagCategoryDto } from "./tag-category/tag-category.dto";
+
+export interface TagDto {
+ uuid: string;
+ name: string;
+ category?: TagCategoryDto;
+ isActive: boolean;
+}
diff --git a/alcs-frontend/src/app/services/tag/tag.service.spec.ts b/alcs-frontend/src/app/services/tag/tag.service.spec.ts
new file mode 100644
index 0000000000..1164b3cd6d
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag.service.spec.ts
@@ -0,0 +1,114 @@
+import { HttpClient } from '@angular/common/http';
+import { TestBed } from '@angular/core/testing';
+import { createMock, DeepMocked } from '@golevelup/ts-jest';
+import { of, throwError } from 'rxjs';
+import { ToastService } from '../toast/toast.service';
+import { TagService } from './tag.service';
+
+describe('TagCategoryService', () => {
+ let service: TagService;
+ 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(TagService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should call post on create', async () => {
+ mockHttpClient.post.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.create({
+ uuid: '',
+ name: '',
+ category: {
+ uuid: '',
+ name: ''
+ },
+ isActive: true,
+ });
+
+ expect(mockHttpClient.post).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+
+ it('should call patch on update', async () => {
+ mockHttpClient.patch.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.update('fake', {
+ uuid: '',
+ name: '',
+ category: {
+ uuid: '',
+ name: ''
+ },
+ isActive: true,
+ });
+
+ expect(mockHttpClient.patch).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+
+ it('should call get on fetch', async () => {
+ mockHttpClient.get.mockReturnValue(of([]));
+
+ await service.fetch(0, 0);
+
+ expect(mockHttpClient.get).toHaveBeenCalledTimes(1);
+ });
+
+ it('should show toast if get fails', async () => {
+ mockHttpClient.get.mockReturnValue(
+ throwError(() => {
+ new Error('');
+ })
+ );
+
+ const res = await service.fetch(0, 0);
+
+ expect(mockHttpClient.get).toHaveBeenCalledTimes(1);
+ expect(mockToastService.showErrorToast).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call delete on delete', async () => {
+ mockHttpClient.delete.mockReturnValue(
+ of({
+ uuid: 'fake',
+ })
+ );
+
+ const res = await service.delete('fake');
+
+ expect(mockHttpClient.delete).toHaveBeenCalledTimes(1);
+ expect(res).toBeDefined();
+ expect(res!.uuid).toEqual('fake');
+ });
+});
diff --git a/alcs-frontend/src/app/services/tag/tag.service.ts b/alcs-frontend/src/app/services/tag/tag.service.ts
new file mode 100644
index 0000000000..de14cb3bfb
--- /dev/null
+++ b/alcs-frontend/src/app/services/tag/tag.service.ts
@@ -0,0 +1,97 @@
+import { HttpClient, HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, firstValueFrom } from 'rxjs';
+import { environment } from '../../../environments/environment';
+import { TagDto } from './tag.dto';
+import { ToastService } from '../toast/toast.service';
+
+export interface PaginatedTagResponse {
+ data: TagDto[];
+ total: number;
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class TagService {
+ private url = `${environment.apiUrl}/tag`;
+
+ constructor(
+ private http: HttpClient,
+ private toastService: ToastService,
+ ) {}
+
+ public $tags = new BehaviorSubject({ data: [], total: 0 });
+
+ async fetch(pageIndex: number, itemsPerPage: number, search?: string) {
+ const result = await this.search(pageIndex, itemsPerPage, search);
+ if (result) {
+ this.$tags.next(result);
+ }
+ }
+
+ async search(pageIndex: number, itemsPerPage: number, search?: string) {
+ const pageQuery = `?pageIndex=${pageIndex}`;
+ const itemsQuery = `&itemsPerPage=${itemsPerPage}`;
+ const searchQuery = search ? `&search=${search}` : '';
+ try {
+ return await firstValueFrom(
+ this.http.get(`${this.url}${pageQuery}${itemsQuery}${searchQuery}`),
+ );
+ } catch (err) {
+ console.error(err);
+ this.toastService.showErrorToast('Failed to fetch tags');
+ }
+ return;
+ }
+
+ async create(createDto: TagDto) {
+ try {
+ return await firstValueFrom(this.http.post(`${this.url}`, createDto));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+ if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('duplicate key')) {
+ throw e as HttpErrorResponse;
+ } else {
+ console.error(e);
+ this.toastService.showErrorToast('Failed to create tag');
+ }
+ }
+ return;
+ }
+
+ async update(uuid: string, updateDto: TagDto) {
+ try {
+ return await firstValueFrom(this.http.patch(`${this.url}/${uuid}`, updateDto));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+ if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('duplicate key')) {
+ throw e as HttpErrorResponse;
+ } else {
+ console.error(e);
+ this.toastService.showErrorToast('Failed to update tag');
+ }
+ }
+ return;
+ }
+
+ async delete(uuid: string) {
+ try {
+ return await firstValueFrom(this.http.delete(`${this.url}/${uuid}`));
+ } catch (e) {
+ const res = e as HttpErrorResponse;
+ if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('update or delete on table')) {
+ throw e as HttpErrorResponse;
+ } else if (
+ res.error.statusCode === HttpStatusCode.Conflict &&
+ res.error.message.includes('Tag is associated with files. Unable to delete.')
+ ) {
+ this.toastService.showErrorToast('Tag is associate with files. Unable to delete.');
+ } else {
+ console.error(e);
+ this.toastService.showErrorToast('Failed to delete tag');
+ }
+ }
+ return;
+ }
+}
diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.html b/alcs-frontend/src/app/shared/application-document/application-document.component.html
index 582405dc0f..e6377bcce8 100644
--- a/alcs-frontend/src/app/shared/application-document/application-document.component.html
+++ b/alcs-frontend/src/app/shared/application-document/application-document.component.html
@@ -29,6 +29,11 @@ {{ i + 1 }}
+
+ Source
+ {{ element.source }}
+
+
Type
{{ element.type?.label }}
@@ -41,16 +46,11 @@
-
- Source
- {{ element.source }}
-
-
Upload Date
{{ element.uploadedAt | momentFormat }}
diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.scss b/alcs-frontend/src/app/shared/application-document/application-document.component.scss
index 202a77ec19..c76a3b61c2 100644
--- a/alcs-frontend/src/app/shared/application-document/application-document.component.scss
+++ b/alcs-frontend/src/app/shared/application-document/application-document.component.scss
@@ -50,7 +50,6 @@
}
a {
- font-size: 14px !important;
cursor: pointer;
}
@@ -88,3 +87,50 @@ a {
.cdk-drag-preview {
background-color: colors.$grey-light !important;
}
+
+
+.mat-column-index {
+ width: 2%;
+}
+
+.mat-column-source {
+ word-wrap: break-word !important;
+ white-space: unset !important;
+ flex: 0 0 10% !important;
+ width: 10% !important;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+}
+
+.mat-column-type {
+ word-wrap: break-word !important;
+ white-space: unset !important;
+ flex: 0 0 21% !important;
+ width: 21% !important;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+}
+
+.mat-column-fileName {
+ word-wrap: break-word !important;
+ white-space: unset !important;
+ flex: 0 0 55% !important;
+ width: 55% !important;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+}
+
+.mat-column-uploadedAt {
+ width: 18%;
+}
+
+.mat-column-action {
+ width: 2%;
+}
+
+.mat-column-sorting {
+ width: 2%;
+}
diff --git a/alcs-frontend/src/app/shared/application-document/application-document.component.ts b/alcs-frontend/src/app/shared/application-document/application-document.component.ts
index a101863473..861c5c5789 100644
--- a/alcs-frontend/src/app/shared/application-document/application-document.component.ts
+++ b/alcs-frontend/src/app/shared/application-document/application-document.component.ts
@@ -19,7 +19,7 @@ export class ApplicationDocumentComponent implements OnChanges {
@ViewChild('orderMenu') orderMenu!: TemplateRef;
- displayedColumns: string[] = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action', 'sorting'];
+ displayedColumns: string[] = ['index', 'source', 'type', 'fileName', 'uploadedAt', 'action', 'sorting'];
documents: ApplicationDocumentDto[] = [];
dataSource = new MatTableDataSource([]);
overlayRef: OverlayRef | null = null;
@@ -36,7 +36,7 @@ export class ApplicationDocumentComponent implements OnChanges {
ngOnChanges(changes: SimpleChanges): void {
this.loadDocuments();
if (!this.sortable) {
- this.displayedColumns = ['index', 'type', 'fileName', 'source', 'uploadedAt', 'action'];
+ this.displayedColumns = ['index', 'source', 'type', 'fileName', 'uploadedAt', 'action'];
}
}
diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.html b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.html
index 2e8348e5e5..0068dbd303 100644
--- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.html
+++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.html
@@ -2,14 +2,15 @@
{{ useShortLabel ? type.shortLabel : type.label }}
-
\ No newline at end of file
+
diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.scss b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.scss
index bba437d9f4..1a85d0b6c1 100644
--- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.scss
+++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.scss
@@ -27,3 +27,8 @@
}
}
}
+
+.pill-header {
+ border-radius: 16px !important;
+ font-size: 16px !important;
+}
diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.ts
index ca9df0977f..386d71e46b 100644
--- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.ts
+++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.component.ts
@@ -16,6 +16,7 @@ export type ApplicationPill = {
export class ApplicationTypePillComponent {
@Input() type!: ApplicationPill;
@Input() useShortLabel = false;
+ @Input() isHeader = false;
constructor() {}
}
diff --git a/alcs-frontend/src/app/shared/confirmation-dialog/confirmation-dialog.component.html b/alcs-frontend/src/app/shared/confirmation-dialog/confirmation-dialog.component.html
index c03989f290..3a6b5f75da 100644
--- a/alcs-frontend/src/app/shared/confirmation-dialog/confirmation-dialog.component.html
+++ b/alcs-frontend/src/app/shared/confirmation-dialog/confirmation-dialog.component.html
@@ -2,7 +2,7 @@
{{ data.title }}
{{ tableTitle }}
{{ tableTitle }}
(click)="onOpen(element.uuid, element.fileName)" [matTooltip]="element.fileName" [matTooltipDisabled]="element.fileName.length <= fileNameTruncLen"> - {{ element.fileName | truncate: fileNameTruncLen}} + {{ element.fileName }}
-
{{ data.body }}
+
diff --git a/alcs-frontend/src/app/shared/constants.ts b/alcs-frontend/src/app/shared/constants.ts
index d85cfdffc1..7e0b8ed4c4 100644
--- a/alcs-frontend/src/app/shared/constants.ts
+++ b/alcs-frontend/src/app/shared/constants.ts
@@ -1 +1 @@
-export const FILE_NAME_TRUNCATE_LENGTH = 30;
\ No newline at end of file
+export const FILE_NAME_TRUNCATE_LENGTH = 30;
diff --git a/alcs-frontend/src/app/shared/details-header/details-header.component.html b/alcs-frontend/src/app/shared/details-header/details-header.component.html
index f6fe65a477..b767fc89e4 100644
--- a/alcs-frontend/src/app/shared/details-header/details-header.component.html
+++ b/alcs-frontend/src/app/shared/details-header/details-header.component.html
@@ -1,86 +1,108 @@
-
- {{ heading }}
-
-
-
-
-
-
-
- {{ _application.fileNumber }}
- (
- {{ _application.applicant }}
- )
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
Edit Section
diff --git a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.scss b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.scss
index 36aafed7e7..5a971346c5 100644
--- a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.scss
+++ b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.scss
@@ -21,3 +21,41 @@
height: rem(16);
}
}
+
+.structure-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);
+
+ @media screen and (min-width: $tabletBreakpoint) {
+ grid-template-columns: 0.55fr 1fr 1fr;
+ }
+
+ @media screen and (min-width: $midBreakpoint) {
+ grid-template-columns: 1fr 1fr 2fr;
+ }
+}
+
+.mobile-structure-card {
+ width: 100%;
+ box-shadow: none;
+ border: none;
+ border-radius: 0;
+ word-wrap: break-word;
+ white-space: normal;
+}
+
+.mobile-structure-card-border {
+ border-top: rem(1) solid #aaaaaa;
+}
+
+.mobile-structure-title {
+ display: block;
+ margin: 0;
+}
+
+.mobile-structure-label {
+ margin: rem(5) 0 rem(5) 0;
+}
diff --git a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts
index df615202ef..1c24317ccd 100644
--- a/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts
+++ b/portal-frontend/src/app/features/applications/application-details/pfrs-details/pfrs-details.component.ts
@@ -5,6 +5,11 @@ import { ApplicationDocumentService } from '../../../../services/application-doc
import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto';
import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto';
import { openFileInline } from '../../../../shared/utils/file';
+import { MOBILE_BREAKPOINT } from '../../../../shared/utils/breakpoints';
+import {
+ STRUCTURE_TYPE_LABEL_MAP,
+ STRUCTURE_TYPES,
+} from '../../../notice-of-intents/edit-submission/additional-information/additional-information.component';
@Component({
selector: 'app-pfrs-details[applicationSubmission]',
@@ -16,11 +21,38 @@ export class PfrsDetailsComponent {
@Input() showEdit = true;
@Input() draftMode = false;
+ isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
+
+ isSoilStructureFarmUseReasonVisible = false;
+ isSoilStructureResidentialUseReasonVisible = false;
+ isSoilAgriParcelActivityVisible = false;
+ isSoilStructureResidentialAccessoryUseReasonVisible = false;
+ isSoilOtherStructureVisible = false;
+
_applicationSubmission: ApplicationSubmissionDetailedDto | undefined;
@Input() set applicationSubmission(applicationSubmission: ApplicationSubmissionDetailedDto | undefined) {
if (applicationSubmission) {
this._applicationSubmission = applicationSubmission;
+
+ this.isSoilStructureFarmUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) =>
+ structure.type === STRUCTURE_TYPES.PRINCIPAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ADDITIONAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilAgriParcelActivityVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialAccessoryUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilOtherStructureVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.OTHER_STRUCTURE,
+ );
}
}
@@ -28,12 +60,14 @@ export class PfrsDetailsComponent {
this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS);
this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP);
this.reclamationPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.RECLAMATION_PLAN);
+ this.buildingPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.BUILDING_PLAN);
this.noticeOfWork = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.NOTICE_OF_WORK);
}
crossSections: ApplicationDocumentDto[] = [];
proposalMap: ApplicationDocumentDto[] = [];
reclamationPlans: ApplicationDocumentDto[] = [];
+ buildingPlans: ApplicationDocumentDto[] = [];
noticeOfWork: ApplicationDocumentDto[] = [];
constructor(private router: Router, private applicationDocumentService: ApplicationDocumentService) {}
@@ -54,4 +88,12 @@ export class PfrsDetailsComponent {
openFileInline(res.url, file.fileName);
}
}
+
+ mapStructureTypeValueToLabel(value: STRUCTURE_TYPES | null): string | null {
+ if (value === null) {
+ return null;
+ }
+
+ return STRUCTURE_TYPE_LABEL_MAP[value];
+ }
}
diff --git a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html
index 4218d96047..351ece46a8 100644
--- a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html
+++ b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.html
@@ -1,4 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Edit Section
diff --git a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.scss b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.scss
index 63cfeaa16b..5d7b57d09e 100644
--- a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.scss
+++ b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.scss
@@ -16,3 +16,41 @@
grid-column-gap: rem(16);
}
}
+
+.structure-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);
+
+ @media screen and (min-width: $tabletBreakpoint) {
+ grid-template-columns: 0.55fr 1fr 1fr;
+ }
+
+ @media screen and (min-width: $midBreakpoint) {
+ grid-template-columns: 1fr 1fr 2fr;
+ }
+}
+
+.mobile-structure-card {
+ width: 100%;
+ box-shadow: none;
+ border: none;
+ border-radius: 0;
+ word-wrap: break-word;
+ white-space: normal;
+}
+
+.mobile-structure-card-border {
+ border-top: rem(1) solid #aaaaaa;
+}
+
+.mobile-structure-title {
+ display: block;
+ margin: 0;
+}
+
+.mobile-structure-label {
+ margin: rem(5) 0 rem(5) 0;
+}
diff --git a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts
index 7e6efd5432..0eb619040e 100644
--- a/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts
+++ b/portal-frontend/src/app/features/applications/application-details/pofo-details/pofo-details.component.ts
@@ -5,6 +5,11 @@ import { ApplicationDocumentService } from '../../../../services/application-doc
import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto';
import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto';
import { openFileInline } from '../../../../shared/utils/file';
+import { MOBILE_BREAKPOINT } from '../../../../shared/utils/breakpoints';
+import {
+ STRUCTURE_TYPE_LABEL_MAP,
+ STRUCTURE_TYPES,
+} from '../../../notice-of-intents/edit-submission/additional-information/additional-information.component';
@Component({
selector: 'app-pofo-details[applicationSubmission]',
@@ -16,11 +21,38 @@ export class PofoDetailsComponent {
@Input() showEdit = true;
@Input() draftMode = false;
+ isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
+
+ isSoilStructureFarmUseReasonVisible = false;
+ isSoilStructureResidentialUseReasonVisible = false;
+ isSoilAgriParcelActivityVisible = false;
+ isSoilStructureResidentialAccessoryUseReasonVisible = false;
+ isSoilOtherStructureVisible = false;
+
_applicationSubmission: ApplicationSubmissionDetailedDto | undefined;
@Input() set applicationSubmission(applicationSubmission: ApplicationSubmissionDetailedDto | undefined) {
if (applicationSubmission) {
this._applicationSubmission = applicationSubmission;
+
+ this.isSoilStructureFarmUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) =>
+ structure.type === STRUCTURE_TYPES.PRINCIPAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ADDITIONAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilAgriParcelActivityVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialAccessoryUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilOtherStructureVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.OTHER_STRUCTURE,
+ );
}
}
@@ -28,11 +60,13 @@ export class PofoDetailsComponent {
this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS);
this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP);
this.reclamationPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.RECLAMATION_PLAN);
+ this.buildingPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.BUILDING_PLAN);
}
crossSections: ApplicationDocumentDto[] = [];
proposalMap: ApplicationDocumentDto[] = [];
reclamationPlans: ApplicationDocumentDto[] = [];
+ buildingPlans: ApplicationDocumentDto[] = [];
constructor(private router: Router, private applicationDocumentService: ApplicationDocumentService) {}
@@ -52,4 +86,12 @@ export class PofoDetailsComponent {
openFileInline(res.url, file.fileName);
}
}
+
+ mapStructureTypeValueToLabel(value: STRUCTURE_TYPES | null): string | null {
+ if (value === null) {
+ return null;
+ }
+
+ return STRUCTURE_TYPE_LABEL_MAP[value];
+ }
}
diff --git a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html
index 083051454b..7917544694 100644
--- a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html
+++ b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.html
@@ -1,4 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Edit Section
diff --git a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.scss b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.scss
index 63cfeaa16b..5d7b57d09e 100644
--- a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.scss
+++ b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.scss
@@ -16,3 +16,41 @@
grid-column-gap: rem(16);
}
}
+
+.structure-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);
+
+ @media screen and (min-width: $tabletBreakpoint) {
+ grid-template-columns: 0.55fr 1fr 1fr;
+ }
+
+ @media screen and (min-width: $midBreakpoint) {
+ grid-template-columns: 1fr 1fr 2fr;
+ }
+}
+
+.mobile-structure-card {
+ width: 100%;
+ box-shadow: none;
+ border: none;
+ border-radius: 0;
+ word-wrap: break-word;
+ white-space: normal;
+}
+
+.mobile-structure-card-border {
+ border-top: rem(1) solid #aaaaaa;
+}
+
+.mobile-structure-title {
+ display: block;
+ margin: 0;
+}
+
+.mobile-structure-label {
+ margin: rem(5) 0 rem(5) 0;
+}
diff --git a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts
index 03ca23a8db..f5507731b8 100644
--- a/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts
+++ b/portal-frontend/src/app/features/applications/application-details/roso-details/roso-details.component.ts
@@ -5,6 +5,11 @@ import { ApplicationDocumentService } from '../../../../services/application-doc
import { ApplicationSubmissionDetailedDto } from '../../../../services/application-submission/application-submission.dto';
import { DOCUMENT_TYPE } from '../../../../shared/dto/document.dto';
import { openFileInline } from '../../../../shared/utils/file';
+import { MOBILE_BREAKPOINT } from '../../../../shared/utils/breakpoints';
+import {
+ STRUCTURE_TYPE_LABEL_MAP,
+ STRUCTURE_TYPES,
+} from '../../../notice-of-intents/edit-submission/additional-information/additional-information.component';
@Component({
selector: 'app-roso-details[applicationSubmission]',
@@ -16,11 +21,38 @@ export class RosoDetailsComponent {
@Input() showEdit = true;
@Input() draftMode = false;
+ isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
+
+ isSoilStructureFarmUseReasonVisible = false;
+ isSoilStructureResidentialUseReasonVisible = false;
+ isSoilAgriParcelActivityVisible = false;
+ isSoilStructureResidentialAccessoryUseReasonVisible = false;
+ isSoilOtherStructureVisible = false;
+
_applicationSubmission: ApplicationSubmissionDetailedDto | undefined;
@Input() set applicationSubmission(applicationSubmission: ApplicationSubmissionDetailedDto | undefined) {
if (applicationSubmission) {
this._applicationSubmission = applicationSubmission;
+
+ this.isSoilStructureFarmUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) =>
+ structure.type === STRUCTURE_TYPES.PRINCIPAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ADDITIONAL_RESIDENCE ||
+ structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilAgriParcelActivityVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.FARM_STRUCTURE,
+ );
+ this.isSoilStructureResidentialAccessoryUseReasonVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.ACCESSORY_STRUCTURE,
+ );
+ this.isSoilOtherStructureVisible = applicationSubmission.soilProposedStructures.some(
+ (structure) => structure.type === STRUCTURE_TYPES.OTHER_STRUCTURE,
+ );
}
}
@@ -28,11 +60,13 @@ export class RosoDetailsComponent {
this.crossSections = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.CROSS_SECTIONS);
this.proposalMap = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.PROPOSAL_MAP);
this.reclamationPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.RECLAMATION_PLAN);
+ this.buildingPlans = documents.filter((document) => document.type?.code === DOCUMENT_TYPE.BUILDING_PLAN);
}
crossSections: ApplicationDocumentDto[] = [];
proposalMap: ApplicationDocumentDto[] = [];
reclamationPlans: ApplicationDocumentDto[] = [];
+ buildingPlans: ApplicationDocumentDto[] = [];
constructor(private router: Router, private applicationDocumentService: ApplicationDocumentService) {}
@@ -52,4 +86,12 @@ export class RosoDetailsComponent {
openFileInline(res.url, file.fileName);
}
}
+
+ mapStructureTypeValueToLabel(value: STRUCTURE_TYPES | null): string | null {
+ if (value === null) {
+ return null;
+ }
+
+ return STRUCTURE_TYPE_LABEL_MAP[value];
+ }
}
diff --git a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts
index 6ffc141e7e..cce01e8d58 100644
--- a/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts
+++ b/portal-frontend/src/app/features/applications/application-details/subd-details/subd-details.component.ts
@@ -43,13 +43,13 @@ export class SubdDetailsComponent {
constructor(
private router: Router,
private applicationDocumentService: ApplicationDocumentService,
- private applicationParcelService: ApplicationParcelService
+ private applicationParcelService: ApplicationParcelService,
) {}
async onEditSection(step: number) {
if (this.draftMode) {
await this.router.navigateByUrl(
- `/alcs/application/${this._applicationSubmission?.fileNumber}/edit/${step}?errors=t`
+ `/alcs/application/${this._applicationSubmission?.fileNumber}/edit/${step}?errors=t`,
);
} else {
await this.router.navigateByUrl(`application/${this._applicationSubmission?.fileNumber}/edit/${step}?errors=t`);
@@ -69,7 +69,7 @@ export class SubdDetailsComponent {
if (parcels) {
this.totalTargetAcres = parcels
.reduce((total, parcel) => total + (parcel.mapAreaHectares ? parseFloat(parcel.mapAreaHectares) : 0), 0)
- .toFixed(2);
+ .toFixed(5);
}
}
}
diff --git a/portal-frontend/src/app/features/applications/edit-submission/proposal/naru-proposal/naru-proposal.component.html b/portal-frontend/src/app/features/applications/edit-submission/proposal/naru-proposal/naru-proposal.component.html
index b9c4b008f7..930fdd121a 100644
--- a/portal-frontend/src/app/features/applications/edit-submission/proposal/naru-proposal/naru-proposal.component.html
+++ b/portal-frontend/src/app/features/applications/edit-submission/proposal/naru-proposal/naru-proposal.component.html
@@ -455,7 +455,7 @@
+
+
-
+ {{ heading }}
+
-
+
+
+
+
+
+
+
+ {{ _application.fileNumber }}
+ (
+ {{ _application.applicant }}
+ )
+
+
+
+
+
+
+
+
+
-
-
- 1">
-
+
+
diff --git a/alcs-frontend/src/app/shared/details-header/details-header.component.scss b/alcs-frontend/src/app/shared/details-header/details-header.component.scss
index 1ac722bcd9..484943bfc4 100644
--- a/alcs-frontend/src/app/shared/details-header/details-header.component.scss
+++ b/alcs-frontend/src/app/shared/details-header/details-header.component.scss
@@ -12,7 +12,7 @@
.header {
padding: 16px 80px;
border-bottom: 1px solid colors.$primary-color-dark;
-
+ display: flex;
.first-row {
display: flex;
align-items: center;
@@ -61,7 +61,6 @@
.status-wrapper {
display: flex;
- flex-direction: row-reverse;
align-items: flex-end;
}
@@ -73,3 +72,26 @@
margin-right: 8px;
}
}
+
+.left-column {
+ div {
+ flex: 1;
+ }
+ flex: 0 0 70%;
+ display: flex;
+ flex-direction: column;
+}
+
+.right-column {
+ & > div {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 15px;
+ }
+ flex: 0 0 30%;
+}
+
+.tag-container {
+ width: 100%;
+}
diff --git a/alcs-frontend/src/app/shared/details-header/details-header.component.ts b/alcs-frontend/src/app/shared/details-header/details-header.component.ts
index c359934215..5b57ed1b85 100644
--- a/alcs-frontend/src/app/shared/details-header/details-header.component.ts
+++ b/alcs-frontend/src/app/shared/details-header/details-header.component.ts
@@ -29,6 +29,7 @@ import { DecisionMeetingService } from '../../services/decision-meeting/decision
import { UpcomingMeetingBoardMapDto } from '../../services/decision-meeting/decision-meeting.dto';
import { IncomingFileService } from '../../services/incoming-file/incoming-file.service';
import { IncomingFileBoardMapDto } from 'src/app/services/incoming-file/incoming-file.dto';
+import { FileTagService } from '../../services/common/file-tag.service';
@Component({
selector: 'app-details-header[application]',
@@ -48,6 +49,7 @@ export class DetailsHeaderComponent implements OnInit, OnDestroy {
@Input() applicationDetailService?: ApplicationDetailService;
@Input() applicationSubmissionService?: ApplicationSubmissionService;
+ @Input() isTagSectionHidden: boolean = true;
legacyId?: string;
@@ -149,6 +151,7 @@ export class DetailsHeaderComponent implements OnInit, OnDestroy {
private boardService: BoardService,
private meetingService: DecisionMeetingService,
private incomingFileService: IncomingFileService,
+ private fileTagService: FileTagService,
) {}
ngOnInit(): void {
diff --git a/alcs-frontend/src/app/shared/inline-editors/inline-textarea-edit/inline-textarea-edit.component.scss b/alcs-frontend/src/app/shared/inline-editors/inline-textarea-edit/inline-textarea-edit.component.scss
index d56cad7913..2e72748af2 100644
--- a/alcs-frontend/src/app/shared/inline-editors/inline-textarea-edit/inline-textarea-edit.component.scss
+++ b/alcs-frontend/src/app/shared/inline-editors/inline-textarea-edit/inline-textarea-edit.component.scss
@@ -9,24 +9,20 @@
font-size: 16px;
width: 100%;
min-height: 96px;
- margin-left: -8px;
- margin-right: -8px;
}
.content {
display: flex;
align-items: stretch;
justify-content: space-between;
- border-radius: 2px;
+ border-radius: 4px;
font-size: 16px;
- border: 2px solid transparent;
- margin-left: -8px;
- margin-right: -8px;
+ border: 1px solid colors.$grey;
min-height: 28px;
}
.text {
- padding: 8px;
+ padding: 16px 16px 18px 8px;
}
.content:hover {
@@ -60,8 +56,8 @@
}
.placeholder {
- font-style: italic;
color: colors.$grey;
+ padding: 8px;
}
.editable:hover {
diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html
index 452d483d38..92024663b0 100644
--- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html
+++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.html
@@ -90,6 +90,7 @@
+
-
+
-
-
-
-
-
-
-
- Local/First Nation Government:
-
- {{ _application.localGovernment?.name }}
-
+
+
+ {{ card.displayName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ {{ _application.localGovernment?.name }}
+
+
-
-
-
-
- {{ days }}:
-
-
None
class="meeting-card" [ngClass]="{ 'meeting-highlighted': meeting.isHighlighted, + 'file-paused': meeting.isPaused, }" >
@@ -118,7 +119,8 @@
-
@@ -173,8 +171,13 @@
-
None
(click)="openMeetings(incomingFile.fileNumber, incomingFile.type)" class="meeting-card" [ngClass]="{ - 'meeting-highlighted': incomingFile.isHighlighted + 'meeting-highlighted': incomingFile.isHighlighted, + 'file-paused': incomingFile.isPaused, }" >
diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss
index 8ff4fed644..d304b2c10b 100644
--- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss
+++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.scss
@@ -316,3 +316,8 @@ mat-panel-description {
.incoming-files-panel {
margin-top: 16px;
}
+
+.file-paused {
+ opacity: 0.8;
+ background-color: colors.$paused-color;
+}
diff --git a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts
index 08b06c4819..3e19b660ba 100644
--- a/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts
+++ b/alcs-frontend/src/app/shared/meeting-overview/meeting-overview.component.spec.ts
@@ -88,6 +88,7 @@ describe('MeetingOverviewComponent', () => {
fileNumber: '',
meetingDate: 0,
type: CardType.APP,
+ isPaused: true,
},
],
});
diff --git a/alcs-frontend/src/app/shared/shared.module.ts b/alcs-frontend/src/app/shared/shared.module.ts
index 901ee5ade3..339b86ec59 100644
--- a/alcs-frontend/src/app/shared/shared.module.ts
+++ b/alcs-frontend/src/app/shared/shared.module.ts
@@ -15,7 +15,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
-import { MatIconModule } from '@angular/material/icon';
+import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -74,6 +74,10 @@ import { ExtensionsDatepickerFormatter } from './utils/extensions-datepicker-for
import { WarningBannerComponent } from './warning-banner/warning-banner.component';
import { DragDropDirective } from './drag-drop-file/drag-drop-file.directive';
import { TruncatePipe } from './pipes/truncate.pipe';
+import { TagsHeaderComponent } from './tags/tags-header/tags-header.component';
+import { MatChipsModule } from '@angular/material/chips';
+import { TagChipComponent } from './tags/tag-chip/tag-chip.component';
+import { DomSanitizer } from '@angular/platform-browser';
@NgModule({
declarations: [
@@ -115,6 +119,8 @@ import { TruncatePipe } from './pipes/truncate.pipe';
InlineButtonToggleComponent,
DragDropDirective,
TruncatePipe,
+ TagsHeaderComponent,
+ TagChipComponent,
],
imports: [
CommonModule,
@@ -140,6 +146,8 @@ import { TruncatePipe } from './pipes/truncate.pipe';
MatDialogModule,
NgSelectModule,
MatSlideToggleModule,
+ MatChipsModule,
+ MatAutocompleteModule,
],
exports: [
CommonModule,
@@ -214,9 +222,17 @@ import { TruncatePipe } from './pipes/truncate.pipe';
InlineButtonToggleComponent,
DragDropDirective,
TruncatePipe,
+ TagsHeaderComponent,
+ TagChipComponent,
],
})
export class SharedModule {
+ constructor(matIconRegistry: MatIconRegistry, domSanitizer: DomSanitizer) {
+ matIconRegistry.addSvgIcon(
+ 'cancel_filled',
+ domSanitizer.bypassSecurityTrustResourceUrl('/assets/icons/cancel_filled.svg'),
+ );
+ }
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
diff --git a/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.html b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.html
new file mode 100644
index 0000000000..aee058b24f
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.html
@@ -0,0 +1,6 @@
+{{ tag.name }}
+
+
+
+
diff --git a/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.scss b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.scss
new file mode 100644
index 0000000000..a7deee2c28
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.scss
@@ -0,0 +1,6 @@
+mat-chip-row {
+ border-radius: 4px;
+ border-color: #929292 !important;
+ border: 1px solid;
+ background-color: #f3f3f3 !important;
+}
diff --git a/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.spec.ts b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.spec.ts
new file mode 100644
index 0000000000..476bb27573
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TagChipComponent } from './tag-chip.component';
+
+describe('TagChipComponent', () => {
+ let component: TagChipComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [TagChipComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(TagChipComponent);
+ component = fixture.componentInstance;
+ component.tag = { uuid: 'tag-uuid', name: 'tag-name', isActive: true };
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.ts b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.ts
new file mode 100644
index 0000000000..6eb2c35a2c
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tag-chip/tag-chip.component.ts
@@ -0,0 +1,16 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { TagDto } from '../../../services/tag/tag.dto';
+
+@Component({
+ selector: 'app-tag-chip',
+ templateUrl: './tag-chip.component.html',
+ styleUrl: './tag-chip.component.scss',
+})
+export class TagChipComponent {
+ @Input() tag!: TagDto;
+ @Output() removeClicked = new EventEmitter();
+
+ onRemove() {
+ this.removeClicked.emit(this.tag);
+ }
+}
diff --git a/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.html b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.html
new file mode 100644
index 0000000000..7b8c753267
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.html
@@ -0,0 +1,33 @@
+();
+ tags: TagDto[] = [];
+ allTags: TagDto[] = [];
+ filteredTags: Observable = of([]);
+ separatorKeysCodes: number[] = [ENTER, COMMA];
+ tagControl = new FormControl();
+ allTagsReceived: boolean = false;
+
+ hovered = false;
+ clicked = false;
+ firstClicked = false;
+ selectClicked = false;
+ selectTyped = false;
+ addTyped = false;
+ showPlaceholder = false;
+
+ @ViewChild('tagInput') tagInput: ElementRef | undefined;
+ @ViewChild(MatAutocompleteTrigger) autoCompleteTrigger!: MatAutocompleteTrigger;
+
+ @Input() application: ApplicationDto | CommissionerApplicationDto | NoticeOfIntentDto | NotificationDto | undefined;
+ @Input() service: FileTagService | undefined;
+ @Input() isHidden: boolean = false;
+
+ constructor(
+ private tagService: TagService,
+ private fileTagService: FileTagService,
+ private confirmationDialogService: ConfirmationDialogService,
+ private toastService: ToastService,
+ private elementRef: ElementRef,
+ ) {}
+
+ ngOnInit(): void {
+ this.filteredTags = this.tagControl.valueChanges.pipe(
+ startWith(''),
+ map((value) => (typeof value === 'string' ? value : value?.name || '')),
+ map((name) => this.filterTags(name || '')),
+ );
+
+ this.fetchTags();
+ this.tagService.$tags.pipe(takeUntil(this.destroy)).subscribe((result: { data: TagDto[]; total: number }) => {
+ this.allTags = result.data.filter((tag) => tag.isActive);
+ this.allTagsReceived = true;
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['application'] && changes['application'].currentValue !== undefined) {
+ this.initFileTags();
+ }
+ }
+
+ onClick(): void {
+ this.clicked = true;
+ if (!this.firstClicked) {
+ this.firstClicked = true;
+ this.tagControl.setValue('');
+ }
+ }
+
+ async fetchTags() {
+ await this.tagService.fetch(0, 0);
+ }
+
+ async initFileTags() {
+ const res = await this.fileTagService.getTags(this.application?.fileNumber!);
+ this.tags = res!;
+ }
+
+ private filterTags(value: string): TagDto[] {
+ const filterValue = value.toLowerCase();
+ return this.allTags.filter(
+ (tag) =>
+ tag.name.toLowerCase().includes(filterValue) && !this.tags.some((selectedTag) => selectedTag.uuid === tag.uuid),
+ );
+ }
+
+ async add(appTagDto: ApplicationTagDto) {
+ const res = await this.fileTagService.addTag(this.application?.fileNumber!, appTagDto);
+ this.tags = res ? res : this.tags;
+ this.tagControl.setValue('');
+ }
+
+ async remove(tagName: string) {
+ const res = await this.fileTagService.deleteTag(this.application?.fileNumber!, tagName);
+ this.tags = res ? res : this.tags;
+ }
+
+ addTag(event: { input: HTMLInputElement; value: string }) {
+ const value = event.value.trim().toLowerCase();
+ const tagToAdd = this.allTags.find((tag) => tag.name.toLowerCase() === value);
+
+ if (tagToAdd) {
+ const appTagDto: ApplicationTagDto = {
+ tagName: tagToAdd.name,
+ };
+
+ this.add(appTagDto);
+ this.addTyped = true;
+ }
+ }
+
+ removeTag(tag: TagDto): void {
+ const index = this.tags.findIndex((t) => t.uuid === tag.uuid);
+
+ if (index >= 0) {
+ const tagName = this.tags[index].name;
+ this.confirmationDialogService
+ .openDialog({
+ body: `Are you sure you want to remove the tag ${tagName} ?`,
+ })
+ .subscribe(async (confirmed) => {
+ if (confirmed) {
+ this.remove(tagName);
+ this.toastService.showSuccessToast(`Tag ${tagName} successfully removed`);
+ }
+ });
+ }
+ }
+
+ selectTag(event: MatAutocompleteSelectedEvent) {
+ const selectedTag = event.option.value as TagDto;
+
+ if (!this.tags.find((tag) => tag.uuid === selectedTag.uuid)) {
+ const appTagDto: ApplicationTagDto = {
+ tagName: selectedTag.name,
+ };
+
+ this.add(appTagDto);
+ this.tagInput!.nativeElement.value = '';
+ if (!this.selectClicked) {
+ this.selectTyped = true;
+ }
+ }
+ }
+
+ @HostListener('document:click', ['$event.target'])
+ public onDocumentClick(targetElement: HTMLElement): void {
+ const clickedInside = this.elementRef.nativeElement.contains(targetElement);
+
+ if (!clickedInside) {
+ if (!this.selectClicked || this.selectTyped || this.addTyped) {
+ this.clicked = false;
+ }
+ }
+
+ if (this.selectClicked) {
+ this.selectClicked = false;
+ }
+
+ if (this.selectTyped) {
+ this.selectTyped = false;
+ }
+
+ if (this.addTyped) {
+ this.addTyped = false;
+ }
+ }
+
+ markClicked() {
+ this.selectClicked = true;
+ }
+}
diff --git a/alcs-frontend/src/assets/icons/cancel_filled.svg b/alcs-frontend/src/assets/icons/cancel_filled.svg
new file mode 100644
index 0000000000..8bd16664de
--- /dev/null
+++ b/alcs-frontend/src/assets/icons/cancel_filled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/alcs-frontend/src/styles/colors.scss b/alcs-frontend/src/styles/colors.scss
index c4bc30bdd2..1a1f07f979 100644
--- a/alcs-frontend/src/styles/colors.scss
+++ b/alcs-frontend/src/styles/colors.scss
@@ -28,3 +28,4 @@ $dark-contrast-text: #000000;
$light-contrast-text: $white;
$pending-star: $grey;
$paused-color: #c3c3c3;
+$field-warning-bg-color: #f8c0a380;
diff --git a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html
index 4042172cd2..630116134e 100644
--- a/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html
+++ b/portal-frontend/src/app/features/applications/application-details/naru-details/naru-details.component.html
@@ -151,9 +151,7 @@
+
+
+
+
+
+
+ {{ tag.name }} : {{ tag.category.name }}
+
+
+
diff --git a/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.scss b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.scss
new file mode 100644
index 0000000000..8b34f703fe
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.scss
@@ -0,0 +1,41 @@
+.tag-container {
+ width: 100%;
+ margin-left: -10px;
+ margin-top: -5px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding-top: -20px;
+
+ &.hovered {
+ border: 1px solid #aaaaaa;
+ }
+
+ &.clicked {
+ border: 1px solid #929292;
+ }
+
+ .tag-field {
+ width: 100%;
+ margin: -10px -5px;
+
+ ::ng-deep .mdc-text-field--outlined.mdc-text-field--focused {
+ .mdc-notched-outline__leading,
+ .mdc-notched-outline__trailing,
+ .mdc-notched-outline__notch {
+ border: none !important;
+ }
+ }
+
+ ::ng-deep .mdc-text-field--outlined {
+ .mdc-notched-outline__leading,
+ .mdc-notched-outline__trailing,
+ .mdc-notched-outline__notch {
+ border: none !important;
+ }
+ }
+ }
+}
+
+.category {
+ color: #a0a0a0;
+}
diff --git a/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.ts b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.ts
new file mode 100644
index 0000000000..8a1c45bfc4
--- /dev/null
+++ b/alcs-frontend/src/app/shared/tags/tags-header/tags-header.component.ts
@@ -0,0 +1,194 @@
+import { COMMA, ENTER } from '@angular/cdk/keycodes';
+import {
+ AfterViewInit,
+ Component,
+ ElementRef,
+ HostListener,
+ Input,
+ OnChanges,
+ SimpleChanges,
+ ViewChild,
+ type OnInit,
+} from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
+import { map, Observable, of, startWith, Subject, takeUntil, tap } from 'rxjs';
+import { TagDto } from '../../../services/tag/tag.dto';
+import { TagService } from '../../../services/tag/tag.service';
+import { ApplicationDto } from '../../../services/application/application.dto';
+import { NoticeOfIntentDto } from '../../../services/notice-of-intent/notice-of-intent.dto';
+import { CommissionerApplicationDto } from '../../../services/commissioner/commissioner.dto';
+import { NotificationDto } from '../../../services/notification/notification.dto';
+import { ApplicationTagDto } from '../../../services/application/application-tag/application-tag.dto';
+import { ConfirmationDialogService } from '../../confirmation-dialog/confirmation-dialog.service';
+import { ToastService } from '../../../services/toast/toast.service';
+import { FileTagService } from '../../../services/common/file-tag.service';
+
+@Component({
+ selector: 'app-tags-header',
+ templateUrl: './tags-header.component.html',
+ styleUrl: './tags-header.component.scss',
+})
+export class TagsHeaderComponent implements OnInit, OnChanges {
+ destroy = new Subject
- {{ i + 1 }}
-
+ #{{ i + 1 }}
- {{ i + 1 }}
-
+ #{{ i + 1 }}
{{ proposedResidence.floorArea }} m2
@@ -217,8 +218,13 @@
Are you removing soil and placing fill in order to build a structure?
+
+
+ {{ _applicationSubmission.soilIsNewStructure ? 'Yes' : 'No' }}
+
+
+
+
Has the ALC previously received an application or Notice of Intent for this proposal?
@@ -193,19 +201,115 @@
- What alternative measures have you considered or attempted before proposing to place fill?
-
-
- {{ _applicationSubmission.soilAlternativeMeasures }}
-
-
+ The total floor area (m2) for each of the proposed structure(s)
+
+
+
+
+ #
+ Type
+ Area
+
+ {{ i + 1 }}
+
+
+ {{ mapStructureTypeValueToLabel(structure.type) }}
+
+
+
+ {{ structure.area }} m2
+
+
+
+
+
- What steps will be taken to reduce impacts to surrounding agricultural land?
-
- {{ _applicationSubmission.soilReduceNegativeImpacts }}
-
-
+ Describe how the structure is necessary for farm use
+
+ {{ _applicationSubmission.soilStructureFarmUseReason }}
+
+
+
+ Describe why placing fill/removing soil is required for the residential structure(s)
+
+
+ {{ _applicationSubmission.soilStructureResidentialUseReason }}
+
+
+ Describe the current or proposed agricultural activity on the parcel(s)
+
+ {{ _applicationSubmission.soilAgriParcelActivity }}
+
+
+
+ Describe the intended use of the residential accessory structure(s) and why placing fill/removing soil is
+ required
+
+
+ {{ _applicationSubmission.soilStructureResidentialAccessoryUseReason }}
+
+
+
+ Describe the intended use of the 'Other' structure(s) and why placing fill/removing soil is required
+
+
+ {{ _applicationSubmission.soilStructureOtherUseReason }}
+
+
+
+ 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
@@ -215,53 +319,67 @@
- Cross Sections
-
-
- {{ file.fileName }}
-
-
-
+ Cross Sections
+
+
+ {{ file.fileName }}
+
+
+
- Reclamation Plan
-
-
- {{ file.fileName }}
-
-
-
+ Reclamation Plan
+
+
+ {{ file.fileName }}
+
+
+
+ Is your proposal for aggregate extraction or placer mining?
-
-
- {{ _applicationSubmission.soilIsExtractionOrMining ? 'Yes' : 'No' }}
-
-
-
+ Detailed Building Plan(s)
+
+
+ {{ file.fileName }}
+
+
+
+
- Have you submitted a Notice of Work to the Ministry of Energy, Mines and Low Carbon Innovation (EMLI)?
-
-
-
- {{ _applicationSubmission.soilHasSubmittedNotice ? 'Yes' : 'No' }}
-
-
-
+ Is your proposal for aggregate extraction or placer mining?
+
+
+ {{ _applicationSubmission.soilIsExtractionOrMining ? 'Yes' : 'No' }}
+
+
+
- Notice of Work
-
-
- {{ file.fileName }}
-
-
-
+
+ Have you submitted a Notice of Work to the Ministry of Energy, Mines and Low Carbon Innovation (EMLI)?
+
+
+
+ {{ _applicationSubmission.soilHasSubmittedNotice ? 'Yes' : 'No' }}
+
+
+
+
+ Notice of Work
+
+
+ {{ file.fileName }}
+
+
+
+
+
- Are you removing soil and placing fill in order to build a structure?
+
+
+ {{ _applicationSubmission.soilIsNewStructure ? 'Yes' : 'No' }}
+
+
+
+
Has the ALC previously received an application or Notice of Intent for this proposal?
@@ -117,19 +125,115 @@
- What alternative measures have you considered or attempted before proposing to place fill?
-
-
- {{ _applicationSubmission.soilAlternativeMeasures }}
-
-
+ The total floor area (m2) for each of the proposed structure(s)
+
+
+
+
+ #
+ Type
+ Area
+
+ {{ i + 1 }}
+
+
+ {{ mapStructureTypeValueToLabel(structure.type) }}
+
+
+
+ {{ structure.area }} m2
+
+
+
+
+
- What steps will be taken to reduce impacts to surrounding agricultural land?
-
- {{ _applicationSubmission.soilReduceNegativeImpacts }}
-
-
+ Describe how the structure is necessary for farm use
+
+ {{ _applicationSubmission.soilStructureFarmUseReason }}
+
+
+
+ Describe why placing fill/removing soil is required for the residential structure(s)
+
+
+ {{ _applicationSubmission.soilStructureResidentialUseReason }}
+
+
+ Describe the current or proposed agricultural activity on the parcel(s)
+
+ {{ _applicationSubmission.soilAgriParcelActivity }}
+
+
+
+ Describe the intended use of the residential accessory structure(s) and why placing fill/removing soil is
+ required
+
+
+ {{ _applicationSubmission.soilStructureResidentialAccessoryUseReason }}
+
+
+
+ Describe the intended use of the 'Other' structure(s) and why placing fill/removing soil is required
+
+
+ {{ _applicationSubmission.soilStructureOtherUseReason }}
+
+
+
+ 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
@@ -139,21 +243,33 @@
- Cross Sections
-
-
- {{ file.fileName }}
-
-
-
+ Cross Sections
+
+
+ {{ file.fileName }}
+
+
+
- Reclamation Plan
-
-
- {{ file.fileName }}
-
-
-
+ Reclamation Plan
+
+
+ {{ file.fileName }}
+
+
+
+ Detailed Building Plan(s)
+
+
+ {{ file.fileName }}
+
+
+
+
+
- Are you removing soil and placing fill in order to build a structure?
+
+
+ {{ _applicationSubmission.soilIsNewStructure ? 'Yes' : 'No' }}
+
+
+
+
Has the ALC previously received an application or Notice of Intent for this proposal?
@@ -117,11 +125,107 @@
What steps will be taken to reduce impacts to surrounding agricultural land?
-
- {{ _applicationSubmission.soilReduceNegativeImpacts }}
-
-
+ The total floor area (m2) for each of the proposed structure(s)
+
+
+
+
+ #
+ Type
+ Area
+
+ {{ i + 1 }}
+
+
+ {{ mapStructureTypeValueToLabel(structure.type) }}
+
+
+
+ {{ structure.area }} m2
+
+
+
+
+
+
+ Describe how the structure is necessary for farm use
+
+ {{ _applicationSubmission.soilStructureFarmUseReason }}
+
+
+
+ Describe why placing fill/removing soil is required for the residential structure(s)
+
+
+ {{ _applicationSubmission.soilStructureResidentialUseReason }}
+
+
+ Describe the current or proposed agricultural activity on the parcel(s)
+
+ {{ _applicationSubmission.soilAgriParcelActivity }}
+
+
+
+ Describe the intended use of the residential accessory structure(s) and why placing fill/removing soil is
+ required
+
+
+ {{ _applicationSubmission.soilStructureResidentialAccessoryUseReason }}
+
+
+
+ Describe the intended use of the 'Other' structure(s) and why placing fill/removing soil is required
+
+
+ {{ _applicationSubmission.soilStructureOtherUseReason }}
+
+
+ What steps will be taken to reduce impacts to surrounding agricultural land?
+
+ {{ _applicationSubmission.soilReduceNegativeImpacts }}
+
+
+ Proposal Map / Site Plan
@@ -131,21 +235,33 @@
- Cross Sections
-
-
- {{ file.fileName }}
-
-
-
+ Cross Sections
+
+
+ {{ file.fileName }}
+
+
+
- Reclamation Plan
-
-
- {{ file.fileName }}
-
-
-
+ Reclamation Plan
+
+
+ {{ file.fileName }}
+
+
+
+ Detailed Building Plan(s)
+
+
+ {{ file.fileName }}
+
+
+
+ Proposal
Existing Residence | -{{ i + 1 }} | +#{{ i + 1 }} |
---|
Proposed Residence | -{{ i + 1 }} | +#{{ i + 1 }} |
---|