UNPKG

@simplenodeorm/simplenodereport

Version:

Simple Node Report is a React report designer for the simplenodeorm. Simple Node Report allows the user to create, save and run WYSIWYG style HTML reports in simplenodeorm. To use the report designer call the application with the desired simplenodeorm c

677 lines (611 loc) 28.7 kB
/* * Copyright (c) 2019 simplenodeorm.org */ import React from 'react'; import ReactDOM from 'react-dom'; import Toolbar from './Toolbar'; import '../app/App.css'; import config from '../config/appconfig'; import defaults from '../config/defaults'; import {BaseDesignComponent} from './BaseDesignComponent'; import {PreferencesPanel} from './PreferencesPanel'; import {SaveReportPanel} from './SaveReportPanel'; import {HelpButton} from './HelpButton'; import { clearContextMenu, clearDocumentDesignData, copyObject, getContextMenu, getFontHeight, getReportColumn, removeWaitMessage, setDefaultReportObjectSize, getPixelsPerInch, getModalContainer, getDocumentDimensions, getServerContext,getRequestHeaders } from './helpers'; import axios from 'axios'; import {DBDataGridSetupPanel} from "./DBDataGridSetupPanel"; import {LabelSetupPanel} from "./LabelSetupPanel"; import {ImageSetupPanel} from "./ImageSetupPanel"; import {LinkSetupPanel} from "./LinkSetupPanel"; import {EmailSetupPanel} from "./EmailSetupPanel"; import {ShapeSetupPanel} from "./ShapeSetupPanel"; import {CurrentDateSetupPanel} from './CurrentDateSetupPanel'; import {PageNumberSetupPanel} from './PageNumberSetupPanel'; import {ParameterInputPanel} from "@simplenodeorm/simplenodeclientbase/lib/ParameterInputPanel"; import {ChartSetupPanel} from "./ChartSetupPanel"; const cfg = config; const reportObjectLoop = (obj, data) => { return data.map((item) => { if (item === 'chart') { return <li><button>{'chart -'}</button><ul>{reportObjectLoop(obj, config.chartTypes)}</ul></li>; } else { return <li><button onClick={obj.addReportObject} value={item}>{item}</button></li> } }); }; class AppToolbar extends BaseDesignComponent { constructor(props) { super(props); this.newReport = this.newReport.bind(this); this.preferences = this.preferences.bind(this); this.savePreferences = this.savePreferences.bind(this); this.alignLeft = this.alignLeft.bind(this); this.alignTop = this.alignTop.bind(this); this.alignRight = this.alignRight.bind(this); this.alignBottom = this.alignBottom.bind(this); this.alignObject = this.alignObject.bind(this); this.deleteReportObjects = this.deleteReportObjects.bind(this); this.onSave = this.onSave.bind(this); this.onRun = this.onRun.bind(this); this.saveReport = this.saveReport.bind(this); this.initializeNewReport = this.initializeNewReport.bind(this); this.addReportObject = this.addReportObject.bind(this); this.showReportObjectPopup = this.showReportObjectPopup.bind(this); this.alignTextLeft = this.alignTextLeft.bind(this); this.alignTextCenter = this.alignTextCenter.bind(this); this.alignTextRight = this.alignTextRight.bind(this); this.alignText = this.alignText.bind(this); this.addReportObjectToReport = this.addReportObjectToReport.bind(this); this.onReportObjectSelect = this.onReportObjectSelect.bind(this); this.showReportObjectPopup = this.showReportObjectPopup.bind(this); this.showInputPanel = this.showInputPanel.bind(this); this.generateReport = this.generateReport.bind(this); this.showReport = this.showReport.bind(this); this.selectedReportObjectCounter = 0; this.state = { canSave: false, canAddObject: false, itemsSelected: false }; } render() { const {canSave, canAddObject, itemsSelected} = this.state; const menu = [ { text: config.textmsg.filemenuname, items: [ { text: config.textmsg.newmenuname, callback: this.newReport }, { text: config.textmsg.preferencesmenuname, callback: this.preferences } ] } ]; return <div> <Toolbar menu={menu} logo="logo.png"/> <div className="buttonbar"> <button className="button" title={config.textmsg.newreport} onClick={this.newReport}> <img alt={config.textmsg.newreport} src='/images/newreport.png'/> <span className="label">{config.textmsg.new}</span> </button> <button className="button" disabled={!canAddObject} title={config.textmsg.newreportobject} onClick={this.showReportObjectPopup}> {canAddObject && <img alt={config.textmsg.newreportobject} src='/images/newobject.png'/>} {!canAddObject && <img alt={config.textmsg.newreportobject} src='/images/newobject-disabled.png'/>} <span className="label">{config.textmsg.add}</span> </button> <div className="aligntool"> <button className="button" title={config.textmsg.alignleft} disabled={!canSave} onClick={this.alignTextLeft}> {itemsSelected && <img alt={config.textmsg.alignleft} src='/images/align-text-left.png'/>} {!itemsSelected && <img alt={config.textmsg.alignleft} src='/images/align-text-left-disabled.png'/>} </button> <button className="button" title={config.textmsg.aligncenter} disabled={!canSave} onClick={this.alignTextCenter}> {itemsSelected && <img alt={config.textmsg.aligncenter} src='/images/align-text-middle.png'/>} {!itemsSelected && <img alt={config.textmsg.aligncenter} src='/images/align-text-middle-disabled.png'/>} </button> <button className="button" title={config.textmsg.alignright} disabled={!canSave} onClick={this.alignTextRight}> {itemsSelected && <img alt={config.textmsg.alignright} src='/images/align-text-right.png'/>} {!itemsSelected && <img alt={config.textmsg.alignright} src='/images/align-text-right-disabled.png'/>} </button> </div> <div className="aligntool"> <button className="button" title={config.textmsg.alignobjectleft} disabled={!canSave} onClick={this.alignLeft}> {itemsSelected && <img alt={config.textmsg.alignobjectleft} src='/images/align-left.png'/>} {!itemsSelected && <img alt={config.textmsg.alignobjectleft} src='/images/align-left-disabled.png'/>} </button> <button className="button" title={config.textmsg.alignobjecttop} disabled={!canSave} onClick={this.alignTop}> {itemsSelected && <img alt={config.textmsg.alignobjecttop} src='/images/align-top.png'/>} {!itemsSelected && <img alt={config.textmsg.alignobjecttop} src='/images/align-top-disabled.png'/>} </button> <button className="button" title={config.textmsg.alignobjectright} disabled={!canSave} onClick={this.alignRight}> {itemsSelected && <img alt={config.textmsg.alignobjectright} src='/images/align-right.png'/>} {!itemsSelected && <img alt={config.textmsg.alignobjectright} src='/images/align-right-disabled.png'/>} </button> <button className="button" title={config.textmsg.alignobjectbottom} disabled={!canSave} onClick={this.alignBottom}> {itemsSelected && <img alt={config.textmsg.alignobjectbottom} src='/images/align-bottom.png'/>} {!itemsSelected && <img alt={config.textmsg.alignobjectbottom} src='/images/align-bottom-disabled.png'/>} </button> </div> <button className="button" title={config.textmsg.delete} disabled={!canSave} onClick={this.deleteReportObjects}> {itemsSelected && <img alt={config.textmsg.delete} src='/images/delete.png'/>} {!itemsSelected && <img alt={config.textmsg.delete} src='/images/delete-disabled.png'/>} <span className="label">{config.textmsg.delete}</span> </button> <button className="button" title={config.textmsg.save} disabled={!canSave} onClick={this.onSave}> {canSave && <img alt={config.textmsg.save} src='/images/save.png'/>} {!canSave && <img alt={config.textmsg.save} src='/images/save-disabled.png'/>} <span className="label">{config.textmsg.save}</span> </button> <button className="button" title={config.textmsg.run} disabled={!canSave} onClick={this.onRun}> {canSave && <img alt={config.textmsg.run} src='/images/run.png'/>} {!canSave && <img alt={config.textmsg.run} src='/images/run-disabled.png'/>} <span className="label">{config.textmsg.run}</span> </button> <HelpButton/> </div> </div>; } newReport() { let rc = {left: 200, top: 75, width: 400, height: 400}; let mc = getModalContainer(rc); ReactDOM.render(<PreferencesPanel newDocument={true} onOk={this.initializeNewReport}/>, mc); } alignLeft() { this.alignObject('left'); } alignTop() { this.alignObject('top'); } alignRight() { this.alignObject('right'); } alignBottom() { this.alignObject('bottom'); } alignObject(pos) { for (let i = 0; i < config.pageSections.length; ++i) { let dc = this.props.getDesignPanel().getReportSectionDesignCanvas(config.pageSections[i]); let selectedObjects = dc.getSelectedReportObjects(); let fs = dc.firstSelected; if (!fs) { fs = selectedObjects[0]; } if (selectedObjects && fs) { for (let j = 0; j < selectedObjects.length; ++j) { if (selectedObjects[j] !== fs) { switch (pos) { case 'left': selectedObjects[j].rect.left = fs.rect.left; dc.mountedReportObjects[selectedObjects[j].myIndex].setState({left: selectedObjects[j].rect.left}); break; case 'top': selectedObjects[j].rect.top = fs.rect.top; dc.mountedReportObjects[selectedObjects[j].myIndex].setState({top: selectedObjects[j].rect.top}); break; case 'right': selectedObjects[j].rect.left = (fs.rect.left + fs.rect.width) - selectedObjects[j].rect.width; dc.mountedReportObjects[selectedObjects[j].myIndex].setState({left: selectedObjects[j].rect.left}); break; case 'bottom': selectedObjects[j].rect.top = (fs.rect.top + fs.rect.height) - selectedObjects[j].rect.height; dc.mountedReportObjects[selectedObjects[j].myIndex].setState({top: selectedObjects[j].rect.top}); break; } } } dc.setState(dc.state); } } } alignTextLeft() { this.alignText('left'); } alignTextCenter() { this.alignText('center'); } alignTextRight() { this.alignText('right'); } alignText(pos) { for (let i = 0; i < config.pageSections.length; ++i) { let dc = this.props.getDesignPanel().getReportSectionDesignCanvas(config.pageSections[i]); let selectedObjects = dc.getSelectedReportObjects(); for (let j = 0; j < selectedObjects.length; ++j) { selectedObjects[j].textAlign = pos; let comp = dc.mountedReportObjects[selectedObjects[j].myIndex]; comp.setState(comp.state); } dc.setState(dc.state); } } deleteReportObjects() { this.props.getDesignPanel().removeSelectedReportObjects(); } initializeNewReport(settings) { clearDocumentDesignData(); let dim = getDocumentDimensions(settings.documentSize, settings.orientation); let ppi = getPixelsPerInch(); let headerHeight; let footerHeight; if (dim[0] < 2) { headerHeight = ppi/4; } else if (dim[0] < 5) { headerHeight = ppi/2; } else { headerHeight = ppi; } if (dim[1] < 2) { footerHeight = ppi/4; } else if (dim[1] < 5) { footerHeight = ppi/2; } else { footerHeight = ppi; } let doc = { document: { reportName: settings.reportName, documentWidth: dim[0] * ppi, documentHeight: dim[1] * ppi, headerHeight: headerHeight, footerHeight: footerHeight, documentSize: settings.documentSize, orientation: settings.orientation, margins: [ ppi * settings.marginLeft, ppi * settings.marginTop, ppi * settings.marginRight, ppi * settings.marginBottom], reportObjects: [] } }; for (let i = 0; i < config.defaultPreferenceNames.length; ++i) { if (!doc.document[config.defaultPreferenceNames[i]]) { doc.document[config.defaultPreferenceNames[i]] = defaults[config.defaultPreferenceNames[i]]; } } doc.document.queryDocumentId = settings.queryDocumentId; this.props.getDesignPanel().removeAllReportObjects(); document.designData = ''; this.props.getDesignPanel().refreshLayout(doc); this.setState({canSave: true, canAddObject: true}); } onSave() { let rc = {left: 200, top: 50, width: 450, height: 425}; let mc = getModalContainer(rc); ReactDOM.render(<SaveReportPanel onOk={this.saveReport}/>, mc); } onRun() { const curcomp = this; const httpcfg = { headers: getRequestHeaders() }; axios.get(getServerContext() + '/api/report/userinputrequired/' + document.designData.currentReport.queryDocumentId, httpcfg) .then((response) => { if (response.status === 200) { if (response.data.userInputRequired) { curcomp.showInputPanel(response.data.whereComparisons); } else { curcomp.generateReport(); } } else { curcomp.props.setStatus('Error: HTTP status ' + response.status, true); } }) .catch((err) => { curcomp.props.setStatus(err.toString(), true); }); } generateReport(params) { this.showWaitMessage(cfg.textmsg.runningreportmsg.replace('?', document.designData.currentReport.reportName)); const curcomp = this; const httpcfg = { headers: getRequestHeaders() }; let inputParams; if (params) { inputParams = params.parameters; } document.designData.currentReport.pixelsPerInch = getPixelsPerInch(); axios.post(getServerContext() + '/api/report/runfordesign', {report: {document: document.designData.currentReport}, parameters: inputParams}, httpcfg) .then((response) => { if (response.status === 200) { curcomp.showReport(response.data); } else { curcomp.props.setStatus('Error: HTTP status ' + response.status, true); } removeWaitMessage(); }) .catch((err) => { removeWaitMessage(); curcomp.props.setStatus(err.toString(), true); }); } showInputPanel(content) { const httpcfg = { headers: getRequestHeaders() }; const curobj = this; const cntnt = content; axios.get(getServerContext() + '/api/report/lookupdefinitions', httpcfg) .then((response) => { if (response.status === 200) { let height = (150 + (22 * cntnt.length)); let rc = {left: 150, top: 100, width: 375, height: height}; let mc = getModalContainer(rc); ReactDOM.render(<ParameterInputPanel config={cfg} lookupDefinitions={response.data} whereComparisons={cntnt} onOk={curobj.generateReport} onCancel={curobj.cancelReport}/>, mc); } else { curobj.props.setStatus(response.statusText, true); } }) .catch((err) => { curobj.props.setStatus(err.toString(), true); }); } showReport(data) { let chartscript = ''; let script = ''; if (data.chartData) { chartscript = '<script src="' + data.chartData.chartjsurl + '"/></script>'; for (let i = 0; i < data.chartData.charts.length; ++i) { script += 'new Chart(document.getElementById("' + data.chartData.charts[i].canvasId + '").getContext("2d"),' + JSON.stringify(data.chartData.charts[i]) + ');\n' } script = '<script>' + script + 'window.stop();</script>' } else { script = '<script>window.stop();</script>' } let myWindow = window.open("", "_blank", "titlebar=yes,toolbar=yes,scrollbars=yes,resizable=yes,top=100,left=100,width=600,height=800"); myWindow.document.write('<html><head><style>' + data.style + '</style>' + chartscript + '</head><body style="background-color: #202020">' + data.html + script + '</body></html>'); } saveReport(params) { this.showWaitMessage(cfg.textmsg.savingreportmsg); const curcomp = this; const httpcfg = { headers: getRequestHeaders() }; let doc = this.getReportDocument(params); let validObjects = []; for(let i = 0; i < doc.document.reportObjects.length; ++i) { if (!doc.document.reportObjects[i].removed) { doc.document.reportObjects[i].selected = false; validObjects.push(doc.document.reportObjects[i]); } } if (validObjects.length > 0) { doc.document.reportObjects.length = 0; for (let i = 0; i < validObjects.length; ++i) { doc.document.reportObjects.push(validObjects[i]); } } doc.document.pixelsPerInch = getPixelsPerInch(); axios.post(getServerContext() + '/api/report/save', doc, httpcfg) .then((response) => { if (response.status === 200) { curcomp.props.setStatus('report saved', false); removeWaitMessage(); curcomp.props.reloadDocuments(); } else { removeWaitMessage(); curcomp.props.setStatus(response.statusText, true); } }) .catch((err) => { curcomp.props.setStatus('' + err, true); removeWaitMessage(); }); } preferences() { let rc = {left: 200, top: 75, width: 400, height: 350}; let mc = getModalContainer(rc); ReactDOM.render(<PreferencesPanel onOk={this.savePreferences}/>, mc); } savePreferences(results) { localStorage.setItem('preferences', JSON.stringify(results)); } showReportObjectPopup(e) { const curobj = this; const cm = getContextMenu({target: curobj, event: e, yOffset: 20}); ReactDOM.render(<ul>{reportObjectLoop(curobj, config.reportObjectTypes)}</ul>, cm); } addReportObject(e) { clearContextMenu(); this.showReportObjectSetupPanel(e.target.value); } onReportObjectSelect(selected) { let saveCount = this.selectedReportObjectCounter; if (selected) { this.selectedReportObjectCounter++; } else { this.selectedReportObjectCounter--; this.selectedReportObjectCounter = Math.max(0, this.selectedReportObjectCounter); } if ((saveCount > 0) && (this.selectedReportObjectCounter === 0)) { this.setState({itemsSelected: false}) } else if ((saveCount === 0) && (this.selectedReportObjectCounter > 0)) { this.setState({itemsSelected: true}) } } showReportObjectSetupPanel(type, reportObject) { let rc; let mc; if (!reportObject) { reportObject = { objectType: type, labelText: config.textmsg.defaultreportlabeltext, textAlign: "right", rect: '' }; } else { reportObject = copyObject(reportObject); } switch(type) { case 'dbdata': rc = {left: 175, top: 50, width: 600, height: 450}; mc = getModalContainer(rc); ReactDOM.render(<DBDataGridSetupPanel getDesignPanel={this.props.getDesignPanel} onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'label': rc = {left: 175, top: 50, width: 300, height: 375}; mc = getModalContainer(rc); ReactDOM.render(<LabelSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'image': rc = {left: 175, top: 50, width: 375, height: 250}; mc = getModalContainer(rc); ReactDOM.render(<ImageSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'link': rc = {left: 175, top: 50, width: 375, height: 450}; mc = getModalContainer(rc); ReactDOM.render(<LinkSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'email': rc = {left: 175, top: 50, width: 375, height: 450}; mc = getModalContainer(rc); ReactDOM.render(<EmailSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'shape': rc = {left: 175, top: 50, width: 275, height: 300}; mc = getModalContainer(rc); ReactDOM.render(<ShapeSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'current date': rc = {left: 175, top: 50, width: 300, height: 375}; mc = getModalContainer(rc); ReactDOM.render(<CurrentDateSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'page number': rc = {left: 175, top: 50, width: 300, height: 375}; mc = getModalContainer(rc); ReactDOM.render(<PageNumberSetupPanel onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; case 'bar': case 'horizontalBar': case 'line': case 'pie': case 'doughnut': case 'polarArea': case 'radar': case 'scatter': reportObject.objectType = 'chart'; reportObject.chartType = type; rc = {left: 175, top: 50, width: 500, height: 425}; mc = getModalContainer(rc); ReactDOM.render(<ChartSetupPanel getDesignPanel={this.props.getDesignPanel} onOk={this.addReportObjectToReport} reportObject={reportObject}/>, mc); break; } } addReportObjectToReport(reportObject) { let designPanel = this.props.getDesignPanel(); if (!document.designData.currentReport.reportObjects) { document.designData.currentReport.reportObjects = []; } if ((reportObject.objectType === 'dbdata') && (reportObject.displayFormat > 2)) { let height = getFontHeight(reportObject.dataFontSettings.font, reportObject.dataFontSettings.fontSize) + config.defaulttablecellpadding; let ypos = 20; let pb = reportObject.pageBreakController; reportObject.pageBreakController = false; let id = document.designData.currentReport.reportObjects.length; for (let i = 0; i < reportObject.reportColumns.length; ++i) { if (reportObject.reportColumns[i].displayResult) { let dbcol = copyObject(getReportColumn(reportObject.reportColumns[i].key)); if (dbcol) { let rol = { objectType: 'label', reportSection: reportObject.reportSection, labelText: dbcol.name, textAlign: 'right', fontSettings: reportObject.headerFontSettings, rect: {top: ypos, left: 20, height: height, width:100} }; let ro = { objectType: 'dbcol', reportSection: reportObject.reportSection, columnPath: dbcol.path, columnName: dbcol.name, displayFormat: reportObject.displayFormat, textAlign: reportObject.reportColumns[i].textAlign, fontSettings: reportObject.dataFontSettings, rect: {top: ypos, left: 125, height: height, width:100} }; rol.id = id++; document.designData.currentReport.reportObjects.push(rol); designPanel.addReportObject(rol); ro.id = id++; document.designData.currentReport.reportObjects.push(ro); designPanel.addReportObject(ro); ypos += (height+5) } } } if (pb) { let ro = document.designData.currentReport.reportObjects[document.designData.currentReport.reportObjects.length - 1]; ro.pageBreakController = true; designPanel.updatePageBreak(ro); } } else { setDefaultReportObjectSize(designPanel, reportObject); if (reportObject.pageBreakController) { designPanel.updatePageBreak(reportObject) } reportObject.id = document.designData.currentReport.reportObjects.length; document.designData.currentReport.reportObjects.push(reportObject); designPanel.addReportObject(reportObject); } } } export {AppToolbar};