1
0
mirror of https://github.com/aclindsa/moneygo.git synced 2024-10-30 07:40:05 -04:00

Hook (almost) everything up to Redux

This commit is contained in:
Aaron Lindsay 2016-10-05 13:36:47 -04:00
parent 071b7ff1e3
commit 6257e9193f
27 changed files with 626 additions and 373 deletions

View File

@ -16,13 +16,13 @@ module.exports = React.createClass({
handleAccountChange: function(account) {
if (this.props.onChange != null &&
account.hasOwnProperty('AccountId') &&
(this.props.account_map.hasOwnProperty([account.AccountId]) ||
(this.props.accounts.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 accounts = getAccountDisplayList(this.props.accounts, this.props.accountChildren, this.props.includeRoot, this.props.rootName);
var className = "";
if (this.props.className)
className = this.props.className;

View File

@ -62,7 +62,7 @@ const TransactionRow = React.createClass({
var number = ""
var accountName = "";
var status = "";
var security = this.props.security_map[this.props.account.SecurityId];
var security = this.props.securities[this.props.account.SecurityId];
if (this.props.transaction.isTransaction()) {
var thisAccountSplit;
@ -78,9 +78,9 @@ const TransactionRow = React.createClass({
var otherSplit = this.props.transaction.Splits[1];
if (otherSplit.AccountId == -1)
var accountName = "Unbalanced " + this.props.security_map[otherSplit.SecurityId].Symbol + " transaction";
var accountName = "Unbalanced " + this.props.securities[otherSplit.SecurityId].Symbol + " transaction";
else
var accountName = getAccountDisplayName(this.props.account_map[otherSplit.AccountId], this.props.account_map);
var accountName = getAccountDisplayName(this.props.accounts[otherSplit.AccountId], this.props.accounts);
} else {
accountName = "--Split Transaction--";
}
@ -277,12 +277,12 @@ const AddEditTransactionModal = React.createClass({
},
handleSubmit: function() {
var errorString = ""
var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
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.account_map)) {
if (!(s.AccountId in this.props.accounts)) {
errorString = "All accounts must be valid"
}
}
@ -312,7 +312,7 @@ const AddEditTransactionModal = React.createClass({
);
}
var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.accounts);
var imbalancedSecurityMap = {};
for (i = 0; i < imbalancedSecurityList.length; i++)
imbalancedSecurityMap[imbalancedSecurityList[i]] = i;
@ -324,11 +324,11 @@ const AddEditTransactionModal = React.createClass({
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];
if (s.AccountId in this.props.accounts) {
security = this.props.securities[this.props.accounts[s.AccountId].SecurityId];
} else {
if (s.SecurityId in this.props.security_map) {
security = this.props.security_map[s.SecurityId];
if (s.SecurityId in this.props.securities) {
security = this.props.securities[s.SecurityId];
}
accountValidation = "has-error";
}
@ -380,7 +380,7 @@ const AddEditTransactionModal = React.createClass({
ref={"memo-"+i} /></Col>
<Col xs={3}><AccountCombobox
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
value={s.AccountId}
includeRoot={false}
onChange={updateAccountFn}
@ -569,7 +569,7 @@ const ImportTransactionsModal = React.createClass({
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.account_map) + "' account:";
accountNameLabel = "Importing to '" + getAccountDisplayName(this.props.account, this.props.accounts) + "' account:";
// Display the progress bar if an upload/import is in progress
var progressBar = [];
@ -684,7 +684,7 @@ module.exports = React.createClass({
newTransaction.Date = new Date();
newTransaction.Splits.push(new Split());
newTransaction.Splits.push(new Split());
newTransaction.Splits[0].AccountId = this.props.selectedAccount.AccountId;
newTransaction.Splits[0].AccountId = this.props.accounts[this.props.selectedAccount].AccountId;
this.setState({
editingTransaction: true,
@ -731,7 +731,7 @@ module.exports = React.createClass({
// 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) {
if (this.props.accounts[this.props.selectedAccount].AccountId == split.AccountId) {
balance = balance.minus(split.Amount);
}
}
@ -758,20 +758,20 @@ module.exports = React.createClass({
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);
if (this.props.selectedAccount != -1) {
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], newpage);
}
this.setState({currentPage: newpage});
}
},
onNewTransaction: function() {
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
onUpdatedTransaction: function() {
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
onDeletedTransaction: function() {
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
createNewTransaction: function(transaction) {
$.ajax({
@ -828,7 +828,7 @@ module.exports = React.createClass({
},
handleImportComplete: function() {
this.setState({importingTransactions: false});
this.getTransactionPage(this.props.selectedAccount, this.state.currentPage);
this.getTransactionPage(this.props.accounts[this.props.selectedAccount], this.state.currentPage);
},
handleDeleteTransaction: function(transaction) {
this.setState({
@ -853,16 +853,16 @@ module.exports = React.createClass({
transactions: [],
currentPage: 0
});
if (nextProps.selectedAccount != null)
this.getTransactionPage(nextProps.selectedAccount, 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 != null) {
name = this.props.selectedAccount.Name;
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++) {
@ -871,11 +871,9 @@ module.exports = React.createClass({
<TransactionRow
key={t.TransactionId}
transaction={t}
account={this.props.selectedAccount}
account={this.props.accounts[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}/>
));
}
@ -901,7 +899,7 @@ module.exports = React.createClass({
);
}
var disabled = (this.props.selectedAccount == null) ? true : false;
var disabled = (this.props.selectedAccount == -1) ? true : false;
return (
<div className="transactions-container">
@ -909,17 +907,15 @@ module.exports = React.createClass({
show={this.state.editingTransaction}
transaction={this.state.selectedTransaction}
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
onCancel={this.handleEditingCancel}
onSubmit={this.handleUpdateTransaction}
onDelete={this.handleDeleteTransaction}
securities={this.props.securities}
security_map={this.props.security_map}/>
securities={this.props.securities} />
<ImportTransactionsModal
show={this.state.importingTransactions}
account={this.props.selectedAccount}
account={this.props.accounts[this.props.selectedAccount]}
accounts={this.props.accounts}
account_map={this.props.account_map}
onCancel={this.handleImportingCancel}
onSubmit={this.handleImportComplete}/>
<div className="transactions-register-toolbar">

View File

@ -14,7 +14,6 @@ var Col = ReactBootstrap.Col;
var models = require('./models.js');
var User = models.User;
var Error = models.Error;
module.exports = React.createClass({
displayName: "AccountSettingsModal",
@ -71,7 +70,6 @@ module.exports = React.createClass({
},
handleSubmit: function(e) {
var u = new User();
var error = "";
e.preventDefault();
u.UserId = this.props.user.UserId;
@ -88,31 +86,8 @@ module.exports = React.createClass({
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),
});
this.props.onUpdateUser(u);
this.props.onSubmit();
},
render: function() {
return (

View File

@ -119,7 +119,7 @@ const AddEditAccountModal = React.createClass({
<Col xs={10}>
<AccountCombobox
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
value={this.state.parentaccountid}
rootName={rootName}
onChange={this.handleParentChange}
@ -130,7 +130,7 @@ const AddEditAccountModal = React.createClass({
<Col componentClass={ControlLabel} xs={2}>Security</Col>
<Col xs={10}>
<Combobox
data={this.props.securities}
data={this.props.security_list}
valueField='SecurityId'
textField={item => item.Name + " - " + item.Description}
defaultValue={this.state.security}
@ -188,10 +188,10 @@ const DeleteAccountModal = React.createClass({
this.setState({checked: !this.state.checked});
},
handleSubmit: function() {
if (this.props.account_map.hasOwnProperty(this.state.accountid)) {
if (this.props.accounts.hasOwnProperty(this.state.accountid)) {
if (this.state.checked) {
if (this.props.onSubmit != null)
this.props.onSubmit(this.props.account_map[this.state.accountid]);
this.props.onSubmit(this.props.accounts[this.state.accountid]);
} else {
this.setState({error: "You must confirm you wish to delete this account."});
}
@ -206,11 +206,11 @@ const DeleteAccountModal = React.createClass({
},
render: function() {
var checkbox = [];
if (this.props.account_map.hasOwnProperty(this.state.accountid)) {
var parentAccountId = this.props.account_map[this.state.accountid].ParentAccountId;
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.account_map[parentAccountId].Name;
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 = (
@ -248,7 +248,7 @@ const DeleteAccountModal = React.createClass({
<AccountCombobox
includeRoot={false}
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
value={this.state.accountid}
onChange={this.handleChange}/>
</Col>
@ -285,17 +285,20 @@ const AccountTreeNode = React.createClass({
},
render: function() {
var glyph = this.state.expanded ? 'minus' : 'plus';
var active = (this.props.selectedAccount != null &&
this.props.account.AccountId == this.props.selectedAccount.AccountId);
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.account.Children.map(function(account) {
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}/>
);
});
@ -334,11 +337,9 @@ const AccountTreeNode = React.createClass({
const AccountTree = React.createClass({
getInitialState: function() {
return {selectedAccount: null,
height: 0};
return {height: 0};
},
handleSelect: function(account) {
this.setState({selectedAccount: account});
if (this.props.onSelect != null) {
this.props.onSelect(account);
}
@ -356,14 +357,18 @@ const AccountTree = React.createClass({
var accounts = this.props.accounts;
var children = [];
for (var i = 0; i < accounts.length; i++) {
if (accounts[i].isRootAccount())
for (var accountId in accounts) {
if (accounts.hasOwnProperty(accountId) &&
accounts[accountId].isRootAccount()) {
children.push((<AccountTreeNode
key={accounts[i].AccountId}
account={accounts[i]}
selectedAccount={this.state.selectedAccount}
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"};
@ -379,7 +384,6 @@ module.exports = React.createClass({
displayName: "AccountsTab",
getInitialState: function() {
return {
selectedAccount: null,
creatingNewAccount: false,
editingAccount: false,
deletingAccount: false
@ -416,46 +420,48 @@ module.exports = React.createClass({
handleRemoveAccount: function(account) {
if (this.props.onDeleteAccount != null)
this.props.onDeleteAccount(account);
this.setState({deletingAccount: false,
selectedAccount: null});
this.setState({deletingAccount: false});
},
handleAccountSelected: function(account) {
this.setState({selectedAccount: account});
this.props.onSelectAccount(account.AccountId);
},
render: function() {
var accounts = this.props.accounts;
var account_map = this.props.account_map;
var disabled = (this.props.selectedAccount == -1) ? true : false;
var disabled = (this.state.selectedAccount == null) ? 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={this.state.selectedAccount}
initialParentAccount={selectedAccount}
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
onCancel={this.handleCreationCancel}
onSubmit={this.handleCreateAccount}
securities={this.props.securities}/>
security_list={this.props.security_list}/>
<AddEditAccountModal
show={this.state.editingAccount}
editAccount={this.state.selectedAccount}
editAccount={selectedAccount}
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
onCancel={this.handleEditingCancel}
onSubmit={this.handleUpdateAccount}
securities={this.props.securities}/>
security_list={this.props.security_list}/>
<DeleteAccountModal
show={this.state.deletingAccount}
initialAccount={this.state.selectedAccount}
initialAccount={selectedAccount}
accounts={this.props.accounts}
account_map={this.props.account_map}
accountChildren={this.props.accountChildren}
onCancel={this.handleDeletionCancel}
onSubmit={this.handleRemoveAccount}/>
<AccountTree
accounts={accounts}
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">
@ -469,11 +475,10 @@ module.exports = React.createClass({
</ButtonGroup>
</Col><Col xs={10} className="fullheight transactions-column">
<AccountRegister
selectedAccount={this.state.selectedAccount}
selectedAccount={this.props.selectedAccount}
accounts={this.props.accounts}
account_map={this.props.account_map}
securities={this.props.securities}
security_map={this.props.security_map} />
accountChildren={this.props.accountChildren}
securities={this.props.securities} />
</Col>
</Row></Grid>
);

View File

@ -6,35 +6,21 @@ 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 TopBarContainer = require('./containers/TopBarContainer');
var NewUserForm = require('./NewUserForm.js');
var AccountSettingsModal = require('./AccountSettingsModal.js');
var AccountsTab = require('./AccountsTab.js');
var AccountSettingsModalContainer = require('./containers/AccountSettingsModalContainer');
var AccountsTabContainer = require('./containers/AccountsTabContainer');
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.props.tryResumingSession();
this.handleHashChange();
if ("onhashchange" in window) {
window.onhashchange = this.handleHashChange;
@ -52,248 +38,34 @@ module.exports = React.createClass({
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) {
handleSettingsCanceled: function() {
this.setState({showAccountSettingsModal: false});
},
handleCreateNewUser: function() {
this.setHash("new_user");
},
handleGoHome: function(user) {
handleGoHome: function() {
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())
if (this.props.user.isUser())
mainContent = (
<Tabs defaultActiveKey={1} id='mainNavigationTabs'>
<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} />
<AccountsTabContainer
className="fullheight" />
</Tab>
<Tab title="Scheduled Transactions" eventKey={2} >Scheduled transactions go here...</Tab>
<Tab title="Budgets" eventKey={3} >Budgets go here...</Tab>
@ -311,18 +83,12 @@ module.exports = React.createClass({
return (
<div className="fullheight ui">
<TopBar
error={this.state.error}
onErrorClear={this.handleErrorClear}
onLoginSubmit={this.handleLoginSubmit}
<TopBarContainer
onCreateNewUser={this.handleCreateNewUser}
user={this.state.user}
onAccountSettings={this.handleAccountSettings}
onLogoutSubmit={this.handleLogoutSubmit} />
onAccountSettings={this.handleAccountSettings} />
{mainContent}
<AccountSettingsModal
<AccountSettingsModalContainer
show={this.state.showAccountSettingsModal}
user={this.state.user}
onSubmit={this.handleSettingsSubmitted}
onCancel={this.handleSettingsCanceled}/>
</div>

View File

@ -29,7 +29,7 @@ const LoginBar = React.createClass({
e.preventDefault();
user.Username = ReactDOM.findDOMNode(this.refs.username).value;
user.Password = ReactDOM.findDOMNode(this.refs.password).value;
this.props.onLoginSubmit(user);
this.props.onLogin(user);
},
handleNewUserSubmit: function(e) {
e.preventDefault();
@ -72,7 +72,7 @@ const LogoutBar = React.createClass({
if (this.props.onAccountSettings != null)
this.props.onAccountSettings();
} else if (key == 2) {
this.props.onLogoutSubmit();
this.props.onLogout();
}
},
render: function() {
@ -102,15 +102,15 @@ module.exports = React.createClass({
var barContents;
var errorAlert;
if (!this.props.user.isUser())
barContents = <LoginBar onLoginSubmit={this.props.onLoginSubmit} onCreateNewUser={this.props.onCreateNewUser} />;
barContents = <LoginBar onLogin={this.props.onLogin} onCreateNewUser={this.props.onCreateNewUser} />;
else
barContents = <LogoutBar user={this.props.user} onLogoutSubmit={this.props.onLogoutSubmit} onAccountSettings={this.props.onAccountSettings}/>;
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.onErrorClear}>
<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.onErrorClear}>Clear</Button>
<Button onClick={this.props.onClearError}>Clear</Button>
</Alert>;
return (

View File

@ -1,6 +1,6 @@
var AccountConstants = require('../constants/AccountConstants');
var ErrorActions = require('ErrorActions');
var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var Account = models.Account;
@ -75,7 +75,6 @@ function fetchAll() {
url: "account/",
success: function(data, status, jqXHR) {
var e = new Error();
var accounts = [];
e.fromJSON(data);
if (e.isError()) {
ErrorActions.serverError(e);

View File

@ -21,7 +21,14 @@ function ajaxError(error) {
};
}
function clearError() {
return {
type: ErrorConstants.CLEAR_ERROR,
};
}
module.exports = {
serverError: serverError,
ajaxError: ajaxError
ajaxError: ajaxError,
clearError: clearError
};

View File

@ -0,0 +1,52 @@
var SecurityConstants = require('../constants/SecurityConstants');
var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var Security = models.Security;
var Error = models.Error;
function fetchSecurities() {
return {
type: SecurityConstants.FETCH_SECURITIES
}
}
function securitiesFetched(securities) {
return {
type: SecurityConstants.SECURITIES_FETCHED,
securities: securities
}
}
function fetchAll() {
return function (dispatch) {
dispatch(fetchSecurities());
$.ajax({
type: "GET",
dataType: "json",
url: "security/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
ErrorActions.serverError(e);
} else {
dispatch(securitiesFetched(data.securities.map(function(json) {
var s = new Security();
s.fromJSON(json);
return s;
})));
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
module.exports = {
fetchAll: fetchAll
};

208
js/actions/UserActions.js Normal file
View File

@ -0,0 +1,208 @@
var UserConstants = require('../constants/UserConstants');
var AccountActions = require('./AccountActions');
var SecurityActions = require('./SecurityActions');
var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var User = models.User;
var Session = models.Session;
var Error = models.Error;
function loginUser() {
return {
type: UserConstants.LOGIN_USER
}
}
function userLoggedIn(session) {
return {
type: UserConstants.USER_LOGGEDIN,
session: session
}
}
function logoutUser() {
return {
type: UserConstants.LOGOUT_USER
}
}
function userLoggedOut() {
return {
type: UserConstants.USER_LOGGEDOUT
}
}
function fetchUser(userId) {
return {
type: UserConstants.FETCH_USER,
userId: userId
}
}
function userFetched(user) {
return {
type: UserConstants.USER_FETCHED,
user: user
}
}
function updateUser(user) {
return {
type: UserConstants.UPDATE_USER,
user: user
}
}
function userUpdated(user) {
return {
type: UserConstants.USER_UPDATED,
user: user
}
}
function fetch(userId) {
return function (dispatch) {
dispatch(fetchUser());
$.ajax({
type: "GET",
dataType: "json",
url: "user/"+userId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
ErrorActions.serverError(e);
} else {
var u = new User();
u.fromJSON(data);
dispatch(userFetched(u));
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
function initializeSession(dispatch, session) {
dispatch(userLoggedIn(session));
dispatch(fetch(session.UserId));
dispatch(AccountActions.fetchAll());
dispatch(SecurityActions.fetchAll());
}
function login(user) {
return function (dispatch) {
dispatch(loginUser());
$.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()) {
ErrorActions.serverError(e);
} else {
var s = new Session();
s.fromJSON(data);
initializeSession(dispatch, s);
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
function tryResumingSession() {
return function (dispatch) {
$.ajax({
type: "GET",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
if (e.ErrorId != 1 /* Not Signed In*/)
ErrorActions.serverError(e);
} else {
var s = new Session();
s.fromJSON(data);
dispatch(loginUser());
initializeSession(dispatch, s);
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
function logout() {
return function (dispatch) {
dispatch(logoutUser());
$.ajax({
type: "DELETE",
dataType: "json",
url: "session/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
ErrorActions.serverError(e);
} else {
dispatch(userLoggedOut());
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
function update(user) {
return function (dispatch) {
dispatch(updateUser());
$.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()) {
ErrorActions.serverError(e);
} else {
var u = new User();
u.fromJSON(data);
dispatch(userUpdated(u));
}
},
error: function(jqXHR, status, error) {
ErrorActions.ajaxError(e);
}
});
};
}
module.exports = {
fetch: fetch,
login: login,
logout: logout,
update: update,
tryResumingSession: tryResumingSession
};

View File

@ -5,4 +5,5 @@ module.exports = keyMirror({
ERROR_SERVER: null,
ERROR_CLIENT: null,
ERROR_USER: null,
CLEAR_ERROR: null
});

View File

@ -0,0 +1,12 @@
var keyMirror = require('keymirror');
module.exports = keyMirror({
FETCH_SECURITIES: null,
SECURITIES_FETCHED: null,
CREATE_SECURITY: null,
SECURITY_CREATED: null,
UPDATE_SECURITY: null,
SECURITY_UPDATED: null,
REMOVE_SECURITY: null,
SECURITY_REMOVED: null
});

View File

@ -0,0 +1,12 @@
var keyMirror = require('keymirror');
module.exports = keyMirror({
LOGIN_USER: null,
USER_LOGGEDIN: null,
LOGOUT_USER: null,
USER_LOGGEDOUT: null,
FETCH_USER: null,
USER_FETCHED: null,
UPDATE_USER: null,
USER_UPDATED: null
});

View File

@ -0,0 +1,21 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
var AccountSettingsModal = require('../AccountSettingsModal');
function mapStateToProps(state) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return {
onUpdateUser: function(user) {dispatch(UserActions.update(user))}
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(AccountSettingsModal)

View File

@ -0,0 +1,33 @@
var connect = require('react-redux').connect;
var AccountActions = require('../actions/AccountActions');
var AccountsTab = require('../AccountsTab');
function mapStateToProps(state) {
var security_list = [];
for (var securityId in state.securities) {
if (state.securities.hasOwnProperty(securityId))
security_list.push(state.securities[securityId]);
}
return {
accounts: state.accounts.map,
accountChildren: state.accounts.children,
securities: state.securities,
security_list: security_list,
selectedAccount: state.selectedAccount
}
}
function mapDispatchToProps(dispatch) {
return {
onCreateAccount: function(account) {dispatch(AccountActions.create(account))},
onUpdateAccount: function(account) {dispatch(AccountActions.update(account))},
onDeleteAccount: function(accountId) {dispatch(AccountActions.remove(accountId))},
onSelectAccount: function(accountId) {dispatch(AccountActions.select(accountId))}
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(AccountsTab)

View File

@ -0,0 +1,22 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
var MoneyGoApp = require('../MoneyGoApp');
function mapStateToProps(state) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return {
tryResumingSession: function() {dispatch(UserActions.tryResumingSession())},
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(MoneyGoApp)

View File

@ -0,0 +1,27 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
var ErrorActions = require('../actions/ErrorActions');
var TopBar = require('../TopBar');
function mapStateToProps(state) {
return {
user: state.user,
error: state.error
}
}
function mapDispatchToProps(dispatch) {
return {
onLogin: function(user) {dispatch(UserActions.login(user))},
onLogout: function() {dispatch(UserActions.logout())},
onUpdateUser: function(user) {dispatch(UserActions.update(user))},
onClearError: function() {dispatch(ErrorActions.clearError())}
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(TopBar)

View File

@ -8,7 +8,7 @@ var ReduxThunk = require('redux-thunk').default;
var Globalize = require('globalize');
var globalizeLocalizer = require('react-widgets/lib/localizers/globalize');
var MoneyGoApp = require('./MoneyGoApp.js');
var MoneyGoAppContainer = require('./containers/MoneyGoAppContainer');
var MoneyGoReducer = require('./reducers/MoneyGoReducer');
// Setup globalization for react-widgets
@ -33,7 +33,7 @@ $(document).ready(function() {
ReactDOM.render(
<Provider store={store}>
<MoneyGoApp />
<MoneyGoAppContainer />
</Provider>,
document.getElementById("content")
);

View File

@ -157,7 +157,6 @@ function Account() {
this.ParentAccountId = -1;
this.Type = -1;
this.Name = "";
this.Children = []; // Not sent across JSON, just used internally
}
Account.prototype.toJSON = function() {

View File

@ -1,8 +1,26 @@
var assign = require('object-assign');
var AccountConstants = require('../constants/AccountConstants');
var UserConstants = require('../constants/UserConstants');
module.exports = function(state = {}, action) {
function accountChildren(accounts) {
var children = {};
for (var accountId in accounts) {
if (accounts.hasOwnProperty(accountId)) {
var parentAccountId = accounts[accountId].ParentAccountId;
if (!children.hasOwnProperty(parentAccountId))
children[parentAccountId] = [];
if (!children.hasOwnProperty(accountId))
children[accountId] = [];
children[parentAccountId].push(accountId);
}
}
return children;
}
const initialState = {map: {}, children: {}};
module.exports = function(state = initialState, action) {
switch (action.type) {
case AccountConstants.ACCOUNTS_FETCHED:
var accounts = {};
@ -10,17 +28,29 @@ module.exports = function(state = {}, action) {
var account = action.accounts[i];
accounts[account.AccountId] = account;
}
return accounts;
return {
map: accounts,
children: accountChildren(accounts)
};
case AccountConstants.ACCOUNT_CREATED:
case AccountConstants.ACCOUNT_UPDATED:
var account = action.account;
return assign({}, state, {
var accounts = assign({}, state.map, {
[account.AccountId]: account
});
return {
map: accounts,
children: accountChildren(accounts)
};
case AccountConstants.ACCOUNT_REMOVED:
var newstate = assign({}, state);
delete newstate[action.accountId];
return newstate;
var accounts = assign({}, state.map);
delete accounts[action.accountId];
return {
map: accounts,
children: accountChildren(accounts)
};
case UserConstants.USER_LOGGEDOUT:
return initialState;
default:
return state;
}

View File

@ -0,0 +1,17 @@
var ErrorConstants = require('../constants/ErrorConstants');
var Error = require('../models').Error;
module.exports = function(state = new Error(), action) {
switch (action.type) {
case ErrorConstants.ERROR_AJAX:
case ErrorConstants.ERROR_SERVER:
case ErrorConstants.ERROR_CLIENT:
case ErrorConstants.ERROR_USER:
return action.error;
case ErrorConstants.CLEAR_ERROR:
return new Error();
default:
return state;
}
};

View File

@ -1,9 +1,17 @@
var Redux = require('redux');
var UserReducer = require('./UserReducer');
var SessionReducer = require('./SessionReducer');
var AccountReducer = require('./AccountReducer');
var SecurityReducer = require('./SecurityReducer');
var SelectedAccountReducer = require('./SelectedAccountReducer');
var ErrorReducer = require('./ErrorReducer');
module.exports = Redux.combineReducers({
user: UserReducer,
session: SessionReducer,
accounts: AccountReducer,
selectedAccount: SelectedAccountReducer
securities: SecurityReducer,
selectedAccount: SelectedAccountReducer,
error: ErrorReducer
});

View File

@ -0,0 +1,30 @@
var assign = require('object-assign');
var SecurityConstants = require('../constants/SecurityConstants');
var UserConstants = require('../constants/UserConstants');
module.exports = function(state = {}, action) {
switch (action.type) {
case SecurityConstants.SECURITIES_FETCHED:
var securities = {};
for (var i = 0; i < action.securities.length; i++) {
var security = action.securities[i];
securities[security.SecurityId] = security;
}
return securities;
case SecurityConstants.SECURITY_CREATED:
case SecurityConstants.SECURITY_UPDATED:
var security = action.security;
return assign({}, state, {
[security.SecurityId]: security
});
case SecurityConstants.SECURITY_REMOVED:
var newstate = assign({}, state);
delete newstate[action.securityId];
return newstate;
case UserConstants.USER_LOGGEDOUT:
return {};
default:
return state;
}
};

View File

@ -1,4 +1,5 @@
var AccountConstants = require('../constants/AccountConstants');
var UserConstants = require('../constants/UserConstants');
module.exports = function(state = -1, action) {
switch (action.type) {
@ -14,6 +15,8 @@ module.exports = function(state = -1, action) {
return state;
case AccountConstants.ACCOUNT_SELECTED:
return action.accountId;
case UserConstants.USER_LOGGEDOUT:
return -1;
default:
return state;
}

View File

@ -0,0 +1,14 @@
var UserConstants = require('../constants/UserConstants');
var Session = require('../models').Session;
module.exports = function(state = new Session(), action) {
switch (action.type) {
case UserConstants.USER_LOGGEDIN:
return action.session;
case UserConstants.USER_LOGGEDOUT:
return new Session();
default:
return state;
}
};

View File

@ -0,0 +1,15 @@
var UserConstants = require('../constants/UserConstants');
var User = require('../models').User;
module.exports = function(state = new User(), action) {
switch (action.type) {
case UserConstants.USER_FETCHED:
case UserConstants.USER_UPDATED:
return action.user;
case UserConstants.USER_LOGGEDOUT:
return new User();
default:
return state;
}
};

View File

@ -1,18 +1,19 @@
const recursiveAccountDisplayInfo = function(account, prefix) {
const recursiveAccountDisplayInfo = function(account, account_map, accountChildren, 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 + "/"));
for (var i = 0; i < accountChildren[account.AccountId].length; i++)
accounts = accounts.concat(recursiveAccountDisplayInfo(account_map[accountChildren[account.AccountId][i]], account_map, accountChildren, name + "/"));
return accounts
};
const getAccountDisplayList = function(account_list, includeRoot, rootName) {
const getAccountDisplayList = function(account_map, accountChildren, 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], ""));
for (var accountId in account_map) {
if (account_map.hasOwnProperty(accountId) &&
account_map[accountId].isRootAccount())
accounts = accounts.concat(recursiveAccountDisplayInfo(account_map[accountId], account_map, accountChildren, ""));
}
return accounts;
};