Skip to content

Commit 7b9f38f

Browse files
authored
Merge pull request #2486 from bcgov/2415-create-header-and-overview-tab-from-submission
Create C&E header and overview tab from submission
2 parents a069236 + 005f930 commit 7b9f38f

28 files changed

+813
-35
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<div class="layout">
2+
<div class="file">
3+
<app-compliance-and-enforcement-details-header [file]="file" />
4+
<div class="content">
5+
<div class="nav">
6+
<div *ngFor="let route of detailsRoutes" class="nav-link" data-testid="details-nav-link">
7+
<div
8+
[routerLinkActiveOptions]="{ exact: route.path === '' }"
9+
[routerLink]="route.path ? route.path : './'"
10+
class="nav-item nav-text"
11+
routerLinkActive="active"
12+
>
13+
<mat-icon>{{ route.icon }}</mat-icon>
14+
{{ route.menuTitle }}
15+
</div>
16+
</div>
17+
</div>
18+
<div cdkScrollable class="child-content">
19+
<router-outlet></router-outlet>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
@use '../../../styles/colors';
2+
3+
.layout {
4+
display: flex;
5+
flex-direction: row;
6+
height: 100%;
7+
width: 100%;
8+
justify-content: center;
9+
}
10+
11+
.file {
12+
width: 100%;
13+
display: flex;
14+
flex-direction: column;
15+
}
16+
17+
.content {
18+
display: flex;
19+
flex-grow: 1;
20+
}
21+
22+
.child-content {
23+
padding: 24px 80px 20px 48px;
24+
flex-grow: 1;
25+
height: calc(100vh - 246px);
26+
overflow-y: auto;
27+
}
28+
29+
.nav {
30+
background-color: colors.$bg-color;
31+
height: 100%;
32+
min-width: fit-content;
33+
}
34+
35+
.nav-link {
36+
div {
37+
padding: 12px 24px;
38+
border: 2px solid transparent;
39+
}
40+
41+
div.active {
42+
font-weight: bold;
43+
background-color: colors.$primary-color-dark;
44+
color: colors.$white;
45+
border-color: colors.$primary-color-dark;
46+
47+
&:hover {
48+
cursor: default;
49+
background-color: colors.$primary-color-dark;
50+
color: colors.$white;
51+
}
52+
}
53+
54+
div:not(.disabled):hover {
55+
cursor: pointer;
56+
border-color: colors.$primary-color-dark;
57+
color: colors.$dark-contrast-text;
58+
}
59+
60+
div.active:not(.disabled):hover {
61+
color: colors.$white;
62+
}
63+
}
64+
65+
.nav-item {
66+
display: flex;
67+
align-items: center;
68+
white-space: pre-wrap;
69+
70+
.mat-icon {
71+
padding-right: 32px;
72+
}
73+
74+
&.disabled {
75+
opacity: 0.5;
76+
}
77+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ComplianceAndEnforcementComponent } from './compliance-and-enforcement.component';
2+
import { ComplianceAndEnforcementService } from '../../services/compliance-and-enforcement/compliance-and-enforcement.service';
3+
import { ActivatedRoute } from '@angular/router';
4+
import { createMock, DeepMocked } from '@golevelup/ts-jest';
5+
import { ComponentFixture, TestBed } from '@angular/core/testing';
6+
7+
describe('ComplianceAndEnforcementComponent', () => {
8+
let component: ComplianceAndEnforcementComponent;
9+
let fixture: ComponentFixture<ComplianceAndEnforcementComponent>;
10+
let mockService: DeepMocked<ComplianceAndEnforcementService>;
11+
let mockRoute: DeepMocked<ActivatedRoute>;
12+
13+
beforeEach(async () => {
14+
mockService = createMock<ComplianceAndEnforcementService>();
15+
mockRoute = createMock<ActivatedRoute>();
16+
17+
await TestBed.configureTestingModule({
18+
imports: [],
19+
declarations: [ComplianceAndEnforcementComponent],
20+
providers: [
21+
{
22+
provide: ComplianceAndEnforcementService,
23+
useValue: mockService,
24+
},
25+
{
26+
provide: ActivatedRoute,
27+
useValue: mockRoute,
28+
},
29+
],
30+
});
31+
32+
fixture = TestBed.createComponent(ComplianceAndEnforcementComponent);
33+
component = fixture.componentInstance;
34+
fixture.detectChanges();
35+
});
36+
37+
it('should create', () => {
38+
expect(component).toBeTruthy();
39+
});
40+
41+
it('should call service.loadFile with correct arguments in loadFile()', async () => {
42+
await component.loadFile('456');
43+
expect(mockService.loadFile).toHaveBeenCalledWith('456', { withProperty: true });
44+
});
45+
46+
it('should complete destroy subject on ngOnDestroy', () => {
47+
const destroyNext = jest.spyOn(component.$destroy, 'next');
48+
const destroyComplete = jest.spyOn(component.$destroy, 'complete');
49+
component.ngOnDestroy();
50+
expect(destroyNext).toHaveBeenCalled();
51+
expect(destroyComplete).toHaveBeenCalled();
52+
});
53+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Component, OnDestroy, OnInit } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { Subject, takeUntil } from 'rxjs';
4+
import { detailsRoutes } from './compliance-and-enforcement.module';
5+
import { ComplianceAndEnforcementDto } from '../../services/compliance-and-enforcement/compliance-and-enforcement.dto';
6+
import { ComplianceAndEnforcementService } from '../../services/compliance-and-enforcement/compliance-and-enforcement.service';
7+
import { ToastService } from '../../services/toast/toast.service';
8+
9+
@Component({
10+
selector: 'app-compliance-and-enforcement',
11+
templateUrl: './compliance-and-enforcement.component.html',
12+
styleUrls: ['./compliance-and-enforcement.component.scss'],
13+
})
14+
export class ComplianceAndEnforcementComponent implements OnInit, OnDestroy {
15+
$destroy = new Subject<void>();
16+
17+
detailsRoutes = detailsRoutes;
18+
19+
fileNumber?: string;
20+
file?: ComplianceAndEnforcementDto;
21+
22+
constructor(
23+
private readonly route: ActivatedRoute,
24+
private readonly service: ComplianceAndEnforcementService,
25+
private readonly toastService: ToastService,
26+
) {}
27+
28+
ngOnInit(): void {
29+
this.service.$file.pipe(takeUntil(this.$destroy)).subscribe((file) => {
30+
this.file = file ?? undefined;
31+
});
32+
33+
this.route.params.pipe(takeUntil(this.$destroy)).subscribe(async (params) => {
34+
const { fileNumber } = params;
35+
36+
if (fileNumber) {
37+
this.fileNumber = fileNumber;
38+
this.loadFile(fileNumber);
39+
}
40+
});
41+
}
42+
43+
ngOnDestroy(): void {
44+
this.$destroy.next();
45+
this.$destroy.complete();
46+
}
47+
48+
async loadFile(fileNumber: string) {
49+
try {
50+
await this.service.loadFile(fileNumber, { withProperty: true });
51+
} catch (error) {
52+
console.error('Error loading file:', error);
53+
this.toastService.showErrorToast('Failed to load file');
54+
}
55+
}
56+
}

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NgModule } from '@angular/core';
2-
import { RouterModule, Routes } from '@angular/router';
2+
import { Route, RouterModule, Routes } from '@angular/router';
33
import { SharedModule } from '../../shared/shared.module';
44
import { OverviewComponent } from './overview/overview.component';
55
import { MatMomentDateModule } from '@angular/material-moment-adapter';
@@ -8,8 +8,26 @@ import { SubmitterComponent } from './submitter/submitter.component';
88
import { PropertyComponent } from './property/property.component';
99
import { ComplianceAndEnforcementDocumentsComponent } from './documents/documents.component';
1010
import { ResponsiblePartiesComponent } from './responsible-parties/responsible-parties.component';
11+
import { DetailsOverviewComponent } from './details/overview/details-overview.component';
12+
import { ComplianceAndEnforcementComponent } from './compliance-and-enforcement.component';
13+
import { CommonModule } from '@angular/common';
14+
import { DetailsHeaderComponent } from './details/header/details-header.component';
15+
16+
export const detailsRoutes: (Route & { icon?: string; menuTitle?: string })[] = [
17+
{
18+
path: '',
19+
component: DetailsOverviewComponent,
20+
icon: 'summarize',
21+
menuTitle: 'Overview',
22+
},
23+
];
1124

1225
const routes: Routes = [
26+
{
27+
path: ':fileNumber',
28+
component: ComplianceAndEnforcementComponent,
29+
children: detailsRoutes.concat([]),
30+
},
1331
{
1432
path: ':fileNumber/draft',
1533
component: DraftComponent,
@@ -24,7 +42,10 @@ const routes: Routes = [
2442
PropertyComponent,
2543
ComplianceAndEnforcementDocumentsComponent,
2644
ResponsiblePartiesComponent,
45+
ComplianceAndEnforcementComponent,
46+
DetailsHeaderComponent,
47+
DetailsOverviewComponent,
2748
],
28-
imports: [SharedModule.forRoot(), RouterModule.forChild(routes), MatMomentDateModule],
49+
imports: [SharedModule.forRoot(), RouterModule.forChild(routes), MatMomentDateModule, CommonModule, SharedModule],
2950
})
3051
export class ComplianceAndEnforcementModule {}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div class="header">
2+
<div class="left-column">
3+
<div>
4+
<span class="subtext heading">C&E File</span>
5+
<div class="first-row">
6+
<div class="title">
7+
<h5 class="detail-heading">
8+
<div class="file-number">{{ file?.fileNumber }}</div>
9+
</h5>
10+
</div>
11+
</div>
12+
</div>
13+
</div>
14+
<div class="right-column">
15+
<div>
16+
<div>Unassigned</div>
17+
<div class="status-wrapper">
18+
<div
19+
*ngIf="status"
20+
class="submission-status-pill"
21+
[ngClass]="{
22+
open: status === Status.OPEN,
23+
closed: status === Status.CLOSED,
24+
}"
25+
>
26+
<ng-container *ngIf="status">{{ status }}</ng-container>
27+
</div>
28+
</div>
29+
<div>
30+
<div class="body-text">{{ file?.property?.localGovernment?.name }}</div>
31+
</div>
32+
</div>
33+
</div>
34+
</div>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
@use '../../../../../styles/colors';
2+
3+
.heading {
4+
color: colors.$primary-color-dark;
5+
margin-bottom: 8px;
6+
}
7+
8+
.header {
9+
padding: 16px 80px;
10+
border-bottom: 1px solid colors.$primary-color-dark;
11+
display: flex;
12+
.first-row {
13+
display: flex;
14+
align-items: center;
15+
justify-content: space-between;
16+
margin-bottom: 16px;
17+
}
18+
19+
.title {
20+
display: flex;
21+
flex-wrap: wrap;
22+
align-items: center;
23+
line-height: 32px;
24+
25+
h5 {
26+
margin-right: 8px !important;
27+
}
28+
}
29+
}
30+
31+
.status-wrapper {
32+
display: flex;
33+
align-items: flex-end;
34+
}
35+
36+
.detail-heading {
37+
display: flex;
38+
align-items: center;
39+
40+
.file-number {
41+
margin-right: 8px;
42+
}
43+
}
44+
45+
.left-column {
46+
div {
47+
flex: 1;
48+
}
49+
flex: 0 0 70%;
50+
display: flex;
51+
flex-direction: column;
52+
}
53+
54+
.right-column {
55+
& > div {
56+
display: flex;
57+
flex-direction: column;
58+
align-items: flex-end;
59+
gap: 15px;
60+
}
61+
flex: 0 0 30%;
62+
}
63+
64+
.submission-status-pill {
65+
border-radius: 20px;
66+
padding: 6px 16px;
67+
font-size: 16px;
68+
letter-spacing: 0.01em;
69+
position: relative;
70+
font-weight: 700 !important;
71+
72+
&.open {
73+
background-color: colors.$primary-color-light;
74+
color: colors.$primary-color;
75+
}
76+
&.closed {
77+
background-color: colors.$error-color-light;
78+
color: colors.$black;
79+
}
80+
}

0 commit comments

Comments
 (0)