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 { RouterModule } from '@angular/router';
|
||||
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component';
|
||||
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
|
||||
|
||||
const routes = [
|
||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||
@ -81,6 +82,14 @@ const routes = [
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'reports',
|
||||
component: ReportsScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'default-colors',
|
||||
component: DefaultColorsScreenComponent,
|
||||
|
||||
@ -33,7 +33,8 @@ export class AdminSideNavComponent {
|
||||
{ screen: 'rules', onlyDevMode: true, label: 'rule-editor' },
|
||||
{ screen: 'default-colors' },
|
||||
{ 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 { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
|
||||
|
||||
const dialogs = [
|
||||
AddEditDossierTemplateDialogComponent,
|
||||
@ -75,7 +76,7 @@ const components = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
declarations: [...components, ReportsScreenComponent],
|
||||
providers: [AdminDialogService],
|
||||
imports: [
|
||||
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,
|
||||
FileAttributeConfig,
|
||||
FileAttributesConfig,
|
||||
ReportTemplateControllerService,
|
||||
SMTPConfigurationModel,
|
||||
TypeValue,
|
||||
User
|
||||
@ -41,6 +42,7 @@ export class AdminDialogService {
|
||||
constructor(
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _dossierTemplateControllerService: DossierTemplateControllerService,
|
||||
private readonly _reportTemplateService: ReportTemplateControllerService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService
|
||||
) {}
|
||||
|
||||
@ -61,6 +63,23 @@ export class AdminDialogService {
|
||||
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(
|
||||
dossierTemplate: DossierTemplateModel,
|
||||
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 { User } from '@redaction/red-ui-http';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@ -11,6 +11,5 @@ export function download(event: HttpResponse<Blob>, altName?: string) {
|
||||
} catch (e) {
|
||||
console.log('[REDACTION] Failed to parse content-disposition: ', contentDisposition);
|
||||
}
|
||||
console.log('save');
|
||||
saveAs(event.body, fileName ? fileName : altName);
|
||||
}
|
||||
|
||||
@ -1005,7 +1005,16 @@
|
||||
},
|
||||
"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",
|
||||
"reports": "Reports",
|
||||
"user-management": "User Management",
|
||||
"license-information": "License Information",
|
||||
"notifications": {
|
||||
|
||||
@ -187,13 +187,14 @@ export class ReportTemplateControllerService {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
return this.httpClient.request<any>(
|
||||
return this.httpClient.request(
|
||||
'get',
|
||||
`${this.basePath}/templateUpload/${encodeURIComponent(
|
||||
String(dossierTemplateId)
|
||||
)}/${encodeURIComponent(String(templateId))}`,
|
||||
{
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
responseType: 'blob',
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
|
||||
@ -74,13 +74,11 @@ export namespace DossierTemplateModel {
|
||||
export type ReportTypesEnum =
|
||||
| 'EXCEL_MULTI_FILE'
|
||||
| 'EXCEL_SINGLE_FILE'
|
||||
| 'WORD_SINGLE_FILE'
|
||||
| 'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE'
|
||||
| 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE';
|
||||
export const ReportTypesEnum = {
|
||||
EXCELMULTIFILE: 'EXCEL_MULTI_FILE' as ReportTypesEnum,
|
||||
EXCELSINGLEFILE: 'EXCEL_SINGLE_FILE' as ReportTypesEnum,
|
||||
WORDSINGLEFILE: 'WORD_SINGLE_FILE' as ReportTypesEnum,
|
||||
WORDSINGLEFILEAPPENDIXA1TEMPLATE:
|
||||
'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE' as ReportTypesEnum,
|
||||
WORDSINGLEFILEAPPENDIXA2TEMPLATE: 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE' as ReportTypesEnum
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user