Skip to content

Commit

Permalink
Merge pull request #1510 from bcgov/feature/ALCS-1423
Browse files Browse the repository at this point in the history
Add Planning Review Decisions
  • Loading branch information
dhaselhan authored Mar 14, 2024
2 parents d2c840a + 9befe33 commit 23f5ec5
Show file tree
Hide file tree
Showing 52 changed files with 3,861 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ <h6 class="card-type-label">Planning Review</h6>
<div class="left">
<h3 class="card-title center">
<span class="margin-right">{{ cardTitle }}</span>
<app-application-legacy-id *ngIf="planningReview.legacyId" [legacyId]="planningReview.legacyId"></app-application-legacy-id>
<app-application-legacy-id
*ngIf="planningReview.legacyId"
[legacyId]="planningReview.legacyId"
></app-application-legacy-id>
<app-application-type-pill *ngIf="planningType" [type]="planningType"></app-application-type-pill>
</h3>
</div>
Expand All @@ -31,7 +34,7 @@ <h3 class="card-title center">
<div class="body-text">
<app-application-type-pill *ngIf="planningReview.open" [type]="OPEN_TYPE"></app-application-type-pill>
<app-application-type-pill *ngIf="!planningReview.open" [type]="CLOSED_TYPE"></app-application-type-pill>
<span>Due Date: {{ planningReferral.dueDate | momentFormat }}</span>
<span *ngIf="planningReferral.dueDate">Due: {{ planningReferral.dueDate | momentFormat }}</span>
</div>
<div class="right">
<button matTooltip="Move Board" [matMenuTriggerFor]="moveMenu" mat-icon-button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<div *ngIf="editable">
<div class="split">
<h3>Documents</h3>
<div>
<button
type="button"
[ngClass]="{ 'upload-button': true, 'error-field-outlined ng-invalid': showError }"
(click)="onUploadFile()"
mat-flat-button
color="primary"
>
Add Document
</button>
<app-error-message *ngIf="showError"></app-error-message>
</div>
</div>
</div>
<table mat-table [dataSource]="dataSource" matSort class="documents">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by type">Type</th>
<td mat-cell *matCellDef="let element" matTooltip="Decision Package">DECPACK</td>
</ng-container>

<ng-container matColumnDef="fileName">
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by name">Document Name</th>
<td mat-cell *matCellDef="let element">
<a (click)="openFile(element.uuid, element.fileName)">{{ element.fileName }}</a>
</td>
</ng-container>

<ng-container matColumnDef="source">
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by source">Source</th>
<td mat-cell *matCellDef="let element">ALC</td>
</ng-container>

<ng-container matColumnDef="visibilityFlags">
<th mat-header-cell *matHeaderCellDef>
Visibility
<div class="subheading">* = Pending</div>
</th>
<td mat-cell *matCellDef="let element">
<span matTooltip="Commissioner">C<span *ngIf="!areDocumentsReleased">*</span></span>
</td>
</ng-container>

<ng-container matColumnDef="uploadedAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by date">Upload Date</th>
<td mat-cell *matCellDef="let element">{{ element.uploadedAt | momentFormat }}</td>
</ng-container>

<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>File Actions</th>
<td mat-cell *matCellDef="let element">
<button type="button" mat-icon-button (click)="downloadFile(element.uuid, element.fileName)">
<mat-icon>file_download</mat-icon>
</button>
<button type="button" *ngIf="editable" mat-icon-button (click)="onEditFile(element)">
<mat-icon>edit</mat-icon>
</button>
<button type="button" *ngIf="editable" mat-icon-button (click)="onDeleteFile(element)">
<mat-icon color="warn">delete</mat-icon>
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="text-center" colspan="6">No Documents</td>
</tr>
</table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@use '../../../../../styles/colors';

.documents {
margin-top: 12px;
border-collapse: collapse;
}

.upload-button {
width: 100%;
margin: 4px 0 12px !important;
}

.mat-mdc-no-data-row {
height: 56px;
color: colors.$grey-dark;
}

a {
word-break: break-all;
}

table {
box-shadow: none;

th {
font-weight: 700;
padding-bottom: 16px;
position: relative;

.subheading {
font-size: 11px;
line-height: 16px;
font-weight: 400;
position: absolute;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material/dialog';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { BehaviorSubject } from 'rxjs';
import { PlanningReviewDecisionDto } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.dto';
import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service';
import { ToastService } from '../../../../services/toast/toast.service';

import { DecisionDocumentsComponent } from './decision-documents.component';

describe('DecisionDocumentsComponent', () => {
let component: DecisionDocumentsComponent;
let fixture: ComponentFixture<DecisionDocumentsComponent>;
let mockPRDecService: DeepMocked<PlanningReviewDecisionService>;
let mockDialog: DeepMocked<MatDialog>;
let mockToastService: DeepMocked<ToastService>;

beforeEach(async () => {
mockPRDecService = createMock();
mockDialog = createMock();
mockToastService = createMock();
mockPRDecService.$decision = new BehaviorSubject<PlanningReviewDecisionDto | undefined>(undefined);

await TestBed.configureTestingModule({
declarations: [DecisionDocumentsComponent],
providers: [
{
provide: PlanningReviewDecisionService,
useValue: mockPRDecService,
},
{
provide: MatDialog,
useValue: mockDialog,
},
{
provide: ToastService,
useValue: mockToastService,
},
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(DecisionDocumentsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Subject } from 'rxjs';
import {
PlanningReviewDecisionDocumentDto,
PlanningReviewDecisionDto,
} from '../../../../services/planning-review/planning-review-decision/planning-review-decision.dto';
import { PlanningReviewDecisionService } from '../../../../services/planning-review/planning-review-decision/planning-review-decision.service';
import { ToastService } from '../../../../services/toast/toast.service';
import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service';
import { DecisionDocumentUploadDialogComponent } from '../decision-input/decision-file-upload-dialog/decision-document-upload-dialog.component';

@Component({
selector: 'app-decision-documents',
templateUrl: './decision-documents.component.html',
styleUrls: ['./decision-documents.component.scss'],
})
export class DecisionDocumentsComponent implements OnDestroy, OnChanges {
$destroy = new Subject<void>();

@Input() editable = true;
@Input() loadData = true;
@Input() decision: PlanningReviewDecisionDto | undefined;
@Input() showError = false;
@Output() beforeDocumentUpload = new EventEmitter<boolean>();

displayedColumns: string[] = ['type', 'fileName', 'source', 'visibilityFlags', 'uploadedAt', 'actions'];
private fileId = '';
areDocumentsReleased = false;

@ViewChild(MatSort) sort!: MatSort;
dataSource: MatTableDataSource<PlanningReviewDecisionDocumentDto> =
new MatTableDataSource<PlanningReviewDecisionDocumentDto>();

constructor(
private decisionService: PlanningReviewDecisionService,
private dialog: MatDialog,
private toastService: ToastService,
private confirmationDialogService: ConfirmationDialogService,
) {}

async openFile(fileUuid: string, fileName: string) {
if (this.decision) {
await this.decisionService.downloadFile(this.decision.uuid, fileUuid, fileName);
}
}

async downloadFile(fileUuid: string, fileName: string) {
if (this.decision) {
await this.decisionService.downloadFile(this.decision.uuid, fileUuid, fileName, false);
}
}

async onUploadFile() {
this.beforeDocumentUpload.emit();
this.openFileDialog();
}

onEditFile(element: PlanningReviewDecisionDocumentDto) {
this.openFileDialog(element);
}

private openFileDialog(existingDocument?: PlanningReviewDecisionDocumentDto) {
if (this.decision) {
this.dialog
.open(DecisionDocumentUploadDialogComponent, {
minWidth: '600px',
maxWidth: '800px',
width: '70%',
data: {
fileId: this.fileId,
decisionUuid: this.decision?.uuid,
existingDocument: existingDocument,
},
})
.beforeClosed()
.subscribe((isDirty: boolean) => {
if (isDirty && this.decision) {
this.decisionService.loadDecision(this.decision.uuid);
}
});
}
}

onDeleteFile(element: PlanningReviewDecisionDocumentDto) {
this.confirmationDialogService
.openDialog({
body: 'Are you sure you want to delete the selected file?',
})
.subscribe(async (accepted) => {
if (accepted && this.decision) {
await this.decisionService.deleteFile(this.decision.uuid, element.uuid);
await this.decisionService.loadDecision(this.decision.uuid);
this.toastService.showSuccessToast('Document deleted');
}
});
}

ngOnDestroy(): void {
this.$destroy.next();
this.$destroy.complete();
}

ngOnChanges(changes: SimpleChanges): void {
this.areDocumentsReleased = true;
if (this.decision) {
this.dataSource = new MatTableDataSource(this.decision.documents);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<div mat-dialog-title>
<h4>{{ title }} Document</h4>
</div>
<div mat-dialog-content>
<form class="form" [formGroup]="form">
<div class="double">
<div>
<mat-label>Document Upload*</mat-label>
</div>
<input hidden type="file" #fileInput (change)="uploadFile($event)" placeholder="Upload file" />
<button
*ngIf="!pendingFile && !existingFile"
class="full-width upload-button"
mat-flat-button
color="accent"
[ngClass]="{
error: showVirusError
}"
(click)="fileInput.click()"
>
Upload
</button>
<div class="file" *ngIf="pendingFile">
<div>
<a (click)="openFile()">{{ pendingFile.name }}</a>
&nbsp;({{ pendingFile.size | filesize }})
</div>
<button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button>
<mat-icon>close</mat-icon>Remove
</button>
</div>
<div class="file" *ngIf="existingFile">
<div>
<a (click)="openExistingFile()">{{ existingFile }}</a>
</div>
<button [disabled]="!allowsFileEdit" (click)="onRemoveFile()" mat-button>
<mat-icon>close</mat-icon>Remove
</button>
</div>
<mat-error class="left" style="display: flex" *ngIf="showVirusError">
<mat-icon>warning</mat-icon>&nbsp;A virus was detected in the file. Choose another file and try again.
</mat-error>
</div>

<div class="double">
<mat-form-field class="full-width" appearance="outline">
<mat-label>Document Name</mat-label>
<input required matInput id="name" [formControl]="name" name="name" />
</mat-form-field>
</div>

<div>
<ng-select
appearance="outline"
required
placeholder="Document Type*"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="documentType"
disabled="true"
>
</ng-select>
</div>
<div>
<mat-form-field class="full-width" appearance="outline">
<mat-label>Source</mat-label>
<mat-select [formControl]="source">
<mat-option *ngFor="let source of documentSources" [value]="source">{{ source }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="double">
<mat-label>Visible To:</mat-label>
<div>
<mat-checkbox [formControl]="visibleToComissioner">Commissioner</mat-checkbox>
</div>
</div>
</form>

<mat-dialog-actions align="end">
<div class="button-container">
<button type="button" mat-stroked-button color="primary" [mat-dialog-close]="false">Close</button>
<button
*ngIf="!isSaving"
type="button"
mat-flat-button
color="primary"
[disabled]="!form.valid || (!pendingFile && !existingFile)"
(click)="onSubmit()"
>
Save
</button>
<button *ngIf="isSaving" type="button" mat-flat-button color="primary" [disabled]="true">
<mat-spinner class="spinner" diameter="20"></mat-spinner>
Uploading
</button>
</div>
</mat-dialog-actions>
</div>
Loading

0 comments on commit 23f5ec5

Please sign in to comment.