1
0
mirror of https://github.com/aclindsa/moneygo.git synced 2025-06-13 21:48:39 -04:00

Move all components into 'components' subdirectory

This commit is contained in:
2016-10-05 13:45:09 -04:00
parent 6257e9193f
commit 0829393918
11 changed files with 18 additions and 19 deletions

View File

@ -0,0 +1,41 @@
var React = require('react');
var Combobox = require('react-widgets').Combobox;
var getAccountDisplayList = require('../utils').getAccountDisplayList;
module.exports = React.createClass({
displayName: "AccountCombobox",
getDefaultProps: function() {
return {
includeRoot: true,
disabled: false,
rootName: "New Top-level Account"
};
},
handleAccountChange: function(account) {
if (this.props.onChange != null &&
account.hasOwnProperty('AccountId') &&
(this.props.accounts.hasOwnProperty([account.AccountId]) ||
account.AccountId == -1)) {
this.props.onChange(account)
}
},
render: function() {
var accounts = getAccountDisplayList(this.props.accounts, this.props.accountChildren, 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"
disabled={this.props.disabled}
className={className} />
);
}
});

View File

@ -0,0 +1,952 @@
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 Form = ReactBootstrap.Form;
var FormGroup = ReactBootstrap.FormGroup;
var FormControl = ReactBootstrap.FormControl;
var InputGroup = ReactBootstrap.InputGroup;
var ControlLabel = ReactBootstrap.ControlLabel;
var HelpBlock = ReactBootstrap.HelpBlock;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var ButtonToolbar = ReactBootstrap.ButtonToolbar;
var ProgressBar = ReactBootstrap.ProgressBar;
var Glyphicon = ReactBootstrap.Glyphicon;
var ReactWidgets = require('react-widgets')
var DateTimePicker = ReactWidgets.DateTimePicker;
var Combobox = ReactWidgets.Combobox;
var DropdownList = ReactWidgets.DropdownList;
var Big = require('big.js');
var models = require('../models');
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').getAccountDisplayName;
var AccountCombobox = require('./AccountCombobox');
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.securities[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.securities[otherSplit.SecurityId].Symbol + " transaction";
else
var accountName = getAccountDisplayName(this.props.accounts[otherSplit.AccountId], this.props.accounts);
} 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() {
ReactDOM.findDOMNode(this.refs.amount).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: ReactDOM.findDOMNode(this.refs.amount).value});
if (this.props.onChange)
this.props.onChange();
},
getValue: function() {
try {
var value = ReactDOM.findDOMNode(this.refs.amount).value;
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;
return (
<FormGroup validationState={this.props.validationState}>
<InputGroup>
<InputGroup.Addon>{symbol}</InputGroup.Addon>
<FormControl type="text"
value={this.state.Amount}
onChange={this.onChange}
ref="amount"/>
</InputGroup>
</FormGroup>
);
}
});
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: ReactDOM.findDOMNode(this.refs.description).value}
})
});
},
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: ReactDOM.findDOMNode(this.refs['number-'+split]).value}
});
this.setState({
transaction: transaction
});
},
handleUpdateMemo: function(split) {
var transaction = this.state.transaction;
transaction.Splits[split] = react_update(transaction.Splits[split], {
Memo: {$set: ReactDOM.findDOMNode(this.refs['memo-'+split]).value}
});
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.accounts);
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.accounts)) {
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.accounts);
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.accounts) {
security = this.props.securities[this.props.accounts[s.AccountId].SecurityId];
} else {
if (s.SecurityId in this.props.securities) {
security = this.props.securities[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}><FormControl
type="text"
value={s.Number}
onChange={updateNumberFn}
ref={"number-"+i} /></Col>
<Col xs={5}><FormControl
type="text"
value={s.Memo}
onChange={updateMemoFn}
ref={"memo-"+i} /></Col>
<Col xs={3}><AccountCombobox
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
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}
validationState={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 horizontal
onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Date</Col>
<Col xs={10}>
<DateTimePicker
time={false}
defaultValue={this.state.transaction.Date}
onChange={this.handleDateChange} />
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Description</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.transaction.Description}
onChange={this.handleDescriptionChange}
ref="description"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Status</Col>
<Col xs={10}>
<Combobox
data={TransactionStatusList}
valueField='StatusId'
textField='Name'
defaultValue={this.state.transaction.Status}
onSelect={this.handleStatusChange}
ref="status" />
</Col>
</FormGroup>
<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 ImportType = {
OFX: 1,
Gnucash: 2
};
var ImportTypeList = [];
for (var type in ImportType) {
if (ImportType.hasOwnProperty(type)) {
var name = ImportType[type] == ImportType.OFX ? "OFX/QFX" : type; //QFX is a special snowflake
ImportTypeList.push({'TypeId': ImportType[type], 'Name': name});
}
}
const ImportTransactionsModal = React.createClass({
getInitialState: function() {
return {
importing: false,
imported: false,
importFile: "",
importType: ImportType.Gnucash,
uploadProgress: -1,
error: null};
},
handleCancel: function() {
this.setState(this.getInitialState());
if (this.props.onCancel != null)
this.props.onCancel();
},
handleImportChange: function() {
this.setState({importFile: ReactDOM.findDOMNode(this.refs.importfile).value});
},
handleTypeChange: function(type) {
this.setState({importType: type.TypeId});
},
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();
formData.append('importfile', file, this.state.importFile);
var url = ""
if (this.state.importType == ImportType.OFX)
url = "account/"+this.props.account.AccountId+"/import/ofx";
else if (this.state.importType == ImportType.Gnucash)
url = "import/gnucash";
this.setState({importing: true});
$.ajax({
type: "POST",
url: url,
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 valid 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 = "Performing global import:"
if (this.props.account != null && this.state.importType != ImportType.Gnucash)
accountNameLabel = "Importing to '" + getAccountDisplayName(this.props.account, this.props.accounts) + "' account:";
// Display the progress bar if an upload/import is in progress
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%" />);
}
// Create panel, possibly displaying error or success messages
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>);
}
// Display proper buttons, possibly disabling them if an import is in progress
var button1 = [];
var button2 = [];
if (!this.state.imported && this.state.error == null) {
button1 = (<Button onClick={this.handleCancel} disabled={this.state.importing} bsStyle="warning">Cancel</Button>);
button2 = (<Button onClick={this.handleImportTransactions} disabled={this.state.importing || this.state.importFile == ""} bsStyle="success">Import</Button>);
} else {
button1 = (<Button onClick={this.handleCancel} disabled={this.state.importing} bsStyle="success">OK</Button>);
}
var inputDisabled = (this.state.importing || this.state.error != null || this.state.imported) ? true : false;
// Disable OFX/QFX imports if no account is selected
var disabledTypes = false;
if (this.props.account == null)
disabledTypes = [ImportTypeList[ImportType.OFX - 1]];
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">
<DropdownList
data={ImportTypeList}
valueField='TypeId'
textField='Name'
onSelect={this.handleTypeChange}
defaultValue={this.state.importType}
disabled={disabledTypes}
ref="importtype" />
<FormGroup>
<ControlLabel>{accountNameLabel}</ControlLabel>
<FormControl type="file"
ref="importfile"
disabled={inputDisabled}
value={this.state.importFile}
onChange={this.handleImportChange} />
<HelpBlock>Select an OFX/QFX file to upload.</HelpBlock>
</FormGroup>
</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.accounts[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.accounts[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 != -1) {
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], newpage);
}
this.setState({currentPage: newpage});
}
},
onNewTransaction: function() {
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
onUpdatedTransaction: function() {
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
onDeletedTransaction: function() {
this.getTransactionPage(this.props.accounts[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});
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
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 != -1)
this.getTransactionPage(nextProps.accounts[nextProps.selectedAccount], 0);
}
},
render: function() {
var name = "Please select an account";
register = [];
if (this.props.selectedAccount != -1) {
name = this.props.accounts[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.accounts[this.props.selectedAccount]}
accounts={this.props.accounts}
securities={this.props.securities}
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 == -1) ? true : false;
return (
<div className="transactions-container">
<AddEditTransactionModal
show={this.state.editingTransaction}
transaction={this.state.selectedTransaction}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
onCancel={this.handleEditingCancel}
onSubmit={this.handleUpdateTransaction}
onDelete={this.handleDeleteTransaction}
securities={this.props.securities} />
<ImportTransactionsModal
show={this.state.importingTransactions}
account={this.props.accounts[this.props.selectedAccount]}
accounts={this.props.accounts}
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 ellipsis
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">
<Glyphicon glyph='import' /> Import
</Button>
</ButtonGroup>
</ButtonToolbar>
</div>
{register}
</div>
);
}
});

View File

@ -0,0 +1,158 @@
var React = require('react');
var ReactDOM = require('react-dom');
var ReactBootstrap = require('react-bootstrap');
var Modal = ReactBootstrap.Modal;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var Form = ReactBootstrap.Form;
var FormGroup = ReactBootstrap.FormGroup;
var FormControl = ReactBootstrap.FormControl;
var ControlLabel = ReactBootstrap.ControlLabel;
var Col = ReactBootstrap.Col;
var User = require('../models').User;
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 (ReactDOM.findDOMNode(this.refs.password).value != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: ReactDOM.findDOMNode(this.refs.name).value,
username: ReactDOM.findDOMNode(this.refs.username).value,
email: ReactDOM.findDOMNode(this.refs.email).value,
password: ReactDOM.findDOMNode(this.refs.password).value,
confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value
});
},
handleSubmit: function(e) {
var u = new User();
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.props.onUpdateUser(u);
this.props.onSubmit();
},
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 horizontal onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Name</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.name}
onChange={this.handleChange}
ref="name"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Username</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.username}
onChange={this.handleChange}
ref="username"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Email</Col>
<Col xs={10}>
<FormControl type="email"
value={this.state.email}
onChange={this.handleChange}
ref="email"/>
</Col>
</FormGroup>
<FormGroup validationState={this.passwordValidationState()}>
<Col componentClass={ControlLabel} xs={2}>Password</Col>
<Col xs={10}>
<FormControl type="password"
value={this.state.password}
onChange={this.handleChange}
ref="password"/>
<FormControl.Feedback/>
</Col>
</FormGroup>
<FormGroup validationState={this.confirmPasswordValidationState()}>
<Col componentClass={ControlLabel} xs={2}>Confirm Password</Col>
<Col xs={10}>
<FormControl type="password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"/>
<FormControl.Feedback/>
</Col>
</FormGroup>
</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>
);
}
});

View File

@ -0,0 +1,486 @@
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 Form = ReactBootstrap.Form;
var FormGroup = ReactBootstrap.FormGroup;
var FormControl = ReactBootstrap.FormControl;
var Checkbox = ReactBootstrap.Checkbox;
var ControlLabel = ReactBootstrap.ControlLabel;
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');
var Security = models.Security;
var Account = models.Account;
var AccountTypeList = models.AccountTypeList;
var AccountCombobox = require('./AccountCombobox');
var AccountRegister = require('./AccountRegister');
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: ReactDOM.findDOMNode(this.refs.name).value,
});
},
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 horizontal onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Name</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.name}
onChange={this.handleChange}
ref="name"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Parent Account</Col>
<Col xs={10}>
<AccountCombobox
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
value={this.state.parentaccountid}
rootName={rootName}
onChange={this.handleParentChange}
ref="parent" />
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Security</Col>
<Col xs={10}>
<Combobox
data={this.props.security_list}
valueField='SecurityId'
textField={item => item.Name + " - " + item.Description}
defaultValue={this.state.security}
onChange={this.handleSecurityChange}
ref="security" />
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Account Type</Col>
<Col xs={10}>
<Combobox
data={AccountTypeList}
valueField='TypeId'
textField='Name'
defaultValue={this.state.type}
onChange={this.handleTypeChange}
ref="type" />
</Col>
</FormGroup>
</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.accounts.hasOwnProperty(this.state.accountid)) {
if (this.state.checked) {
if (this.props.onSubmit != null)
this.props.onSubmit(this.props.accounts[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.accounts.hasOwnProperty(this.state.accountid)) {
var parentAccountId = this.props.accounts[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.accounts[parentAccountId].Name;
var warningString = "I understand that deleting this account cannot be undone and that all transactions " + parentAccount;
checkbox = (
<FormGroup>
<Col xsOffset={2} sm={10}>
<Checkbox
checked={this.state.checked ? "checked" : ""}
onClick={this.handleCheckboxClick}>
{warningString}
</Checkbox>
</Col>
</FormGroup>);
}
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 horizontal onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Delete Account</Col>
<Col xs={10}>
<AccountCombobox
includeRoot={false}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
value={this.state.accountid}
onChange={this.handleChange}/>
</Col>
</FormGroup>
{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 != -1 &&
this.props.account.AccountId == this.props.selectedAccount);
var buttonStyle = active ? "info" : "link";
var self = this;
var children = this.props.accountChildren[this.props.account.AccountId].map(function(childId) {
var account = self.props.accounts[childId];
return (
<AccountTreeNode
key={account.AccountId}
account={account}
selectedAccount={self.props.selectedAccount}
accounts={self.props.accounts}
accountChildren={self.props.accountChildren}
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 {height: 0};
},
handleSelect: function(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 accountId in accounts) {
if (accounts.hasOwnProperty(accountId) &&
accounts[accountId].isRootAccount()) {
children.push((<AccountTreeNode
key={accounts[accountId].AccountId}
account={accounts[accountId]}
selectedAccount={this.props.selectedAccount}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
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 {
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});
},
handleAccountSelected: function(account) {
this.props.onSelectAccount(account.AccountId);
},
render: function() {
var disabled = (this.props.selectedAccount == -1) ? true : false;
var selectedAccount = null;
if (this.props.accounts.hasOwnProperty(this.props.selectedAccount))
selectedAccount = this.props.accounts[this.props.selectedAccount];
return (
<Grid fluid className="fullheight"><Row className="fullheight">
<Col xs={2} className="fullheight account-column">
<AddEditAccountModal
show={this.state.creatingNewAccount}
initialParentAccount={selectedAccount}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
onCancel={this.handleCreationCancel}
onSubmit={this.handleCreateAccount}
security_list={this.props.security_list}/>
<AddEditAccountModal
show={this.state.editingAccount}
editAccount={selectedAccount}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
onCancel={this.handleEditingCancel}
onSubmit={this.handleUpdateAccount}
security_list={this.props.security_list}/>
<DeleteAccountModal
show={this.state.deletingAccount}
initialAccount={selectedAccount}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
onCancel={this.handleDeletionCancel}
onSubmit={this.handleRemoveAccount}/>
<AccountTree
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
selectedAccount={this.props.selectedAccount}
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.props.selectedAccount}
accounts={this.props.accounts}
accountChildren={this.props.accountChildren}
securities={this.props.securities} />
</Col>
</Row></Grid>
);
}
});

View File

@ -0,0 +1,97 @@
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 TopBarContainer = require('../containers/TopBarContainer');
var NewUserForm = require('./NewUserForm');
var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer');
var AccountsTabContainer = require('../containers/AccountsTabContainer');
module.exports = React.createClass({
displayName: "MoneyGoApp",
getInitialState: function() {
return {
hash: "home",
showAccountSettingsModal: false
};
},
componentDidMount: function() {
this.props.tryResumingSession();
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});
},
handleAccountSettings: function() {
this.setState({showAccountSettingsModal: true});
},
handleSettingsSubmitted: function(user) {
this.setState({
showAccountSettingsModal: false
});
},
handleSettingsCanceled: function() {
this.setState({showAccountSettingsModal: false});
},
handleCreateNewUser: function() {
this.setHash("new_user");
},
handleGoHome: function() {
this.setHash("home");
},
render: function() {
var mainContent;
if (this.state.hash == "new_user") {
mainContent = <NewUserForm onNewUser={this.handleGoHome} onCancel={this.handleGoHome}/>
} else {
if (this.props.user.isUser())
mainContent = (
<Tabs defaultActiveKey={1} id='mainNavigationTabs'>
<Tab title="Accounts" eventKey={1} >
<AccountsTabContainer
className="fullheight" />
</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">
<TopBarContainer
onCreateNewUser={this.handleCreateNewUser}
onAccountSettings={this.handleAccountSettings} />
{mainContent}
<AccountSettingsModalContainer
show={this.state.showAccountSettingsModal}
onSubmit={this.handleSettingsSubmitted}
onCancel={this.handleSettingsCanceled}/>
</div>
);
}
});

View File

@ -0,0 +1,165 @@
var React = require('react');
var ReactDOM = require('react-dom');
var ReactBootstrap = require('react-bootstrap');
var Panel = ReactBootstrap.Panel;
var Form = ReactBootstrap.Form;
var FormGroup = ReactBootstrap.FormGroup;
var FormControl = ReactBootstrap.FormControl;
var ControlLabel = ReactBootstrap.ControlLabel;
var Col = ReactBootstrap.Col;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
var models = require('../models');
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 (ReactDOM.findDOMNode(this.refs.password).value != this.state.initial_password)
this.setState({passwordChanged: true});
this.setState({
name: ReactDOM.findDOMNode(this.refs.name).value,
username: ReactDOM.findDOMNode(this.refs.username).value,
email: ReactDOM.findDOMNode(this.refs.email).value,
password: ReactDOM.findDOMNode(this.refs.password).value,
confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value
});
},
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 horizontal onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Name</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.name}
onChange={this.handleChange}
ref="name"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Username</Col>
<Col xs={10}>
<FormControl type="text"
value={this.state.username}
onChange={this.handleChange}
ref="username"/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} xs={2}>Email</Col>
<Col xs={10}>
<FormControl type="email"
value={this.state.email}
onChange={this.handleChange}
ref="email"/>
</Col>
</FormGroup>
<FormGroup validationState={this.passwordValidationState()}>
<Col componentClass={ControlLabel} xs={2}>Password</Col>
<Col xs={10}>
<FormControl type="password"
value={this.state.password}
onChange={this.handleChange}
ref="password"/>
<FormControl.Feedback/>
</Col>
</FormGroup>
<FormGroup validationState={this.confirmPasswordValidationState()}>
<Col componentClass={ControlLabel} xs={2}>Confirm Password</Col>
<Col xs={10}>
<FormControl type="password"
value={this.state.confirm_password}
onChange={this.handleChange}
ref="confirm_password"/>
<FormControl.Feedback/>
</Col>
</FormGroup>
<ButtonGroup className="pull-right">
<Button onClick={this.handleCancel}
bsStyle="warning">Cancel</Button>
<Button type="submit"
bsStyle="success">Create New User</Button>
</ButtonGroup>
</Form>
</Panel>
);
}
});

123
js/components/TopBar.js Normal file
View File

@ -0,0 +1,123 @@
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var Alert = ReactBootstrap.Alert;
var FormGroup = ReactBootstrap.FormGroup;
var FormControl = ReactBootstrap.FormControl;
var Button = ReactBootstrap.Button;
var DropdownButton = ReactBootstrap.DropdownButton;
var MenuItem = ReactBootstrap.MenuItem;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
var ReactDOM = require('react-dom');
var User = require('../models').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 = ReactDOM.findDOMNode(this.refs.username).value;
user.Password = ReactDOM.findDOMNode(this.refs.password).value;
this.props.onLogin(user);
},
handleNewUserSubmit: function(e) {
e.preventDefault();
this.props.onCreateNewUser();
},
render: function() {
return (
<form onSubmit={this.handleSubmit}>
<FormGroup>
<Row>
<Col xs={4}></Col>
<Col xs={2}>
<Button bsStyle="link"
onClick={this.handleNewUserSubmit}>Create New User</Button>
</Col>
<Col xs={2}>
<FormControl type="text"
placeholder="Username..."
ref="username"/>
</Col>
<Col xs={2}>
<FormControl type="password"
placeholder="Password..."
ref="password"/>
</Col>
<Col xs={2}>
<Button type="submit" bsStyle="primary" block>
Login</Button>
</Col>
</Row>
</FormGroup>
</form>
);
}
});
const LogoutBar = React.createClass({
handleOnSelect: function(key) {
if (key == 1) {
if (this.props.onAccountSettings != null)
this.props.onAccountSettings();
} else if (key == 2) {
this.props.onLogout();
}
},
render: function() {
var signedInString = "Signed in as "+this.props.user.Name;
return (
<FormGroup>
<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>
</FormGroup>
);
}
});
module.exports = React.createClass({
displayName: "TopBar",
render: function() {
var barContents;
var errorAlert;
if (!this.props.user.isUser())
barContents = <LoginBar onLogin={this.props.onLogin} onCreateNewUser={this.props.onCreateNewUser} />;
else
barContents = <LogoutBar user={this.props.user} onLogout={this.props.onLogout} onAccountSettings={this.props.onAccountSettings}/>;
if (this.props.error.isError())
errorAlert =
<Alert bsStyle="danger" onDismiss={this.props.onClearError}>
<h4>Error!</h4>
<p>Error {this.props.error.ErrorId}: {this.props.error.ErrorString}</p>
<Button onClick={this.props.onClearError}>Clear</Button>
</Alert>;
return (
<div>
{barContents}
{errorAlert}
</div>
);
}
});