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:
commit
a9b14a5ff1
@ -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,
|
||||||
|
|||||||
@ -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 }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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" />
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user