[BP-841] Add cyclic reporting overview component
Signed-off-by: Christopher Keim <keim@develop-group.de>
diff --git a/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.html b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.html
new file mode 100644
index 0000000..52d81b8
--- /dev/null
+++ b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.html
@@ -0,0 +1,47 @@
+<!--
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+-->
+
+<div class="container-fluid">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+ <h1 class="h2" id="overviewHeader">Zyklische Reports</h1>
+ <div class="btn-toolbar mb-2 mb-md-0">
+ <div class="btn-group mr-2">
+ <button class="btn btn-primary mr-1" routerLink="new">
+ Neuer Eintrag
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12">
+ <ag-grid-angular
+ #gridNg2
+ [localeText]="localeText"
+ domLayout="autoHeight"
+ class="ag-theme-balham cyclic-reports-table"
+ [defaultColDef]="defaultColDef"
+ [rowData]="data"
+ [columnDefs]="columnDefs"
+ (gridReady)="onGridReady()"
+ (rowClicked)="selectEntry($event?.data)"
+ pagination="true"
+ paginationPageSize="30"
+ id="oragnisationGrid">
+ </ag-grid-angular>
+ </div>
+ </div>
+</div>
diff --git a/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.scss b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.scss
new file mode 100644
index 0000000..c99c596
--- /dev/null
+++ b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.scss
@@ -0,0 +1,9 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
diff --git a/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.spec.ts b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.spec.ts
new file mode 100644
index 0000000..e1f667a
--- /dev/null
+++ b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.spec.ts
@@ -0,0 +1,126 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CommonModule} from '@angular/common';
+import {HttpClientTestingModule} from '@angular/common/http/testing';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {RouterTestingModule} from '@angular/router/testing';
+import {SharedModule} from '@shared/shared.module';
+import {StandbylistObject} from '@shared/model/StandbylistObject';
+import {CyclicReportObject} from '@shared/model/CyclicReportObject';
+import {RowNode} from 'ag-grid-community';
+import {of} from 'rxjs';
+import {CyclicReportsOverviewComponent} from './cyclic-reports-overview.component';
+
+function createMockData<T extends object>(data: Partial<T>): T {
+ return (data == null ? {} : data) as T;
+}
+
+describe('CyclicReportsOverviewComponent', () => {
+
+ let component: CyclicReportsOverviewComponent;
+ let fixture: ComponentFixture<CyclicReportsOverviewComponent>;
+
+ const standByListData: StandbylistObject[] = Array(42).fill(0)
+ .map((_, id) => createMockData<StandbylistObject>({ id, title: 'QVL ' + id }));
+
+ const reportData: CyclicReportObject[] = Array(100).fill(0)
+ .map((_, id) => createMockData<CyclicReportObject>({
+ id,
+ name: 'Cyclic Report ' + id,
+ fileNamePattern: '{Date}_{Time}_{Week}_' + id,
+ subject: 'Subject ' + id,
+ to: [],
+
+ reportName: 'ReportName ' + id,
+ printFormat: 'pdf',
+ standByListId: id % 42,
+ statusId: 2,
+
+ triggerWeekDay: (id + 6) % 7 + 1,
+ triggerHour: id % 24,
+ triggerMinute: (id % 12) * 5,
+ validFromDayOffset: - id % 7,
+ validFromHour: (id + 1) % 24,
+ validFromMinute: (id + 2) % 60,
+ validToDayOffset: 1 + id % 7,
+ validToHour: (id + 3) % 24,
+ validToMinute: (id + 4) % 24
+ }));
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ CyclicReportsOverviewComponent
+ ],
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterTestingModule,
+ HttpClientTestingModule
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CyclicReportsOverviewComponent);
+ component = fixture.componentInstance;
+ spyOn(component.masterdataService, 'getStandbyListSelection').and.returnValue(of(standByListData));
+ spyOn(component.cyclicReportingService, 'getCyclicReports').and.returnValue(of(reportData));
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeDefined();
+ });
+
+ it('should compare trigger dates', () => {
+ const comparator = component.columnDefs.find((col) => col.colId === 'trigger').comparator;
+ const nodeA = createMockData<RowNode>({ data: reportData[0] });
+ const nodeB = createMockData<RowNode>({ data: reportData[1] });
+ expect(comparator('', '', nodeA, nodeA)).toBe(0);
+ expect(comparator('', '', nodeA, nodeB)).toBe(6);
+ expect(comparator('', '', nodeB, nodeA)).toBe(-6);
+ expect(comparator('', '', nodeB, nodeB)).toBe(0);
+ });
+
+ it('should sort table', async () => {
+ expect(() => component.sort('trigger', 'desc')).not.toThrow();
+ await fixture.whenStable();
+ });
+
+ it('should select entries', async () => {
+ const navigateSpy = spyOn(component.router, 'navigate');
+ navigateSpy.calls.reset();
+ await component.selectEntry(null);
+ await component.selectEntry(createMockData<CyclicReportObject>({}));
+ expect(navigateSpy).not.toHaveBeenCalled();
+ await component.selectEntry(createMockData<CyclicReportObject>({ id: 19 }));
+ expect(navigateSpy).toHaveBeenCalledWith([19], { relativeTo: component.activatedRoute });
+ });
+
+ it('should format data', () => {
+ component.fetch();
+ expect(component.formatData().length).toBe(reportData.length);
+ expect(component.formatData(null)).toEqual([]);
+ expect(component.formatData([ null, null ])).toEqual([]);
+ });
+
+ it('should format list name', () => {
+ component.fetch();
+ expect(component.data[0].listName).toBe('QVL 0');
+ expect(component.formatListName(null)).toEqual('');
+ expect(component.formatListName(createMockData<CyclicReportObject>({ standByListId: 19 }))).toEqual(standByListData[19].title);
+ expect(component.formatListName(createMockData<CyclicReportObject>({ standByListId: standByListData.length }))).toEqual('');
+ component.standByList = null;
+ expect(component.formatListName(createMockData<CyclicReportObject>({ standByListId: 19 }))).toEqual('');
+ });
+
+});
diff --git a/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.ts b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.ts
new file mode 100644
index 0000000..7d8be30
--- /dev/null
+++ b/src/app/cyclic-reporting/components/overview/cyclic-reports-overview.component.ts
@@ -0,0 +1,143 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {DatePipe} from '@angular/common';
+import {Component, OnDestroy, ViewChild} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+import {MasterdataService} from '@masterdata/services/masterdata.service';
+import {StandbylistObject} from '@shared/model/StandbylistObject';
+import {CyclicReportObject} from '@shared/model/CyclicReportObject';
+import {AGGRID_LOCALETEXT, DEFAULT_COL_DEF} from '@shared/utils/list.util';
+import {AgGridNg2} from 'ag-grid-angular';
+import {ColDef} from 'ag-grid-community';
+import {Subscription} from 'rxjs';
+import {CyclicReportingUtilService} from '../../services/cyclic-reporting-util.service';
+import {CyclicReportingService} from '../../services/cyclic-reporting.service';
+
+export interface CyclicReportTableEntry extends CyclicReportObject {
+ trigger: string;
+ listName: string;
+}
+
+@Component({
+ selector: 'ok-cyclic-reports-overview',
+ styleUrls: ['cyclic-reports-overview.component.scss'],
+ templateUrl: 'cyclic-reports-overview.component.html',
+ providers: [
+ DatePipe
+ ]
+})
+export class CyclicReportsOverviewComponent implements OnDestroy {
+
+ public hourFormat = 'HH:mm';
+
+ public defaultColDef = DEFAULT_COL_DEF;
+
+ public columnDefs: ColDef[] = [
+ {
+ headerName: 'Name',
+ field: 'name',
+ colId: 'name'
+ },
+ {
+ headerName: 'Planart',
+ field: 'reportName',
+ colId: 'reportName',
+ },
+ {
+ headerName: 'Liste',
+ field: 'listName',
+ colId: 'listName',
+ },
+ {
+ headerName: 'Auslösezeitpunkt',
+ field: 'trigger',
+ colId: 'trigger',
+ comparator: (valueA, valueB, nodeA, nodeB, isInverted) => {
+ return this.cyclicReportingUtilService.cyclicReportTriggerComparator(nodeA.data, nodeB.data);
+ }
+ }
+ ];
+
+ public localeText = AGGRID_LOCALETEXT;
+
+ public data: CyclicReportTableEntry[];
+
+ public standByList: StandbylistObject[] = [];
+
+ private subscriptions: Subscription[] = [];
+
+ @ViewChild(AgGridNg2)
+ private grid: AgGridNg2;
+
+ public constructor(
+ public cyclicReportingService: CyclicReportingService,
+ public cyclicReportingUtilService: CyclicReportingUtilService,
+ public masterdataService: MasterdataService,
+ public router: Router,
+ public activatedRoute: ActivatedRoute,
+ public datePipe: DatePipe
+ ) {
+
+ }
+
+ public ngOnDestroy() {
+ this.subscriptions.forEach((subscription) => subscription.unsubscribe());
+ }
+
+ public onGridReady() {
+ this.fetch();
+ this.grid.api.sizeColumnsToFit();
+ this.sort('trigger');
+ }
+
+ public sort(colId: string, sort = 'asc') {
+ this.grid.api.setSortModel([{colId, sort}]);
+ }
+
+ public fetch() {
+ this.subscriptions.push(...[
+ this.cyclicReportingService.getCyclicReports().subscribe((data) => this.formatData(data)),
+ this.masterdataService.getStandbyListSelection().subscribe((data) => {
+ this.standByList = data;
+ this.formatData();
+ })
+ ]);
+ }
+
+ public async selectEntry(report: CyclicReportObject) {
+ const id = report != null ? report.id : undefined;
+ if (id != null) {
+ return this.router.navigate([id], { relativeTo: this.activatedRoute });
+ }
+ }
+
+ public formatData(data: CyclicReportObject[] = this.data) {
+ return this.data = (Array.isArray(data) ? data : [])
+ .filter((entry) => entry != null)
+ .map((entry) => {
+ const nextTriggerDate = this.cyclicReportingUtilService.getNextTriggerDate(entry);
+ const weekDayLabel = this.cyclicReportingUtilService.formatWeekDay(entry.triggerWeekDay);
+ return {
+ ...entry,
+ trigger: this.datePipe.transform(nextTriggerDate, `'${weekDayLabel}', ${this.hourFormat}`),
+ listName: this.formatListName(entry)
+ };
+ });
+ }
+
+ public formatListName(report: CyclicReportObject): string {
+ const standByList = Array.isArray(this.standByList) ? this.standByList : [];
+ const standByListId = report != null ? report.standByListId : undefined;
+ const selectedList = standByList.find((entry) => entry.id === standByListId);
+ return selectedList != null ? selectedList.title : '';
+ }
+
+}