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