diff --git a/app/components/BudgetGridRow/index.js b/app/components/BudgetGridRow/index.js
index e9bab1e..11306f0 100644
--- a/app/components/BudgetGridRow/index.js
+++ b/app/components/BudgetGridRow/index.js
@@ -1,5 +1,9 @@
// @flow
import * as React from 'react';
+import { Link } from 'react-router-dom';
+
+import permalinks from 'routes/permalinks';
+
import formatAmount from 'utils/formatAmount';
import type { Transaction } from 'modules/transactions';
import type { Categories } from 'modules/categories';
@@ -22,10 +26,14 @@ const BudgetGridRow = ({ transaction, categories }: BudgetGridRowProps) => {
Category
{category}
+
Description
- {description}
+
+ {description}
+
|
+
Amount
{amount.text}
diff --git a/app/components/DonutChart/index.js b/app/components/DonutChart/index.js
index 4153dbc..695e57e 100644
--- a/app/components/DonutChart/index.js
+++ b/app/components/DonutChart/index.js
@@ -45,8 +45,11 @@ class DonutChart extends React.Component {
getPathArc = () => {
const { height, innerRatio } = this.props;
+ const calculatedInnerRadius = innerRatio===0 ? innerRatio : (height / innerRatio);
+ // zero innerRadius makes donut chart look like pie chart
+
return arc()
- .innerRadius(height / innerRatio)
+ .innerRadius( calculatedInnerRadius )
.outerRadius(height / 2);
};
@@ -70,7 +73,7 @@ class DonutChart extends React.Component {
};
render() {
- const { data, dataLabel, dataValue, dataKey } = this.props;
+ const { data, dataLabel, dataValue, dataKey, format } = this.props;
const { outerRadius, pathArc, colorFn, boxLength, chartPadding } = this;
return (
@@ -86,7 +89,11 @@ class DonutChart extends React.Component {
))}
-
+
+
);
}
diff --git a/app/components/Legend/LegendItem.js b/app/components/Legend/LegendItem.js
index 4ea2676..1c397e3 100644
--- a/app/components/Legend/LegendItem.js
+++ b/app/components/Legend/LegendItem.js
@@ -9,10 +9,10 @@ type LegendItemProps = {
label: string,
};
-const LegendItem = ({ color, label, value }: LegendItemProps) => (
+const LegendItem = ({ color, label, value, format }: LegendItemProps) => (
{label}
- {formatAmount(value).text}
+ {formatAmount(value, false, format).text}
);
diff --git a/app/components/Legend/index.js b/app/components/Legend/index.js
index 1426ef6..dcc0f85 100644
--- a/app/components/Legend/index.js
+++ b/app/components/Legend/index.js
@@ -14,10 +14,15 @@ type LegendType = {
reverse: boolean,
};
-const Legend = ({ data, color, dataValue, dataLabel, dataKey, reverse }: LegendType) => (
+const Legend = ({ data, color, dataValue, dataLabel, dataKey, reverse, format }: LegendType) => (
{data.map((item, idx) => (
-
+
))}
);
diff --git a/app/containers/App/index.js b/app/containers/App/index.js
index 4429fc2..0e3ba89 100644
--- a/app/containers/App/index.js
+++ b/app/containers/App/index.js
@@ -7,17 +7,21 @@ import AppError from 'components/AppError';
import Header from 'components/Header';
import Budget from 'routes/Budget';
import Reports from 'routes/Reports';
+import Transaction from 'routes/Transaction';
import './style.scss';
+import permalinks from 'routes/permalinks';
+
const App = () => (
-
+
-
+
+
diff --git a/app/containers/TransactionDetails/index.js b/app/containers/TransactionDetails/index.js
new file mode 100644
index 0000000..a8e6984
--- /dev/null
+++ b/app/containers/TransactionDetails/index.js
@@ -0,0 +1,173 @@
+// @flow
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
+
+import permalinks from 'routes/permalinks';
+
+import transactionReducer from 'modules/transactions';
+import { injectAsyncReducers } from 'store';
+
+import {
+ getTransactions,
+ getInflowBalance,
+ getOutflowBalance,
+} from 'selectors/transactions';
+
+import type { Transaction } from 'modules/transactions';
+import formatAmount from 'utils/formatAmount';
+import styles from './style.scss'
+import BudgetGridRowStyles from 'components/BudgetGridRow/style.scss';
+import BudgetGridStyles from 'containers/BudgetGrid/style.scss';
+
+import DonutChart from 'components/DonutChart';
+
+// inject reducers that might not have been originally there
+injectAsyncReducers({
+ transactions: transactionReducer,
+});
+
+
+type TransactionDetailsProps = {
+ transactions: Transaction[],
+};
+
+class TransactionDetails extends React.Component {
+ static defaultProps = {
+ transactions: [],
+ };
+
+ state = {
+ TransactionID: this.props.params.id,
+ transaction: this.props.transactions.find( item => item.id === parseInt(this.props.params.id) ),
+ }
+
+ componentWillMount() {
+ const transaction = this.state.transaction;
+
+ if (!transaction) {
+ return;
+ }
+
+ const amount = formatAmount(transaction.value),
+ t = Object.assign({}, this.state.transaction);
+
+ let chartData = new Array(),
+ percent = 0;
+
+ this.state.amount = amount;
+ this.state.amountCls = amount.isNegative ? BudgetGridRowStyles.neg : BudgetGridRowStyles.pos;
+
+ if( amount.isNegative ) {
+ percent = t.value / this.props.totals.outflow * 100;
+
+ t.percentWithSign = percent;
+ t.value = percent * -1;
+
+ chartData.push({
+ id: 0,
+ value: 100 + percent,
+ description: "Rest outflow",
+ });
+ } else {
+ percent = t.value / this.props.totals.inflow * 100 ;
+
+ t.percentWithSign = percent;
+ t.value = percent;
+
+ chartData.push({
+ id: 0,
+ value: 100 - percent,
+ description: "Rest inflow",
+ });
+ }
+ chartData.unshift(t);
+
+ this.state.chartData = chartData;
+ }
+
+ render() {
+ return (
+
+
+ < back to all transactions
+
+ {this.state.transaction ? (
+
+ {this.renderDetails()}
+ {this.renderPieChart()}
+
+ ) : (
+ Transaction not found.
+ )}
+
+
+ )
+ }
+
+ renderDetails() {
+ const { transaction, amount, amountCls, chartData } = this.state,
+ formattedAmount = formatAmount(chartData[0].percentWithSign, true, "percentage").text;
+
+ return (
+
+ {transaction.description}
+ {formattedAmount}
+
+
+
+
+ | Item: |
+ {transaction.id} |
+
+
+
+ | Description: |
+ {transaction.description} |
+
+
+
+ | Amount: |
+
+ {amount.text}
+ |
+
+
+
+ | Percentage of budget: |
+ {formattedAmount} |
+
+
+
+
+
+ );
+ }
+
+ renderPieChart() {
+ const { chartData } = this.state ;
+
+ return (
+
+ );
+ }
+
+
+}
+
+const mapStateToProps = state => ({
+ transactions: getTransactions(state),
+ totals: {
+ inflow: getInflowBalance(state),
+ outflow: Math.abs(getOutflowBalance(state)),
+ },
+});
+
+export default connect(mapStateToProps)(TransactionDetails);
diff --git a/app/containers/TransactionDetails/style.scss b/app/containers/TransactionDetails/style.scss
new file mode 100644
index 0000000..b1de6c8
--- /dev/null
+++ b/app/containers/TransactionDetails/style.scss
@@ -0,0 +1,36 @@
+@import 'theme/variables';
+
+.backButton {
+ text-decoration: none;
+ color: $gray;
+ padding: 6px 8px 8px;
+ border: 1px solid;
+ border-radius: 4px;
+ display: inline-block;
+ margin-bottom: 20px;
+ &:hover {
+ color: black;
+ }
+}
+
+.clearfix {
+ &:before,&:after {
+ content: " "; // 1
+ display: table; // 2
+ }
+
+ &:after {
+ clear: both;
+ }
+}
+
+.leftCol {
+ float: left;
+ width: 50%;
+}
+
+.rightCol {
+ float: left;
+ width: 50%;
+}
+
diff --git a/app/routes/Transaction/index.js b/app/routes/Transaction/index.js
new file mode 100644
index 0000000..626b194
--- /dev/null
+++ b/app/routes/Transaction/index.js
@@ -0,0 +1,15 @@
+// @flow
+import React, { Component } from 'react';
+
+import Chunk from 'components/Chunk';
+
+const loadTransactionDetails = () => import('containers/TransactionDetails');
+
+class Transaction extends Component<> {
+ render() {
+ return ;
+ }
+}
+
+
+export default Transaction;
diff --git a/app/routes/permalinks.js b/app/routes/permalinks.js
new file mode 100644
index 0000000..f9ca223
--- /dev/null
+++ b/app/routes/permalinks.js
@@ -0,0 +1,7 @@
+
+const permalinks = {
+ budget: "budget",
+ transaction: "transaction",
+};
+
+export default permalinks;
diff --git a/app/theme/_variables.scss b/app/theme/_variables.scss
index 8626749..7b37d4e 100644
--- a/app/theme/_variables.scss
+++ b/app/theme/_variables.scss
@@ -7,7 +7,10 @@ $white: #fff;
$gray: #b1b1b1;
$lightgray: #dadada;
$verylightgray: #f0f0f0;
+
$darkgray: rgba(0,0,0,0.16);
+// NOTE: darkgray name is correct, rgba(0,0,0,0.16) is lighter than $gray: #b1b1b1;
+
$red: #eb2a2a;
$green: #189c2d;
diff --git a/app/utils/formatAmount.js b/app/utils/formatAmount.js
index f174992..dea6228 100644
--- a/app/utils/formatAmount.js
+++ b/app/utils/formatAmount.js
@@ -5,12 +5,21 @@ export type FormattedAmount = {
isNegative: boolean,
};
-export default function formatAmount(amount: number, showSign: boolean = true): FormattedAmount {
+export default function formatAmount(amount: number, showSign: boolean = true, format = null): FormattedAmount {
const isNegative = amount < 0;
- const formatValue = Math.abs(amount).toLocaleString('en-us', {
- style: 'currency',
- currency: 'USD',
- });
+ let formatValue;
+
+ if ( format === "percentage" ) {
+ let valueForFormat = amount / 100;
+ formatValue = Math.abs(valueForFormat).toLocaleString('en-us', {
+ style: 'percent',
+ });
+ } else {
+ formatValue = Math.abs(amount).toLocaleString('en-us', {
+ style: 'currency',
+ currency: 'USD',
+ });
+ }
return {
text: `${isNegative && showSign ? '-' : ''}${formatValue}`,
|