mirror of
https://github.com/aclindsa/moneygo.git
synced 2025-06-13 13:39:23 -04:00
Reorganization around building JavaScript differently
This commit is contained in:
@ -1,39 +0,0 @@
|
||||
var React = require('react');
|
||||
|
||||
var Combobox = require('react-widgets').Combobox;
|
||||
|
||||
var getAccountDisplayList = require('./utils.js').getAccountDisplayList;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "AccountCombobox",
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
includeRoot: true,
|
||||
rootName: "New Top-level Account"
|
||||
};
|
||||
},
|
||||
handleAccountChange: function(account) {
|
||||
if (this.props.onChange != null &&
|
||||
account.hasOwnProperty('AccountId') &&
|
||||
(this.props.account_map.hasOwnProperty([account.AccountId]) ||
|
||||
account.AccountId == -1)) {
|
||||
this.props.onChange(account)
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var accounts = getAccountDisplayList(this.props.accounts, this.props.includeRoot, this.props.rootName);
|
||||
var className = "";
|
||||
if (this.props.className)
|
||||
className = this.props.className;
|
||||
return (
|
||||
<Combobox
|
||||
data={accounts}
|
||||
valueField='AccountId'
|
||||
textField='Name'
|
||||
defaultValue={this.props.value}
|
||||
onChange={this.handleAccountChange}
|
||||
ref="account"
|
||||
className={className} />
|
||||
);
|
||||
}
|
||||
});
|
@ -1,908 +0,0 @@
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var react_update = require('react-addons-update');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
var Alert = ReactBootstrap.Alert;
|
||||
var Modal = ReactBootstrap.Modal;
|
||||
var Pagination = ReactBootstrap.Pagination;
|
||||
var Label = ReactBootstrap.Label;
|
||||
var Table = ReactBootstrap.Table;
|
||||
var Grid = ReactBootstrap.Grid;
|
||||
var Row = ReactBootstrap.Row;
|
||||
var Col = ReactBootstrap.Col;
|
||||
var Panel = ReactBootstrap.Panel;
|
||||
var Input = ReactBootstrap.Input;
|
||||
var Button = ReactBootstrap.Button;
|
||||
var ButtonGroup = ReactBootstrap.ButtonGroup;
|
||||
var ButtonToolbar = ReactBootstrap.ButtonToolbar;
|
||||
var ProgressBar = ReactBootstrap.ProgressBar;
|
||||
var Glyphicon = ReactBootstrap.Glyphicon;
|
||||
|
||||
var DateTimePicker = require('react-widgets').DateTimePicker;
|
||||
var Combobox = require('react-widgets').Combobox;
|
||||
|
||||
var models = require('./models.js');
|
||||
var Security = models.Security;
|
||||
var Account = models.Account;
|
||||
var Split = models.Split;
|
||||
var Transaction = models.Transaction;
|
||||
var TransactionStatus = models.TransactionStatus;
|
||||
var TransactionStatusList = models.TransactionStatusList;
|
||||
var TransactionStatusMap = models.TransactionStatusMap;
|
||||
var Error = models.Error;
|
||||
|
||||
var getAccountDisplayName = require('./utils.js').getAccountDisplayName;
|
||||
|
||||
var AccountCombobox = require('./AccountCombobox.js');
|
||||
|
||||
const TransactionRow = React.createClass({
|
||||
handleClick: function(e) {
|
||||
const refs = ["date", "number", "description", "account", "status", "amount"];
|
||||
for (var ref in refs) {
|
||||
if (this.refs[refs[ref]] == e.target) {
|
||||
this.props.onEdit(this.props.transaction, refs[ref]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var date = this.props.transaction.Date;
|
||||
var dateString = date.getFullYear() + "/" + (date.getMonth()+1) + "/" + date.getDate();
|
||||
var number = ""
|
||||
var accountName = "";
|
||||
var status = "";
|
||||
var security = this.props.security_map[this.props.account.SecurityId];
|
||||
|
||||
if (this.props.transaction.isTransaction()) {
|
||||
var thisAccountSplit;
|
||||
for (var i = 0; i < this.props.transaction.Splits.length; i++) {
|
||||
if (this.props.transaction.Splits[i].AccountId == this.props.account.AccountId) {
|
||||
thisAccountSplit = this.props.transaction.Splits[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.props.transaction.Splits.length == 2) {
|
||||
var otherSplit = this.props.transaction.Splits[0];
|
||||
if (otherSplit.AccountId == this.props.account.AccountId)
|
||||
var otherSplit = this.props.transaction.Splits[1];
|
||||
|
||||
if (otherSplit.AccountId == -1)
|
||||
var accountName = "Unbalanced " + this.props.security_map[otherSplit.SecurityId].Symbol + " transaction";
|
||||
else
|
||||
var accountName = getAccountDisplayName(this.props.account_map[otherSplit.AccountId], this.props.account_map);
|
||||
} else {
|
||||
accountName = "--Split Transaction--";
|
||||
}
|
||||
|
||||
var amount = security.Symbol + " " + thisAccountSplit.Amount.toFixed(security.Precision);
|
||||
var balance = security.Symbol + " " + this.props.transaction.Balance.toFixed(security.Precision);
|
||||
status = TransactionStatusMap[this.props.transaction.Status];
|
||||
number = thisAccountSplit.Number;
|
||||
} else {
|
||||
var amount = security.Symbol + " " + (new Big(0.0)).toFixed(security.Precision);
|
||||
var balance = security.Symbol + " " + (new Big(0.0)).toFixed(security.Precision);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td ref="date" onClick={this.handleClick}>{dateString}</td>
|
||||
<td ref="number" onClick={this.handleClick}>{number}</td>
|
||||
<td ref="description" onClick={this.handleClick}>{this.props.transaction.Description}</td>
|
||||
<td ref="account" onClick={this.handleClick}>{accountName}</td>
|
||||
<td ref="status" onClick={this.handleClick}>{status}</td>
|
||||
<td ref="amount" onClick={this.handleClick}>{amount}</td>
|
||||
<td>{balance}</td>
|
||||
</tr>);
|
||||
}
|
||||
});
|
||||
|
||||
const AmountInput = React.createClass({
|
||||
_getInitialState: function(props) {
|
||||
// Ensure we can edit this without screwing up other copies of it
|
||||
var a;
|
||||
if (props.security)
|
||||
a = props.value.toFixed(props.security.Precision);
|
||||
else
|
||||
a = props.value.toString();
|
||||
|
||||
return {
|
||||
LastGoodAmount: a,
|
||||
Amount: a
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return this._getInitialState(this.props);
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if ((!nextProps.value.eq(this.props.value) &&
|
||||
!nextProps.value.eq(this.getValue())) ||
|
||||
nextProps.security !== this.props.security) {
|
||||
this.setState(this._getInitialState(nextProps));
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.refs.amount.getInputDOMNode().onblur = this.onBlur;
|
||||
},
|
||||
onBlur: function() {
|
||||
var a;
|
||||
if (this.props.security)
|
||||
a = (new Big(this.getValue())).toFixed(this.props.security.Precision);
|
||||
else
|
||||
a = (new Big(this.getValue())).toString();
|
||||
this.setState({
|
||||
Amount: a
|
||||
});
|
||||
},
|
||||
onChange: function() {
|
||||
this.setState({Amount: this.refs.amount.getValue()});
|
||||
if (this.props.onChange)
|
||||
this.props.onChange();
|
||||
},
|
||||
getValue: function() {
|
||||
try {
|
||||
var value = this.refs.amount.getValue();
|
||||
var ret = new Big(value);
|
||||
this.setState({LastGoodAmount: value});
|
||||
return ret;
|
||||
} catch(err) {
|
||||
return new Big(this.state.LastGoodAmount);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var symbol = "?";
|
||||
if (this.props.security)
|
||||
symbol = this.props.security.Symbol;
|
||||
var bsStyle = undefined;
|
||||
if (this.props.bsStyle)
|
||||
bsStyle = this.props.bsStyle;
|
||||
|
||||
return (
|
||||
<Input type="text"
|
||||
value={this.state.Amount}
|
||||
onChange={this.onChange}
|
||||
addonBefore={symbol}
|
||||
bsStyle={bsStyle}
|
||||
ref="amount"/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const AddEditTransactionModal = React.createClass({
|
||||
_getInitialState: function(props) {
|
||||
// Ensure we can edit this without screwing up other copies of it
|
||||
var t = props.transaction.deepCopy();
|
||||
return {
|
||||
errorAlert: [],
|
||||
transaction: t
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return this._getInitialState(this.props);
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.show && !this.props.show) {
|
||||
this.setState(this._getInitialState(nextProps));
|
||||
}
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
handleDescriptionChange: function() {
|
||||
this.setState({
|
||||
transaction: react_update(this.state.transaction, {
|
||||
Description: {$set: this.refs.description.getValue()}
|
||||
})
|
||||
});
|
||||
},
|
||||
handleDateChange: function(date, string) {
|
||||
if (date == null)
|
||||
return;
|
||||
this.setState({
|
||||
transaction: react_update(this.state.transaction, {
|
||||
Date: {$set: date}
|
||||
})
|
||||
});
|
||||
},
|
||||
handleStatusChange: function(status) {
|
||||
if (status.hasOwnProperty('StatusId')) {
|
||||
this.setState({
|
||||
transaction: react_update(this.state.transaction, {
|
||||
Status: {$set: status.StatusId}
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
handleAddSplit: function() {
|
||||
this.setState({
|
||||
transaction: react_update(this.state.transaction, {
|
||||
Splits: {$push: [new Split()]}
|
||||
})
|
||||
});
|
||||
},
|
||||
handleDeleteSplit: function(split) {
|
||||
this.setState({
|
||||
transaction: react_update(this.state.transaction, {
|
||||
Splits: {$splice: [[split, 1]]}
|
||||
})
|
||||
});
|
||||
},
|
||||
handleUpdateNumber: function(split) {
|
||||
var transaction = this.state.transaction;
|
||||
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||
Number: {$set: this.refs['number-'+split].getValue()}
|
||||
});
|
||||
this.setState({
|
||||
transaction: transaction
|
||||
});
|
||||
},
|
||||
handleUpdateMemo: function(split) {
|
||||
var transaction = this.state.transaction;
|
||||
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||
Memo: {$set: this.refs['memo-'+split].getValue()}
|
||||
});
|
||||
this.setState({
|
||||
transaction: transaction
|
||||
});
|
||||
},
|
||||
handleUpdateAccount: function(account, split) {
|
||||
var transaction = this.state.transaction;
|
||||
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||
SecurityId: {$set: -1},
|
||||
AccountId: {$set: account.AccountId}
|
||||
});
|
||||
this.setState({
|
||||
transaction: transaction
|
||||
});
|
||||
},
|
||||
handleUpdateAmount: function(split) {
|
||||
var transaction = this.state.transaction;
|
||||
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||
Amount: {$set: new Big(this.refs['amount-'+split].getValue())}
|
||||
});
|
||||
this.setState({
|
||||
transaction: transaction
|
||||
});
|
||||
},
|
||||
handleSubmit: function() {
|
||||
var errorString = ""
|
||||
var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
|
||||
if (imbalancedSecurityList.length > 0)
|
||||
errorString = "Transaction must balance"
|
||||
for (var i = 0; i < this.state.transaction.Splits.length; i++) {
|
||||
var s = this.state.transaction.Splits[i];
|
||||
if (!(s.AccountId in this.props.account_map)) {
|
||||
errorString = "All accounts must be valid"
|
||||
}
|
||||
}
|
||||
|
||||
if (errorString.length > 0) {
|
||||
this.setState({
|
||||
errorAlert: (<Alert className='saving-transaction-alert' bsStyle='danger'><strong>Error Saving Transaction:</strong> {errorString}</Alert>)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.onSubmit != null)
|
||||
this.props.onSubmit(this.state.transaction);
|
||||
},
|
||||
handleDelete: function() {
|
||||
if (this.props.onDelete != null)
|
||||
this.props.onDelete(this.state.transaction);
|
||||
},
|
||||
render: function() {
|
||||
var editing = this.props.transaction != null && this.props.transaction.isTransaction();
|
||||
var headerText = editing ? "Edit" : "Create New";
|
||||
var buttonText = editing ? "Save Changes" : "Create Transaction";
|
||||
var deleteButton = [];
|
||||
if (editing) {
|
||||
deleteButton = (
|
||||
<Button key={1} onClick={this.handleDelete} bsStyle="danger">Delete Transaction</Button>
|
||||
);
|
||||
}
|
||||
|
||||
var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
|
||||
var imbalancedSecurityMap = {};
|
||||
for (i = 0; i < imbalancedSecurityList.length; i++)
|
||||
imbalancedSecurityMap[imbalancedSecurityList[i]] = i;
|
||||
|
||||
splits = [];
|
||||
for (var i = 0; i < this.state.transaction.Splits.length; i++) {
|
||||
var self = this;
|
||||
var s = this.state.transaction.Splits[i];
|
||||
var security = null;
|
||||
var amountValidation = undefined;
|
||||
var accountValidation = "";
|
||||
if (s.AccountId in this.props.account_map) {
|
||||
security = this.props.security_map[this.props.account_map[s.AccountId].SecurityId];
|
||||
} else {
|
||||
if (s.SecurityId in this.props.security_map) {
|
||||
security = this.props.security_map[s.SecurityId];
|
||||
}
|
||||
accountValidation = "has-error";
|
||||
}
|
||||
if (security != null && security.SecurityId in imbalancedSecurityMap)
|
||||
amountValidation = "error";
|
||||
|
||||
// Define all closures for calling split-updating functions
|
||||
var deleteSplitFn = (function() {
|
||||
var j = i;
|
||||
return function() {self.handleDeleteSplit(j);};
|
||||
})();
|
||||
var updateNumberFn = (function() {
|
||||
var j = i;
|
||||
return function() {self.handleUpdateNumber(j);};
|
||||
})();
|
||||
var updateMemoFn = (function() {
|
||||
var j = i;
|
||||
return function() {self.handleUpdateMemo(j);};
|
||||
})();
|
||||
var updateAccountFn = (function() {
|
||||
var j = i;
|
||||
return function(account) {self.handleUpdateAccount(account, j);};
|
||||
})();
|
||||
var updateAmountFn = (function() {
|
||||
var j = i;
|
||||
return function() {self.handleUpdateAmount(j);};
|
||||
})();
|
||||
|
||||
var deleteSplitButton = [];
|
||||
if (this.state.transaction.Splits.length > 2) {
|
||||
deleteSplitButton = (
|
||||
<Col xs={1}><Button onClick={deleteSplitFn}
|
||||
bsStyle="danger">
|
||||
<Glyphicon glyph='trash' /></Button></Col>
|
||||
);
|
||||
}
|
||||
|
||||
splits.push((
|
||||
<Row key={s.SplitId == -1 ? (i+999) : s.SplitId}>
|
||||
<Col xs={1}><Input
|
||||
type="text"
|
||||
value={s.Number}
|
||||
onChange={updateNumberFn}
|
||||
ref={"number-"+i} /></Col>
|
||||
<Col xs={5}><Input
|
||||
type="text"
|
||||
value={s.Memo}
|
||||
onChange={updateMemoFn}
|
||||
ref={"memo-"+i} /></Col>
|
||||
<Col xs={3}><AccountCombobox
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
value={s.AccountId}
|
||||
includeRoot={false}
|
||||
onChange={updateAccountFn}
|
||||
ref={"account-"+i}
|
||||
className={accountValidation}/></Col>
|
||||
<Col xs={2}><AmountInput type="text"
|
||||
value={s.Amount}
|
||||
security={security}
|
||||
onChange={updateAmountFn}
|
||||
ref={"amount-"+i}
|
||||
bsStyle={amountValidation}/></Col>
|
||||
{deleteSplitButton}
|
||||
</Row>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="large">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{headerText} Transaction</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<form onSubmit={this.handleSubmit}
|
||||
className="form-horizontal">
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Date"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<DateTimePicker
|
||||
time={false}
|
||||
defaultValue={this.state.transaction.Date}
|
||||
onChange={this.handleDateChange} />
|
||||
</Input>
|
||||
<Input type="text"
|
||||
label="Description"
|
||||
value={this.state.transaction.Description}
|
||||
onChange={this.handleDescriptionChange}
|
||||
ref="description"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Status"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<Combobox
|
||||
data={TransactionStatusList}
|
||||
valueField='StatusId'
|
||||
textField='Name'
|
||||
defaultValue={this.state.transaction.Status}
|
||||
onSelect={this.handleStatusChange}
|
||||
ref="status" />
|
||||
</Input>
|
||||
<Grid fluid={true}><Row>
|
||||
<span className="split-header col-xs-1">#</span>
|
||||
<span className="split-header col-xs-5">Memo</span>
|
||||
<span className="split-header col-xs-3">Account</span>
|
||||
<span className="split-header col-xs-2">Amount</span>
|
||||
</Row>
|
||||
{splits}
|
||||
<Row>
|
||||
<span className="col-xs-11"></span>
|
||||
<Col xs={1}><Button onClick={this.handleAddSplit}
|
||||
bsStyle="success">
|
||||
<Glyphicon glyph='plus-sign' /></Button></Col>
|
||||
</Row>
|
||||
<Row>{this.state.errorAlert}</Row>
|
||||
</Grid>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup>
|
||||
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
|
||||
{deleteButton}
|
||||
<Button onClick={this.handleSubmit} bsStyle="success">{buttonText}</Button>
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const ImportTransactionsModal = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
importing: false,
|
||||
imported: false,
|
||||
importFile: "",
|
||||
uploadProgress: -1,
|
||||
error: null};
|
||||
},
|
||||
handleCancel: function() {
|
||||
this.setState({
|
||||
importing: false,
|
||||
imported: false,
|
||||
importFile: "",
|
||||
uploadProgress: -1,
|
||||
error: null
|
||||
});
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
onImportChanged: function() {
|
||||
this.setState({importFile: this.refs.importfile.getValue()});
|
||||
},
|
||||
handleSubmit: function() {
|
||||
if (this.props.onSubmit != null)
|
||||
this.props.onSubmit(this.props.account);
|
||||
},
|
||||
handleSetProgress: function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var pct = Math.round(e.loaded/e.total*100);
|
||||
this.setState({uploadProgress: pct});
|
||||
} else {
|
||||
this.setState({uploadProgress: 50});
|
||||
}
|
||||
},
|
||||
handleImportTransactions: function() {
|
||||
var file = this.refs.importfile.getInputDOMNode().files[0];
|
||||
var formData = new FormData();
|
||||
this.setState({importing: true});
|
||||
formData.append('importfile', file, this.state.importFile);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "account/"+this.props.account.AccountId+"/import",
|
||||
data: formData,
|
||||
xhr: function() {
|
||||
var xhrObject = $.ajaxSettings.xhr();
|
||||
if (xhrObject.upload) {
|
||||
xhrObject.upload.addEventListener('progress', this.handleSetProgress, false);
|
||||
} else {
|
||||
console.log("File upload failed because !xhr.upload")
|
||||
}
|
||||
return xhrObject;
|
||||
}.bind(this),
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
var errString = e.ErrorString;
|
||||
if (e.ErrorId == 3 /* Invalid Request */) {
|
||||
errString = "Please check that the file you uploaded is a valid OFX file for this account and try again.";
|
||||
}
|
||||
this.setState({
|
||||
importing: false,
|
||||
error: errString
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
uploadProgress: 100,
|
||||
importing: false,
|
||||
imported: true
|
||||
});
|
||||
}.bind(this),
|
||||
error: function(e) {
|
||||
this.setState({importing: false});
|
||||
console.log("error handler", e);
|
||||
},
|
||||
// So jQuery doesn't try to process teh data or content-type
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var accountNameLabel = ""
|
||||
if (this.props.account != null )
|
||||
accountNameLabel = "Importing to '" + getAccountDisplayName(this.props.account, this.props.account_map) + "' account:";
|
||||
var progressBar = [];
|
||||
if (this.state.importing && this.state.uploadProgress == 100) {
|
||||
progressBar = (<ProgressBar now={this.state.uploadProgress} active label="Importing transactions..." />);
|
||||
} else if (this.state.importing && this.state.uploadProgress != -1) {
|
||||
progressBar = (<ProgressBar now={this.state.uploadProgress} active label="Uploading... %(percent)s%" />);
|
||||
}
|
||||
|
||||
var panel = [];
|
||||
if (this.state.error != null) {
|
||||
panel = (<Panel header="Error Importing Transactions" bsStyle="danger">{this.state.error}</Panel>);
|
||||
} else if (this.state.imported) {
|
||||
panel = (<Panel header="Successfully Imported Transactions" bsStyle="success">Your import is now complete.</Panel>);
|
||||
}
|
||||
|
||||
var buttonsDisabled = (this.state.importing) ? true : false;
|
||||
var button1 = [];
|
||||
var button2 = [];
|
||||
if (!this.state.imported && this.state.error == null) {
|
||||
button1 = (<Button onClick={this.handleCancel} disabled={buttonsDisabled} bsStyle="warning">Cancel</Button>);
|
||||
button2 = (<Button onClick={this.handleImportTransactions} disabled={buttonsDisabled} bsStyle="success">Import</Button>);
|
||||
} else {
|
||||
button1 = (<Button onClick={this.handleCancel} disabled={buttonsDisabled} bsStyle="success">OK</Button>);
|
||||
}
|
||||
var inputDisabled = (this.state.importing || this.state.error != null || this.state.imported) ? true : false;
|
||||
return (
|
||||
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="small">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Import Transactions</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<form onSubmit={this.handleImportTransactions}
|
||||
encType="multipart/form-data"
|
||||
ref="importform">
|
||||
<Input type="file"
|
||||
ref="importfile"
|
||||
disabled={inputDisabled}
|
||||
value={this.state.importFile}
|
||||
label={accountNameLabel}
|
||||
help="Select an OFX/QFX file to upload."
|
||||
onChange={this.onImportChanged} />
|
||||
</form>
|
||||
{progressBar}
|
||||
{panel}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup>
|
||||
{button1}
|
||||
{button2}
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "AccountRegister",
|
||||
getInitialState: function() {
|
||||
return {
|
||||
importingTransactions: false,
|
||||
editingTransaction: false,
|
||||
selectedTransaction: new Transaction(),
|
||||
transactions: [],
|
||||
pageSize: 20,
|
||||
numPages: 0,
|
||||
currentPage: 0,
|
||||
height: 0
|
||||
};
|
||||
},
|
||||
resize: function() {
|
||||
var div = ReactDOM.findDOMNode(this);
|
||||
this.setState({height: div.parentElement.clientHeight - 64});
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.resize();
|
||||
var self = this;
|
||||
$(window).resize(function() {self.resize();});
|
||||
},
|
||||
handleEditTransaction: function(transaction) {
|
||||
this.setState({
|
||||
selectedTransaction: transaction,
|
||||
editingTransaction: true
|
||||
});
|
||||
},
|
||||
handleEditingCancel: function() {
|
||||
this.setState({
|
||||
editingTransaction: false
|
||||
});
|
||||
},
|
||||
handleNewTransactionClicked: function() {
|
||||
var newTransaction = new Transaction();
|
||||
newTransaction.Status = TransactionStatus.Entered;
|
||||
newTransaction.Date = new Date();
|
||||
newTransaction.Splits.push(new Split());
|
||||
newTransaction.Splits.push(new Split());
|
||||
newTransaction.Splits[0].AccountId = this.props.selectedAccount.AccountId;
|
||||
|
||||
this.setState({
|
||||
editingTransaction: true,
|
||||
selectedTransaction: newTransaction
|
||||
});
|
||||
},
|
||||
handleImportClicked: function() {
|
||||
this.setState({
|
||||
importingTransactions: true
|
||||
});
|
||||
},
|
||||
handleImportingCancel: function() {
|
||||
this.setState({
|
||||
importingTransactions: false
|
||||
});
|
||||
},
|
||||
ajaxError: function(jqXHR, status, error) {
|
||||
var e = new Error();
|
||||
e.ErrorId = 5;
|
||||
e.ErrorString = "Request Failed: " + status + error;
|
||||
this.setState({error: e});
|
||||
},
|
||||
getTransactionPage: function(account, page) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: "account/"+account.AccountId+"/transactions?sort=date-desc&limit="+this.state.pageSize+"&page="+page,
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
return;
|
||||
}
|
||||
|
||||
var transactions = [];
|
||||
var balance = new Big(data.EndingBalance);
|
||||
|
||||
for (var i = 0; i < data.Transactions.length; i++) {
|
||||
var t = new Transaction();
|
||||
t.fromJSON(data.Transactions[i]);
|
||||
|
||||
t.Balance = balance.plus(0); // Make a copy of the current balance
|
||||
// Keep a talley of the running balance of these transactions
|
||||
for (var j = 0; j < data.Transactions[i].Splits.length; j++) {
|
||||
var split = data.Transactions[i].Splits[j];
|
||||
if (this.props.selectedAccount.AccountId == split.AccountId) {
|
||||
balance = balance.minus(split.Amount);
|
||||
}
|
||||
}
|
||||
transactions.push(t);
|
||||
}
|
||||
var a = new Account();
|
||||
a.fromJSON(data.Account);
|
||||
|
||||
var pages = Math.ceil(data.TotalTransactions / this.state.pageSize);
|
||||
|
||||
this.setState({
|
||||
transactions: transactions,
|
||||
numPages: pages
|
||||
});
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleSelectPage: function(event, selectedEvent) {
|
||||
var newpage = selectedEvent.eventKey - 1;
|
||||
// Don't do pages that don't make sense
|
||||
if (newpage < 0)
|
||||
newpage = 0;
|
||||
if (newpage >= this.state.numPages)
|
||||
newpage = this.state.numPages-1;
|
||||
if (newpage != this.state.currentPage) {
|
||||
if (this.props.selectedAccount != null) {
|
||||
this.getTransactionPage(this.props.selectedAccount, newpage);
|
||||
}
|
||||
this.setState({currentPage: newpage});
|
||||
}
|
||||
},
|
||||
onNewTransaction: function() {
|
||||
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
|
||||
},
|
||||
onUpdatedTransaction: function() {
|
||||
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
|
||||
},
|
||||
onDeletedTransaction: function() {
|
||||
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
|
||||
},
|
||||
createNewTransaction: function(transaction) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: "transaction/",
|
||||
data: {transaction: transaction.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.onNewTransaction();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
updateTransaction: function(transaction) {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
dataType: "json",
|
||||
url: "transaction/"+transaction.TransactionId+"/",
|
||||
data: {transaction: transaction.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.onUpdatedTransaction();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
deleteTransaction: function(transaction) {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
url: "transaction/"+transaction.TransactionId+"/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.onDeletedTransaction();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleImportComplete: function() {
|
||||
this.setState({importingTransactions: false});
|
||||
},
|
||||
handleDeleteTransaction: function(transaction) {
|
||||
this.setState({
|
||||
editingTransaction: false
|
||||
});
|
||||
this.deleteTransaction(transaction);
|
||||
},
|
||||
handleUpdateTransaction: function(transaction) {
|
||||
this.setState({
|
||||
editingTransaction: false
|
||||
});
|
||||
if (transaction.TransactionId != -1) {
|
||||
this.updateTransaction(transaction);
|
||||
} else {
|
||||
this.createNewTransaction(transaction);
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.selectedAccount != this.props.selectedAccount) {
|
||||
this.setState({
|
||||
selectedTransaction: new Transaction(),
|
||||
transactions: [],
|
||||
currentPage: 0
|
||||
});
|
||||
if (nextProps.selectedAccount != null)
|
||||
this.getTransactionPage(nextProps.selectedAccount, 0);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var name = "Please select an account";
|
||||
register = [];
|
||||
|
||||
if (this.props.selectedAccount != null) {
|
||||
name = this.props.selectedAccount.Name;
|
||||
|
||||
var transactionRows = [];
|
||||
for (var i = 0; i < this.state.transactions.length; i++) {
|
||||
var t = this.state.transactions[i];
|
||||
transactionRows.push((
|
||||
<TransactionRow
|
||||
key={t.TransactionId}
|
||||
transaction={t}
|
||||
account={this.props.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
securities={this.props.securities}
|
||||
security_map={this.props.security_map}
|
||||
onEdit={this.handleEditTransaction}/>
|
||||
));
|
||||
}
|
||||
|
||||
var style = {height: this.state.height + "px"};
|
||||
register = (
|
||||
<div style={style} className="transactions-register">
|
||||
<Table bordered striped condensed hover>
|
||||
<thead><tr>
|
||||
<th>Date</th>
|
||||
<th>#</th>
|
||||
<th>Description</th>
|
||||
<th>Account</th>
|
||||
<th>Status</th>
|
||||
<th>Amount</th>
|
||||
<th>Balance</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{transactionRows}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var disabled = (this.props.selectedAccount == null) ? true : false;
|
||||
|
||||
return (
|
||||
<div className="transactions-container">
|
||||
<AddEditTransactionModal
|
||||
show={this.state.editingTransaction}
|
||||
transaction={this.state.selectedTransaction}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
onCancel={this.handleEditingCancel}
|
||||
onSubmit={this.handleUpdateTransaction}
|
||||
onDelete={this.handleDeleteTransaction}
|
||||
securities={this.props.securities}
|
||||
security_map={this.props.security_map}/>
|
||||
<ImportTransactionsModal
|
||||
show={this.state.importingTransactions}
|
||||
account={this.props.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
onCancel={this.handleImportingCancel}
|
||||
onSubmit={this.handleImportComplete}/>
|
||||
<div className="transactions-register-toolbar">
|
||||
Transactions for '{name}'
|
||||
<ButtonToolbar className="pull-right">
|
||||
<ButtonGroup>
|
||||
<Pagination
|
||||
className="skinny-pagination"
|
||||
prev next first last ellipses
|
||||
items={this.state.numPages}
|
||||
maxButtons={Math.min(5, this.state.numPages)}
|
||||
activePage={this.state.currentPage+1}
|
||||
onSelect={this.handleSelectPage} />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={this.handleNewTransactionClicked}
|
||||
bsStyle="success"
|
||||
disabled={disabled}>
|
||||
<Glyphicon glyph='plus-sign' /> New Transaction
|
||||
</Button>
|
||||
<Button
|
||||
onClick={this.handleImportClicked}
|
||||
bsStyle="primary"
|
||||
disabled={disabled}>
|
||||
<Glyphicon glyph='import' /> Import
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
{register}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,171 +0,0 @@
|
||||
var React = require('react');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
var Modal = ReactBootstrap.Modal;
|
||||
var Button = ReactBootstrap.Button;
|
||||
var ButtonGroup = ReactBootstrap.ButtonGroup;
|
||||
var Input = ReactBootstrap.Input;
|
||||
|
||||
var models = require('./models.js');
|
||||
var User = models.User;
|
||||
var Error = models.Error;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "AccountSettingsModal",
|
||||
_getInitialState: function(props) {
|
||||
return {error: "",
|
||||
name: props.user.Name,
|
||||
username: props.user.Username,
|
||||
email: props.user.Email,
|
||||
password: models.BogusPassword,
|
||||
confirm_password: models.BogusPassword,
|
||||
passwordChanged: false,
|
||||
initial_password: models.BogusPassword};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return this._getInitialState(this.props);
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.show && !this.props.show) {
|
||||
this.setState(this._getInitialState(nextProps));
|
||||
}
|
||||
},
|
||||
passwordValidationState: function() {
|
||||
if (this.state.passwordChanged) {
|
||||
if (this.state.password.length >= 10)
|
||||
return "success";
|
||||
else if (this.state.password.length >= 6)
|
||||
return "warning";
|
||||
else
|
||||
return "error";
|
||||
}
|
||||
},
|
||||
confirmPasswordValidationState: function() {
|
||||
if (this.state.confirm_password.length > 0) {
|
||||
if (this.state.confirm_password == this.state.password)
|
||||
return "success";
|
||||
else
|
||||
return "error";
|
||||
}
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
handleChange: function() {
|
||||
if (this.refs.password.getValue() != this.state.initial_password)
|
||||
this.setState({passwordChanged: true});
|
||||
this.setState({
|
||||
name: this.refs.name.getValue(),
|
||||
username: this.refs.username.getValue(),
|
||||
email: this.refs.email.getValue(),
|
||||
password: this.refs.password.getValue(),
|
||||
confirm_password: this.refs.confirm_password.getValue()
|
||||
});
|
||||
},
|
||||
handleSubmit: function(e) {
|
||||
var u = new User();
|
||||
var error = "";
|
||||
e.preventDefault();
|
||||
|
||||
u.UserId = this.props.user.UserId;
|
||||
u.Name = this.state.name;
|
||||
u.Username = this.state.username;
|
||||
u.Email = this.state.email;
|
||||
if (this.state.passwordChanged) {
|
||||
u.Password = this.state.password;
|
||||
if (u.Password != this.state.confirm_password) {
|
||||
this.setState({error: "Error: password do not match"});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
u.Password = models.BogusPassword;
|
||||
}
|
||||
|
||||
this.handleSaveSettings(u);
|
||||
},
|
||||
handleSaveSettings: function(user) {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
dataType: "json",
|
||||
url: "user/"+user.UserId+"/",
|
||||
data: {user: user.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
user.Password = "";
|
||||
this.props.onSubmit(user);
|
||||
}
|
||||
}.bind(this),
|
||||
error: function(jqXHR, status, error) {
|
||||
var e = new Error();
|
||||
e.ErrorId = 5;
|
||||
e.ErrorString = "Request Failed: " + status + error;
|
||||
this.setState({error: e});
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Modal show={this.props.show} onHide={this.handleCancel} bsSize="large">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Edit Account Settings</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<span color="red">{this.state.error}</span>
|
||||
<form onSubmit={this.handleSubmit}
|
||||
className="form-horizontal">
|
||||
<Input type="text"
|
||||
label="Name"
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
ref="name"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="text"
|
||||
label="Username"
|
||||
value={this.state.username}
|
||||
onChange={this.handleChange}
|
||||
ref="username"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="email"
|
||||
label="Email"
|
||||
value={this.state.email}
|
||||
onChange={this.handleChange}
|
||||
ref="email"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="password"
|
||||
label="Password"
|
||||
value={this.state.password}
|
||||
onChange={this.handleChange}
|
||||
ref="password"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"
|
||||
bsStyle={this.passwordValidationState()}
|
||||
hasFeedback/>
|
||||
<Input type="password"
|
||||
label="Confirm Password"
|
||||
value={this.state.confirm_password}
|
||||
onChange={this.handleChange}
|
||||
ref="confirm_password"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"
|
||||
bsStyle={this.confirmPasswordValidationState()}
|
||||
hasFeedback/>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup>
|
||||
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
|
||||
<Button onClick={this.handleSubmit} bsStyle="success">Save Settings</Button>
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,473 +0,0 @@
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
var Grid = ReactBootstrap.Grid;
|
||||
var Row = ReactBootstrap.Row;
|
||||
var Col = ReactBootstrap.Col;
|
||||
var Input = ReactBootstrap.Input;
|
||||
var Button = ReactBootstrap.Button;
|
||||
var ButtonGroup = ReactBootstrap.ButtonGroup;
|
||||
var Glyphicon = ReactBootstrap.Glyphicon;
|
||||
var ListGroup = ReactBootstrap.ListGroup;
|
||||
var ListGroupItem = ReactBootstrap.ListGroupItem;
|
||||
var Collapse = ReactBootstrap.Collapse;
|
||||
var Alert = ReactBootstrap.Alert;
|
||||
var Modal = ReactBootstrap.Modal;
|
||||
var Collapse = ReactBootstrap.Collapse;
|
||||
|
||||
var Combobox = require('react-widgets').Combobox;
|
||||
|
||||
var models = require('./models.js');
|
||||
var Security = models.Security;
|
||||
var Account = models.Account;
|
||||
var AccountTypeList = models.AccountTypeList;
|
||||
|
||||
var AccountCombobox = require('./AccountCombobox.js');
|
||||
var AccountRegister = require('./AccountRegister.js');
|
||||
|
||||
const AddEditAccountModal = React.createClass({
|
||||
getInitialState: function() {
|
||||
var s = {
|
||||
accountid: -1,
|
||||
security: 1,
|
||||
parentaccountid: -1,
|
||||
type: 1,
|
||||
name: ""
|
||||
};
|
||||
if (this.props.editAccount != null) {
|
||||
s.accountid = this.props.editAccount.AccountId;
|
||||
s.name = this.props.editAccount.Name;
|
||||
s.security = this.props.editAccount.SecurityId;
|
||||
s.parentaccountid = this.props.editAccount.ParentAccountId;
|
||||
s.type = this.props.editAccount.Type;
|
||||
} else if (this.props.initialParentAccount != null) {
|
||||
s.security = this.props.initialParentAccount.SecurityId;
|
||||
s.parentaccountid = this.props.initialParentAccount.AccountId;
|
||||
s.type = this.props.initialParentAccount.Type;
|
||||
}
|
||||
return s;
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
handleChange: function() {
|
||||
this.setState({
|
||||
name: this.refs.name.getValue(),
|
||||
});
|
||||
},
|
||||
handleSecurityChange: function(security) {
|
||||
if (security.hasOwnProperty('SecurityId'))
|
||||
this.setState({
|
||||
security: security.SecurityId
|
||||
});
|
||||
},
|
||||
handleTypeChange: function(type) {
|
||||
if (type.hasOwnProperty('TypeId'))
|
||||
this.setState({
|
||||
type: type.TypeId
|
||||
});
|
||||
},
|
||||
handleParentChange: function(parentAccount) {
|
||||
this.setState({parentaccountid: parentAccount.AccountId});
|
||||
},
|
||||
handleSubmit: function() {
|
||||
var a = new Account();
|
||||
|
||||
if (this.props.editAccount != null)
|
||||
a.AccountId = this.state.accountid;
|
||||
a.Name = this.state.name;
|
||||
a.ParentAccountId = this.state.parentaccountid;
|
||||
a.SecurityId = this.state.security;
|
||||
a.Type = this.state.type;
|
||||
|
||||
if (this.props.onSubmit != null)
|
||||
this.props.onSubmit(a);
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.show && !this.props.show) {
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var headerText = (this.props.editAccount != null) ? "Edit" : "Create New";
|
||||
var buttonText = (this.props.editAccount != null) ? "Save Changes" : "Create Account";
|
||||
var rootName = (this.props.editAccount != null) ? "Top-level Account" : "New Top-level Account";
|
||||
return (
|
||||
<Modal show={this.props.show} onHide={this.handleCancel}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{headerText} Account</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<form onSubmit={this.handleSubmit}
|
||||
className="form-horizontal">
|
||||
<Input type="text"
|
||||
label="Name"
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
ref="name"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Parent Account"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<AccountCombobox
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
value={this.state.parentaccountid}
|
||||
rootName={rootName}
|
||||
onChange={this.handleParentChange}
|
||||
ref="parent" />
|
||||
</Input>
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Security"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<Combobox
|
||||
data={this.props.securities}
|
||||
valueField='SecurityId'
|
||||
textField='Name'
|
||||
value={this.state.security}
|
||||
onChange={this.handleSecurityChange}
|
||||
ref="security" />
|
||||
</Input>
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Account Type"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<Combobox
|
||||
data={AccountTypeList}
|
||||
valueField='TypeId'
|
||||
textField='Name'
|
||||
value={this.state.type}
|
||||
onChange={this.handleTypeChange}
|
||||
ref="type" />
|
||||
</Input>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup className="pull-right">
|
||||
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
|
||||
<Button onClick={this.handleSubmit} bsStyle="success">{buttonText}</Button>
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const DeleteAccountModal = React.createClass({
|
||||
getInitialState: function() {
|
||||
if (this.props.initialAccount != null)
|
||||
var accountid = this.props.initialAccount.AccountId;
|
||||
else if (this.props.accounts.length > 0)
|
||||
var accountid = this.props.accounts[0].AccountId;
|
||||
else
|
||||
var accountid = -1;
|
||||
return {error: "",
|
||||
accountid: accountid,
|
||||
checked: false,
|
||||
show: false};
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
handleChange: function(account) {
|
||||
this.setState({accountid: account.AccountId});
|
||||
},
|
||||
handleCheckboxClick: function() {
|
||||
this.setState({checked: !this.state.checked});
|
||||
},
|
||||
handleSubmit: function() {
|
||||
if (this.props.account_map.hasOwnProperty(this.state.accountid)) {
|
||||
if (this.state.checked) {
|
||||
if (this.props.onSubmit != null)
|
||||
this.props.onSubmit(this.props.account_map[this.state.accountid]);
|
||||
} else {
|
||||
this.setState({error: "You must confirm you wish to delete this account."});
|
||||
}
|
||||
} else {
|
||||
this.setState({error: "You must select an account."});
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.show && !this.props.show) {
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var checkbox = [];
|
||||
if (this.props.account_map.hasOwnProperty(this.state.accountid)) {
|
||||
var parentAccountId = this.props.account_map[this.state.accountid].ParentAccountId;
|
||||
var parentAccount = "will be deleted and any child accounts will become top-level accounts.";
|
||||
if (parentAccountId != -1)
|
||||
parentAccount = "and any child accounts will be re-parented to: " + this.props.account_map[parentAccountId].Name;
|
||||
|
||||
var warningString = "I understand that deleting this account cannot be undone and that all transactions " + parentAccount;
|
||||
checkbox = (<Input
|
||||
type='checkbox'
|
||||
checked={this.state.checked ? "checked" : ""}
|
||||
onClick={this.handleCheckboxClick}
|
||||
label={warningString}
|
||||
wrapperClassName="col-xs-offset-2 col-xs-10"/>);
|
||||
}
|
||||
var warning = [];
|
||||
if (this.state.error.length != "") {
|
||||
warning = (
|
||||
<Alert bsStyle="danger"><strong>Error: </strong>{this.state.error}</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show={this.props.show}
|
||||
onHide={this.handleCancel}
|
||||
ref="modal">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Account</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{warning}
|
||||
<form onSubmit={this.handleSubmit}
|
||||
className="form-horizontal">
|
||||
<Input wrapperClassName="wrapper"
|
||||
label="Delete Account"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10">
|
||||
<AccountCombobox
|
||||
includeRoot={false}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
value={this.state.accountid}
|
||||
onChange={this.handleChange}/>
|
||||
</Input>
|
||||
{checkbox}
|
||||
</form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<ButtonGroup className="pull-right">
|
||||
<Button onClick={this.handleCancel} bsStyle="warning">Cancel</Button>
|
||||
<Button onClick={this.handleSubmit} bsStyle="success">Delete Account</Button>
|
||||
</ButtonGroup>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const AccountTreeNode = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {expanded: false};
|
||||
},
|
||||
handleToggle: function(e) {
|
||||
e.preventDefault();
|
||||
this.setState({expanded:!this.state.expanded});
|
||||
},
|
||||
handleChildSelect: function(account) {
|
||||
if (this.props.onSelect != null)
|
||||
this.props.onSelect(account);
|
||||
},
|
||||
handleSelect: function() {
|
||||
if (this.props.onSelect != null)
|
||||
this.props.onSelect(this.props.account);
|
||||
},
|
||||
render: function() {
|
||||
var glyph = this.state.expanded ? 'minus' : 'plus';
|
||||
var active = (this.props.selectedAccount != null &&
|
||||
this.props.account.AccountId == this.props.selectedAccount.AccountId);
|
||||
var buttonStyle = active ? "info" : "link";
|
||||
|
||||
var self = this;
|
||||
var children = this.props.account.Children.map(function(account) {
|
||||
return (
|
||||
<AccountTreeNode
|
||||
key={account.AccountId}
|
||||
account={account}
|
||||
selectedAccount={self.props.selectedAccount}
|
||||
onSelect={self.handleChildSelect}/>
|
||||
);
|
||||
});
|
||||
var accounttreeClasses = "accounttree"
|
||||
var expandButton = [];
|
||||
if (children.length > 0) {
|
||||
expandButton.push((
|
||||
<Button onClick={this.handleToggle}
|
||||
key={1}
|
||||
bsSize="xsmall"
|
||||
bsStyle="link"
|
||||
className="accounttree-expandbutton">
|
||||
<Glyphicon glyph={glyph} bsSize="xsmall"/>
|
||||
</Button>
|
||||
));
|
||||
} else {
|
||||
accounttreeClasses += "-nochildren";
|
||||
}
|
||||
return (
|
||||
<div className={accounttreeClasses}>
|
||||
{expandButton}
|
||||
<Button onClick={this.handleSelect}
|
||||
bsStyle={buttonStyle}
|
||||
className="accounttree-name">
|
||||
{this.props.account.Name}
|
||||
</Button>
|
||||
<Collapse in={this.state.expanded}>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const AccountTree = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {selectedAccount: null,
|
||||
height: 0};
|
||||
},
|
||||
handleSelect: function(account) {
|
||||
this.setState({selectedAccount: account});
|
||||
if (this.props.onSelect != null) {
|
||||
this.props.onSelect(account);
|
||||
}
|
||||
},
|
||||
resize: function() {
|
||||
var div = ReactDOM.findDOMNode(this);
|
||||
this.setState({height: div.parentElement.clientHeight - 73});
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.resize();
|
||||
var self = this;
|
||||
$(window).resize(function() {self.resize();});
|
||||
},
|
||||
render: function() {
|
||||
var accounts = this.props.accounts;
|
||||
|
||||
var children = [];
|
||||
for (var i = 0; i < accounts.length; i++) {
|
||||
if (accounts[i].isRootAccount())
|
||||
children.push((<AccountTreeNode
|
||||
key={accounts[i].AccountId}
|
||||
account={accounts[i]}
|
||||
selectedAccount={this.state.selectedAccount}
|
||||
onSelect={this.handleSelect}/>));
|
||||
}
|
||||
|
||||
var style = {height: this.state.height + "px"};
|
||||
|
||||
return (
|
||||
<div className="accounttree-root" style={style} >
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "AccountsTab",
|
||||
getInitialState: function() {
|
||||
return {
|
||||
selectedAccount: null,
|
||||
creatingNewAccount: false,
|
||||
editingAccount: false,
|
||||
deletingAccount: false
|
||||
};
|
||||
},
|
||||
handleNewAccount: function() {
|
||||
this.setState({creatingNewAccount: true});
|
||||
},
|
||||
handleEditAccount: function() {
|
||||
this.setState({editingAccount: true});
|
||||
},
|
||||
handleDeleteAccount: function() {
|
||||
this.setState({deletingAccount: true});
|
||||
},
|
||||
handleCreationCancel: function() {
|
||||
this.setState({creatingNewAccount: false});
|
||||
},
|
||||
handleEditingCancel: function() {
|
||||
this.setState({editingAccount: false});
|
||||
},
|
||||
handleDeletionCancel: function() {
|
||||
this.setState({deletingAccount: false});
|
||||
},
|
||||
handleCreateAccount: function(account) {
|
||||
if (this.props.onCreateAccount != null)
|
||||
this.props.onCreateAccount(account);
|
||||
this.setState({creatingNewAccount: false});
|
||||
},
|
||||
handleUpdateAccount: function(account) {
|
||||
if (this.props.onUpdateAccount != null)
|
||||
this.props.onUpdateAccount(account);
|
||||
this.setState({editingAccount: false});
|
||||
},
|
||||
handleRemoveAccount: function(account) {
|
||||
if (this.props.onDeleteAccount != null)
|
||||
this.props.onDeleteAccount(account);
|
||||
this.setState({deletingAccount: false,
|
||||
selectedAccount: null});
|
||||
},
|
||||
handleAccountSelected: function(account) {
|
||||
this.setState({selectedAccount: account});
|
||||
},
|
||||
render: function() {
|
||||
var accounts = this.props.accounts;
|
||||
var account_map = this.props.account_map;
|
||||
|
||||
var disabled = (this.state.selectedAccount == null) ? true : false;
|
||||
|
||||
return (
|
||||
<Grid fluid className="fullheight"><Row className="fullheight">
|
||||
<Col xs={2} className="fullheight account-column">
|
||||
<AddEditAccountModal
|
||||
show={this.state.creatingNewAccount}
|
||||
initialParentAccount={this.state.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
onCancel={this.handleCreationCancel}
|
||||
onSubmit={this.handleCreateAccount}
|
||||
securities={this.props.securities}/>
|
||||
<AddEditAccountModal
|
||||
show={this.state.editingAccount}
|
||||
editAccount={this.state.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
onCancel={this.handleEditingCancel}
|
||||
onSubmit={this.handleUpdateAccount}
|
||||
securities={this.props.securities}/>
|
||||
<DeleteAccountModal
|
||||
show={this.state.deletingAccount}
|
||||
initialAccount={this.state.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
onCancel={this.handleDeletionCancel}
|
||||
onSubmit={this.handleRemoveAccount}/>
|
||||
<AccountTree
|
||||
accounts={accounts}
|
||||
onSelect={this.handleAccountSelected}/>
|
||||
<ButtonGroup className="account-buttongroup">
|
||||
<Button onClick={this.handleNewAccount} bsStyle="success">
|
||||
<Glyphicon glyph='plus-sign' /></Button>
|
||||
<Button onClick={this.handleEditAccount}
|
||||
bsStyle="primary" disabled={disabled}>
|
||||
<Glyphicon glyph='cog' /></Button>
|
||||
<Button onClick={this.handleDeleteAccount}
|
||||
bsStyle="danger" disabled={disabled}>
|
||||
<Glyphicon glyph='trash' /></Button>
|
||||
</ButtonGroup>
|
||||
</Col><Col xs={10} className="fullheight transactions-column">
|
||||
<AccountRegister
|
||||
selectedAccount={this.state.selectedAccount}
|
||||
accounts={this.props.accounts}
|
||||
account_map={this.props.account_map}
|
||||
securities={this.props.securities}
|
||||
security_map={this.props.security_map} />
|
||||
</Col>
|
||||
</Row></Grid>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,4 +0,0 @@
|
||||
all:
|
||||
browserify -t [ babelify --presets [ react ] ] main.js -o bundle.js
|
||||
|
||||
.PHONY = all
|
@ -1,331 +0,0 @@
|
||||
var React = require('react');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
var Jumbotron = ReactBootstrap.Jumbotron;
|
||||
var Tabs = ReactBootstrap.Tabs;
|
||||
var Tab = ReactBootstrap.Tab;
|
||||
var Modal = ReactBootstrap.Modal;
|
||||
|
||||
var models = require('./models.js');
|
||||
var User = models.User;
|
||||
var Session = models.Session;
|
||||
var Security = models.Security;
|
||||
var Account = models.Account;
|
||||
var Error = models.Error;
|
||||
|
||||
var TopBar = require('./TopBar.js');
|
||||
var NewUserForm = require('./NewUserForm.js');
|
||||
var AccountSettingsModal = require('./AccountSettingsModal.js');
|
||||
var AccountsTab = require('./AccountsTab.js');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "MoneyGoApp",
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hash: "home",
|
||||
session: new Session(),
|
||||
user: new User(),
|
||||
accounts: [],
|
||||
account_map: {},
|
||||
securities: [],
|
||||
security_map: {},
|
||||
error: new Error(),
|
||||
showAccountSettingsModal: false
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.getSession();
|
||||
this.handleHashChange();
|
||||
if ("onhashchange" in window) {
|
||||
window.onhashchange = this.handleHashChange;
|
||||
}
|
||||
},
|
||||
handleHashChange: function() {
|
||||
var hash = location.hash.replace(/^#/, '');
|
||||
if (hash.length == 0)
|
||||
hash = "home";
|
||||
if (hash != this.state.hash)
|
||||
this.setHash(hash);
|
||||
},
|
||||
setHash: function(hash) {
|
||||
location.hash = hash;
|
||||
if (this.state.hash != hash)
|
||||
this.setState({hash: hash});
|
||||
},
|
||||
ajaxError: function(jqXHR, status, error) {
|
||||
var e = new Error();
|
||||
e.ErrorId = 5;
|
||||
e.ErrorString = "Request Failed: " + status + error;
|
||||
this.setState({error: e});
|
||||
},
|
||||
getSession: function() {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: "session/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
var s = new Session();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
if (e.ErrorId != 1 /* Not Signed In*/)
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
s.fromJSON(data);
|
||||
}
|
||||
this.setState({session: s});
|
||||
this.getUser();
|
||||
this.getAccounts();
|
||||
this.getSecurities();
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
getUser: function() {
|
||||
if (!this.state.session.isSession())
|
||||
return;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: "user/"+this.state.session.UserId+"/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
var u = new User();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
u.fromJSON(data);
|
||||
}
|
||||
this.setState({user: u});
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
getSecurities: function() {
|
||||
if (!this.state.session.isSession()) {
|
||||
this.setState({securities: [], security_map: {}});
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: "security/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
var securities = [];
|
||||
var security_map = {};
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
for (var i = 0; i < data.securities.length; i++) {
|
||||
var s = new Security();
|
||||
s.fromJSON(data.securities[i]);
|
||||
securities.push(s);
|
||||
security_map[s.SecurityId] = s;
|
||||
}
|
||||
}
|
||||
this.setState({securities: securities, security_map: security_map});
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
getAccounts: function() {
|
||||
if (!this.state.session.isSession()) {
|
||||
this.setState({accounts: [], account_map: {}});
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: "account/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
var accounts = [];
|
||||
var account_map = {};
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
for (var i = 0; i < data.accounts.length; i++) {
|
||||
var a = new Account();
|
||||
a.fromJSON(data.accounts[i]);
|
||||
accounts.push(a);
|
||||
account_map[a.AccountId] = a;
|
||||
}
|
||||
//Populate Children arrays in account objects
|
||||
for (var i = 0; i < accounts.length; i++) {
|
||||
var a = accounts[i];
|
||||
if (!a.isRootAccount())
|
||||
account_map[a.ParentAccountId].Children.push(a);
|
||||
}
|
||||
}
|
||||
this.setState({accounts: accounts, account_map: account_map});
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleErrorClear: function() {
|
||||
this.setState({error: new Error()});
|
||||
},
|
||||
handleLoginSubmit: function(user) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: "session/",
|
||||
data: {user: user.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.getSession();
|
||||
this.setHash("home");
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleLogoutSubmit: function() {
|
||||
this.setState({accounts: [], account_map: {}});
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
url: "session/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
}
|
||||
this.setState({session: new Session(), user: new User()});
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleAccountSettings: function() {
|
||||
this.setState({showAccountSettingsModal: true});
|
||||
},
|
||||
handleSettingsSubmitted: function(user) {
|
||||
this.setState({
|
||||
user: user,
|
||||
showAccountSettingsModal: false
|
||||
});
|
||||
},
|
||||
handleSettingsCanceled: function(user) {
|
||||
this.setState({showAccountSettingsModal: false});
|
||||
},
|
||||
handleCreateNewUser: function() {
|
||||
this.setHash("new_user");
|
||||
},
|
||||
handleGoHome: function(user) {
|
||||
this.setHash("home");
|
||||
},
|
||||
handleCreateAccount: function(account) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: "account/",
|
||||
data: {account: account.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.getAccounts();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleUpdateAccount: function(account) {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
dataType: "json",
|
||||
url: "account/"+account.AccountId+"/",
|
||||
data: {account: account.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.getAccounts();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
handleDeleteAccount: function(account) {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
url: "account/"+account.AccountId+"/",
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.getAccounts();
|
||||
}
|
||||
}.bind(this),
|
||||
error: this.ajaxError
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var mainContent;
|
||||
if (this.state.hash == "new_user") {
|
||||
mainContent = <NewUserForm onNewUser={this.handleGoHome} onCancel={this.handleGoHome}/>
|
||||
} else {
|
||||
if (this.state.user.isUser())
|
||||
mainContent = (
|
||||
<Tabs defaultActiveKey={1}>
|
||||
<Tab title="Accounts" eventKey={1} >
|
||||
<AccountsTab
|
||||
className="fullheight"
|
||||
accounts={this.state.accounts}
|
||||
account_map={this.state.account_map}
|
||||
securities={this.state.securities}
|
||||
security_map={this.state.security_map}
|
||||
onCreateAccount={this.handleCreateAccount}
|
||||
onUpdateAccount={this.handleUpdateAccount}
|
||||
onDeleteAccount={this.handleDeleteAccount} />
|
||||
</Tab>
|
||||
<Tab title="Scheduled Transactions" eventKey={2} >Scheduled transactions go here...</Tab>
|
||||
<Tab title="Budgets" eventKey={3} >Budgets go here...</Tab>
|
||||
<Tab title="Reports" eventKey={4} >Reports go here...</Tab>
|
||||
</Tabs>);
|
||||
else
|
||||
mainContent = (
|
||||
<Jumbotron>
|
||||
<center>
|
||||
<h1>Money<i>Go</i></h1>
|
||||
<p><i>Go</i> manage your money.</p>
|
||||
</center>
|
||||
</Jumbotron>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fullheight ui">
|
||||
<TopBar
|
||||
error={this.state.error}
|
||||
onErrorClear={this.handleErrorClear}
|
||||
onLoginSubmit={this.handleLoginSubmit}
|
||||
onCreateNewUser={this.handleCreateNewUser}
|
||||
user={this.state.user}
|
||||
onAccountSettings={this.handleAccountSettings}
|
||||
onLogoutSubmit={this.handleLogoutSubmit} />
|
||||
{mainContent}
|
||||
<AccountSettingsModal
|
||||
show={this.state.showAccountSettingsModal}
|
||||
user={this.state.user}
|
||||
onSubmit={this.handleSettingsSubmitted}
|
||||
onCancel={this.handleSettingsCanceled}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,151 +0,0 @@
|
||||
var React = require('react');
|
||||
|
||||
var Panel = require('react-bootstrap').Panel;
|
||||
var Input = require('react-bootstrap').Input;
|
||||
var Button = require('react-bootstrap').Button;
|
||||
var ButtonGroup = require('react-bootstrap').ButtonGroup;
|
||||
|
||||
var models = require('./models.js');
|
||||
var User = models.User;
|
||||
var Error = models.Error;
|
||||
|
||||
module.exports = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {error: "",
|
||||
name: "",
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirm_password: "",
|
||||
passwordChanged: false,
|
||||
initial_password: ""};
|
||||
},
|
||||
passwordValidationState: function() {
|
||||
if (this.state.passwordChanged) {
|
||||
if (this.state.password.length >= 10)
|
||||
return "success";
|
||||
else if (this.state.password.length >= 6)
|
||||
return "warning";
|
||||
else
|
||||
return "error";
|
||||
}
|
||||
},
|
||||
confirmPasswordValidationState: function() {
|
||||
if (this.state.confirm_password.length > 0) {
|
||||
if (this.state.confirm_password == this.state.password)
|
||||
return "success";
|
||||
else
|
||||
return "error";
|
||||
}
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel != null)
|
||||
this.props.onCancel();
|
||||
},
|
||||
handleChange: function() {
|
||||
if (this.refs.password.getValue() != this.state.initial_password)
|
||||
this.setState({passwordChanged: true});
|
||||
this.setState({
|
||||
name: this.refs.name.getValue(),
|
||||
username: this.refs.username.getValue(),
|
||||
email: this.refs.email.getValue(),
|
||||
password: this.refs.password.getValue(),
|
||||
confirm_password: this.refs.confirm_password.getValue()
|
||||
});
|
||||
},
|
||||
handleSubmit: function(e) {
|
||||
var u = new User();
|
||||
var error = "";
|
||||
e.preventDefault();
|
||||
|
||||
u.Name = this.state.name;
|
||||
u.Username = this.state.username;
|
||||
u.Email = this.state.email;
|
||||
u.Password = this.state.password;
|
||||
if (u.Password != this.state.confirm_password) {
|
||||
this.setState({error: "Error: password do not match"});
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleCreateNewUser(u);
|
||||
},
|
||||
handleCreateNewUser: function(user) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: "user/",
|
||||
data: {user: user.toJSON()},
|
||||
success: function(data, status, jqXHR) {
|
||||
var e = new Error();
|
||||
e.fromJSON(data);
|
||||
if (e.isError()) {
|
||||
this.setState({error: e});
|
||||
} else {
|
||||
this.props.onNewUser();
|
||||
}
|
||||
}.bind(this),
|
||||
error: function(jqXHR, status, error) {
|
||||
var e = new Error();
|
||||
e.ErrorId = 5;
|
||||
e.ErrorString = "Request Failed: " + status + error;
|
||||
this.setState({error: e});
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var title = <h3>Create New User</h3>;
|
||||
return (
|
||||
<Panel header={title} bsStyle="info">
|
||||
<span color="red">{this.state.error}</span>
|
||||
<form onSubmit={this.handleSubmit}
|
||||
className="form-horizontal">
|
||||
<Input type="text"
|
||||
label="Name"
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
ref="name"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="text"
|
||||
label="Username"
|
||||
value={this.state.username}
|
||||
onChange={this.handleChange}
|
||||
ref="username"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="email"
|
||||
label="Email"
|
||||
value={this.state.email}
|
||||
onChange={this.handleChange}
|
||||
ref="email"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"/>
|
||||
<Input type="password"
|
||||
label="Password"
|
||||
value={this.state.password}
|
||||
onChange={this.handleChange}
|
||||
ref="password"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"
|
||||
bsStyle={this.passwordValidationState()}
|
||||
hasFeedback/>
|
||||
<Input type="password"
|
||||
label="Confirm Password"
|
||||
value={this.state.confirm_password}
|
||||
onChange={this.handleChange}
|
||||
ref="confirm_password"
|
||||
labelClassName="col-xs-2"
|
||||
wrapperClassName="col-xs-10"
|
||||
bsStyle={this.confirmPasswordValidationState()}
|
||||
hasFeedback/>
|
||||
<ButtonGroup className="pull-right">
|
||||
<Button onClick={this.handleCancel}
|
||||
bsStyle="warning">Cancel</Button>
|
||||
<Button type="submit"
|
||||
bsStyle="success">Create New User</Button>
|
||||
</ButtonGroup>
|
||||
</form>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
});
|
120
static/TopBar.js
120
static/TopBar.js
@ -1,120 +0,0 @@
|
||||
var React = require('react');
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap');
|
||||
var Alert = ReactBootstrap.Alert;
|
||||
var Input = ReactBootstrap.Input;
|
||||
var Button = ReactBootstrap.Button;
|
||||
var DropdownButton = ReactBootstrap.DropdownButton;
|
||||
var MenuItem = ReactBootstrap.MenuItem;
|
||||
var Row = ReactBootstrap.Row;
|
||||
var Col = ReactBootstrap.Col;
|
||||
|
||||
var User = require('./models.js').User;
|
||||
|
||||
const LoginBar = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {username: '', password: ''};
|
||||
},
|
||||
onUsernameChange: function(e) {
|
||||
this.setState({username: e.target.value});
|
||||
},
|
||||
onPasswordChange: function(e) {
|
||||
this.setState({password: e.target.value});
|
||||
},
|
||||
handleSubmit: function(e) {
|
||||
var user = new User();
|
||||
e.preventDefault();
|
||||
user.Username = this.refs.username.getValue();
|
||||
user.Password = this.refs.password.getValue();
|
||||
this.props.onLoginSubmit(user);
|
||||
},
|
||||
handleNewUserSubmit: function(e) {
|
||||
e.preventDefault();
|
||||
this.props.onCreateNewUser();
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<Input wrapperClassName="wrapper">
|
||||
<Row>
|
||||
<Col xs={4}></Col>
|
||||
<Col xs={2}>
|
||||
<Button bsStyle="link"
|
||||
onClick={this.handleNewUserSubmit}>Create New User</Button>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<Input type="text"
|
||||
placeholder="Username..."
|
||||
ref="username"/>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<Input type="password"
|
||||
placeholder="Password..."
|
||||
ref="password" block/>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<Button type="submit" bsStyle="primary" block>
|
||||
Login</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Input>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const LogoutBar = React.createClass({
|
||||
handleOnSelect: function(e, key) {
|
||||
if (key == 1) {
|
||||
if (this.props.onAccountSettings != null)
|
||||
this.props.onAccountSettings();
|
||||
} else if (key == 2) {
|
||||
this.props.onLogoutSubmit();
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var signedInString = "Signed in as "+this.props.user.Name;
|
||||
return (
|
||||
<Input wrapperClassName="wrapper">
|
||||
<Row>
|
||||
<Col xs={2}><label className="control-label pull-left">Money<i>Go</i></label></Col>
|
||||
<Col xs={6}></Col>
|
||||
<Col xs={4}>
|
||||
<div className="pull-right">
|
||||
<DropdownButton id="logout-settings-dropdown" title={signedInString} onSelect={this.handleOnSelect} bsStyle="info">
|
||||
<MenuItem eventKey="1">Account Settings</MenuItem>
|
||||
<MenuItem eventKey="2">Logout</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Input>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "TopBar",
|
||||
render: function() {
|
||||
var barContents;
|
||||
var errorAlert;
|
||||
if (!this.props.user.isUser())
|
||||
barContents = <LoginBar onLoginSubmit={this.props.onLoginSubmit} onCreateNewUser={this.props.onCreateNewUser} />;
|
||||
else
|
||||
barContents = <LogoutBar user={this.props.user} onLogoutSubmit={this.props.onLogoutSubmit} onAccountSettings={this.props.onAccountSettings}/>;
|
||||
if (this.props.error.isError())
|
||||
errorAlert =
|
||||
<Alert bsStyle="danger" onDismiss={this.props.onErrorClear}>
|
||||
<h4>Error!</h4>
|
||||
<p>Error {this.props.error.ErrorId}: {this.props.error.ErrorString}</p>
|
||||
<Button onClick={this.props.onErrorClear}>Clear</Button>
|
||||
</Alert>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{barContents}
|
||||
{errorAlert}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -4,12 +4,11 @@
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="static/node_modules/react-widgets/dist/css/react-widgets.css">
|
||||
<link rel="stylesheet" href="static/react-widgets/css/react-widgets.css">
|
||||
<link rel="stylesheet" href="static/stylesheet.css">
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="static/external/big/big.min.js"></script>
|
||||
<script src="static/external/classnames/index.js"></script>
|
||||
|
||||
<script type="text/javascript" src="static/bundle.js"></script>
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var Globalize = require('globalize');
|
||||
var globalizeLocalizer = require('react-widgets/lib/localizers/globalize');
|
||||
|
||||
var MoneyGoApp = require('./MoneyGoApp.js');
|
||||
|
||||
// Setup globalization for react-widgets
|
||||
//Globalize.load(require("cldr-data").entireSupplemental());
|
||||
Globalize.load(
|
||||
require("cldr-data/main/en/ca-gregorian"),
|
||||
require("cldr-data/main/en/numbers"),
|
||||
require("cldr-data/supplemental/likelySubtags"),
|
||||
require("cldr-data/supplemental/timeData"),
|
||||
require("cldr-data/supplemental/weekData")
|
||||
);
|
||||
Globalize.locale('en');
|
||||
globalizeLocalizer(Globalize);
|
||||
|
||||
$(document).ready(function() {
|
||||
ReactDOM.render(<MoneyGoApp />, document.getElementById("content"));
|
||||
});
|
409
static/models.js
409
static/models.js
@ -1,409 +0,0 @@
|
||||
function getJSONObj(json_input) {
|
||||
if (typeof json_input == "string")
|
||||
return $.parseJSON(json_input)
|
||||
else if (typeof json_input == "object")
|
||||
return json_input;
|
||||
|
||||
console.error("Unable to parse json:", json_input);
|
||||
return null
|
||||
}
|
||||
|
||||
function User() {
|
||||
this.UserId = -1;
|
||||
this.Name = "";
|
||||
this.Username = "";
|
||||
this.Password = "";
|
||||
this.Email = "";
|
||||
}
|
||||
|
||||
User.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.UserId = this.UserId;
|
||||
json_obj.Name = this.Name;
|
||||
json_obj.Username = this.Username;
|
||||
json_obj.Password = this.Password;
|
||||
json_obj.Email = this.Email;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
User.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("UserId"))
|
||||
this.UserId = json_obj.UserId;
|
||||
if (json_obj.hasOwnProperty("Name"))
|
||||
this.Name = json_obj.Name;
|
||||
if (json_obj.hasOwnProperty("Username"))
|
||||
this.Username = json_obj.Username;
|
||||
if (json_obj.hasOwnProperty("Password"))
|
||||
this.Password = json_obj.Password;
|
||||
if (json_obj.hasOwnProperty("Email"))
|
||||
this.Email = json_obj.Email;
|
||||
}
|
||||
|
||||
User.prototype.isUser = function() {
|
||||
var empty_user = new User();
|
||||
return this.UserId != empty_user.UserId ||
|
||||
this.Username != empty_user.Username;
|
||||
}
|
||||
|
||||
function Session() {
|
||||
this.SessionId = -1;
|
||||
this.UserId = -1;
|
||||
}
|
||||
|
||||
Session.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.SessionId = this.SessionId;
|
||||
json_obj.UserId = this.UserId;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
Session.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("SessionId"))
|
||||
this.SessionId = json_obj.SessionId;
|
||||
if (json_obj.hasOwnProperty("UserId"))
|
||||
this.UserId = json_obj.UserId;
|
||||
}
|
||||
|
||||
Session.prototype.isSession = function() {
|
||||
var empty_session = new Session();
|
||||
return this.SessionId != empty_session.SessionId ||
|
||||
this.UserId != empty_session.UserId;
|
||||
}
|
||||
|
||||
const SecurityType = {
|
||||
Currency: 1,
|
||||
Stock: 2
|
||||
}
|
||||
var SecurityTypeList = [];
|
||||
for (var type in SecurityType) {
|
||||
if (SecurityType.hasOwnProperty(type)) {
|
||||
SecurityTypeList.push({'TypeId': SecurityType[type], 'Name': type});
|
||||
}
|
||||
}
|
||||
|
||||
function Security() {
|
||||
this.SecurityId = -1;
|
||||
this.Name = "";
|
||||
this.Symbol = "";
|
||||
this.Precision = -1;
|
||||
this.Type = -1;
|
||||
}
|
||||
|
||||
Security.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.SecurityId = this.SecurityId;
|
||||
json_obj.Name = this.Name;
|
||||
json_obj.Symbol = this.Symbol;
|
||||
json_obj.Precision = this.Precision;
|
||||
json_obj.Type = this.Type;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
Security.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("SecurityId"))
|
||||
this.SecurityId = json_obj.SecurityId;
|
||||
if (json_obj.hasOwnProperty("Name"))
|
||||
this.Name = json_obj.Name;
|
||||
if (json_obj.hasOwnProperty("Symbol"))
|
||||
this.Symbol = json_obj.Symbol;
|
||||
if (json_obj.hasOwnProperty("Precision"))
|
||||
this.Precision = json_obj.Precision;
|
||||
if (json_obj.hasOwnProperty("Type"))
|
||||
this.Type = json_obj.Type;
|
||||
}
|
||||
|
||||
Security.prototype.isSecurity = function() {
|
||||
var empty_account = new Security();
|
||||
return this.SecurityId != empty_account.SecurityId ||
|
||||
this.Type != empty_account.Type;
|
||||
}
|
||||
|
||||
const AccountType = {
|
||||
Bank: 1,
|
||||
Cash: 2,
|
||||
Asset: 3,
|
||||
Liability: 4,
|
||||
Investment: 5,
|
||||
Income: 6,
|
||||
Expense: 7,
|
||||
Trading: 8
|
||||
}
|
||||
var AccountTypeList = [];
|
||||
for (var type in AccountType) {
|
||||
if (AccountType.hasOwnProperty(type)) {
|
||||
AccountTypeList.push({'TypeId': AccountType[type], 'Name': type});
|
||||
}
|
||||
}
|
||||
|
||||
function Account() {
|
||||
this.AccountId = -1;
|
||||
this.UserId = -1;
|
||||
this.SecurityId = -1;
|
||||
this.ParentAccountId = -1;
|
||||
this.Type = -1;
|
||||
this.Name = "";
|
||||
this.Children = []; // Not sent across JSON, just used internally
|
||||
}
|
||||
|
||||
Account.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.AccountId = this.AccountId;
|
||||
json_obj.UserId = this.UserId;
|
||||
json_obj.SecurityId = this.SecurityId;
|
||||
json_obj.ParentAccountId = this.ParentAccountId;
|
||||
json_obj.Type = this.Type;
|
||||
json_obj.Name = this.Name;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
Account.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("AccountId"))
|
||||
this.AccountId = json_obj.AccountId;
|
||||
if (json_obj.hasOwnProperty("UserId"))
|
||||
this.UserId = json_obj.UserId;
|
||||
if (json_obj.hasOwnProperty("SecurityId"))
|
||||
this.SecurityId = json_obj.SecurityId;
|
||||
if (json_obj.hasOwnProperty("ParentAccountId"))
|
||||
this.ParentAccountId = json_obj.ParentAccountId;
|
||||
if (json_obj.hasOwnProperty("Type"))
|
||||
this.Type = json_obj.Type;
|
||||
if (json_obj.hasOwnProperty("Name"))
|
||||
this.Name = json_obj.Name;
|
||||
}
|
||||
|
||||
Account.prototype.isAccount = function() {
|
||||
var empty_account = new Account();
|
||||
return this.AccountId != empty_account.AccountId ||
|
||||
this.UserId != empty_account.UserId;
|
||||
}
|
||||
|
||||
Account.prototype.isRootAccount = function() {
|
||||
var empty_account = new Account();
|
||||
return this.ParentAccountId == empty_account.ParentAccountId;
|
||||
}
|
||||
|
||||
function Split() {
|
||||
this.SplitId = -1;
|
||||
this.TransactionId = -1;
|
||||
this.AccountId = -1;
|
||||
this.SecurityId = -1;
|
||||
this.Number = "";
|
||||
this.Memo = "";
|
||||
this.Amount = new Big(0.0);
|
||||
this.Debit = false;
|
||||
}
|
||||
|
||||
Split.prototype.toJSONobj = function() {
|
||||
var json_obj = {};
|
||||
json_obj.SplitId = this.SplitId;
|
||||
json_obj.TransactionId = this.TransactionId;
|
||||
json_obj.AccountId = this.AccountId;
|
||||
json_obj.SecurityId = this.SecurityId;
|
||||
json_obj.Number = this.Number;
|
||||
json_obj.Memo = this.Memo;
|
||||
json_obj.Amount = this.Amount.toFixed();
|
||||
json_obj.Debit = this.Debit;
|
||||
return json_obj;
|
||||
}
|
||||
|
||||
Split.prototype.fromJSONobj = function(json_obj) {
|
||||
if (json_obj.hasOwnProperty("SplitId"))
|
||||
this.SplitId = json_obj.SplitId;
|
||||
if (json_obj.hasOwnProperty("TransactionId"))
|
||||
this.TransactionId = json_obj.TransactionId;
|
||||
if (json_obj.hasOwnProperty("AccountId"))
|
||||
this.AccountId = json_obj.AccountId;
|
||||
if (json_obj.hasOwnProperty("SecurityId"))
|
||||
this.SecurityId = json_obj.SecurityId;
|
||||
if (json_obj.hasOwnProperty("Number"))
|
||||
this.Number = json_obj.Number;
|
||||
if (json_obj.hasOwnProperty("Memo"))
|
||||
this.Memo = json_obj.Memo;
|
||||
if (json_obj.hasOwnProperty("Amount"))
|
||||
this.Amount = new Big(json_obj.Amount);
|
||||
if (json_obj.hasOwnProperty("Debit"))
|
||||
this.Debit = json_obj.Debit;
|
||||
}
|
||||
|
||||
Split.prototype.isSplit = function() {
|
||||
var empty_split = new Split();
|
||||
return this.SplitId != empty_split.SplitId ||
|
||||
this.TransactionId != empty_split.TransactionId ||
|
||||
this.AccountId != empty_split.AccountId ||
|
||||
this.SecurityId != empty_split.SecurityId;
|
||||
}
|
||||
|
||||
const TransactionStatus = {
|
||||
Imported: 1,
|
||||
Entered: 2,
|
||||
Cleared: 3,
|
||||
Reconciled: 4,
|
||||
Voided: 5
|
||||
}
|
||||
var TransactionStatusList = [];
|
||||
for (var type in TransactionStatus) {
|
||||
if (TransactionStatus.hasOwnProperty(type)) {
|
||||
TransactionStatusList.push({'StatusId': TransactionStatus[type], 'Name': type});
|
||||
}
|
||||
}
|
||||
var TransactionStatusMap = {};
|
||||
for (var status in TransactionStatus) {
|
||||
if (TransactionStatus.hasOwnProperty(status)) {
|
||||
TransactionStatusMap[TransactionStatus[status]] = status;
|
||||
}
|
||||
}
|
||||
|
||||
function Transaction() {
|
||||
this.TransactionId = -1;
|
||||
this.UserId = -1;
|
||||
this.Description = "";
|
||||
this.Status = -1;
|
||||
this.Date = new Date();
|
||||
this.Splits = [];
|
||||
}
|
||||
|
||||
Transaction.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.TransactionId = this.TransactionId;
|
||||
json_obj.UserId = this.UserId;
|
||||
json_obj.Description = this.Description;
|
||||
json_obj.Status = this.Status;
|
||||
json_obj.Date = this.Date.toJSON();
|
||||
json_obj.Splits = [];
|
||||
for (var i = 0; i < this.Splits.length; i++)
|
||||
json_obj.Splits.push(this.Splits[i].toJSONobj());
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
Transaction.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("TransactionId"))
|
||||
this.TransactionId = json_obj.TransactionId;
|
||||
if (json_obj.hasOwnProperty("UserId"))
|
||||
this.UserId = json_obj.UserId;
|
||||
if (json_obj.hasOwnProperty("Description"))
|
||||
this.Description = json_obj.Description;
|
||||
if (json_obj.hasOwnProperty("Status"))
|
||||
this.Status = json_obj.Status;
|
||||
if (json_obj.hasOwnProperty("Date")) {
|
||||
this.Date = json_obj.Date
|
||||
if (typeof this.Date === 'string') {
|
||||
var t = Date.parse(this.Date);
|
||||
if (t)
|
||||
this.Date = new Date(t);
|
||||
else
|
||||
this.Date = new Date(0);
|
||||
} else
|
||||
this.Date = new Date(0);
|
||||
}
|
||||
if (json_obj.hasOwnProperty("Splits")) {
|
||||
for (var i = 0; i < json_obj.Splits.length; i++) {
|
||||
var s = new Split();
|
||||
s.fromJSONobj(json_obj.Splits[i]);
|
||||
this.Splits.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transaction.prototype.isTransaction = function() {
|
||||
var empty_transaction = new Transaction();
|
||||
return this.TransactionId != empty_transaction.TransactionId ||
|
||||
this.UserId != empty_transaction.UserId;
|
||||
}
|
||||
|
||||
Transaction.prototype.deepCopy = function() {
|
||||
var t = new Transaction();
|
||||
t.fromJSON(this.toJSON());
|
||||
return t;
|
||||
}
|
||||
|
||||
Transaction.prototype.imbalancedSplitSecurities = function(account_map) {
|
||||
// Return a list of SecurityIDs for those securities that aren't balanced
|
||||
// in this transaction's splits. If a split's AccountId is invalid, that
|
||||
// split is ignored, so those must be checked elsewhere
|
||||
var splitBalances = {};
|
||||
const emptySplit = new Split();
|
||||
for (var i = 0; i < this.Splits.length; i++) {
|
||||
split = this.Splits[i];
|
||||
var securityId = -1;
|
||||
if (split.AccountId != emptySplit.AccountId) {
|
||||
securityId = account_map[split.AccountId].SecurityId;
|
||||
} else if (split.SecurityId != emptySplit.SecurityId) {
|
||||
securityId = split.SecurityId;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (securityId in splitBalances) {
|
||||
splitBalances[securityId] = split.Amount.plus(splitBalances[securityId]);
|
||||
} else {
|
||||
splitBalances[securityId] = split.Amount.plus(0);
|
||||
}
|
||||
}
|
||||
var imbalancedIDs = [];
|
||||
for (var id in splitBalances) {
|
||||
if (!splitBalances[id].eq(0)) {
|
||||
imbalancedIDs.push(id);
|
||||
}
|
||||
}
|
||||
return imbalancedIDs;
|
||||
}
|
||||
|
||||
function Error() {
|
||||
this.ErrorId = -1;
|
||||
this.ErrorString = "";
|
||||
}
|
||||
|
||||
Error.prototype.toJSON = function() {
|
||||
var json_obj = {};
|
||||
json_obj.ErrorId = this.ErrorId;
|
||||
json_obj.ErrorString = this.ErrorString;
|
||||
return JSON.stringify(json_obj);
|
||||
}
|
||||
|
||||
Error.prototype.fromJSON = function(json_input) {
|
||||
var json_obj = getJSONObj(json_input);
|
||||
|
||||
if (json_obj.hasOwnProperty("ErrorId"))
|
||||
this.ErrorId = json_obj.ErrorId;
|
||||
if (json_obj.hasOwnProperty("ErrorString"))
|
||||
this.ErrorString = json_obj.ErrorString;
|
||||
}
|
||||
|
||||
Error.prototype.isError = function() {
|
||||
var empty_error = new Error();
|
||||
return this.ErrorId != empty_error.ErrorId ||
|
||||
this.ErrorString != empty_error.ErrorString;
|
||||
}
|
||||
|
||||
module.exports = models = {
|
||||
|
||||
// Classes
|
||||
User: User,
|
||||
Session: Session,
|
||||
Security: Security,
|
||||
Account: Account,
|
||||
Split: Split,
|
||||
Transaction: Transaction,
|
||||
Error: Error,
|
||||
|
||||
// Enums, Lists
|
||||
AccountType: AccountType,
|
||||
AccountTypeList: AccountTypeList,
|
||||
SecurityType: SecurityType,
|
||||
SecurityTypeList: SecurityTypeList,
|
||||
TransactionStatus: TransactionStatus,
|
||||
TransactionStatusList: TransactionStatusList,
|
||||
TransactionStatusMap: TransactionStatusMap,
|
||||
|
||||
// Constants
|
||||
BogusPassword: "password"
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
const recursiveAccountDisplayInfo = function(account, prefix) {
|
||||
var name = prefix + account.Name;
|
||||
var accounts = [{AccountId: account.AccountId, Name: name}];
|
||||
for (var i = 0; i < account.Children.length; i++)
|
||||
accounts = accounts.concat(recursiveAccountDisplayInfo(account.Children[i], name + "/"));
|
||||
return accounts
|
||||
};
|
||||
|
||||
const getAccountDisplayList = function(account_list, includeRoot, rootName) {
|
||||
var accounts = []
|
||||
if (includeRoot)
|
||||
accounts.push({AccountId: -1, Name: rootName});
|
||||
for (var i = 0; i < account_list.length; i++) {
|
||||
if (account_list[i].isRootAccount())
|
||||
accounts = accounts.concat(recursiveAccountDisplayInfo(account_list[i], ""));
|
||||
}
|
||||
return accounts;
|
||||
};
|
||||
|
||||
const getAccountDisplayName = function(account, account_map) {
|
||||
var name = account.Name;
|
||||
while (account.ParentAccountId >= 0) {
|
||||
account = account_map[account.ParentAccountId];
|
||||
name = account.Name + "/" + name;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAccountDisplayList: getAccountDisplayList,
|
||||
getAccountDisplayName: getAccountDisplayName
|
||||
};
|
Reference in New Issue
Block a user