call list pdf export

This commit is contained in:
Maximilian Krambach 2019-01-31 19:18:58 +01:00
parent 448ea6df28
commit 181d00ea94
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 documentContent the content of the pdf as object
* @param metadata * @param metadata
* @param imageUrls Array of optional images (url, placeholder) to be inserted * @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 * @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.initFonts();
this.imageUrls = imageUrls ? imageUrls : []; this.imageUrls = imageUrls ? imageUrls : [];
pdfMake.vfs = await this.initVfs(); pdfMake.vfs = await this.initVfs();
const result = { const result = {
pageSize: 'A4', pageSize: 'A4',
pageMargins: [75, 90, 75, 75], pageOrientation: landscape ? landscape : 'portrait',
pageMargins: customMargins || [75, 90, 75, 75],
defaultStyle: { defaultStyle: {
font: 'PdfFont', font: 'PdfFont',
fontSize: this.configService.instant('general_export_pdf_fontsize') 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 // TODO: option for no footer, wherever this can be defined
footer: (currentPage, pageCount) => { footer: (currentPage, pageCount) => {
return this.getFooter(currentPage, pageCount); return this.getFooter(
currentPage,
pageCount,
customMargins ? [customMargins[0], customMargins[2]] : null
);
}, },
info: metadata, info: metadata,
content: documentContent, content: documentContent,
@ -188,9 +201,10 @@ export class PdfDocumentService {
/** /**
* Creates the header doc definition for normal PDF documents * Creates the header doc definition for normal PDF documents
* *
* @param lrMargin optional margin overrides
* @returns an object that contains the necessary header definition * @returns an object that contains the necessary header definition
*/ */
private getHeader(): object { private getHeader(lrMargin?: [number, number]): object {
// check for the required logos // check for the required logos
let logoHeaderLeftUrl = this.configService.instant<any>('logo_pdf_header_L').path; let logoHeaderLeftUrl = this.configService.instant<any>('logo_pdf_header_L').path;
let logoHeaderRightUrl = this.configService.instant<any>('logo_pdf_header_R').path; let logoHeaderRightUrl = this.configService.instant<any>('logo_pdf_header_R').path;
@ -247,11 +261,13 @@ export class PdfDocumentService {
}); });
this.imageUrls.push(logoHeaderRightUrl); this.imageUrls.push(logoHeaderRightUrl);
} }
const margin = [lrMargin ? lrMargin[0] : 75, 30, lrMargin ? lrMargin[0] : 75, 10];
// pdfmake order: [left, top, right, bottom]
return { return {
color: '#555', color: '#555',
fontSize: 9, fontSize: 9,
margin: [75, 30, 75, 10], // [left, top, right, bottom] margin: margin,
columns: columns, columns: columns,
columnGap: 10 columnGap: 10
}; };
@ -265,9 +281,10 @@ export class PdfDocumentService {
* *
* @param currentPage holds the number of the current page * @param currentPage holds the number of the current page
* @param pageCount holds the page count * @param pageCount holds the page count
* @param lrMargin optionally overriding the margins
* @returns the footer doc definition * @returns the footer doc definition
*/ */
private getFooter(currentPage: number, pageCount: number): object { private getFooter(currentPage: number, pageCount: number, lrMargin?: [number, number]): object {
const columns = []; const columns = [];
let logoContainerWidth: string; let logoContainerWidth: string;
let pageNumberPosition: string; let pageNumberPosition: string;
@ -330,8 +347,9 @@ export class PdfDocumentService {
this.imageUrls.push(logoFooterRightUrl); this.imageUrls.push(logoFooterRightUrl);
} }
const margin = [lrMargin ? lrMargin[0] : 75, 0, lrMargin ? lrMargin[0] : 75, 10];
return { return {
margin: [75, 0, 75, 10], margin: margin,
columns: columns, columns: columns,
columnGap: 10 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. * Downloads a pdf with the ballot papet page definitions.
* *

View File

@ -4,10 +4,10 @@
<h2 translate>Call list</h2> <h2 translate>Call list</h2>
</div> </div>
<!-- Export --> <!-- Export menu -->
<div class="menu-slot"> <div class="menu-slot">
<button mat-icon-button (click)="csvExportCallList()"> <button type="button" mat-icon-button [matMenuTriggerFor]="downloadMenu">
<mat-icon>archive</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</div> </div>
</os-head-bar> </os-head-bar>
@ -15,7 +15,25 @@
<mat-card> <mat-card>
<button mat-button (click)="expandCollapseAll(true)">{{ 'Expand all' | translate }}</button> <button mat-button (click)="expandCollapseAll(true)">{{ 'Expand all' | translate }}</button>
<button mat-button (click)="expandCollapseAll(false)">{{ 'Collapse all' | translate }}</button> <button mat-button (click)="expandCollapseAll(false)">{{ 'Collapse all' | translate }}</button>
<os-sorting-tree #sorter (sort)="sort($event)" parentIdKey="sort_parent_id" <os-sorting-tree
weightKey="weight" [modelsObservable]="motionsObservable" [expandCollapseAll]="expandCollapse"> #sorter
(sort)="sort($event)"
parentIdKey="sort_parent_id"
weightKey="weight"
[modelsObservable]="motionsObservable"
[expandCollapseAll]="expandCollapse"
>
</os-sorting-tree> </os-sorting-tree>
</mat-card> </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 { BaseViewComponent } from '../../../base/base-view';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; 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 { ViewMotion } from '../../models/view-motion';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; 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. * Sort view for the call list.
@ -46,7 +48,8 @@ export class CallListComponent extends BaseViewComponent {
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private motionRepo: MotionRepositoryService, private motionRepo: MotionRepositoryService,
private motionCsvExport: MotionCsvExportService private motionCsvExport: MotionCsvExportService,
private motionPdfExport: MotionPdfExportService
) { ) {
super(title, translate, matSnackBar); super(title, translate, matSnackBar);
@ -83,4 +86,11 @@ export class CallListComponent extends BaseViewComponent {
public csvExportCallList(): void { public csvExportCallList(): void {
this.motionCsvExport.exportCallList(this.motions); 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); 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 { 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 { 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 { 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 * 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 {}; 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 : '' }
];
}
} }