Skip to content

Commit e1c48f1

Browse files
authored
Merge pull request #910 from utmstack/bugfix/10.5.13/compliance-report-module
Added compliance report view
2 parents 734bbef + 817b199 commit e1c48f1

File tree

56 files changed

+2117
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2117
-92
lines changed

frontend/src/app/app.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ export class AppComponent implements OnInit {
120120
}
121121

122122
isInExportRoute() {
123-
return this.router.url.includes('dashboard/export/') || this.router.url.includes('dashboard/export-compliance') ||
123+
return this.router.url.includes('dashboard/export/') ||
124+
this.router.url.includes('dashboard/export-compliance') ||
125+
this.router.url.includes('compliance/print-view') ||
124126
this.router.url.includes('/getting-started') ||
125127
this.router.url.includes('/dashboard/export-report/') || this.iframeView || this.router.url.includes('/data/alert/detail/');
126128
}

frontend/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {NewAlertBehavior} from './shared/behaviors/new-alert.behavior';
3535
import {TimezoneFormatService} from './shared/services/utm-timezone.service';
3636
import {UtmSharedModule} from './shared/utm-shared.module';
3737
import {AccountService} from "./core/auth/account.service";
38+
import {AlertManagementSharedModule} from "./data-management/alert-management/shared/alert-management-shared.module";
3839

3940
export function initTimezoneFormat(timezoneService: TimezoneFormatService) {
4041
return () => timezoneService.loadTimezoneAndFormat();
@@ -73,6 +74,7 @@ export function initTimezoneFormat(timezoneService: TimezoneFormatService) {
7374
Ng2TelInputModule,
7475
NgxFlagIconCssModule,
7576
Ng2Webstorage.forRoot(),
77+
AlertManagementSharedModule,
7678
],
7779
providers: [
7880
LocalStorageService,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.full-height {
2+
height: 90vh;
3+
overflow-y: auto;
4+
}
5+
::ng-deep app-compliance-report-viewer gridster {
6+
background-color: unset !important;
7+
}
8+
9+
.section-icon {
10+
width: 50%;
11+
padding-bottom: 5px;
12+
transition: border-color 0.3s ease, background-color 0.3s ease, transform 0.1s ease; /* Transición suave */
13+
}
14+
15+
.section-icon img {
16+
width: 1.5rem; /* Ajusta el tamaño de la imagen */
17+
}
18+
19+
.section-icon i {
20+
font-size: 1.5rem;
21+
color: #333;
22+
}
23+
24+
.section-label {
25+
font-size: 0.9rem;
26+
font-weight: 500;
27+
color: #333;
28+
text-align: center;
29+
}
30+
31+
.active-section {
32+
background-color: #e9e9e9;
33+
border-bottom: 3px solid #28a745; /* Borde inferior verde */
34+
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2); /* Sombra interna para simular presión */
35+
}
36+
37+
.section-icon:hover {
38+
background-color: #f0f0f0;
39+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra al pasar el ratón */
40+
}
41+
42+
43+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<div class="container-fluid p-2">
2+
<div class="d-flex justify-content-between align-items-center mb-2 row no-gutters">
3+
<div class="col-md-6">
4+
<h5 class="card-title mb-0 text-uppercase label-header">Reports Templates {{ standard ? ': ' + standard.standardName : '' }}</h5>
5+
</div>
6+
<div class="col-md-6">
7+
<div *appHasAnyAuthority="admin" class="d-flex justify-content-end">
8+
<button (click)="manageStandards()" class="btn utm-button utm-button-primary">
9+
<i class="icon-cog3 mr-1"></i> Change framework
10+
</button>
11+
<a (click)="exportToPdf()" class="btn utm-button utm-button-primary ml-2">
12+
<i [ngClass]="pdfExport ? 'icon-download10' : 'icon-file-pdf'" class="mr-1"></i>
13+
{{ pdfExport ? 'Generating...' : 'Save to PDF' }}
14+
</a>
15+
</div>
16+
</div>
17+
</div>
18+
19+
<div class="row m-0">
20+
<div style="max-width: 300px" class="col-lg-3 col-md-3 col-sm-12 pl-0 pr-0 full-height mr-2">
21+
<div class="h-100 card m-0">
22+
<div class="card-header header-elements-sm-inline p-0 bg-white card-header-title">
23+
<div class="d-flex justify-content-between w-100">
24+
<div class="section-icon d-flex flex-column align-items-center cursor-pointer pt-2"
25+
[ngClass]="{'active-section': action === 'compliance'}"
26+
(click)="selectAction('compliance')">
27+
<img src="assets/icons/compliance/regulatory-compliance.png" alt="compliance">
28+
<span class="section-label">Compliance</span>
29+
</div>
30+
31+
<div class="section-icon d-flex flex-column align-items-center cursor-pointer pt-2"
32+
[ngClass]="{'active-section': action === 'reports'}"
33+
(click)="selectAction('reports')">
34+
<img src="assets/icons/compliance/analysis.png" alt="reports">
35+
<span class="section-label">Reports</span>
36+
</div>
37+
</div>
38+
</div>
39+
40+
<div class="card-body p-0 m-0">
41+
<div *ngFor="let section of sections$ | async; let index = index; trackBy: trackFn" class="d-flex flex-column">
42+
<app-utm-cp-section [section]="section"
43+
[loadFirst]="index === activeIndexSection"
44+
[index]="index"
45+
(isActive)="onChangeSectionActive($event)"
46+
[expandable]="action==='reports'">
47+
</app-utm-cp-section>
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
53+
<div class="flex-grow-1">
54+
<div class="h-100 card m-0">
55+
<app-compliance-reports-view [section]="activeSection" *ngIf="action === 'compliance' && activeSection"></app-compliance-reports-view>
56+
<app-compliance-result-view *ngIf="action === 'reports'" [showExport]="false" [template]="'compliance'" ></app-compliance-result-view>
57+
</div>
58+
</div>
59+
</div>
60+
</div>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {HttpErrorResponse} from '@angular/common/http';
2+
import {AfterViewInit, Component, OnDestroy, OnInit} from '@angular/core';
3+
import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
4+
import {NgxSpinnerService} from 'ngx-spinner';
5+
import {LocalStorageService} from 'ngx-webstorage';
6+
import {EMPTY, Observable, Subject} from 'rxjs';
7+
import {catchError, concatMap, filter, map, takeUntil, tap} from 'rxjs/operators';
8+
import {UtmToastService} from '../../shared/alert/utm-toast.service';
9+
import {ADMIN_ROLE} from '../../shared/constants/global.constant';
10+
import {ExportPdfService} from '../../shared/services/util/export-pdf.service';
11+
import {UtmCpStandardComponent} from '../shared/components/utm-cp-standard/utm-cp-standard.component';
12+
import {CpReportsService} from '../shared/services/cp-reports.service';
13+
import {CpStandardSectionService} from '../shared/services/cp-standard-section.service';
14+
import {ComplianceReportType} from '../shared/type/compliance-report.type';
15+
import {ComplianceStandardSectionType} from '../shared/type/compliance-standard-section.type';
16+
import {ComplianceStandardType} from '../shared/type/compliance-standard.type';
17+
18+
@Component({
19+
selector: 'app-compliance-report-viewer',
20+
templateUrl: './compliance-report-viewer.component.html',
21+
styleUrls: ['./compliance-report-viewer.component.css']
22+
})
23+
export class ComplianceReportViewerComponent implements OnInit, AfterViewInit, OnDestroy {
24+
admin = ADMIN_ROLE;
25+
sections$: Observable<ComplianceStandardSectionType[]>;
26+
standard: ComplianceStandardType;
27+
activeIndexSection = 0;
28+
destroy$: Subject<void> = new Subject();
29+
report: ComplianceReportType;
30+
pdfExport = false;
31+
action: 'reports' | 'compliance' = 'compliance';
32+
activeSection: ComplianceStandardSectionType = null;
33+
34+
constructor(private standardSectionService: CpStandardSectionService,
35+
private toastService: UtmToastService,
36+
private modalService: NgbModal,
37+
private $localStorage: LocalStorageService,
38+
private spinner: NgxSpinnerService,
39+
private reportsService: CpReportsService,
40+
private exportPdfService: ExportPdfService) {
41+
this.standard = this.$localStorage.retrieve('selectedStandard');
42+
}
43+
44+
ngOnInit() {
45+
this.sections$ = this.standardSectionService.onRefresh$
46+
.pipe(takeUntil(this.destroy$),
47+
filter(refresh => !!refresh && refresh.loading),
48+
concatMap(() => this.standardSectionService.fetchData({
49+
page: 0,
50+
size: 1000,
51+
'standardId.equals': this.standard.id
52+
})),
53+
map((res) => {
54+
return res.body.map((s, index) => {
55+
return {
56+
...s,
57+
isCollapsed: this.activeIndexSection === index,
58+
isActive: this.activeIndexSection === index
59+
};
60+
});
61+
}),
62+
tap((sections) => {
63+
this.activeSection = {
64+
...sections[this.activeIndexSection]
65+
};
66+
}),
67+
catchError((err: HttpErrorResponse) => {
68+
this.toastService.showError('Error',
69+
'Unable to retrieve the list of compliance standard sections. Please try again or contact support.');
70+
return EMPTY;
71+
}));
72+
73+
this.reportsService.onLoadReport$
74+
.pipe(takeUntil(this.destroy$),
75+
filter((params) => !!params))
76+
.subscribe((params) => this.report = params.template);
77+
}
78+
79+
ngAfterViewInit(): void {
80+
if (!this.standard) {
81+
this.manageStandards();
82+
} else {
83+
this.standardSectionService.notifyRefresh({
84+
loading: true,
85+
activeSection: 0
86+
});
87+
}
88+
}
89+
90+
manageStandards() {
91+
const options: NgbModalOptions = {
92+
backdrop: 'static',
93+
keyboard: false,
94+
centered: true
95+
};
96+
const modalRef = this.modalService.open(UtmCpStandardComponent, options);
97+
98+
modalRef.result.then((standard) => {
99+
this.standard = standard;
100+
this.standardSectionService.notifyRefresh({
101+
loading: true,
102+
activeSection: 0
103+
});
104+
});
105+
}
106+
107+
onChangeSectionActive(index: number) {
108+
this.activeIndexSection = index;
109+
this.standardSectionService.notifyRefresh({
110+
loading: true,
111+
activeSection: index
112+
});
113+
}
114+
115+
exportToPdf() {
116+
this.spinner.show('buildPrintPDF');
117+
const url = this.getUrl();
118+
const fileName = this.report ? this.report.associatedDashboard.name.replace(/ /g, '_') : 'Reports';
119+
this.exportPdfService.getPdf(url, fileName, 'PDF_TYPE_TOKEN').subscribe(response => {
120+
this.spinner.hide('buildPrintPDF').then(() =>
121+
this.exportPdfService.handlePdfResponse(response));
122+
}, error => {
123+
this.spinner.hide('buildPrintPDF').then(() =>
124+
this.toastService.showError('Error', 'An error occurred while creating a PDF.'));
125+
});
126+
}
127+
128+
selectAction(action: 'reports' | 'compliance' ) {
129+
this.action = action;
130+
}
131+
132+
trackFn(index: number, section: ComplianceStandardSectionType) {
133+
return section.id;
134+
}
135+
136+
getUrl(){
137+
return this.action === 'reports' ? '/dashboard/export-compliance/' + this.report.id :
138+
`/compliance/print-view/?section=${this.getActiveSectionParams()}`;
139+
}
140+
141+
getActiveSectionParams(){
142+
return encodeURIComponent(JSON.stringify({
143+
standardId: this.activeSection.standardId,
144+
id: this.activeSection.id
145+
}));
146+
}
147+
148+
ngOnDestroy(): void {
149+
this.reportsService.loadReport(null);
150+
this.destroy$.next();
151+
this.destroy$.complete();
152+
this.standardSectionService.notifyRefresh(null);
153+
}
154+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.container-fluid {
2+
background-color: #f8f9fa;
3+
}
4+
5+
img{
6+
margin-right: 5px;
7+
}
8+
9+
table {
10+
tr {
11+
border-top: 1px solid #dee2e6;
12+
}
13+
td {
14+
border: none !important;
15+
}
16+
}
17+
18+
.card-title {
19+
font-weight: bold;
20+
}
21+
22+
.label-header {
23+
font-size: 12px;
24+
}
25+
26+
.border-left-danger {
27+
border-left: 5px solid #dc3545 !important;
28+
}
29+
30+
.border-left-success{
31+
border-left: 5px solid #28a745 !important;
32+
}
33+
34+
.button-pdf {
35+
margin-right: 25px !important;
36+
margin-top: 0;
37+
}

0 commit comments

Comments
 (0)