diff --git a/src/App.js b/src/App.js
index 39e4f6a..be71301 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,319 +1,43 @@
import React, { Component } from 'react';
-import {
- Row,
- Col,
- Button,
- Form
- } from 'react-bootstrap'
-import './App.css';
+import DataTable from './components/DataTable.js';
+// import DataEntry from './components/DataEntry.js';
+import Calculations from './components/Calculations.js';
+import seedData from './data/seedData';
class App extends Component {
- constructor() {
- super()
- // "seed" data initially
+ constructor(props) {
+ super(props);
this.state = {
- revenue: [
- {
- name: 'Item 1',
- oneTime: 100,
- monthly: 50
- },
- {
- name: 'Item 2',
- oneTime: 50,
- monthly: 25
- },
- {
- name: 'Item 3',
- oneTime: 25,
- monthly: 85
- }],
- expenses:[{
- name: 'Expense 1',
- oneTime: 500,
- monthly: 20.00
- },
- {
- name: 'Expense 2',
- oneTime: 200,
- monthly: 40
- }],
- oneTimeRevenue: 175,
- oneTimeExpense: 700,
- monthlyRevenue: 160,
- monthlyExpense: 60,
- newType: '',
- newName: '',
- newOneTime: '',
- newMonthly: '',
- error: false
- }
-
- this.handleDelete = this.handleDelete.bind(this)
- this.handleAdd = this.handleAdd.bind(this)
-
- // controlled form elements functions
- this.handleTypeChange = this.handleTypeChange.bind(this)
- this.handleNameChange = this.handleNameChange.bind(this)
- this.handleOneTimeChange = this.handleOneTimeChange.bind(this)
- this.handleMonthlyChange = this.handleMonthlyChange.bind(this)
- }
-
- // Delete expense or revenue from list
- handleDelete(type, index) {
- // listType will be 'expenses' or 'revenue' depending on item to delete
- let listType = this.state[type]
- // recalculate and set totals in state
- if (type === 'expenses') {
- this.setState({
- oneTimeExpense: this.state.oneTimeExpense - this.state.expenses[index]['oneTime'],
- monthlyExpense: this.state.monthlyExpense - this.state.expenses[index]['monthly'],
- })
- } else {
- // for revenue
- this.setState({
- oneTimeRevenue: this.state.oneTimeRevenue - this.state.revenue[index]['oneTime'],
- monthlyRevenue: this.state.monthlyRevenue - this.state.revenue[index]['monthly'],
- })
- }
- // remove list item from state array
- this.setState({
- [listType]: listType.splice(index, 1),
- })
- }
-
- // controlled form elements, watch for changes
- handleTypeChange(e) {
- this.setState({
- newType: e.target.value
- })
- }
- handleNameChange(e) {
- this.setState({
- newName: e.target.value
- })
- }
-
- handleMonthlyChange(e) {
- this.setState({
- newMonthly: Number(e.target.value)
- })
- }
- handleOneTimeChange(e) {
- this.setState({
- newOneTime: Number(e.target.value)
- })
- }
-
- // add new expense or revenue
- handleAdd(e) {
- e.preventDefault()
- // handle form errors, allows one-time and revenue amounts to be 0
- if (!this.state.newType || !this.state.newName || (!this.state.newOneTime && this.state.newOneTime !== 0) || (!this.state.newMonthly && this.state.newMonthly !== 0)) {
- this.setState({
- error: true
- })
- }
- // if there are no form errors, add accordingly
- else {
- // typeOfAmount will be either 'expenses' or 'revenue'
- let typeOfAmount = this.state.newType
- let monthly = typeOfAmount === 'expenses' ? 'monthlyExpense' : 'monthlyRevenue'
- let oneTime = typeOfAmount === 'expenses' ? 'oneTimeExpense' : 'oneTimeRevenue'
- // grab state array of revenues or expenses
- let items = this.state[typeOfAmount]
- items.push({
- name: this.state.newName,
- oneTime:this.state.newOneTime,
- monthly: this.state.newMonthly
- })
- // set state with new totals and items array, clear errors displaying and form contents
- this.setState({
- error: false,
- [typeOfAmount]: items,
- [monthly]: this.state[monthly] + this.state.newMonthly,
- [oneTime]: this.state[oneTime] + this.state.newOneTime,
- // Clear values in form
- newName: '',
- newMonthly: '',
- newOneTime: '',
- newType: ''
- })
+ revenue: seedData.revenue,
+ expenses: seedData.expenses,
}
}
render() {
- // create table rows from revenue state list
- let revenueTableData = this.state.revenue.map((item, index) => {
- return (
-
- | {item.name} |
- ${item.oneTime.toFixed(2)} |
- ${item.monthly.toFixed(2)} |
- |
-
- )
- })
- // create table rows from expenses state list
- let expensesTableData = this.state.expenses.map((expense, index) => {
- return (
-
- | {expense.name} |
- ${expense.oneTime.toFixed(2)} |
- ${expense.monthly.toFixed(2)} |
- |
-
- )
- })
-
- // Calculations for totals
- let totalRevenue = this.state.oneTimeRevenue + (this.state.monthlyRevenue * 12)
- let totalExpense = this.state.oneTimeExpense + (this.state.monthlyExpense * 12)
- let monthlyContributionProfit = this.state.monthlyRevenue - this.state.monthlyExpense
- let totalContributionProfit = totalRevenue - totalExpense
- // handle case where totalRevenue is 0 (to avoid -Infinity and NaN)
- let contributionMargin = totalRevenue !== 0 ? (totalContributionProfit / totalRevenue * 100).toFixed(0) : 0
- // handle case where totalExpense and totalRevenue are 0 (to avoid NaN)
- let capitalROI = (totalExpense === 0 && totalRevenue === 0) ? 0 : ((this.state.oneTimeExpense - this.state.oneTimeRevenue) / monthlyContributionProfit).toFixed(1)
+ const { revenue, expenses } = this.state;
+ console.log(revenue);
+ console.log(expenses);
return (
ROI Calculator
- {/* Add new expense or revenue form */}
-
- {/* form errors */}
- { this.state.error &&
-
Please fill out all fields
- }
-
- {/* Revenue Table */}
-
-
-
- | Revenue |
-
-
- |
- One-Time |
- Monthly |
- |
-
-
-
- {revenueTableData}
-
-
- {/* Expenses Table */}
-
-
-
- | Expenses |
-
-
- |
- One-Time |
- Monthly |
- |
-
-
-
- {expensesTableData}
-
-
- {/* Totals Table */}
-
-
-
- |
- One-Time |
- Monthly |
- Total |
-
-
-
-
- | Revenue |
- ${(this.state.oneTimeRevenue).toFixed(2)} |
- ${(this.state.monthlyRevenue).toFixed(2)} |
- ${totalRevenue.toFixed(2)} |
-
-
- | Expenses |
- ${(this.state.oneTimeExpense).toFixed(2)} |
- ${(this.state.monthlyExpense).toFixed(2)} |
- ${totalExpense.toFixed(2)} |
-
-
- | Contribution Profit |
- |
- ${ monthlyContributionProfit.toFixed(2)} |
- ${ totalContributionProfit.toFixed(2)} |
-
-
- | Contribution Margin |
- |
- |
- {contributionMargin}% |
-
-
- | Capital ROI (monthly) |
- |
- |
- {capitalROI} |
-
-
-
-
+
this.setState({ revenue })}
+ />
+ this.setState({ expenses })}
+ />
+
);
}
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/src/components/Calculations.js b/src/components/Calculations.js
new file mode 100644
index 0000000..8b82cea
--- /dev/null
+++ b/src/components/Calculations.js
@@ -0,0 +1,97 @@
+import React, { Component } from 'react';
+import './Data.css';
+
+class Calculations extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ monthTerm: 24
+ }
+ }
+
+ render() {
+ const { monthTerm } = this.state;
+ const { revenue, expenses } = this.props;
+
+ // Calculations for totals
+ let oneTimeRevenue = revenue.reduce(function (prev, cur) {
+ return prev + cur.oneTime;
+ }, 0);
+
+ let oneTimeExpense = expenses.reduce(function (prev, cur) {
+ return prev + cur.oneTime;
+ }, 0);
+
+ let monthlyRevenue = revenue.reduce(function (prev, cur) {
+ return prev + cur.monthly;
+ }, 0);
+
+ let monthlyExpense = expenses.reduce(function (prev, cur) {
+ return prev + cur.monthly;
+ }, 0);
+
+ let totalRevenue = oneTimeRevenue + (monthlyRevenue * monthTerm)
+ let totalExpense = oneTimeExpense + (monthlyExpense * monthTerm)
+ let monthlyContributionProfit = monthlyRevenue - monthlyExpense
+ let totalContributionProfit = totalRevenue - totalExpense
+ // handle case where totalRevenue is 0 (to avoid -Infinity and NaN)
+ let contributionMargin = totalRevenue !== 0 ? (totalContributionProfit / totalRevenue * 100).toFixed(0) : 0
+ // handle case where totalExpense and totalRevenue are 0 (to avoid NaN)
+ let capitalROI = (totalExpense === 0 && totalRevenue === 0) ? 0 : ((oneTimeExpense - oneTimeRevenue) / monthlyContributionProfit).toFixed(1)
+
+ return (
+
+
+ {/* Totals Table */}
+
+
+
+ | {monthTerm} Month Term |
+
+
+ | Total |
+ One-Time |
+ Monthly |
+ Total |
+
+
+
+
+ | Revenue |
+ ${(oneTimeRevenue).toFixed(2)} |
+ ${(monthlyRevenue).toFixed(2)} |
+ ${totalRevenue.toFixed(2)} |
+
+
+ | Expenses |
+ ${(oneTimeExpense).toFixed(2)} |
+ ${(monthlyExpense).toFixed(2)} |
+ ${totalExpense.toFixed(2)} |
+
+
+ | Contribution Profit |
+ |
+ ${monthlyContributionProfit.toFixed(2)} |
+ ${totalContributionProfit.toFixed(2)} |
+
+
+ | Contribution Margin |
+ |
+ |
+ {contributionMargin}% |
+
+
+ | Capital ROI (monthly) |
+ |
+ |
+ {capitalROI} |
+
+
+
+
+
+ );
+ }
+}
+
+export default Calculations;
\ No newline at end of file
diff --git a/src/App.css b/src/components/Data.css
similarity index 81%
rename from src/App.css
rename to src/components/Data.css
index bf73fb7..849a7aa 100644
--- a/src/App.css
+++ b/src/components/Data.css
@@ -1,33 +1,33 @@
.addExpenseOrRevenueForm {
- padding-top: 3em;
-}
-
+ padding-top: .5em;
+ }
+
.input-field, .add-form-button {
- padding-bottom: 1em;
-}
-
+ padding-bottom: .5em;
+ }
+
.roi-tables {
- padding-top: 2em;
+ padding-top: 1em;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
-}
-
+ }
+
.expenses-table, .revenue-table, .totals-table {
margin-bottom: 3em;
-}
+ }
.expenses-table th, .revenue-table th, .totals-table th {
- width: 150px;
-}
+ width: 200px;
+ }
.expenses-table td, .revenue-table td, .totals-table td {
width: 100px
-}
-
+ }
+
.error {
color: red;
-}
-
+ }
+
@media only screen and (max-width:775px) {
.input-field, .add-form-button {
margin-left: .25em;
@@ -36,4 +36,4 @@
.roi-tables {
padding-left: 1.5em;
}
-}
+}
\ No newline at end of file
diff --git a/src/components/DataEntry.js b/src/components/DataEntry.js
new file mode 100644
index 0000000..3828a6d
--- /dev/null
+++ b/src/components/DataEntry.js
@@ -0,0 +1,113 @@
+import React, { Component } from 'react';
+import {
+ Row,
+ Col,
+ Button,
+ Form
+ } from 'react-bootstrap';
+import './Data.css';
+
+class DataEntry extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ newName: '',
+ newOneTime: '',
+ newMonthly: '',
+ error: false
+ }
+ }
+
+ // controlled form elements, watch for changes
+ handleNameChange(e) {
+ this.setState({
+ newName: e.target.value
+ })
+ }
+
+ handleMonthlyChange(e) {
+ this.setState({
+ newMonthly: Number(e.target.value)
+ })
+ }
+ handleOneTimeChange(e) {
+ this.setState({
+ newOneTime: Number(e.target.value)
+ })
+ }
+
+ handleAdd(e) {
+ e.preventDefault()
+ const { newName, newOneTime, newMonthly } = this.state;
+ // handle form errors, allows one-time and revenue amounts to be 0
+ if (!newName || (!newOneTime && newOneTime !== 0) || (!newMonthly && newMonthly !== 0)) {
+ this.setState({
+ error: true
+ })
+ }
+ // if there are no form errors, add accordingly
+ else {
+ this.props.onAddData( newName, newOneTime, newMonthly );
+ this.setState({
+ error: false,
+ newName: '',
+ newMonthly: '',
+ newOneTime: '',
+ newType: ''
+ })
+ }
+ }
+
+ render() {
+ const { newName, newOneTime, newMonthly } = this.state;
+
+ return (
+
+ {/* Add new expense or revenue form */}
+
+ {/* form errors */}
+ { this.state.error &&
+
Please fill out all fields
+ }
+
+ );
+ }
+}
+
+export default DataEntry;
\ No newline at end of file
diff --git a/src/components/DataTable.js b/src/components/DataTable.js
new file mode 100644
index 0000000..d786769
--- /dev/null
+++ b/src/components/DataTable.js
@@ -0,0 +1,73 @@
+import React, { Component } from 'react';
+import { Button } from 'react-bootstrap'
+import DataEntry from './DataEntry.js';
+import './Data.css';
+
+class DataTable extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {}
+ }
+ // Delete expense or revenue from list
+ handleDelete(index) {
+ const { data, onUpdateTableData } = this.props;
+ data.splice(index, 1);
+ onUpdateTableData(data);
+ }
+
+ // add new expense or revenue
+ handleAdd(newName, newOneTime, newMonthly) {
+ const { data, onUpdateTableData } = this.props;
+ data.push({
+ name: newName,
+ oneTime: newOneTime,
+ monthly: newMonthly
+ })
+ onUpdateTableData(data);
+ }
+
+ render() {
+ const { title, data } = this.props;
+
+ // create table rows from revenue state list
+ let tableData = data.map((item, index) => {
+ return (
+
+ | {item.name} |
+ ${item.oneTime.toFixed(2)} |
+ ${item.monthly.toFixed(2)} |
+ |
+
+ )
+ })
+
+ return (
+
+
+ {/* Revenue Table */}
+
+
+
+ | {title} |
+
+
+ |
+ One-Time |
+ Monthly |
+ |
+
+
+
+ {tableData}
+
+
+
this.handleAdd(newName, newOneTime, newMonthly)}
+ />
+
+
+ );
+ }
+}
+
+export default DataTable;
\ No newline at end of file
diff --git a/src/data/seedData.js b/src/data/seedData.js
new file mode 100644
index 0000000..c8f7f0f
--- /dev/null
+++ b/src/data/seedData.js
@@ -0,0 +1,32 @@
+export default {
+ revenue: [
+ {
+ name: 'Item 1',
+ oneTime: 100,
+ monthly: 50
+ },
+ {
+ name: 'Item 2',
+ oneTime: 50,
+ monthly: 25
+ },
+ {
+ name: 'Item 3',
+ oneTime: 25,
+ monthly: 85
+ }],
+ expenses: [{
+ name: 'Expense 1',
+ oneTime: 500,
+ monthly: 20.00
+ },
+ {
+ name: 'Expense 2',
+ oneTime: 200,
+ monthly: 40
+ }],
+ oneTimeRevenue: 175,
+ oneTimeExpense: 700,
+ monthlyRevenue: 160,
+ monthlyExpense: 60
+};
\ No newline at end of file