mirror of
https://github.com/aclindsa/moneygo.git
synced 2024-12-25 23:23:21 -05:00
Basic Report UI complete!
This commit is contained in:
parent
5dff27e7f7
commit
9844785b8d
6
Makefile
6
Makefile
@ -1,6 +1,6 @@
|
||||
JS_SOURCES = $(wildcard js/*.js) $(wildcard js/*/*.js)
|
||||
|
||||
all: static/bundle.js static/react-widgets security_templates.go
|
||||
all: static/bundle.js static/react-widgets static/codemirror/codemirror.css security_templates.go
|
||||
|
||||
node_modules:
|
||||
npm install
|
||||
@ -11,6 +11,10 @@ static/bundle.js: $(JS_SOURCES) node_modules
|
||||
static/react-widgets: node_modules/react-widgets/dist node_modules
|
||||
rsync -a node_modules/react-widgets/dist/ static/react-widgets/
|
||||
|
||||
static/codemirror/codemirror.css: node_modules/codemirror/lib/codemirror.js node_modules
|
||||
mkdir -p static/codemirror
|
||||
cp node_modules/codemirror/lib/codemirror.css static/codemirror/codemirror.css
|
||||
|
||||
security_templates.go: cusip_list.csv
|
||||
./scripts/gen_security_list.py > security_templates.go
|
||||
|
||||
|
@ -138,9 +138,9 @@ function create(report) {
|
||||
if (e.isError()) {
|
||||
dispatch(ErrorActions.serverError(e));
|
||||
} else {
|
||||
var a = new Report();
|
||||
a.fromJSON(data);
|
||||
dispatch(reportCreated(a));
|
||||
var r = new Report();
|
||||
r.fromJSON(data);
|
||||
dispatch(reportCreated(r));
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, status, error) {
|
||||
@ -165,9 +165,10 @@ function update(report) {
|
||||
if (e.isError()) {
|
||||
dispatch(ErrorActions.serverError(e));
|
||||
} else {
|
||||
var a = new Report();
|
||||
a.fromJSON(data);
|
||||
dispatch(reportUpdated(a));
|
||||
var r = new Report();
|
||||
r.fromJSON(data);
|
||||
dispatch(reportUpdated(r));
|
||||
dispatch(tabulate(r));
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, status, error) {
|
||||
|
@ -1,9 +1,25 @@
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
|
||||
var Col = ReactBootstrap.Col;
|
||||
var Form = ReactBootstrap.Form;
|
||||
var FormGroup = ReactBootstrap.FormGroup;
|
||||
var FormControl = ReactBootstrap.FormControl;
|
||||
var ControlLabel = ReactBootstrap.ControlLabel;
|
||||
var Button = ReactBootstrap.Button;
|
||||
var ButtonGroup = ReactBootstrap.ButtonGroup;
|
||||
var ButtonToolbar = ReactBootstrap.ButtonToolbar;
|
||||
var Glyphicon = ReactBootstrap.Glyphicon;
|
||||
var Panel = ReactBootstrap.Panel;
|
||||
var Modal = ReactBootstrap.Modal;
|
||||
var ProgressBar = ReactBootstrap.ProgressBar;
|
||||
|
||||
var Combobox = require('react-widgets').Combobox;
|
||||
|
||||
var CodeMirror = require('react-codemirror');
|
||||
require('codemirror/mode/lua/lua');
|
||||
|
||||
var StackedBarChart = require('../components/StackedBarChart');
|
||||
var PieChart = require('../components/PieChart');
|
||||
@ -12,13 +28,121 @@ var models = require('../models')
|
||||
var Report = models.Report;
|
||||
var Tabulation = models.Tabulation;
|
||||
|
||||
class AddEditReportModal extends React.Component {
|
||||
getInitialState(props) {
|
||||
var s = {
|
||||
reportid: -1,
|
||||
name: "",
|
||||
lua: ""
|
||||
};
|
||||
if (props && props.editReport != null) {
|
||||
s.reportid = props.editReport.ReportId;
|
||||
s.name = props.editReport.Name;
|
||||
s.lua = props.editReport.Lua;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.state = this.getInitialState();
|
||||
this.onCancel = this.handleCancel.bind(this);
|
||||
this.onNameChange = this.handleNameChange.bind(this);
|
||||
this.onLuaChange = this.handleLuaChange.bind(this);
|
||||
this.onSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.show && !this.props.show) {
|
||||
this.setState(this.getInitialState(nextProps));
|
||||
}
|
||||
}
|
||||
handleCancel() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
}
|
||||
handleNameChange() {
|
||||
this.setState({
|
||||
name: ReactDOM.findDOMNode(this.refs.name).value,
|
||||
});
|
||||
}
|
||||
handleLuaChange(lua) {
|
||||
this.setState({
|
||||
lua: lua
|
||||
});
|
||||
}
|
||||
handleSubmit() {
|
||||
var r = new Report();
|
||||
|
||||
if (this.props.editReport != null)
|
||||
r.ReportId = this.state.reportid;
|
||||
r.Name = this.state.name;
|
||||
r.Lua = this.state.lua;
|
||||
|
||||
if (this.props.onSubmit != null)
|
||||
this.props.onSubmit(r);
|
||||
}
|
||||
render() {
|
||||
var headerText = (this.props.editReport != null) ? "Edit" : "Create New";
|
||||
var buttonText = (this.props.editReport != null) ? "Save Changes" : "Create Report";
|
||||
|
||||
var codeMirrorOptions = {
|
||||
lineNumbers: true,
|
||||
mode: 'lua',
|
||||
};
|
||||
return (
|
||||
<Modal show={this.props.show} onHide={this.onCancel} bsSize="large">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{headerText} Report</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form horizontal onSubmit={this.onSubmit}>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} xs={3}>Name</Col>
|
||||
<Col xs={9}>
|
||||
<FormControl type="text"
|
||||
value={this.state.name}
|
||||
onChange={this.onNameChange}
|
||||
ref="name"/>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} xs={3}>Lua Code</Col>
|
||||
<Col xs={9}>
|
||||
<CodeMirror
|
||||
value={this.state.lua}
|
||||
onChange={this.onLuaChange}
|
||||
options={codeMirrorOptions} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup className="pull-right">
|
||||
<Button onClick={this.onCancel} bsStyle="warning">Cancel</Button>
|
||||
<Button onClick={this.onSubmit} bsStyle="success">{buttonText}</Button>
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReportsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
initialized: false
|
||||
initialized: false,
|
||||
creatingNewReport: false,
|
||||
editingReport: false
|
||||
}
|
||||
this.onSelectSeries = this.handleSelectSeries.bind(this);
|
||||
this.onSelectReport = this.handleSelectReport.bind(this);
|
||||
this.onNewReport = this.handleNewReport.bind(this);
|
||||
this.onEditReport = this.handleEditReport.bind(this);
|
||||
this.onDeleteReport = this.handleDeleteReport.bind(this);
|
||||
this.onCreationCancel = this.handleCreationCancel.bind(this);
|
||||
this.onCreationSubmit = this.handleCreationSubmit.bind(this);
|
||||
this.onEditingCancel = this.handleEditingCancel.bind(this);
|
||||
this.onEditingSubmit = this.handleEditingSubmit.bind(this);
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.onFetchAllReports();
|
||||
@ -27,80 +151,164 @@ class ReportsTab extends React.Component {
|
||||
var selected = nextProps.reports.selected;
|
||||
if (!this.state.initialized) {
|
||||
if (selected == -1 &&
|
||||
nextProps.reports.list.length > 0)
|
||||
nextProps.reports.list.length > 0) {
|
||||
nextProps.onSelectReport(nextProps.reports.map[nextProps.reports.list[0]]);
|
||||
this.setState({initialized: true});
|
||||
} else if (selected != -1 && !nextProps.reports.tabulations.hasOwnProperty(selected)) {
|
||||
nextProps.onTabulateReport(nextProps.reports.map[nextProps.reports.list[0]]);
|
||||
} else if (selected != -1 && nextProps.reports.selectedTabulation == null) {
|
||||
nextProps.onSelectSeries(nextProps.reports.tabulations[nextProps.reports.list[0]]);
|
||||
nextProps.onTabulateReport(nextProps.reports.map[nextProps.reports.list[0]]);
|
||||
this.setState({initialized: true});
|
||||
}
|
||||
} else if (selected != -1 &&
|
||||
nextProps.reports.tabulations.hasOwnProperty(selected) &&
|
||||
nextProps.reports.selectedTabulation == null) {
|
||||
nextProps.onSelectSeries(nextProps.reports.tabulations[selected]);
|
||||
}
|
||||
}
|
||||
handleSelectSeries(series) {
|
||||
if (series == Tabulation.topLevelSeriesName())
|
||||
return;
|
||||
var seriesTraversal = this.props.selectedTabulation.seriesTraversal.slice();
|
||||
var seriesTraversal = this.props.reports.seriesTraversal.slice();
|
||||
seriesTraversal.push(series);
|
||||
var selectedTabulation = this.props.reports.tabulations[this.props.reports.selected];
|
||||
this.props.onSelectSeries(selectedTabulation, seriesTraversal);
|
||||
}
|
||||
handleSelectReport(report) {
|
||||
this.props.onSelectReport(report);
|
||||
if (!this.props.reports.tabulations.hasOwnProperty(report.ReportId))
|
||||
this.props.onTabulateReport(report);
|
||||
}
|
||||
handleNewReport() {
|
||||
this.setState({creatingNewReport: true});
|
||||
}
|
||||
handleEditReport() {
|
||||
this.setState({editingReport: true});
|
||||
}
|
||||
handleDeleteReport() {
|
||||
this.props.onDeleteReport(this.props.reports.map[this.props.reports.selected]);
|
||||
}
|
||||
handleCreationCancel() {
|
||||
this.setState({creatingNewReport: false});
|
||||
}
|
||||
handleCreationSubmit(report) {
|
||||
this.setState({creatingNewReport: false});
|
||||
this.props.onCreateReport(report);
|
||||
}
|
||||
handleEditingCancel() {
|
||||
this.setState({editingReport: false});
|
||||
}
|
||||
handleEditingSubmit(report) {
|
||||
this.setState({editingReport: false});
|
||||
this.props.onUpdateReport(report);
|
||||
}
|
||||
render() {
|
||||
var selectedTabulation = this.props.reports.selectedTabulation;
|
||||
if (!selectedTabulation) {
|
||||
return (
|
||||
<div></div>
|
||||
var reportPanel = [];
|
||||
if (selectedTabulation) {
|
||||
var titleTracks = [];
|
||||
var seriesTraversal = [];
|
||||
|
||||
for (var i = 0; i < this.props.reports.seriesTraversal.length; i++) {
|
||||
var name = this.props.reports.selectedTabulation.Title;
|
||||
if (i > 0)
|
||||
name = this.props.reports.seriesTraversal[i-1];
|
||||
|
||||
// Make a closure for going up the food chain
|
||||
var self = this;
|
||||
var navOnClick = function() {
|
||||
var onSelectSeries = self.props.onSelectSeries;
|
||||
var tabulation = self.props.reports.tabulations[self.props.reports.selected];
|
||||
var mySeriesTraversal = seriesTraversal.slice();
|
||||
return function() {
|
||||
onSelectSeries(tabulation, mySeriesTraversal);
|
||||
};
|
||||
}();
|
||||
titleTracks.push((
|
||||
<Button key={i*2} bsStyle="link"
|
||||
onClick={navOnClick}>
|
||||
{name}
|
||||
</Button>
|
||||
));
|
||||
titleTracks.push((<span key={i*2+1}>/</span>));
|
||||
seriesTraversal.push(this.props.reports.seriesTraversal[i]);
|
||||
}
|
||||
if (titleTracks.length == 0) {
|
||||
titleTracks.push((
|
||||
<Button key={0} bsStyle="link">
|
||||
{this.props.reports.selectedTabulation.Title}
|
||||
</Button>
|
||||
));
|
||||
} else {
|
||||
var i = this.props.reports.seriesTraversal.length-1;
|
||||
titleTracks.push((
|
||||
<Button key={i*2+2} bsStyle="link">
|
||||
{this.props.reports.seriesTraversal[i]}
|
||||
</Button>
|
||||
));
|
||||
}
|
||||
|
||||
if (this.props.reports.selectedTabulation.Labels.length > 1)
|
||||
var report = (
|
||||
<StackedBarChart
|
||||
report={this.props.reports.selectedTabulation}
|
||||
onSelectSeries={this.onSelectSeries}
|
||||
seriesTraversal={this.props.reports.seriesTraversal} />
|
||||
);
|
||||
else
|
||||
var report = (
|
||||
<PieChart
|
||||
report={this.props.reports.selectedTabulation}
|
||||
onSelectSeries={this.onSelectSeries}
|
||||
seriesTraversal={this.props.reports.seriesTraversal} />
|
||||
);
|
||||
|
||||
reportPanel = (
|
||||
<Panel header={titleTracks}>
|
||||
{report}
|
||||
</Panel>
|
||||
);
|
||||
} else if (this.props.reports.selected != -1) {
|
||||
reportPanel = (
|
||||
<Panel header={this.props.reports.map[this.props.reports.selected].Name}>
|
||||
<ProgressBar active now={100} label={"Tabulating Report..."} />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
var titleTracks = [];
|
||||
var seriesTraversal = [];
|
||||
|
||||
for (var i = 0; i < this.props.selectedTabulation.seriesTraversal.length; i++) {
|
||||
var name = this.props.selectedTabulation.tabulation.Title;
|
||||
if (i > 0)
|
||||
name = this.props.selectedTabulation.seriesTraversal[i-1];
|
||||
|
||||
// Make a closure for going up the food chain
|
||||
var self = this;
|
||||
var navOnClick = function() {
|
||||
var onSelectTabulation = self.props.onSelectTabulation;
|
||||
var report = self.props.reports[self.props.selectedTabulation.tabulation.ReportId];
|
||||
var mySeriesTraversal = seriesTraversal.slice();
|
||||
return function() {
|
||||
onSelectTabulation(report, mySeriesTraversal);
|
||||
};
|
||||
}();
|
||||
titleTracks.push((
|
||||
<Button key={i*2} bsStyle="link"
|
||||
onClick={navOnClick}>
|
||||
{name}
|
||||
</Button>
|
||||
));
|
||||
titleTracks.push((<span key={i*2+1}>/</span>));
|
||||
seriesTraversal.push(this.props.selectedTabulation.seriesTraversal[i]);
|
||||
}
|
||||
if (titleTracks.length == 0) {
|
||||
titleTracks.push((
|
||||
<Button key={0} bsStyle="link">
|
||||
{this.props.selectedTabulation.tabulation.Title}
|
||||
</Button>
|
||||
));
|
||||
} else {
|
||||
var i = this.props.selectedTabulation.seriesTraversal.length-1;
|
||||
titleTracks.push((
|
||||
<Button key={i*2+2} bsStyle="link">
|
||||
{this.props.selectedTabulation.seriesTraversal[i]}
|
||||
</Button>
|
||||
));
|
||||
}
|
||||
var noReportSelected = this.props.reports.selected == -1;
|
||||
var selectedReport = -1;
|
||||
if (this.props.reports.map.hasOwnProperty(this.props.reports.selected))
|
||||
selectedReport = this.props.reports.map[this.props.reports.selected];
|
||||
|
||||
return (
|
||||
<Panel header={titleTracks}>
|
||||
<PieChart
|
||||
report={this.props.selectedTabulation.tabulation}
|
||||
onSelectSeries={this.onSelectSeries}
|
||||
seriesTraversal={this.props.selectedTabulation.seriesTraversal} />
|
||||
</Panel>
|
||||
<div>
|
||||
<AddEditReportModal
|
||||
show={this.state.creatingNewReport}
|
||||
onCancel={this.onCreationCancel}
|
||||
onSubmit={this.onCreationSubmit} />
|
||||
<AddEditReportModal
|
||||
show={this.state.editingReport}
|
||||
editReport={selectedReport}
|
||||
onCancel={this.onEditingCancel}
|
||||
onSubmit={this.onEditingSubmit} />
|
||||
<ButtonToolbar>
|
||||
<ButtonGroup>
|
||||
<Button onClick={this.onNewReport} bsStyle="success"><Glyphicon glyph='plus-sign'/> New Report</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Combobox
|
||||
data={this.props.report_list}
|
||||
valueField='ReportId'
|
||||
textField={item => typeof item === 'string' ? item : item.Name}
|
||||
value={selectedReport}
|
||||
onChange={this.onSelectReport}
|
||||
suggest
|
||||
filter='contains'
|
||||
ref="report" />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button onClick={this.onEditReport} bsStyle="primary" disabled={noReportSelected}><Glyphicon glyph='cog'/> Edit Report</Button>
|
||||
<Button onClick={this.onDeleteReport} bsStyle="danger" disabled={noReportSelected}><Glyphicon glyph='trash'/> Delete Report</Button>
|
||||
</ButtonGroup></ButtonToolbar>
|
||||
{reportPanel}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -135,12 +135,12 @@ class StackedBarChart extends React.Component {
|
||||
if (value == 0)
|
||||
continue;
|
||||
if (value > 0) {
|
||||
rectHeight = y(value) - y(0);
|
||||
var rectHeight = y(value) - y(0);
|
||||
positiveSum[j] += rectHeight;
|
||||
rectY = height - y(0) - positiveSum[j];
|
||||
var rectY = height - y(0) - positiveSum[j];
|
||||
} else {
|
||||
rectHeight = y(0) - y(value);
|
||||
rectY = height - y(0) + negativeSum[j];
|
||||
var rectHeight = y(0) - y(value);
|
||||
var rectY = height - y(0) + negativeSum[j];
|
||||
negativeSum[j] += rectHeight;
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,14 @@ var ReportActions = require('../actions/ReportActions');
|
||||
var ReportsTab = require('../components/ReportsTab');
|
||||
|
||||
function mapStateToProps(state) {
|
||||
var report_list = [];
|
||||
for (var reportId in state.reports.map) {
|
||||
if (state.reports.map.hasOwnProperty(reportId))
|
||||
report_list.push(state.reports.map[reportId]);
|
||||
}
|
||||
return {
|
||||
reports: state.reports
|
||||
reports: state.reports,
|
||||
report_list: report_list
|
||||
}
|
||||
}
|
||||
|
||||
|
32
js/models.js
32
js/models.js
@ -435,6 +435,35 @@ class Error {
|
||||
}
|
||||
}
|
||||
|
||||
class Report {
|
||||
constructor() {
|
||||
this.ReportId = -1;
|
||||
this.UserId = -1;
|
||||
this.Name = "";
|
||||
this.Lua = "";
|
||||
}
|
||||
toJSON() {
|
||||
var json_obj = {};
|
||||
json_obj.ReportId = this.ReportId;
|
||||
json_obj.UserId = this.UserId;
|
||||
json_obj.Name = this.Name;
|
||||
json_obj.Lua = this.Lua;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
fromJSON(json_input) {
|
||||
var json_obj = getJSONObj(json_input)
|
||||
|
||||
if (json_obj.hasOwnProperty("ReportId"))
|
||||
this.ReportId = json_obj.ReportId;
|
||||
if (json_obj.hasOwnProperty("UserId"))
|
||||
this.UserId = json_obj.UserId;
|
||||
if (json_obj.hasOwnProperty("Name"))
|
||||
this.Name = json_obj.Name;
|
||||
if (json_obj.hasOwnProperty("Lua"))
|
||||
this.Lua = json_obj.Lua;
|
||||
}
|
||||
}
|
||||
|
||||
class Series {
|
||||
constructor() {
|
||||
this.Values = [];
|
||||
@ -496,7 +525,7 @@ class Series {
|
||||
|
||||
class Tabulation {
|
||||
constructor() {
|
||||
this.ReportId = "";
|
||||
this.ReportId = -1;
|
||||
this.Title = "";
|
||||
this.Subtitle = "";
|
||||
this.Units = "";
|
||||
@ -578,6 +607,7 @@ module.exports = {
|
||||
Account: Account,
|
||||
Split: Split,
|
||||
Transaction: Transaction,
|
||||
Report: Report,
|
||||
Tabulation: Tabulation,
|
||||
OFXDownload: OFXDownload,
|
||||
Error: Error,
|
||||
|
@ -63,16 +63,19 @@ module.exports = function(state = initialState, action) {
|
||||
selectedTabulation: null,
|
||||
seriesTraversal: []
|
||||
});
|
||||
case ReportConstants.TABULATION_FETCHED:
|
||||
case ReportConstants.REPORT_TABULATED:
|
||||
var tabulation = action.tabulation;
|
||||
return assign({}, state, {
|
||||
var tabulations = assign({}, state.tabulations, {
|
||||
[tabulation.ReportId]: tabulation
|
||||
});
|
||||
return assign({}, state, {
|
||||
tabulations: tabulations
|
||||
});
|
||||
case ReportConstants.SERIES_SELECTED:
|
||||
return {
|
||||
return assign({}, state, {
|
||||
selectedTabulation: action.tabulation,
|
||||
seriesTraversal: action.seriesTraversal
|
||||
};
|
||||
});
|
||||
case UserConstants.USER_LOGGEDOUT:
|
||||
return initialState;
|
||||
default:
|
||||
|
@ -19,6 +19,7 @@
|
||||
"react-dom": "^15.3.2",
|
||||
"react-redux": "^5.0.5",
|
||||
"react-widgets": "^3.4.4",
|
||||
"react-codemirror": "^1.0.0",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^2.1.0"
|
||||
},
|
||||
|
@ -203,6 +203,8 @@ func ReportTabulationHandler(w http.ResponseWriter, r *http.Request, user *User,
|
||||
return
|
||||
}
|
||||
|
||||
tabulation.ReportId = reportid
|
||||
|
||||
err = tabulation.Write(w)
|
||||
if err != nil {
|
||||
WriteError(w, 999 /*Internal Error*/)
|
||||
|
@ -5,6 +5,7 @@
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="static/react-widgets/css/react-widgets.css">
|
||||
<link rel="stylesheet" href="static/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" href="static/css/stylesheet.css">
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user