diff --git a/Makefile b/Makefile
index 86c3e17..1e27874 100644
--- a/Makefile
+++ b/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
diff --git a/js/actions/ReportActions.js b/js/actions/ReportActions.js
index bc344ff..cb60f89 100644
--- a/js/actions/ReportActions.js
+++ b/js/actions/ReportActions.js
@@ -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) {
diff --git a/js/components/ReportsTab.js b/js/components/ReportsTab.js
index 6771528..029e9cc 100644
--- a/js/components/ReportsTab.js
+++ b/js/components/ReportsTab.js
@@ -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 (
+
+
+ {headerText} Report
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
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 (
-
+ 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((
+
+ ));
+ titleTracks.push((/));
+ seriesTraversal.push(this.props.reports.seriesTraversal[i]);
+ }
+ if (titleTracks.length == 0) {
+ titleTracks.push((
+
+ ));
+ } else {
+ var i = this.props.reports.seriesTraversal.length-1;
+ titleTracks.push((
+
+ ));
+ }
+
+ if (this.props.reports.selectedTabulation.Labels.length > 1)
+ var report = (
+
+ );
+ else
+ var report = (
+
+ );
+
+ reportPanel = (
+
+ {report}
+
+ );
+ } else if (this.props.reports.selected != -1) {
+ reportPanel = (
+
+
+
);
}
- 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((
-
- ));
- titleTracks.push((/));
- seriesTraversal.push(this.props.selectedTabulation.seriesTraversal[i]);
- }
- if (titleTracks.length == 0) {
- titleTracks.push((
-
- ));
- } else {
- var i = this.props.selectedTabulation.seriesTraversal.length-1;
- titleTracks.push((
-
- ));
- }
+ 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 (
-
-
-
+
+
+
+
+
+
+
+
+ typeof item === 'string' ? item : item.Name}
+ value={selectedReport}
+ onChange={this.onSelectReport}
+ suggest
+ filter='contains'
+ ref="report" />
+
+
+
+
+
+ {reportPanel}
+
);
}
}
diff --git a/js/components/StackedBarChart.js b/js/components/StackedBarChart.js
index f807ddd..8fe8910 100644
--- a/js/components/StackedBarChart.js
+++ b/js/components/StackedBarChart.js
@@ -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;
}
diff --git a/js/containers/ReportsTabContainer.js b/js/containers/ReportsTabContainer.js
index f4df013..84e3f93 100644
--- a/js/containers/ReportsTabContainer.js
+++ b/js/containers/ReportsTabContainer.js
@@ -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
}
}
diff --git a/js/models.js b/js/models.js
index 3b5cae3..2890840 100644
--- a/js/models.js
+++ b/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,
diff --git a/js/reducers/ReportReducer.js b/js/reducers/ReportReducer.js
index ed5198a..6405ec7 100644
--- a/js/reducers/ReportReducer.js
+++ b/js/reducers/ReportReducer.js
@@ -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:
diff --git a/package.json b/package.json
index 5fcea6e..36bd5fc 100644
--- a/package.json
+++ b/package.json
@@ -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"
},
diff --git a/reports.go b/reports.go
index 0701381..d84dc9d 100644
--- a/reports.go
+++ b/reports.go
@@ -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*/)
diff --git a/static/index.html b/static/index.html
index 34b4c9c..4d0ded5 100644
--- a/static/index.html
+++ b/static/index.html
@@ -5,6 +5,7 @@
+