From c1b44c2c3d42319925f6ded6ae9f2f91a9b73850 Mon Sep 17 00:00:00 2001
From: Cameron Pettit <71421099+cameronpettit@users.noreply.github.com>
Date: Wed, 5 Oct 2022 15:51:22 -0700
Subject: [PATCH] BRS-817 locking records/fiscal years front end (#163)
BRS-817 adding keycloaksettings for new lock-records route
BRS-817 Locked records show as locked
---
src/app/app-routing.module.ts | 12 ++
src/app/app.module.ts | 2 +
...ackcountry-cabins-accordion.component.html | 1 +
...ckcountry-camping-accordion.component.html | 1 +
.../boating-accordion.component.html | 1 +
.../day-use-accordion.component.html | 1 +
...ontcountry-cabins-accordion.component.html | 1 +
...ntcountry-camping-accordion.component.html | 1 +
.../group-camping-accordion.component.html | 1 +
src/app/guards/auth.guard.ts | 4 +
src/app/header/header.component.ts | 5 +-
src/app/home/home.component.html | 4 +-
src/app/home/home.component.ts | 9 ++
.../fiscal-year-lock-table.component.html | 6 +
.../fiscal-year-lock-table.component.scss | 0
.../fiscal-year-lock-table.component.spec.ts | 24 ++++
.../fiscal-year-lock-table.component.ts | 82 +++++++++++++
.../fiscal-year-unlocker.component.html | 3 +
.../fiscal-year-unlocker.component.scss | 0
.../fiscal-year-unlocker.component.spec.ts | 28 +++++
.../fiscal-year-unlocker.component.ts | 20 +++
.../lock-records/lock-records.component.html | 51 ++++++++
.../lock-records/lock-records.component.scss | 0
.../lock-records.component.spec.ts | 34 ++++++
.../lock-records/lock-records.component.ts | 56 +++++++++
src/app/lock-records/lock-records.module.ts | 28 +++++
.../resolvers/lock-records.resolver.spec.ts | 21 ++++
src/app/resolvers/lock-records.resolver.ts | 16 +++
.../services/fiscal-year-lock.service.spec.ts | 21 ++++
src/app/services/fiscal-year-lock.service.ts | 114 ++++++++++++++++++
src/app/services/keycloak.service.ts | 2 +-
.../accordion/accordion.component.html | 24 +++-
.../accordion/accordion.component.spec.ts | 6 +-
.../accordion/accordion.component.ts | 44 ++++++-
.../components/sidebar/sidebar.component.ts | 3 +-
.../table/table-row/table-row.component.html | 14 +++
.../table/table-row/table-row.component.scss | 3 +
.../table-row/table-row.component.spec.ts | 24 ++++
.../table/table-row/table-row.component.ts | 57 +++++++++
.../components/table/table.component.html | 29 +++++
.../components/table/table.component.scss | 5 +
.../components/table/table.component.spec.ts | 24 ++++
.../components/table/table.component.ts | 52 ++++++++
.../shared/components/table/table.module.ts | 11 ++
src/app/shared/utils/constants.ts | 4 +
src/app/shared/utils/utils.ts | 10 ++
46 files changed, 841 insertions(+), 18 deletions(-)
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.html
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.scss
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.spec.ts
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.ts
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.html
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.scss
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.spec.ts
create mode 100644 src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.ts
create mode 100644 src/app/lock-records/lock-records.component.html
create mode 100644 src/app/lock-records/lock-records.component.scss
create mode 100644 src/app/lock-records/lock-records.component.spec.ts
create mode 100644 src/app/lock-records/lock-records.component.ts
create mode 100644 src/app/lock-records/lock-records.module.ts
create mode 100644 src/app/resolvers/lock-records.resolver.spec.ts
create mode 100644 src/app/resolvers/lock-records.resolver.ts
create mode 100644 src/app/services/fiscal-year-lock.service.spec.ts
create mode 100644 src/app/services/fiscal-year-lock.service.ts
create mode 100644 src/app/shared/components/table/table-row/table-row.component.html
create mode 100644 src/app/shared/components/table/table-row/table-row.component.scss
create mode 100644 src/app/shared/components/table/table-row/table-row.component.spec.ts
create mode 100644 src/app/shared/components/table/table-row/table-row.component.ts
create mode 100644 src/app/shared/components/table/table.component.html
create mode 100644 src/app/shared/components/table/table.component.scss
create mode 100644 src/app/shared/components/table/table.component.spec.ts
create mode 100644 src/app/shared/components/table/table.component.ts
create mode 100644 src/app/shared/components/table/table.module.ts
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 2c02f77..5034d51 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -16,6 +16,8 @@ import { LoginComponent } from './login/login.component';
import { ExportResolver } from './resolvers/export.resolver';
import { FormResolver } from './resolvers/form.resolver';
import { SubAreaResolver } from './resolvers/sub-area.resolver';
+import { LockRecordsComponent } from './lock-records/lock-records.component';
+import { LockRecordsResolver } from './resolvers/lock-records.resolver';
const routes: Routes = [
{
@@ -126,6 +128,16 @@ const routes: Routes = [
},
resolve: [ExportResolver],
},
+ {
+ path: 'lock-records',
+ component: LockRecordsComponent,
+ canActivate: [AuthGuard],
+ data: {
+ label: 'Lock Records',
+ breadcrumb: 'Lock Records',
+ },
+ resolve: [LockRecordsResolver],
+ },
{
path: 'unauthorized',
pathMatch: 'full',
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index d6903f7..515ed49 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -28,6 +28,7 @@ import { InfiniteLoadingBarModule } from './shared/components/infinite-loading-b
import { ToastrModule } from 'ngx-toastr';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { LoadingService } from './services/loading.service';
+import { LockRecordsModule } from './lock-records/lock-records.module';
export function initConfig(
configService: ConfigService,
@@ -57,6 +58,7 @@ export function initConfig(
BreadcrumbModule,
ExportReportsModule,
EnterDataModule,
+ LockRecordsModule,
HeaderModule,
FooterModule,
HomeModule,
diff --git a/src/app/enter-data/accordion-manager/backcountry-cabins-accordion/backcountry-cabins-accordion.component.html b/src/app/enter-data/accordion-manager/backcountry-cabins-accordion/backcountry-cabins-accordion.component.html
index 0452c53..3692034 100644
--- a/src/app/enter-data/accordion-manager/backcountry-cabins-accordion/backcountry-cabins-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/backcountry-cabins-accordion/backcountry-cabins-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'backcountry-cabins'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/backcountry-camping-accordion/backcountry-camping-accordion.component.html b/src/app/enter-data/accordion-manager/backcountry-camping-accordion/backcountry-camping-accordion.component.html
index 855c468..08b5497 100644
--- a/src/app/enter-data/accordion-manager/backcountry-camping-accordion/backcountry-camping-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/backcountry-camping-accordion/backcountry-camping-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'backcountry-camping'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/boating-accordion/boating-accordion.component.html b/src/app/enter-data/accordion-manager/boating-accordion/boating-accordion.component.html
index bbf7d62..2ba6b18 100644
--- a/src/app/enter-data/accordion-manager/boating-accordion/boating-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/boating-accordion/boating-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'boating'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/day-use-accordion/day-use-accordion.component.html b/src/app/enter-data/accordion-manager/day-use-accordion/day-use-accordion.component.html
index fb98dbb..154ba2b 100644
--- a/src/app/enter-data/accordion-manager/day-use-accordion/day-use-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/day-use-accordion/day-use-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'day-use'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/frontcountry-cabins-accordion/frontcountry-cabins-accordion.component.html b/src/app/enter-data/accordion-manager/frontcountry-cabins-accordion/frontcountry-cabins-accordion.component.html
index 315b4ad..39d2ec8 100644
--- a/src/app/enter-data/accordion-manager/frontcountry-cabins-accordion/frontcountry-cabins-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/frontcountry-cabins-accordion/frontcountry-cabins-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'frontcountry-cabins'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/frontcountry-camping-accordion/frontcountry-camping-accordion.component.html b/src/app/enter-data/accordion-manager/frontcountry-camping-accordion/frontcountry-camping-accordion.component.html
index a0421ab..9409945 100644
--- a/src/app/enter-data/accordion-manager/frontcountry-camping-accordion/frontcountry-camping-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/frontcountry-camping-accordion/frontcountry-camping-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'frontcountry-camping'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/enter-data/accordion-manager/group-camping-accordion/group-camping-accordion.component.html b/src/app/enter-data/accordion-manager/group-camping-accordion/group-camping-accordion.component.html
index f6d9050..68ee433 100644
--- a/src/app/enter-data/accordion-manager/group-camping-accordion/group-camping-accordion.component.html
+++ b/src/app/enter-data/accordion-manager/group-camping-accordion/group-camping-accordion.component.html
@@ -6,5 +6,6 @@
[notes]="data?.notes"
[summaries]="summaries"
[editLink]="'group-camping'"
+ [recordLock]="data?.isLocked"
>
diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts
index 1df877d..ac35e5c 100644
--- a/src/app/guards/auth.guard.ts
+++ b/src/app/guards/auth.guard.ts
@@ -63,6 +63,10 @@ export class AuthGuard implements CanActivate {
return this.router.parseUrl('/');
}
+ if (!this.keycloakService.isAllowed('lock-records') && state.url === '/lock-records') {
+ return this.router.parseUrl('/');
+ }
+
// Show the requested page.
return true;
}
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 457af70..ae41903 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -30,7 +30,10 @@ export class HeaderComponent implements OnDestroy {
this.routes = router.config.filter(function (obj) {
if (obj.path === 'export-reports') {
return keycloakService.isAllowed('export-reports');
- } else {
+ } else if (obj.path === 'lock-records') {
+ return keycloakService.isAllowed('lock-records')
+ }
+ {
return obj.path !== '**' && obj.path !== 'unauthorized';
}
});
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index 9ac3ed8..c47dffe 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -4,8 +4,8 @@
BC Parks - Attendance and Revenue
-
-
+
+
Locked records
+
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.scss b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.spec.ts b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.spec.ts
new file mode 100644
index 0000000..e94bf09
--- /dev/null
+++ b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.spec.ts
@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FiscalYearLockTableComponent } from './fiscal-year-lock-table.component';
+
+describe('FiscalYearLockTableComponent', () => {
+ let component: FiscalYearLockTableComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [FiscalYearLockTableComponent],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FiscalYearLockTableComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.ts b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.ts
new file mode 100644
index 0000000..dfd7b36
--- /dev/null
+++ b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-lock-table.component.ts
@@ -0,0 +1,82 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { DataService } from 'src/app/services/data.service';
+import { columnSchema } from 'src/app/shared/components/table/table.component';
+import { Constants } from 'src/app/shared/utils/constants';
+import { FiscalYearUnlockerComponent } from './fiscal-year-unlocker/fiscal-year-unlocker.component';
+
+@Component({
+ selector: 'app-fiscal-year-lock-table',
+ templateUrl: './fiscal-year-lock-table.component.html',
+ styleUrls: ['./fiscal-year-lock-table.component.scss'],
+})
+export class FiscalYearLockTableComponent implements OnInit {
+ @Input() data: any[];
+
+ private subscriptions = new Subscription();
+ public columnSchema: columnSchema[] = [];
+ public tableRows: any[] = [];
+
+ constructor(protected dataService: DataService) {
+ this.subscriptions.add(
+ dataService
+ .getItemValue(Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA)
+ .subscribe((res) => {
+ if (res && res.length) {
+ this.tableRows = this.filterLockedYears(res);
+ }
+ })
+ );
+ }
+
+ ngOnInit(): void {
+ this.createColumnSchema();
+ }
+
+ filterLockedYears(data) {
+ let lockedYears: any[] = [];
+ for (const year of data) {
+ if (year.isLocked) {
+ lockedYears.push(year);
+ }
+ }
+ return lockedYears;
+ }
+
+ // fiscalYearEndObject schema
+ // pk: fiscalYearEnd
+ // sk: 2022
+ // isLocked: true
+ createColumnSchema() {
+ this.columnSchema = [
+ {
+ id: 'year',
+ displayHeader: 'Year',
+ columnClasses: 'ps-3 pe-5',
+ mapValue: (row) => row.sk,
+ },
+ {
+ id: 'parkName',
+ displayHeader: 'Park',
+ width: '70%',
+ columnClasses: 'px-5',
+ mapValue: () => 'All Parks',
+ },
+ {
+ id: 'lockedStatus',
+ displayHeader: 'Unlock',
+ width: '10%',
+ columnClasses: 'ps-5 pe-3',
+ mapValue: (row) => row.isLocked,
+ cellTemplate: (row) => {
+ return {
+ component: FiscalYearUnlockerComponent,
+ data: {
+ data: row,
+ },
+ };
+ },
+ },
+ ];
+ }
+}
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.html b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.html
new file mode 100644
index 0000000..bea839c
--- /dev/null
+++ b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.html
@@ -0,0 +1,3 @@
+
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.scss b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.spec.ts b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.spec.ts
new file mode 100644
index 0000000..fbdaf6d
--- /dev/null
+++ b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.spec.ts
@@ -0,0 +1,28 @@
+import { HttpClientModule } from '@angular/common/http';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ConfigService } from 'src/app/services/config.service';
+
+import { FiscalYearUnlockerComponent } from './fiscal-year-unlocker.component';
+
+describe('FiscalYearUnlockerComponent', () => {
+ let component: FiscalYearUnlockerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HttpClientModule],
+ declarations: [FiscalYearUnlockerComponent],
+ providers: [ConfigService],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FiscalYearUnlockerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.ts b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.ts
new file mode 100644
index 0000000..b169b0d
--- /dev/null
+++ b/src/app/lock-records/fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component.ts
@@ -0,0 +1,20 @@
+import { Component, Input } from '@angular/core';
+import { FiscalYearLockService } from 'src/app/services/fiscal-year-lock.service';
+
+@Component({
+ selector: 'app-fiscal-year-unlocker',
+ templateUrl: './fiscal-year-unlocker.component.html',
+ styleUrls: ['./fiscal-year-unlocker.component.scss'],
+})
+export class FiscalYearUnlockerComponent {
+ @Input() data: any;
+
+ constructor(private fiscalYearLockService: FiscalYearLockService) {}
+
+ unlockFiscalYear() {
+ this.fiscalYearLockService.lockUnlockFiscalYear(
+ this.data.year.value,
+ false
+ );
+ }
+}
diff --git a/src/app/lock-records/lock-records.component.html b/src/app/lock-records/lock-records.component.html
new file mode 100644
index 0000000..cc11d6c
--- /dev/null
+++ b/src/app/lock-records/lock-records.component.html
@@ -0,0 +1,51 @@
+
+ Lock or Unlock Records
+
+ Select a date rage below to lock or unlock all records for all parks for the
+ selected dates.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/lock-records/lock-records.component.scss b/src/app/lock-records/lock-records.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/lock-records/lock-records.component.spec.ts b/src/app/lock-records/lock-records.component.spec.ts
new file mode 100644
index 0000000..5cb1196
--- /dev/null
+++ b/src/app/lock-records/lock-records.component.spec.ts
@@ -0,0 +1,34 @@
+import { HttpClientModule } from '@angular/common/http';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
+import { ConfigService } from '../services/config.service';
+import { DatePickerModule } from '../shared/components/date-picker/date-picker.module';
+
+import { LockRecordsComponent } from './lock-records.component';
+
+describe('LockRecordsComponent', () => {
+ let component: LockRecordsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ HttpClientModule,
+ DatePickerModule,
+ BsDatepickerModule.forRoot(),
+ ],
+ declarations: [LockRecordsComponent],
+ providers: [ConfigService],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LockRecordsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/lock-records/lock-records.component.ts b/src/app/lock-records/lock-records.component.ts
new file mode 100644
index 0000000..6a970e8
--- /dev/null
+++ b/src/app/lock-records/lock-records.component.ts
@@ -0,0 +1,56 @@
+import { Component, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { DataService } from '../services/data.service';
+import { FiscalYearLockService } from '../services/fiscal-year-lock.service';
+import { Constants } from '../shared/utils/constants';
+
+@Component({
+ selector: 'app-lock-records',
+ templateUrl: './lock-records.component.html',
+ styleUrls: ['./lock-records.component.scss'],
+})
+export class LockRecordsComponent implements OnInit {
+
+ private subscriptions = new Subscription();
+ public fiscalYearStartMonth = 'April';
+ public fiscalYearEndMonth = 'March';
+ public loading = true;
+ public fiscalYearsList: any[] = [];
+ public modelDate = NaN;
+ public maxDate = new Date();
+ public fiscalYearRangeString = 'Select a fiscal year';
+
+ constructor(
+ protected dataService: DataService,
+ protected fiscalYearLockService: FiscalYearLockService
+ ) {
+ this.subscriptions.add(
+ dataService
+ .getItemValue(Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA)
+ .subscribe((res) => {
+ this.fiscalYearsList = res;
+ })
+ );
+ }
+
+ ngOnInit(): void {
+ this.fiscalYearLockService.fetchFiscalYear();
+ }
+
+ onOpenCalendar(container) {
+ container.setViewMode('year');
+ }
+
+ datePickerOutput(event) {
+ const selectedYear = new Date(event).getFullYear();
+ this.modelDate = selectedYear;
+ const startDate = this.fiscalYearStartMonth + ' ' + (selectedYear - 1);
+ const endDate = this.fiscalYearEndMonth + ' ' + selectedYear;
+ const displayRange = `${startDate} - ${endDate}`;
+ this.fiscalYearRangeString = displayRange;
+ }
+
+ submit() {
+ this.fiscalYearLockService.lockUnlockFiscalYear(this.modelDate, true);
+ }
+}
diff --git a/src/app/lock-records/lock-records.module.ts b/src/app/lock-records/lock-records.module.ts
new file mode 100644
index 0000000..096726d
--- /dev/null
+++ b/src/app/lock-records/lock-records.module.ts
@@ -0,0 +1,28 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { DatePickerModule } from '../shared/components/date-picker/date-picker.module';
+import { LockRecordsComponent } from './lock-records.component';
+import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
+import { RouterModule } from '@angular/router';
+import { TextToLoadingSpinnerModule } from '../shared/components/text-to-loading-spinner/text-to-loading-spinner.module';
+import { FiscalYearLockTableComponent } from './fiscal-year-lock-table/fiscal-year-lock-table.component';
+import { TableModule } from '../shared/components/table/table.module';
+import { FiscalYearUnlockerComponent } from './fiscal-year-lock-table/fiscal-year-unlocker/fiscal-year-unlocker.component';
+
+@NgModule({
+ declarations: [
+ LockRecordsComponent,
+ FiscalYearLockTableComponent,
+ FiscalYearUnlockerComponent,
+ ],
+ imports: [
+ CommonModule,
+ DatePickerModule,
+ BsDatepickerModule.forRoot(),
+ RouterModule,
+ TextToLoadingSpinnerModule,
+ TableModule,
+ ],
+ exports: [LockRecordsComponent],
+})
+export class LockRecordsModule {}
diff --git a/src/app/resolvers/lock-records.resolver.spec.ts b/src/app/resolvers/lock-records.resolver.spec.ts
new file mode 100644
index 0000000..68c6a48
--- /dev/null
+++ b/src/app/resolvers/lock-records.resolver.spec.ts
@@ -0,0 +1,21 @@
+import { HttpClientModule } from '@angular/common/http';
+import { TestBed } from '@angular/core/testing';
+import { ConfigService } from '../services/config.service';
+
+import { LockRecordsResolver } from './lock-records.resolver';
+
+describe('LockRecordsResolver', () => {
+ let resolver: LockRecordsResolver;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientModule],
+ providers: [ConfigService]
+ });
+ resolver = TestBed.inject(LockRecordsResolver);
+ });
+
+ it('should be created', () => {
+ expect(resolver).toBeTruthy();
+ });
+});
diff --git a/src/app/resolvers/lock-records.resolver.ts b/src/app/resolvers/lock-records.resolver.ts
new file mode 100644
index 0000000..0352ba2
--- /dev/null
+++ b/src/app/resolvers/lock-records.resolver.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import {
+ Resolve,
+} from '@angular/router';
+import { FiscalYearLockService } from '../services/fiscal-year-lock.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LockRecordsResolver implements Resolve {
+ constructor(private fiscalYearLockService: FiscalYearLockService) {}
+ resolve() {
+ this.fiscalYearLockService.fetchFiscalYear();
+ }
+}
+
diff --git a/src/app/services/fiscal-year-lock.service.spec.ts b/src/app/services/fiscal-year-lock.service.spec.ts
new file mode 100644
index 0000000..f824e84
--- /dev/null
+++ b/src/app/services/fiscal-year-lock.service.spec.ts
@@ -0,0 +1,21 @@
+import { HttpClientModule } from '@angular/common/http';
+import { TestBed } from '@angular/core/testing';
+import { ConfigService } from './config.service';
+
+import { FiscalYearLockService } from './fiscal-year-lock.service';
+
+describe('FiscalYearLockService', () => {
+ let service: FiscalYearLockService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientModule],
+ providers: [ConfigService],
+ });
+ service = TestBed.inject(FiscalYearLockService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/fiscal-year-lock.service.ts b/src/app/services/fiscal-year-lock.service.ts
new file mode 100644
index 0000000..7077f8e
--- /dev/null
+++ b/src/app/services/fiscal-year-lock.service.ts
@@ -0,0 +1,114 @@
+import { Injectable } from '@angular/core';
+import { firstValueFrom } from 'rxjs';
+import { Constants } from '../shared/utils/constants';
+import { ApiService } from './api.service';
+import { DataService } from './data.service';
+import { EventKeywords, EventObject, EventService } from './event.service';
+import { LoadingService } from './loading.service';
+import { ToastService, ToastTypes } from './toast.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class FiscalYearLockService {
+ constructor(
+ private dataService: DataService,
+ private apiService: ApiService,
+ private toastService: ToastService,
+ private loadingService: LoadingService,
+ private eventService: EventService
+ ) {}
+
+ // get/check fiscal years
+ // passing year = null returns all fiscal year objects
+ async fetchFiscalYear(year?) {
+ this.loadingService.addToFetchList(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA
+ );
+ let res;
+ let errorSubject = '';
+ try {
+ errorSubject = 'lock-records-fiscal-years-data';
+ if (year) {
+ res = await firstValueFrom(
+ this.apiService.get('fiscalYearEnd', { fiscalYearEnd: year })
+ );
+ } else {
+ res = await firstValueFrom(this.apiService.get('fiscalYearEnd'));
+ }
+ this.dataService.setItemValue(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA,
+ res
+ );
+ } catch (e) {
+ this.toastService.addMessage(
+ 'Please refresh the page.',
+ `Error getting ${errorSubject}`,
+ ToastTypes.ERROR
+ );
+ this.eventService.setError(
+ new EventObject(
+ EventKeywords.ERROR,
+ String(e),
+ 'Fiscal Year Lock Service'
+ )
+ );
+ this.dataService.setItemValue(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA,
+ 'error'
+ );
+ }
+ this.loadingService.removeToFetchList(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA
+ );
+ return res;
+ }
+
+ // lock = true locks, lock = false unlocks
+ // must provide year.
+ async lockUnlockFiscalYear(year, lock: boolean) {
+ this.loadingService.addToFetchList(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA
+ );
+ let res;
+ let errorSubject = '';
+ let subPath = 'lock';
+ let ptString = 'locked';
+ if (!lock) {
+ subPath = 'unlock';
+ ptString = 'unlocked';
+ }
+ try {
+ errorSubject = `lock-records-${subPath}-fiscal-year`;
+ res = await firstValueFrom(
+ this.apiService.post(`fiscalYearEnd/${subPath}`, null, {
+ fiscalYearEnd: year,
+ })
+ );
+ const prevYear = year - 1;
+ // trigger refresh of cached list of fetched fiscal year locks
+ this.fetchFiscalYear();
+ this.toastService.addMessage(
+ `Fiscal year from April ${prevYear} to March ${year} successfully ${ptString}`,
+ `Fiscal year ${ptString}`,
+ ToastTypes.SUCCESS
+ );
+ } catch (e) {
+ this.toastService.addMessage(
+ `Something went wrong during fiscal year ${subPath}`,
+ `Error: ${errorSubject}`,
+ ToastTypes.ERROR
+ );
+ this.eventService.setError(
+ new EventObject(
+ EventKeywords.ERROR,
+ String(e),
+ 'Fiscal Year Lock Service'
+ )
+ );
+ }
+ this.loadingService.removeToFetchList(
+ Constants.dataIds.LOCK_RECORDS_FISCAL_YEARS_DATA
+ );
+ }
+}
diff --git a/src/app/services/keycloak.service.ts b/src/app/services/keycloak.service.ts
index fd55579..76dcb8a 100644
--- a/src/app/services/keycloak.service.ts
+++ b/src/app/services/keycloak.service.ts
@@ -153,7 +153,7 @@ export class KeycloakService {
* @memberof KeycloakService
*/
isAllowed(service): boolean {
- if (service !== 'export-reports') {
+ if (service !== 'export-reports' && service !== 'lock-records') {
return true;
}
const token = this.getToken();
diff --git a/src/app/shared/components/accordion/accordion.component.html b/src/app/shared/components/accordion/accordion.component.html
index 7f0958b..1667f6d 100644
--- a/src/app/shared/components/accordion/accordion.component.html
+++ b/src/app/shared/components/accordion/accordion.component.html
@@ -7,10 +7,16 @@
@@ -56,10 +62,16 @@
diff --git a/src/app/shared/components/accordion/accordion.component.spec.ts b/src/app/shared/components/accordion/accordion.component.spec.ts
index 18f58e3..7001436 100644
--- a/src/app/shared/components/accordion/accordion.component.spec.ts
+++ b/src/app/shared/components/accordion/accordion.component.spec.ts
@@ -1,5 +1,7 @@
+import { HttpClientModule } from '@angular/common/http';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
+import { ConfigService } from 'src/app/services/config.service';
import { AccordionComponent } from './accordion.component';
@@ -9,9 +11,9 @@ describe('AccordionComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [RouterTestingModule],
+ imports: [RouterTestingModule, HttpClientModule],
declarations: [AccordionComponent],
- providers: [],
+ providers: [ConfigService],
}).compileComponents();
});
diff --git a/src/app/shared/components/accordion/accordion.component.ts b/src/app/shared/components/accordion/accordion.component.ts
index a676f92..b4d9ea7 100644
--- a/src/app/shared/components/accordion/accordion.component.ts
+++ b/src/app/shared/components/accordion/accordion.component.ts
@@ -1,7 +1,9 @@
-import { Component, Input, OnDestroy } from '@angular/core';
+import { ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
+import { Utils } from '../../utils/utils';
import { Subscription } from 'rxjs';
import { DataService } from 'src/app/services/data.service';
+import { FiscalYearLockService } from 'src/app/services/fiscal-year-lock.service';
import { Constants } from '../../utils/constants';
import { summarySection } from './summary-section/summary-section.component';
@@ -18,17 +20,36 @@ export class AccordionComponent implements OnDestroy {
@Input() notes: string = '';
@Input() summaries: Array = [];
@Input() editLink: string = '';
+ @Input() set recordLock(value: boolean) {
+ if (value !== null){
+ this._recordLock = value;
+ } else {
+ this._recordLock = true;
+ }
+ this.lockRecords();
+ this.changeDetectorRef.detectChanges();
+ };
- private subscriptions = new Subscription();
+ get recordLock(): boolean {
+ return this._recordLock
+ }
- private formParams;
+ public _recordLock = true;
+
+ public FISCAL_YEAR_FINAL_MONTH = 3;
+ private subscriptions = new Subscription();
+ private formParams;
+ private utils = new Utils();
+ public isLocked = true;
public readonly iconSize = 50; // icon size in px
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
- protected dataService: DataService
+ private fiscalYearLockService: FiscalYearLockService,
+ protected dataService: DataService,
+ private changeDetectorRef: ChangeDetectorRef
) {
this.subscriptions.add(
dataService
@@ -41,12 +62,25 @@ export class AccordionComponent implements OnDestroy {
);
}
+ async lockRecords() {
+ // extract year from form params
+ if (this.formParams?.date) {
+ const year = this.utils.getFiscalYearFromYYYYMM(this.formParams.date);
+ const lock = await this.fiscalYearLockService.fetchFiscalYear(year);
+ if (!this._recordLock) {
+ this.isLocked = lock.isLocked;
+ } else {
+ this.isLocked = this._recordLock;
+ }
+ }
+ }
+
edit() {
this.router.navigate([this.editLink], {
relativeTo: this.activatedRoute,
queryParams: this.formParams,
});
- window.scrollTo(0,0);
+ window.scrollTo(0, 0);
}
ngOnDestroy() {
diff --git a/src/app/shared/components/sidebar/sidebar.component.ts b/src/app/shared/components/sidebar/sidebar.component.ts
index efc7b95..385000f 100644
--- a/src/app/shared/components/sidebar/sidebar.component.ts
+++ b/src/app/shared/components/sidebar/sidebar.component.ts
@@ -26,10 +26,11 @@ export class SidebarComponent implements OnDestroy {
protected subAreaService: SubAreaService,
protected keyCloakService: KeycloakService
) {
-
this.routes = router.config.filter(function (obj) {
if (obj.path === 'export-reports') {
return keyCloakService.isAllowed('export-reports');
+ } else if (obj.path === 'lock-records') {
+ return keyCloakService.isAllowed('lock-records');
} else {
return obj.path !== '**' && obj.path !== 'unauthorized';
}
diff --git a/src/app/shared/components/table/table-row/table-row.component.html b/src/app/shared/components/table/table-row/table-row.component.html
new file mode 100644
index 0000000..804f118
--- /dev/null
+++ b/src/app/shared/components/table/table-row/table-row.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ {{ rowData[column.id].value }}
+
+
+ |
diff --git a/src/app/shared/components/table/table-row/table-row.component.scss b/src/app/shared/components/table/table-row/table-row.component.scss
new file mode 100644
index 0000000..a60f17f
--- /dev/null
+++ b/src/app/shared/components/table/table-row/table-row.component.scss
@@ -0,0 +1,3 @@
+.table-cell {
+ vertical-align: middle;
+}
\ No newline at end of file
diff --git a/src/app/shared/components/table/table-row/table-row.component.spec.ts b/src/app/shared/components/table/table-row/table-row.component.spec.ts
new file mode 100644
index 0000000..c7a6035
--- /dev/null
+++ b/src/app/shared/components/table/table-row/table-row.component.spec.ts
@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TableRowComponent } from './table-row.component';
+
+describe('TableRowComponent', () => {
+ let component: TableRowComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [TableRowComponent],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TableRowComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/table/table-row/table-row.component.ts b/src/app/shared/components/table/table-row/table-row.component.ts
new file mode 100644
index 0000000..9094747
--- /dev/null
+++ b/src/app/shared/components/table/table-row/table-row.component.ts
@@ -0,0 +1,57 @@
+import {
+ AfterViewChecked,
+ Component,
+ Input,
+ QueryList,
+ ViewChildren,
+ ViewContainerRef,
+} from '@angular/core';
+import { columnSchema } from '../table.component';
+
+@Component({
+ // Throws the following linting error: https://angular.io/guide/styleguide#style-05-03
+ // This component is given an [attribute] selector because it augments
+ // the HTML element . We do this so that the child
elements of a
+ // can be custom components while remaining aligned with the parent component.
+ // eslint-disable-next-line
+ selector: '[app-table-row]',
+ templateUrl: './table-row.component.html',
+ styleUrls: ['./table-row.component.scss'],
+})
+export class TableRowComponent implements AfterViewChecked {
+ @Input() columnSchema: columnSchema[];
+ @Input() rowData: any;
+
+ @ViewChildren('cellTemplateComponent', { read: ViewContainerRef })
+ cellTemplateComponents: QueryList;
+
+ ngAfterViewChecked(): void {
+ this.loadComponents();
+ }
+
+ getComponentIdList() {
+ // gather list of components in the row
+ const keys = Object.keys(this.rowData);
+ return keys.filter((e) => this.rowData[e].cellTemplate !== undefined);
+ }
+
+ // Load components and map them to their respective cells in the row
+ loadComponents() {
+ if (this.cellTemplateComponents) {
+ this.cellTemplateComponents.map(
+ (vcr: ViewContainerRef, index: number) => {
+ vcr.clear();
+ const componentIdList = this.getComponentIdList();
+ const id = componentIdList.filter(
+ (id) => componentIdList.indexOf(id) === index
+ )[0];
+ const template = this.rowData[id].cellTemplate;
+ const cellTemplateComponent = vcr.createComponent<
+ typeof template.component
+ >(template.component);
+ cellTemplateComponent.instance.data = this.rowData;
+ }
+ );
+ }
+ }
+}
diff --git a/src/app/shared/components/table/table.component.html b/src/app/shared/components/table/table.component.html
new file mode 100644
index 0000000..10222ca
--- /dev/null
+++ b/src/app/shared/components/table/table.component.html
@@ -0,0 +1,29 @@
+
+
+
+
+ {{ emptyTableMsg }}
+
+
+
+
diff --git a/src/app/shared/components/table/table.component.scss b/src/app/shared/components/table/table.component.scss
new file mode 100644
index 0000000..fa774d2
--- /dev/null
+++ b/src/app/shared/components/table/table.component.scss
@@ -0,0 +1,5 @@
+@import "src/assets/themes/variables";
+
+.header{
+ background-color: $form-grey;
+}
\ No newline at end of file
diff --git a/src/app/shared/components/table/table.component.spec.ts b/src/app/shared/components/table/table.component.spec.ts
new file mode 100644
index 0000000..257eb1c
--- /dev/null
+++ b/src/app/shared/components/table/table.component.spec.ts
@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TableComponent } from './table.component';
+
+describe('TableComponent', () => {
+ let component: TableComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [TableComponent],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TableComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/table/table.component.ts b/src/app/shared/components/table/table.component.ts
new file mode 100644
index 0000000..4f4471d
--- /dev/null
+++ b/src/app/shared/components/table/table.component.ts
@@ -0,0 +1,52 @@
+import { Component, Input, OnChanges } from '@angular/core';
+
+export interface columnSchema {
+ id: string;
+ displayHeader: string;
+ mapValue: Function;
+ cellTemplate?: Function;
+ width?: string;
+ columnClasses?: string;
+}
+
+@Component({
+ selector: 'app-table',
+ templateUrl: './table.component.html',
+ styleUrls: ['./table.component.scss'],
+})
+export class TableComponent implements OnChanges {
+ @Input() columnSchema: columnSchema[];
+ @Input() data: any[];
+ @Input() emptyTableMsg = 'This table is empty.';
+
+ public columns;
+ public rows: any = [];
+
+ constructor() {}
+
+ ngOnChanges() {
+ this.parseData();
+ }
+
+ async parseData() {
+ this.columns = [];
+ this.rows = [];
+ this.columns = this.columnSchema.map((id) => id.displayHeader);
+ if (this.data && this.data.length > 0) {
+ for (const item of this.data) {
+ let row: any = {};
+ this.columnSchema.map(async (col) => {
+ // we pass the whole row to column functions
+ row[col.id] = {
+ value: col.mapValue(item),
+ };
+ if (col.cellTemplate) {
+ row[col.id].cellTemplate = col.cellTemplate(item);
+ }
+ row['raw'] = item;
+ });
+ this.rows.push(row);
+ }
+ }
+ }
+}
diff --git a/src/app/shared/components/table/table.module.ts b/src/app/shared/components/table/table.module.ts
new file mode 100644
index 0000000..ce6e625
--- /dev/null
+++ b/src/app/shared/components/table/table.module.ts
@@ -0,0 +1,11 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { TableComponent } from './table.component';
+import { TableRowComponent } from './table-row/table-row.component';
+
+@NgModule({
+ declarations: [TableComponent, TableRowComponent],
+ imports: [CommonModule],
+ exports: [TableComponent],
+})
+export class TableModule {}
diff --git a/src/app/shared/utils/constants.ts b/src/app/shared/utils/constants.ts
index 5599e43..be232c8 100644
--- a/src/app/shared/utils/constants.ts
+++ b/src/app/shared/utils/constants.ts
@@ -11,12 +11,16 @@ export class Constants {
ACCORDION_BACKCOUNTRY_CABINS: 'accordion-Backcountry Cabins',
ENTER_DATA_URL_PARAMS: 'enter-data-url-params',
EXPORT_ALL_POLLING_DATA: 'export-all-polling-data',
+ LOCK_RECORDS_FISCAL_YEARS_DATA: 'lock-records-fiscal-years-data',
};
public static readonly ApplicationRoles: any = {
ADMIN: 'sysadmin',
};
+ // March
+ public static readonly FiscalYearFinalMonth: number = 3;
+
public static readonly iconUrls = {
frontcountryCamping: '../../assets/images/walk-in-camping.svg',
frontcountryCabins: '../../assets/images/shelter.svg',
diff --git a/src/app/shared/utils/utils.ts b/src/app/shared/utils/utils.ts
index 6b45535..55ff23f 100644
--- a/src/app/shared/utils/utils.ts
+++ b/src/app/shared/utils/utils.ts
@@ -1,4 +1,5 @@
import * as moment from 'moment';
+import { Constants } from './constants';
export class Utils {
public convertArrayIntoObjForTypeAhead(
@@ -45,6 +46,15 @@ export class Utils {
};
}
+ public getFiscalYearFromYYYYMM(date) {
+ let year = Number(date.substring(0, 4));
+ const month = Number(date.slice(-2));
+ if (month > Constants.FiscalYearFinalMonth) {
+ year += 1;
+ }
+ return year;
+ }
+
public convertJSDateToYYYYMM(date: Date) {
return moment(date).format('YYYYMM');
}