Expenses Report

This sample shows how to create an expense report and save it to a PDF file using the FlexGridPdfConverter and PdfDocument API. The sample creates two FlexGrid instances (data & footer) internally and exports them to a document using the FlexGridPdfConverter.draw method in order to display an expense table. drawText and other vector graphics methods of PdfDocument are used to draw captions and expected handwritten entries.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wijmo from '@grapecity/wijmo'; import * as grid from '@grapecity/wijmo.grid'; import * as pdf from '@grapecity/wijmo.pdf'; import * as gridPdf from '@grapecity/wijmo.grid.pdf'; import { getEmployees } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { document.querySelector('#btnExport').addEventListener('click', () => { let doc = new pdf.PdfDocument({ header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (sender, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // getEmployees().forEach((employee, i, arr) => { drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); }); } // function drawEmployee(doc, employee) { let tot = employee.expenses.totals; let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()); // let minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', width: 105 }, { header: 'Description', binding: 'description', format: 'c', width: 105 }, { header: 'Hotel', binding: 'hotel', format: 'c', width: 105 }, { header: 'Transport', binding: 'transport', format: 'c', width: 105 }, { header: 'Meal', binding: 'meal', format: 'c', width: 105 }, { header: 'Fuel', binding: 'fuel', format: 'c', width: 105 }, { header: 'Misc', binding: 'misc', format: 'c', width: 105 }, { header: 'Total', binding: 'total', format: 'c', width: 105 } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'), thinPen = new pdf.PdfPen('#000000', 0.5); // let flexGrid, footer; // try { // * setup FlexGrid * flexGrid = new grid.FlexGrid('#flexGrid'); footer = new grid.FlexGrid('#flexGridFooter'); // flexGrid.initialize({ autoGenerateColumns: false, allowMerging: grid.AllowMerging.All, columns: columns, headersVisibility: grid.HeadersVisibility.Column, itemsSource: expenses }); // footer.initialize({ allowMerging: grid.AllowMerging.All, autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.None, columns: columns, itemsSource: [{ date: null, description: null, hotel: tot.hotel, transport: tot.transport, meal: tot.meal, fuel: tot.fuel, misc: tot.misc, total: tot.total }] }); // footer.cells.setCellData(0, 'date', 'Total', false); footer.cells.setCellData(0, 'description', 'Total', false); footer.cells.rows[0].allowMerging = true; // // * draw captions * doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); // doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); // doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(maxDate, wijmo.DataType.String, 'd')); // doc.moveDown(2); // let y = doc.y; doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); // doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); // doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); // y = doc.y; doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); // doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); // doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); // doc.moveDown(2); // // draw FlexGrid gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // // draw footer gridPdf.FlexGridPdfConverter.draw(footer, doc, doc.width, null, { styles: { cellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd', borderColor: '#c6c6c6' } } }); // doc.moveDown(2); // // * draw captions * doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total - employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total, wijmo.DataType.String, 'c')); doc.moveDown(2); // checkLineAvailable(doc); // y = doc.y; let sz = doc.drawText('Employee signature: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width + 5, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); doc.moveDown(); // checkLineAvailable(doc); // y = doc.y; sz = doc.drawText('Approved by: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); } finally { if (flexGrid) { flexGrid.dispose(); } // if (footer) { footer.dispose(); } } } // function checkLineAvailable(doc) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expenses report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" id="btnExport">Export</button> <!-- FlexGrid --> <div id="flexGrid" class="grid"></div> <div id="flexGridFooter" class="gridFooter"></div> </div> </body> </html> // export function getEmployees() { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: getExpenses() } ]; } // function getExpenses() { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; } body { margin-bottom: 24px; } .grid, .gridFooter { visibility: hidden; } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@grapecity/wijmo'; import '@grapecity/wijmo.chart.render'; import * as grid from '@grapecity/wijmo.grid'; import * as pdf from '@grapecity/wijmo.pdf'; import * as gridPdf from '@grapecity/wijmo.grid.pdf'; // import { Component, Inject, enableProdMode, NgModule, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { DataService, IEmployee, IExpenseDetails } from './app.data'; import { WjGridModule } from '@grapecity/wijmo.angular2.grid'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { constructor(@Inject(DataService) private dataService: DataService) { } // export() { let doc = new pdf.PdfDocument({ header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (sender: pdf.PdfDocument, args: pdf.PdfDocumentEndedEventArgs) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // this.dataService.getEmployees().forEach((employee, i, arr) => { this.drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); } // private drawEmployee(doc: pdf.PdfDocument, employee: IEmployee) { let tot = employee.expenses.totals; let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()); // let minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', width: 105 }, { header: 'Description', binding: 'description', format: 'c', width: 105 }, { header: 'Hotel', binding: 'hotel', format: 'c', width: 105 }, { header: 'Transport', binding: 'transport', format: 'c', width: 105 }, { header: 'Meal', binding: 'meal', format: 'c', width: 105 }, { header: 'Fuel', binding: 'fuel', format: 'c', width: 105 }, { header: 'Misc', binding: 'misc', format: 'c', width: 105 }, { header: 'Total', binding: 'total', format: 'c', width: 105 } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'), thinPen = new pdf.PdfPen('#000000', 0.5); // let flexGrid: grid.FlexGrid, footer: grid.FlexGrid; // try { // * setup FlexGrid * flexGrid = new grid.FlexGrid('#flexGrid'); footer = new grid.FlexGrid('#flexGridFooter'); // flexGrid.initialize({ autoGenerateColumns: false, allowMerging: grid.AllowMerging.All, columns: columns, headersVisibility: grid.HeadersVisibility.Column, itemsSource: expenses }); // footer.initialize({ allowMerging: grid.AllowMerging.All, autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.None, columns: columns, itemsSource: <IExpenseDetails[]>[{ date: null, description: null, hotel: tot.hotel, transport: tot.transport, meal: tot.meal, fuel: tot.fuel, misc: tot.misc, total: tot.total }] }); // footer.cells.setCellData(0, 'date', 'Total', false); footer.cells.setCellData(0, 'description', 'Total', false); footer.cells.rows[0].allowMerging = true; // // * draw captions * doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); // doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); // doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(maxDate, wijmo.DataType.String, 'd')); // doc.moveDown(2); // let y = doc.y; doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); // doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); // doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); // y = doc.y; doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); // doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); // doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); // doc.moveDown(2); // // draw FlexGrid gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // // draw footer gridPdf.FlexGridPdfConverter.draw(footer, doc, doc.width, null, { styles: { cellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd', borderColor: '#c6c6c6' } } }); // doc.moveDown(2); // // * draw captions * doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total - employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total, wijmo.DataType.String, 'c')); doc.moveDown(2); // this.checkLineAvailable(doc); // y = doc.y; let sz = doc.drawText('Employee signature: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width + 5, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); doc.moveDown(); // this.checkLineAvailable(doc); // y = doc.y; sz = doc.drawText('Approved by: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); } finally { if (flexGrid) { flexGrid.dispose(); } // if (footer) { footer.dispose(); } } } // private checkLineAvailable(doc: pdf.PdfDocument) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } } } // @NgModule({ imports: [WjGridModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expenses report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html> <div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" (click)="export()">Export</button> <!-- FlexGrid --> <div id="flexGrid" class="grid"></div> <div id="flexGridFooter" class="gridFooter"></div> </div> import { Injectable } from '@angular/core'; // export interface IExpense { hotel: number; transport: number; fuel: number; meal: number; misc: number; total: number; } // export interface IExpenseDetails extends IExpense { date: Date; description: string; } // export interface IExpenses { items: IExpenseDetails[]; totals: IExpense; } // export interface IEmployee { id: string; name: string; department: string; position: string; ssn: string; manager: string; purpose: string; attachment: boolean; advance: number; expenses: IExpenses; } // @Injectable() export class DataService { getEmployees(): IEmployee[] { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: this.getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: this.getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: this.getExpenses() } ]; } // private getExpenses(): IExpenses { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret: IExpenses = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item: IExpenseDetails = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; } } body { margin-bottom: 24px; } .grid, .gridFooter { visibility: hidden; } <template> <div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" @click="exportPDF">Export</button> <!-- FlexGrid --> <div id="flexGrid" class="grid"></div> <div id="flexGridFooter" class="gridFooter"></div> </div> </template> <script> import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import Vue from 'vue'; import { getEmployees } from './data'; import { WjGridModule } from '@grapecity/wijmo.vue2.grid'; import * as wijmo from '@grapecity/wijmo'; import '@grapecity/wijmo.chart.render'; import * as grid from '@grapecity/wijmo.grid'; import * as pdf from '@grapecity/wijmo.pdf'; import * as gridPdf from '@grapecity/wijmo.grid.pdf'; new Vue({ el: '#app', data: { }, methods:{ exportPDF() { let doc = new pdf.PdfDocument({ header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (sender, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // getEmployees().forEach((employee, i, arr) => { this.drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); }, drawEmployee(doc, employee) { let tot = employee.expenses.totals; let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()); // let minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', width: 105 }, { header: 'Description', binding: 'description', format: 'c', width: 105 }, { header: 'Hotel', binding: 'hotel', format: 'c', width: 105 }, { header: 'Transport', binding: 'transport', format: 'c', width: 105 }, { header: 'Meal', binding: 'meal', format: 'c', width: 105 }, { header: 'Fuel', binding: 'fuel', format: 'c', width: 105 }, { header: 'Misc', binding: 'misc', format: 'c', width: 105 }, { header: 'Total', binding: 'total', format: 'c', width: 105 } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'), thinPen = new pdf.PdfPen('#000000', 0.5); // let flexGrid, footer; // try { // * setup FlexGrid * flexGrid = new grid.FlexGrid('#flexGrid'); footer = new grid.FlexGrid('#flexGridFooter'); // flexGrid.initialize({ autoGenerateColumns: false, allowMerging: grid.AllowMerging.All, columns: columns, headersVisibility: grid.HeadersVisibility.Column, itemsSource: expenses }); // footer.initialize({ allowMerging: grid.AllowMerging.All, autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.None, columns: columns, itemsSource: [{ date: null, description: null, hotel: tot.hotel, transport: tot.transport, meal: tot.meal, fuel: tot.fuel, misc: tot.misc, total: tot.total }] }); // footer.cells.setCellData(0, 'date', 'Total', false); footer.cells.setCellData(0, 'description', 'Total', false); footer.cells.rows[0].allowMerging = true; // // * draw captions * doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); // doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); // doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(maxDate, wijmo.DataType.String, 'd')); // doc.moveDown(2); // let y = doc.y; doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); // doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); // doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); // y = doc.y; doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); // doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); // doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); // doc.moveDown(2); // // draw FlexGrid gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // // draw footer gridPdf.FlexGridPdfConverter.draw(footer, doc, doc.width, null, { styles: { cellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd', borderColor: '#c6c6c6' } } }); // doc.moveDown(2); // // * draw captions * doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total - employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(employee.advance, wijmo.DataType.String, 'c')); // doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total, wijmo.DataType.String, 'c')); doc.moveDown(2); // this.checkLineAvailable(doc); // y = doc.y; let sz = doc.drawText('Employee signature: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width + 5, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); doc.moveDown(); // this.checkLineAvailable(doc); // y = doc.y; sz = doc.drawText('Approved by: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); } finally { if (flexGrid) { flexGrid.dispose(); } // if (footer) { footer.dispose(); } } }, checkLineAvailable(doc) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } } } }); </script> <style> body { margin-bottom: 24px; } .grid, .gridFooter { visibility: hidden; } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expenses report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> export function getEmployees() { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: getExpenses() } ]; } // function getExpenses() { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; } import "@grapecity/wijmo.styles/wijmo.css"; import "bootstrap.css"; import "./app.css"; // import * as React from 'react'; import * as ReactDOM from 'react-dom'; // import * as wjcCore from "@grapecity/wijmo"; import * as gridPdf from "@grapecity/wijmo.grid.pdf"; import * as pdf from '@grapecity/wijmo.pdf'; import * as grid from '@grapecity/wijmo.grid'; import { getEmployees } from "./data"; class App extends React.Component { render() { return <div className="container-fluid"> <button className="btn btn-default" onClick={this.exportPDF.bind(this)}>Export</button> <div id="flexGrid" className="grid"></div> <div id="flexGridFooter" className="gridFooter"></div> </div>; } exportPDF() { let doc = new pdf.PdfDocument({ header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (sender, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // getEmployees().forEach((employee, i, arr) => { this.drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); } drawEmployee(doc, employee) { let tot = employee.expenses.totals; let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()); // let minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', width: 105 }, { header: 'Description', binding: 'description', format: 'c', width: 105 }, { header: 'Hotel', binding: 'hotel', format: 'c', width: 105 }, { header: 'Transport', binding: 'transport', format: 'c', width: 105 }, { header: 'Meal', binding: 'meal', format: 'c', width: 105 }, { header: 'Fuel', binding: 'fuel', format: 'c', width: 105 }, { header: 'Misc', binding: 'misc', format: 'c', width: 105 }, { header: 'Total', binding: 'total', format: 'c', width: 105 } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'), thinPen = new pdf.PdfPen('#000000', 0.5); // let flexGrid, footer; // try { // * setup FlexGrid * flexGrid = new grid.FlexGrid('#flexGrid'); footer = new grid.FlexGrid('#flexGridFooter'); // flexGrid.initialize({ autoGenerateColumns: false, allowMerging: grid.AllowMerging.All, columns: columns, headersVisibility: grid.HeadersVisibility.Column, itemsSource: expenses }); // footer.initialize({ allowMerging: grid.AllowMerging.All, autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.None, columns: columns, itemsSource: [{ date: null, description: null, hotel: tot.hotel, transport: tot.transport, meal: tot.meal, fuel: tot.fuel, misc: tot.misc, total: tot.total }] }); // footer.cells.setCellData(0, 'date', 'Total', false); footer.cells.setCellData(0, 'description', 'Total', false); footer.cells.rows[0].allowMerging = true; // // * draw captions * doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); // doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(minDate, wjcCore.DataType.String, 'd')); // doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(maxDate, wjcCore.DataType.String, 'd')); // doc.moveDown(2); // let y = doc.y; doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); // doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); // doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); // y = doc.y; doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); // doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); // doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); // doc.moveDown(2); // // draw FlexGrid gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // // draw footer gridPdf.FlexGridPdfConverter.draw(footer, doc, doc.width, null, { styles: { cellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd', borderColor: '#c6c6c6' } } }); // doc.moveDown(2); // // * draw captions * doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(tot.total - employee.advance, wjcCore.DataType.String, 'c')); // doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(employee.advance, wjcCore.DataType.String, 'c')); // doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(tot.total, wjcCore.DataType.String, 'c')); doc.moveDown(2); // this.checkLineAvailable(doc); // y = doc.y; let sz = doc.drawText('Employee signature: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width + 5, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); doc.moveDown(); // this.checkLineAvailable(doc); // y = doc.y; sz = doc.drawText('Approved by: ', 0, y); doc.paths.moveTo(sz.size.width, doc.y).lineTo(sz.size.width + 150, doc.y).stroke(thinPen); sz = doc.drawText('Date: ', 300, y); doc.paths.moveTo(300 + sz.size.width, doc.y).lineTo(300 + sz.size.width + 100, doc.y).stroke(thinPen); } finally { if (flexGrid) { flexGrid.dispose(); } // if (footer) { footer.dispose(); } } } checkLineAvailable(doc) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } } } ReactDOM.render(<App />, document.getElementById('app')); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Grapecity Wijmo OLAP Pivot Chart Overview</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html> body { margin-bottom: 24px; } .grid, .gridFooter { visibility: hidden; } export function getEmployees() { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: getExpenses() } ]; } // function getExpenses() { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; }