Pull request #218: Report templates

Merge in RED/ui from RED-1547 to master

* commit '566e26ea2e17bc6db42a9bb58897737844a18906':
  RED-1704 Remove "WORD_SINGLE_FILE" entry as a temporary workaround
  Reports only dev mode
  Report templates
This commit is contained in:
Timo Bejan 2021-06-28 14:27:36 +02:00
commit a9b14a5ff1
12 changed files with 323 additions and 7 deletions

View File

@ -17,6 +17,7 @@ import { DigitalSignatureScreenComponent } from './screens/digital-signature/dig
import { AuditScreenComponent } from './screens/audit/audit-screen.component'; import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component'; import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component';
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
const routes = [ const routes = [
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' }, { path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
@ -81,6 +82,14 @@ const routes = [
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
} }
}, },
{
path: 'reports',
component: ReportsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{ {
path: 'default-colors', path: 'default-colors',
component: DefaultColorsScreenComponent, component: DefaultColorsScreenComponent,

View File

@ -33,7 +33,8 @@ export class AdminSideNavComponent {
{ screen: 'rules', onlyDevMode: true, label: 'rule-editor' }, { screen: 'rules', onlyDevMode: true, label: 'rule-editor' },
{ screen: 'default-colors' }, { screen: 'default-colors' },
{ screen: 'watermark' }, { screen: 'watermark' },
{ screen: 'file-attributes', onlyAdmin: true } { screen: 'file-attributes', onlyAdmin: true },
{ screen: 'reports', onlyAdmin: true, onlyDevMode: true }
] ]
}; };

View File

@ -33,6 +33,7 @@ import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attribute
import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component'; import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component';
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component'; import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
const dialogs = [ const dialogs = [
AddEditDossierTemplateDialogComponent, AddEditDossierTemplateDialogComponent,
@ -75,7 +76,7 @@ const components = [
]; ];
@NgModule({ @NgModule({
declarations: [...components], declarations: [...components, ReportsScreenComponent],
providers: [AdminDialogService], providers: [AdminDialogService],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -0,0 +1,86 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions flex-1">
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossier-templates"></redaction-admin-side-nav>
<div class="content-container" redactionHasScrollbar>
<div class="heading-xl" translate="reports-screen.title"></div>
<div class="description" translate="reports-screen.description"></div>
<div class="document-setup">
<div class="all-caps-label" translate="reports-screen.document-setup-heading"></div>
<div translate="reports-screen.document-setup-description"></div>
</div>
<div class="placeholders">
<div class="all-caps-label">Placeholders</div>
<div class="all-caps-label">Description</div>
<ng-container *ngFor="let placeholder of placeholders">
<div class="placeholder">{{ getPlaceholderDisplayValue(placeholder) }}</div>
<div class="description">
What is and how to use it in your document. The readable content of a page
when looking at its layout must be nice.
</div>
</ng-container>
</div>
</div>
<div class="right-container" redactionHasScrollbar>
<div class="header">
<div class="heading" translate="reports-screen.report-documents"></div>
<redaction-circle-button
(action)="fileInput.click()"
icon="red:upload"
tooltip="reports-screen.upload-document"
></redaction-circle-button>
</div>
<div
(click)="fileInput.click()"
*ngIf="!availableTemplates?.length"
class="template upload-button"
translate="reports-screen.upload-document"
></div>
<div *ngFor="let template of availableTemplates" class="template">
<div class="name">{{ template.fileName }}</div>
<div class="actions">
<redaction-circle-button
(action)="download(template)"
[iconSize]="12"
[size]="18"
icon="red:download"
></redaction-circle-button>
<redaction-circle-button
(action)="deleteTemplate(template)"
[iconSize]="12"
[size]="18"
icon="red:trash"
></redaction-circle-button>
</div>
</div>
</div>
</div>
</section>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>
<input #fileInput (change)="uploadFile($event)" hidden type="file" />

View File

@ -0,0 +1,113 @@
@import '../../../../../assets/styles/red-variables';
@import '../../../../../assets/styles/red-mixins';
.page-header .actions {
display: flex;
justify-content: flex-end;
}
.content-container,
.right-container {
flex: 1;
padding: 30px;
overflow: auto;
@include scroll-bar;
}
.right-container {
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 8px;
margin-bottom: 8px;
}
.template {
padding: 8px 10px;
background-color: $grey-6;
border-radius: 4px;
transition: background-color 0.2s;
position: relative;
&:not(:last-child) {
margin-bottom: 2px;
}
.name {
max-width: calc(100% - 100px);
@include line-clamp(1);
}
.actions {
right: 16px;
position: absolute;
top: 6px;
display: none;
align-items: center;
> *:not(:last-child) {
margin-right: 12px;
}
}
&:hover {
background-color: $grey-4;
.actions {
display: flex;
}
}
}
.template.upload-button {
display: flex;
justify-content: center;
cursor: pointer;
}
}
.content-container {
> .heading-xl,
> .description,
> .document-setup {
margin-bottom: 24px;
> .all-caps-label {
margin-bottom: 8px;
}
}
.placeholders {
width: calc(100% + 60px);
margin-left: -30px;
display: grid;
grid-template-columns: 1fr 2fr;
.all-caps-label {
border-top: 1px solid $separator;
border-bottom: 1px solid $separator;
padding: 8px 32px;
&:nth-child(2) {
padding: 8px 16px;
}
}
.placeholder,
.description {
padding: 16px 16px;
border-bottom: 1px solid $separator;
}
.placeholder {
font-weight: 600;
text-transform: capitalize;
margin-left: 16px;
}
.description {
margin-right: 16px;
}
}
}

View File

@ -0,0 +1,80 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AppStateService } from '../../../../state/app-state.service';
import { ReportTemplate, ReportTemplateControllerService } from '@redaction/red-ui-http';
import { download } from '../../../../utils/file-download-utils';
import { AdminDialogService } from '../../services/admin-dialog.service';
@Component({
selector: 'redaction-reports-screen',
templateUrl: './reports-screen.component.html',
styleUrls: ['./reports-screen.component.scss']
})
export class ReportsScreenComponent implements OnInit {
viewReady = false;
placeholders: string[] = [
'report',
'predefined placeholder 1',
'signature 01',
'new attribute'
];
availableTemplates: ReportTemplate[];
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _reportTemplateService: ReportTemplateControllerService,
private readonly _dialogService: AdminDialogService
) {
this._appStateService.activateDossierTemplate(
_activatedRoute.snapshot.params.dossierTemplateId
);
}
getPlaceholderDisplayValue(placeholder: string): string {
return `{{${placeholder}}}`;
}
async ngOnInit() {
await this._loadReportTemplates();
this.viewReady = true;
}
async uploadFile($event) {
this.viewReady = false;
const file = $event.target.files[0];
await this._reportTemplateService
.uploadTemplateForm(this._appStateService.activeDossierTemplateId, file)
.toPromise();
this._fileInput.nativeElement.value = null;
await this._loadReportTemplates();
this.viewReady = true;
}
async download(template: ReportTemplate) {
const data = await this._reportTemplateService
.downloadReportTemplate(template.dossierTemplateId, template.templateId, 'response')
.toPromise();
download(data, template.fileName);
}
deleteTemplate(template: ReportTemplate) {
this._dialogService.openDeleteReportTemplateDialog(
template.templateId,
template.dossierTemplateId,
async () => {
await this._loadReportTemplates();
}
);
}
private async _loadReportTemplates() {
this.availableTemplates = await this._reportTemplateService
.getAvailableReportTemplates(this._appStateService.activeDossierTemplateId)
.toPromise();
}
}

View File

@ -7,6 +7,7 @@ import {
DossierTemplateModel, DossierTemplateModel,
FileAttributeConfig, FileAttributeConfig,
FileAttributesConfig, FileAttributesConfig,
ReportTemplateControllerService,
SMTPConfigurationModel, SMTPConfigurationModel,
TypeValue, TypeValue,
User User
@ -41,6 +42,7 @@ export class AdminDialogService {
constructor( constructor(
private readonly _dialog: MatDialog, private readonly _dialog: MatDialog,
private readonly _dossierTemplateControllerService: DossierTemplateControllerService, private readonly _dossierTemplateControllerService: DossierTemplateControllerService,
private readonly _reportTemplateService: ReportTemplateControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService private readonly _dictionaryControllerService: DictionaryControllerService
) {} ) {}
@ -61,6 +63,23 @@ export class AdminDialogService {
return ref; return ref;
} }
openDeleteReportTemplateDialog(
templateId: string,
dossierTemplateId: string,
cb?: Function
): MatDialogRef<ConfirmationDialogComponent> {
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async result => {
if (result) {
await this._reportTemplateService
.deleteTemplate(dossierTemplateId, templateId)
.toPromise();
if (cb) cb();
}
});
return ref;
}
openDeleteDossierTemplateDialog( openDeleteDossierTemplateDialog(
dossierTemplate: DossierTemplateModel, dossierTemplate: DossierTemplateModel,
cb?: () => void cb?: () => void

View File

@ -1,4 +1,4 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { User } from '@redaction/red-ui-http'; import { User } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';

View File

@ -11,6 +11,5 @@ export function download(event: HttpResponse<Blob>, altName?: string) {
} catch (e) { } catch (e) {
console.log('[REDACTION] Failed to parse content-disposition: ', contentDisposition); console.log('[REDACTION] Failed to parse content-disposition: ', contentDisposition);
} }
console.log('save');
saveAs(event.body, fileName ? fileName : altName); saveAs(event.body, fileName ? fileName : altName);
} }

View File

@ -1005,7 +1005,16 @@
}, },
"title": "Watermark" "title": "Watermark"
}, },
"reports-screen": {
"title": "Reports",
"description": "A short text explaining how to create report documents. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
"document-setup-heading": "Document Setup",
"document-setup-description": "A short text explaining what placeholders are and how to use them in your report template. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
"report-documents": "Report Documents",
"upload-document": "Upload a Document"
},
"dictionaries": "Dictionaries", "dictionaries": "Dictionaries",
"reports": "Reports",
"user-management": "User Management", "user-management": "User Management",
"license-information": "License Information", "license-information": "License Information",
"notifications": { "notifications": {

View File

@ -187,13 +187,14 @@ export class ReportTemplateControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected); headers = headers.set('Accept', httpHeaderAcceptSelected);
} }
return this.httpClient.request<any>( return this.httpClient.request(
'get', 'get',
`${this.basePath}/templateUpload/${encodeURIComponent( `${this.basePath}/templateUpload/${encodeURIComponent(
String(dossierTemplateId) String(dossierTemplateId)
)}/${encodeURIComponent(String(templateId))}`, )}/${encodeURIComponent(String(templateId))}`,
{ {
withCredentials: this.configuration.withCredentials, withCredentials: this.configuration.withCredentials,
responseType: 'blob',
headers: headers, headers: headers,
observe: observe, observe: observe,
reportProgress: reportProgress reportProgress: reportProgress

View File

@ -74,13 +74,11 @@ export namespace DossierTemplateModel {
export type ReportTypesEnum = export type ReportTypesEnum =
| 'EXCEL_MULTI_FILE' | 'EXCEL_MULTI_FILE'
| 'EXCEL_SINGLE_FILE' | 'EXCEL_SINGLE_FILE'
| 'WORD_SINGLE_FILE'
| 'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE' | 'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE'
| 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE'; | 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE';
export const ReportTypesEnum = { export const ReportTypesEnum = {
EXCELMULTIFILE: 'EXCEL_MULTI_FILE' as ReportTypesEnum, EXCELMULTIFILE: 'EXCEL_MULTI_FILE' as ReportTypesEnum,
EXCELSINGLEFILE: 'EXCEL_SINGLE_FILE' as ReportTypesEnum, EXCELSINGLEFILE: 'EXCEL_SINGLE_FILE' as ReportTypesEnum,
WORDSINGLEFILE: 'WORD_SINGLE_FILE' as ReportTypesEnum,
WORDSINGLEFILEAPPENDIXA1TEMPLATE: WORDSINGLEFILEAPPENDIXA1TEMPLATE:
'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE' as ReportTypesEnum, 'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE' as ReportTypesEnum,
WORDSINGLEFILEAPPENDIXA2TEMPLATE: 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE' as ReportTypesEnum WORDSINGLEFILEAPPENDIXA2TEMPLATE: 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE' as ReportTypesEnum