Skip to content

Commit d59cf56

Browse files
authored
Merge pull request #2463 from bcgov/2376-add-complaintreferral-document-to-intake-form
Add submission doc to C&E draft
2 parents 5dfeffb + a3d1188 commit d59cf56

26 files changed

+1020
-38
lines changed

alcs-frontend/src/app/features/compliance-and-enforcement/compliance-and-enforcement.module.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MatMomentDateModule } from '@angular/material-moment-adapter';
66
import { DraftComponent } from './draft/draft.component';
77
import { SubmitterComponent } from './submitter/submitter.component';
88
import { PropertyComponent } from './property/property.component';
9+
import { ComplianceAndEnforcementDocumentsComponent } from './documents/documents.component';
910

1011
const routes: Routes = [
1112
{
@@ -15,7 +16,13 @@ const routes: Routes = [
1516
];
1617

1718
@NgModule({
18-
declarations: [DraftComponent, OverviewComponent, SubmitterComponent, PropertyComponent],
19+
declarations: [
20+
DraftComponent,
21+
OverviewComponent,
22+
SubmitterComponent,
23+
PropertyComponent,
24+
ComplianceAndEnforcementDocumentsComponent,
25+
],
1926
imports: [SharedModule.forRoot(), RouterModule.forChild(routes), MatMomentDateModule],
2027
})
2128
export class ComplianceAndEnforcementModule {}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<div class="header">
2+
<h3>{{ title }}</h3>
3+
<button (click)="openUploadDialog()" mat-flat-button color="primary">+ Add Document</button>
4+
</div>
5+
6+
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z3 documents full-width">
7+
<ng-container matColumnDef="source">
8+
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by source">Source</th>
9+
<td mat-cell *matCellDef="let document">{{ document.source }}</td>
10+
</ng-container>
11+
12+
<ng-container matColumnDef="type">
13+
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by type">Type</th>
14+
<td mat-cell *matCellDef="let document" [matTooltip]="document.type.label">
15+
{{ document.type.oatsCode }}
16+
</td>
17+
</ng-container>
18+
19+
<ng-container matColumnDef="fileName">
20+
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by name">Document Name</th>
21+
<td mat-cell *matCellDef="let document">
22+
<a
23+
routerLink="/document/{{ document.documentUuid }}"
24+
target="_blank"
25+
[matTooltip]="document.fileName"
26+
[matTooltipDisabled]="document.fileName.length <= fileNameTruncLen"
27+
>
28+
{{ document.fileName }}
29+
</a>
30+
</td>
31+
</ng-container>
32+
33+
<ng-container matColumnDef="uploadedAt">
34+
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by date">Upload Date</th>
35+
<td mat-cell *matCellDef="let document">{{ document.uploadedAt | date }}</td>
36+
</ng-container>
37+
38+
<ng-container matColumnDef="actions">
39+
<th mat-header-cell *matHeaderCellDef class="actions-column">Actions</th>
40+
<td mat-cell *matCellDef="let document">
41+
<button class="action-button" mat-icon-button (click)="openEditDialog(document)">
42+
<mat-icon>edit</mat-icon>
43+
</button>
44+
<button class="action-button" mat-icon-button (click)="deleteFile(document)">
45+
<mat-icon color="warn">delete</mat-icon>
46+
</button>
47+
</td>
48+
</ng-container>
49+
50+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
51+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
52+
<tr class="mat-row" *matNoDataRow>
53+
<td class="text-center" colspan="6">No Complaint/Referral</td>
54+
</tr>
55+
</table>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@use '../../../../styles/colors.scss' as colors;
2+
3+
.header {
4+
display: flex;
5+
justify-content: space-between;
6+
margin-bottom: 24px;
7+
}
8+
9+
.documents {
10+
box-shadow: none;
11+
}
12+
13+
.text-center {
14+
padding-top: 10px;
15+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
2+
import { Subject } from 'rxjs';
3+
import { ComplianceAndEnforcementDocumentDto } from '../../../services/compliance-and-enforcement/documents/document.dto';
4+
import { MatSort } from '@angular/material/sort';
5+
import { MatTableDataSource } from '@angular/material/table';
6+
import { FILE_NAME_TRUNCATE_LENGTH } from '../../../shared/constants';
7+
import { DOCUMENT_SYSTEM } from '../../../shared/document/document.dto';
8+
import {
9+
DocumentUploadDialogData,
10+
DocumentUploadDialogOptions,
11+
} from '../../../shared/document-upload-dialog/document-upload-dialog.interface';
12+
import { DocumentUploadDialogComponent } from '../../../shared/document-upload-dialog/document-upload-dialog.component';
13+
import { MatDialog } from '@angular/material/dialog';
14+
import {
15+
ComplianceAndEnforcementDocumentService,
16+
Section,
17+
} from '../../../services/compliance-and-enforcement/documents/document.service';
18+
import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service';
19+
import { ToastService } from '../../../services/toast/toast.service';
20+
21+
@Component({
22+
selector: 'app-compliance-and-enforcement-documents',
23+
templateUrl: './documents.component.html',
24+
styleUrls: ['./documents.component.scss'],
25+
})
26+
export class ComplianceAndEnforcementDocumentsComponent implements OnInit, OnDestroy {
27+
$destroy = new Subject<void>();
28+
29+
isPatching = false;
30+
isSubscribed = false;
31+
32+
@Input() title?: string;
33+
@Input() fileNumber?: string;
34+
@Input() options?: DocumentUploadDialogOptions;
35+
@Input() section?: Section;
36+
37+
displayedColumns: string[] = ['source', 'type', 'fileName', 'uploadedAt', 'actions'];
38+
39+
@ViewChild(MatSort) sort!: MatSort;
40+
dataSource: MatTableDataSource<ComplianceAndEnforcementDocumentDto> =
41+
new MatTableDataSource<ComplianceAndEnforcementDocumentDto>();
42+
43+
readonly fileNameTruncLen = FILE_NAME_TRUNCATE_LENGTH;
44+
readonly documentSystem = DOCUMENT_SYSTEM;
45+
46+
constructor(
47+
public dialog: MatDialog,
48+
private readonly documentService: ComplianceAndEnforcementDocumentService,
49+
private confirmationDialogService: ConfirmationDialogService,
50+
private toastService: ToastService,
51+
) {}
52+
53+
ngOnInit() {
54+
this.loadDocuments();
55+
}
56+
57+
openEditDialog(document: ComplianceAndEnforcementDocumentDto) {
58+
if (!this.fileNumber) {
59+
console.error('File number is required to open the upload dialog.');
60+
return;
61+
}
62+
63+
const data: DocumentUploadDialogData = {
64+
...this.options,
65+
...{
66+
fileId: this.fileNumber,
67+
existingDocument: document,
68+
documentService: this.documentService,
69+
section: this.section,
70+
},
71+
};
72+
73+
this.dialog
74+
.open(DocumentUploadDialogComponent, {
75+
minWidth: '600px',
76+
maxWidth: '800px',
77+
width: '70%',
78+
data,
79+
})
80+
.afterClosed()
81+
.subscribe((isDirty) => {
82+
if (isDirty) {
83+
this.loadDocuments();
84+
}
85+
});
86+
}
87+
88+
deleteFile(document: ComplianceAndEnforcementDocumentDto) {
89+
this.confirmationDialogService
90+
.openDialog({
91+
body: `Are you sure you want to delete ${document.fileName}?`,
92+
})
93+
.subscribe(async (accepted) => {
94+
if (accepted) {
95+
await this.documentService.delete(document.uuid);
96+
97+
this.toastService.showSuccessToast('Document deleted');
98+
99+
this.loadDocuments();
100+
}
101+
});
102+
}
103+
104+
openUploadDialog() {
105+
if (!this.fileNumber) {
106+
console.error('File number is required to open the upload dialog.');
107+
return;
108+
}
109+
110+
const data: DocumentUploadDialogData = {
111+
...this.options,
112+
...{
113+
documentService: this.documentService,
114+
fileId: this.fileNumber,
115+
section: this.section,
116+
},
117+
};
118+
119+
this.dialog
120+
.open(DocumentUploadDialogComponent, {
121+
minWidth: '600px',
122+
maxWidth: '800px',
123+
width: '70%',
124+
data,
125+
})
126+
.afterClosed()
127+
.subscribe((isDirty) => {
128+
if (isDirty) {
129+
this.loadDocuments();
130+
}
131+
});
132+
}
133+
134+
async loadDocuments() {
135+
if (this.fileNumber) {
136+
const documents = await this.documentService.list(this.fileNumber, this.section);
137+
this.dataSource.data = documents;
138+
}
139+
}
140+
141+
ngOnDestroy(): void {
142+
this.$destroy.next();
143+
this.$destroy.complete();
144+
}
145+
}

alcs-frontend/src/app/features/compliance-and-enforcement/draft/draft.component.html

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ <h2>C&E File ID: {{ file?.fileNumber }}</h2>
1010
[submitter]="submitter"
1111
[initialSubmissionType]="initialSubmissionType"
1212
/>
13+
14+
<app-compliance-and-enforcement-documents
15+
#submissionDocumentsComponent
16+
[title]="'Complaint/Referral Submission'"
17+
[fileNumber]="fileNumber"
18+
[options]="submissionDocumentOptions"
19+
[section]="Section.SUBMISSION"
20+
/>
1321
</section>
1422

1523
<section class="form-section">
16-
<app-compliance-and-enforcement-property
17-
#propertyComponent
18-
[parentForm]="form"
19-
[property]="property"
20-
/>
24+
<app-compliance-and-enforcement-property #propertyComponent [parentForm]="form" [property]="property" />
2125
</section>
2226

2327
<div class="button-container">

alcs-frontend/src/app/features/compliance-and-enforcement/draft/draft.component.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,34 @@ import { SubmitterComponent } from '../submitter/submitter.component';
1313
import { ComplianceAndEnforcementSubmitterDto } from '../../../services/compliance-and-enforcement/submitter/submitter.dto';
1414
import { ComplianceAndEnforcementSubmitterService } from '../../../services/compliance-and-enforcement/submitter/submitter.service';
1515
import { PropertyComponent, cleanPropertyUpdate } from '../property/property.component';
16-
import { ComplianceAndEnforcementPropertyDto, UpdateComplianceAndEnforcementPropertyDto } from '../../../services/compliance-and-enforcement/property/property.dto';
16+
import { ComplianceAndEnforcementPropertyDto } from '../../../services/compliance-and-enforcement/property/property.dto';
1717
import { ComplianceAndEnforcementPropertyService } from '../../../services/compliance-and-enforcement/property/property.service';
18+
import { DOCUMENT_SOURCE, DOCUMENT_TYPE } from '../../../shared/document/document.dto';
19+
import { DocumentUploadDialogOptions } from '../../../shared/document-upload-dialog/document-upload-dialog.interface';
20+
import { Section } from '../../../services/compliance-and-enforcement/documents/document.service';
1821

1922
@Component({
2023
selector: 'app-compliance-and-enforcement-draft',
2124
templateUrl: './draft.component.html',
2225
styleUrls: ['./draft.component.scss'],
2326
})
2427
export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
28+
Section = Section;
29+
30+
submissionDocumentOptions: DocumentUploadDialogOptions = {
31+
allowedVisibilityFlags: [],
32+
allowsFileEdit: true,
33+
allowedDocumentSources: [
34+
DOCUMENT_SOURCE.COMPLAINANT,
35+
DOCUMENT_SOURCE.PUBLIC,
36+
DOCUMENT_SOURCE.LFNG,
37+
DOCUMENT_SOURCE.BC_GOVERNMENT,
38+
DOCUMENT_SOURCE.OTHER_AGENCY,
39+
DOCUMENT_SOURCE.ALC,
40+
],
41+
allowedDocumentTypes: [DOCUMENT_TYPE.COMPLAINT, DOCUMENT_TYPE.REFERRAL],
42+
};
43+
2544
$destroy = new Subject<void>();
2645

2746
fileNumber?: string;
@@ -112,8 +131,8 @@ export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
112131
debounceTime(1000),
113132
switchMap((property) => {
114133
// Only auto-save if there are meaningful changes (non-empty fields)
115-
const hasActualData = Object.values(cleanPropertyUpdate(property)).some(value =>
116-
value !== null && value !== undefined && value !== '' && value !== 0
134+
const hasActualData = Object.values(cleanPropertyUpdate(property)).some(
135+
(value) => value !== null && value !== undefined && value !== '' && value !== 0,
117136
);
118137

119138
if (!hasActualData) {
@@ -123,9 +142,9 @@ export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
123142
if (this.property?.uuid) {
124143
return this.complianceAndEnforcementPropertyService.update(this.property.uuid, property);
125144
} else if (this.file?.uuid) {
126-
return this.complianceAndEnforcementPropertyService.create({
145+
return this.complianceAndEnforcementPropertyService.create({
127146
fileUuid: this.file.uuid,
128-
...cleanPropertyUpdate(property)
147+
...cleanPropertyUpdate(property),
129148
});
130149
} else {
131150
return EMPTY;
@@ -153,7 +172,7 @@ export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
153172
this.file = await this.complianceAndEnforcementService.fetchByFileNumber(fileNumber, true);
154173
this.submitter = this.file.submitters[0];
155174
this.initialSubmissionType = this.file.initialSubmissionType ?? undefined;
156-
175+
157176
// Load property data
158177
if (this.file.uuid) {
159178
try {
@@ -187,7 +206,7 @@ export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
187206

188207
try {
189208
await firstValueFrom(this.complianceAndEnforcementService.update(this.file.uuid, overviewUpdate));
190-
209+
191210
if (this.submitter?.uuid) {
192211
await firstValueFrom(
193212
this.complianceAndEnforcementSubmitterService.update(this.submitter.uuid, submitterUpdate),
@@ -197,14 +216,12 @@ export class DraftComponent implements OnInit, AfterViewInit, OnDestroy {
197216
}
198217

199218
if (this.property?.uuid) {
200-
await firstValueFrom(
201-
this.complianceAndEnforcementPropertyService.update(this.property.uuid, propertyUpdate),
202-
);
219+
await firstValueFrom(this.complianceAndEnforcementPropertyService.update(this.property.uuid, propertyUpdate));
203220
} else {
204221
this.property = await firstValueFrom(
205222
this.complianceAndEnforcementPropertyService.create({
206223
fileUuid: this.file.uuid,
207-
...cleanPropertyUpdate(propertyUpdate)
224+
...cleanPropertyUpdate(propertyUpdate),
208225
}),
209226
);
210227
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { DOCUMENT_SOURCE, DOCUMENT_SYSTEM, DocumentTypeDto } from '../../../shared/document/document.dto';
2+
import { Section } from './document.service';
3+
4+
export interface ComplianceAndEnforcementDocumentDto {
5+
uuid: string;
6+
type: DocumentTypeDto;
7+
8+
documentUuid: string;
9+
source: DOCUMENT_SOURCE;
10+
system: DOCUMENT_SYSTEM;
11+
fileName: string;
12+
mimeType: string;
13+
uploadedAt: number;
14+
fileSize?: number;
15+
}
16+
17+
export interface UpdateComplianceAndEnforcementDocumentDto {
18+
typeCode?: string;
19+
20+
source?: DOCUMENT_SOURCE;
21+
fileName?: string;
22+
section?: Section;
23+
}
24+
25+
export interface CreateComplianceAndEnforcementDocumentDto extends UpdateComplianceAndEnforcementDocumentDto {
26+
file: File;
27+
}

0 commit comments

Comments
 (0)