Merge pull request #4244 from MaximilianKrambach/callListPdf

call list pdf export
This commit is contained in:
Emanuel Schütze 2019-02-01 18:04:48 +01:00 committed by GitHub
commit f26f7f9c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 19 deletions

View File

@ -119,23 +119,36 @@ export class PdfDocumentService {
* @param documentContent the content of the pdf as object
* @param metadata
* @param imageUrls Array of optional images (url, placeholder) to be inserted
* @param customMargins optionally overrides the margins
* @param landscape optional landscape page orientation instead of default portrait
* @returns the pdf document definition ready to export
*/
private async getStandardPaper(documentContent: object, metadata?: object, imageUrls?: string[]): Promise<object> {
private async getStandardPaper(
documentContent: object,
metadata?: object,
imageUrls?: string[],
customMargins?: [number, number, number, number],
landscape?: boolean
): Promise<object> {
this.initFonts();
this.imageUrls = imageUrls ? imageUrls : [];
pdfMake.vfs = await this.initVfs();
const result = {
pageSize: 'A4',
pageMargins: [75, 90, 75, 75],
pageOrientation: landscape ? landscape : 'portrait',
pageMargins: customMargins || [75, 90, 75, 75],
defaultStyle: {
font: 'PdfFont',
fontSize: this.configService.instant('general_export_pdf_fontsize')
},
header: this.getHeader(),
header: this.getHeader(customMargins ? [customMargins[0], customMargins[2]] : null),
// TODO: option for no footer, wherever this can be defined
footer: (currentPage, pageCount) => {
return this.getFooter(currentPage, pageCount);
return this.getFooter(
currentPage,
pageCount,
customMargins ? [customMargins[0], customMargins[2]] : null
);
},
info: metadata,
content: documentContent,
@ -188,9 +201,10 @@ export class PdfDocumentService {
/**
* Creates the header doc definition for normal PDF documents
*
* @param lrMargin optional margin overrides
* @returns an object that contains the necessary header definition
*/
private getHeader(): object {
private getHeader(lrMargin?: [number, number]): object {
// check for the required logos
let logoHeaderLeftUrl = this.configService.instant<any>('logo_pdf_header_L').path;
let logoHeaderRightUrl = this.configService.instant<any>('logo_pdf_header_R').path;
@ -247,11 +261,13 @@ export class PdfDocumentService {
});
this.imageUrls.push(logoHeaderRightUrl);
}
const margin = [lrMargin ? lrMargin[0] : 75, 30, lrMargin ? lrMargin[0] : 75, 10];
// pdfmake order: [left, top, right, bottom]
return {
color: '#555',
fontSize: 9,
margin: [75, 30, 75, 10], // [left, top, right, bottom]
margin: margin,
columns: columns,
columnGap: 10
};
@ -265,9 +281,10 @@ export class PdfDocumentService {
*
* @param currentPage holds the number of the current page
* @param pageCount holds the page count
* @param lrMargin optionally overriding the margins
* @returns the footer doc definition
*/
private getFooter(currentPage: number, pageCount: number): object {
private getFooter(currentPage: number, pageCount: number, lrMargin?: [number, number]): object {
const columns = [];
let logoContainerWidth: string;
let pageNumberPosition: string;
@ -330,8 +347,9 @@ export class PdfDocumentService {
this.imageUrls.push(logoFooterRightUrl);
}
const margin = [lrMargin ? lrMargin[0] : 75, 0, lrMargin ? lrMargin[0] : 75, 10];
return {
margin: [75, 0, 75, 10],
margin: margin,
columns: columns,
columnGap: 10
};
@ -359,6 +377,18 @@ export class PdfDocumentService {
});
}
/**
* Downloads a pdf in landscape orientation
*
* @param docDefinition the structure of the PDF document
* @param filename the name of the file to use
* @param metadata
*/
public downloadLandscape(docDefinition: object, filename: string, metadata?: object): void {
this.getStandardPaper(docDefinition, metadata, null, [50, 80, 50, 75], true).then(doc => {
this.createPdf(doc, filename);
});
}
/**
* Downloads a pdf with the ballot papet page definitions.
*

View File

@ -4,10 +4,10 @@
<h2 translate>Call list</h2>
</div>
<!-- Export -->
<!-- Export menu -->
<div class="menu-slot">
<button mat-icon-button (click)="csvExportCallList()">
<mat-icon>archive</mat-icon>
<button type="button" mat-icon-button [matMenuTriggerFor]="downloadMenu">
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-head-bar>
@ -15,7 +15,25 @@
<mat-card>
<button mat-button (click)="expandCollapseAll(true)">{{ 'Expand all' | translate }}</button>
<button mat-button (click)="expandCollapseAll(false)">{{ 'Collapse all' | translate }}</button>
<os-sorting-tree #sorter (sort)="sort($event)" parentIdKey="sort_parent_id"
weightKey="weight" [modelsObservable]="motionsObservable" [expandCollapseAll]="expandCollapse">
<os-sorting-tree
#sorter
(sort)="sort($event)"
parentIdKey="sort_parent_id"
weightKey="weight"
[modelsObservable]="motionsObservable"
[expandCollapseAll]="expandCollapse"
>
</os-sorting-tree>
</mat-card>
<mat-menu #downloadMenu="matMenu">
<button mat-menu-item (click)="pdfExportCallList()">
<mat-icon>picture_as_pdf</mat-icon>
<span translate>Export (PDF)</span>
</button>
<button mat-menu-item (click)="csvExportCallList()">
<mat-icon>archive</mat-icon>
<span translate>Export (CSV)</span>
</button>
</mat-menu>

View File

@ -7,9 +7,11 @@ import { Observable } from 'rxjs';
import { BaseViewComponent } from '../../../base/base-view';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
import { MotionPdfExportService } from '../../services/motion-pdf-export.service';
import { ViewMotion } from '../../models/view-motion';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
/**
* Sort view for the call list.
@ -46,7 +48,8 @@ export class CallListComponent extends BaseViewComponent {
translate: TranslateService,
matSnackBar: MatSnackBar,
private motionRepo: MotionRepositoryService,
private motionCsvExport: MotionCsvExportService
private motionCsvExport: MotionCsvExportService,
private motionPdfExport: MotionPdfExportService
) {
super(title, translate, matSnackBar);
@ -83,4 +86,11 @@ export class CallListComponent extends BaseViewComponent {
public csvExportCallList(): void {
this.motionCsvExport.exportCallList(this.motions);
}
/**
* Triggers a pdf export of the call list
*/
public pdfExportCallList(): void {
this.motionPdfExport.exportPdfCallList(this.motions);
}
}

View File

@ -70,4 +70,18 @@ export class MotionPdfExportService {
};
this.pdfDocumentService.download(doc, filename, metadata);
}
/**
* Exports a table of the motions in order of their call list
*
* @param motions the motions to export
*/
public exportPdfCallList(motions: ViewMotion[]): void {
const doc = this.motionPdfService.callListToDoc(motions);
const filename = this.translate.instant('Call list');
const metadata = {
title: filename
};
this.pdfDocumentService.downloadLandscape(doc, filename, metadata);
}
}

View File

@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
import { MotionRepositoryService } from '../../../core/repositories/motions/motion-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { ChangeRecommendationRepositoryService } from '../../../core/repositories/motions/change-recommendation-repository.service';
import { ViewUnifiedChange } from '../models/view-unified-change';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionRepositoryService } from '../../../core/repositories/motions/motion-repository.service';
import { HtmlToPdfService } from 'app/core/ui-services/html-to-pdf.service';
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
import { ViewUnifiedChange } from '../models/view-unified-change';
/**
* Converts a motion to pdf. Can be used from the motion detail view or executed on a list of motions
@ -413,4 +413,88 @@ export class MotionPdfService {
return {};
}
}
/**
* Creates pdfMake definitions for the call list of given motions
*
* @param motions A list of motions
* @returns definitions ready to be opened or exported via {@link PdfDocumentService}
*/
public callListToDoc(motions: ViewMotion[]): object {
motions.sort((a, b) => a.callListWeight - b.callListWeight);
const title = {
text: this.translate.instant('Call list'),
style: 'title'
};
const callListTableBody: object[] = [
[
{
text: this.translate.instant('Called'),
style: 'tableHeader'
},
{
text: this.translate.instant('Called with'),
style: 'tableHeader'
},
{
text: this.translate.instant('Submitters'),
style: 'tableHeader'
},
{
text: this.translate.instant('Title'),
style: 'tableHeader'
},
{
text: this.translate.instant('Recommendation'),
style: 'tableHeader'
},
{
text: this.translate.instant('Motion block'),
style: 'tableHeader'
}
]
];
const callListRows = motions.map(motion => this.createCallListRow(motion));
const table: object = {
table: {
widths: ['auto', 'auto', 'auto', '*', 'auto', 'auto'],
headerRows: 1,
body: callListTableBody.concat(callListRows)
},
layout: {
hLineWidth: rowIndex => {
return rowIndex === 1;
},
vLineWidth: () => {
return 0;
},
fillColor: rowIndex => {
return rowIndex % 2 === 0 ? '#EEEEEE' : null;
}
}
};
return [title, table];
}
/**
* Creates the pdfMake definitions for a row of the call List table
*
* @param motion
* @returns pdfmakre definitions
*/
private createCallListRow(motion: ViewMotion): object {
return [
{
text: motion.sort_parent_id ? '' : motion.identifierOrTitle
},
{ text: motion.sort_parent_id ? motion.identifierOrTitle : '' },
{ text: motion.submitters.length ? motion.submitters.map(s => s.short_name).join(', ') : '' },
{ text: motion.title },
{
text: motion.recommendation ? this.translate.instant(motion.recommendation.recommendation_label) : ''
},
{ text: motion.motion_block ? motion.motion_block.title : '' }
];
}
}