diff --git a/js/AccountCombobox.js b/js/AccountCombobox.js index 94ce5de..098d789 100644 --- a/js/AccountCombobox.js +++ b/js/AccountCombobox.js @@ -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; diff --git a/js/AccountRegister.js b/js/AccountRegister.js index 936c86c..4887cf5 100644 --- a/js/AccountRegister.js +++ b/js/AccountRegister.js @@ -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} /> = 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({ )); } @@ -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 (
@@ -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} />
diff --git a/js/AccountSettingsModal.js b/js/AccountSettingsModal.js index 02443c5..70963fa 100644 --- a/js/AccountSettingsModal.js +++ b/js/AccountSettingsModal.js @@ -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 ( diff --git a/js/AccountsTab.js b/js/AccountsTab.js index 131d795..cc22ff4 100644 --- a/js/AccountsTab.js +++ b/js/AccountsTab.js @@ -119,7 +119,7 @@ const AddEditAccountModal = React.createClass({ Security 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({ @@ -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 ( ); }); @@ -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,13 +357,17 @@ 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(()); + } } 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 ( + security_list={this.props.security_list}/> + security_list={this.props.security_list}/> + ; return ( diff --git a/js/actions/AccountActions.js b/js/actions/AccountActions.js index 661c919..2fe1ce5 100644 --- a/js/actions/AccountActions.js +++ b/js/actions/AccountActions.js @@ -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); diff --git a/js/actions/ErrorActions.js b/js/actions/ErrorActions.js index d75ea9b..38479b4 100644 --- a/js/actions/ErrorActions.js +++ b/js/actions/ErrorActions.js @@ -21,7 +21,14 @@ function ajaxError(error) { }; } +function clearError() { + return { + type: ErrorConstants.CLEAR_ERROR, + }; +} + module.exports = { serverError: serverError, - ajaxError: ajaxError + ajaxError: ajaxError, + clearError: clearError }; diff --git a/js/actions/SecurityActions.js b/js/actions/SecurityActions.js new file mode 100644 index 0000000..268dac4 --- /dev/null +++ b/js/actions/SecurityActions.js @@ -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 +}; diff --git a/js/actions/UserActions.js b/js/actions/UserActions.js new file mode 100644 index 0000000..abe4e62 --- /dev/null +++ b/js/actions/UserActions.js @@ -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 +}; diff --git a/js/constants/ErrorConstants.js b/js/constants/ErrorConstants.js index a793905..d3a0d1c 100644 --- a/js/constants/ErrorConstants.js +++ b/js/constants/ErrorConstants.js @@ -5,4 +5,5 @@ module.exports = keyMirror({ ERROR_SERVER: null, ERROR_CLIENT: null, ERROR_USER: null, + CLEAR_ERROR: null }); diff --git a/js/constants/SecurityConstants.js b/js/constants/SecurityConstants.js new file mode 100644 index 0000000..5c38b5b --- /dev/null +++ b/js/constants/SecurityConstants.js @@ -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 +}); diff --git a/js/constants/UserConstants.js b/js/constants/UserConstants.js new file mode 100644 index 0000000..f090be4 --- /dev/null +++ b/js/constants/UserConstants.js @@ -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 +}); diff --git a/js/containers/AccountSettingsModalContainer.js b/js/containers/AccountSettingsModalContainer.js new file mode 100644 index 0000000..1dc7761 --- /dev/null +++ b/js/containers/AccountSettingsModalContainer.js @@ -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) diff --git a/js/containers/AccountsTabContainer.js b/js/containers/AccountsTabContainer.js new file mode 100644 index 0000000..5381ae4 --- /dev/null +++ b/js/containers/AccountsTabContainer.js @@ -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) diff --git a/js/containers/MoneyGoAppContainer.js b/js/containers/MoneyGoAppContainer.js new file mode 100644 index 0000000..50e192f --- /dev/null +++ b/js/containers/MoneyGoAppContainer.js @@ -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) diff --git a/js/containers/TopBarContainer.js b/js/containers/TopBarContainer.js new file mode 100644 index 0000000..c683152 --- /dev/null +++ b/js/containers/TopBarContainer.js @@ -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) diff --git a/js/main.js b/js/main.js index 976cefd..1b5ab0f 100644 --- a/js/main.js +++ b/js/main.js @@ -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( - + , document.getElementById("content") ); diff --git a/js/models.js b/js/models.js index f9955dd..51c8a8a 100644 --- a/js/models.js +++ b/js/models.js @@ -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() { diff --git a/js/reducers/AccountReducer.js b/js/reducers/AccountReducer.js index 16fb033..b9171db 100644 --- a/js/reducers/AccountReducer.js +++ b/js/reducers/AccountReducer.js @@ -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; } diff --git a/js/reducers/ErrorReducer.js b/js/reducers/ErrorReducer.js new file mode 100644 index 0000000..7d93f41 --- /dev/null +++ b/js/reducers/ErrorReducer.js @@ -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; + } +}; diff --git a/js/reducers/MoneyGoReducer.js b/js/reducers/MoneyGoReducer.js index fdd8702..0c4e12e 100644 --- a/js/reducers/MoneyGoReducer.js +++ b/js/reducers/MoneyGoReducer.js @@ -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 }); diff --git a/js/reducers/SecurityReducer.js b/js/reducers/SecurityReducer.js new file mode 100644 index 0000000..f4e6e44 --- /dev/null +++ b/js/reducers/SecurityReducer.js @@ -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; + } +}; diff --git a/js/reducers/SelectedAccountReducer.js b/js/reducers/SelectedAccountReducer.js index fa2f294..504e2cd 100644 --- a/js/reducers/SelectedAccountReducer.js +++ b/js/reducers/SelectedAccountReducer.js @@ -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; } diff --git a/js/reducers/SessionReducer.js b/js/reducers/SessionReducer.js new file mode 100644 index 0000000..1ce65b6 --- /dev/null +++ b/js/reducers/SessionReducer.js @@ -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; + } +}; diff --git a/js/reducers/UserReducer.js b/js/reducers/UserReducer.js new file mode 100644 index 0000000..21102dc --- /dev/null +++ b/js/reducers/UserReducer.js @@ -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; + } +}; diff --git a/js/utils.js b/js/utils.js index 358464c..0e5b296 100644 --- a/js/utils.js +++ b/js/utils.js @@ -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; };