diff --git a/app/assets/stylesheets/grid.scss b/app/assets/stylesheets/grid.scss
index 51fce80..8e121fe 100644
--- a/app/assets/stylesheets/grid.scss
+++ b/app/assets/stylesheets/grid.scss
@@ -15,6 +15,10 @@
margin-top: 10px;
}
+.mt-50-sm {
+ margin-top: 50px;
+}
+
@media (min-width: 750px) {
.column, .columns {
margin-left: 4%;
@@ -79,7 +83,7 @@
display: flex !important;
}
- .mt-10-sm {
+ .mt-10-sm, .mt-50-sm {
margin-top: 0;
}
}
diff --git a/app/assets/stylesheets/pages/insights.scss b/app/assets/stylesheets/pages/insights.scss
index 0268dd6..bb3de65 100644
--- a/app/assets/stylesheets/pages/insights.scss
+++ b/app/assets/stylesheets/pages/insights.scss
@@ -5,3 +5,18 @@
box-shadow: 2px 2px #d6d4d4;
margin-top: 30px;
}
+
+#insights .totals table {
+ width: 100%;
+ border-collapse: collapse;
+
+ td {
+ border: 1px solid #d3d3d3;
+ text-align: left;
+ padding: 15px 10px;
+ }
+
+ td.total {
+ background-color: #e7e7e7;
+ }
+}
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index a0e4f29..5a2c124 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -1,23 +1,49 @@
module Api; module V1
class ReportsController < BaseController
def year
- results = ActiveRecord::Base.connection.execute(%{
- select categories.name AS category, to_char(date_trunc('month', paid_at), 'Mon') AS month, sum(expenses.amount)/100.0 AS amount
+ total = ActiveRecord::Base.connection.execute(%{
+ select sum(expenses.amount) as amount
+ from expenses
+ where paid_at >= '#{params[:year].to_i}-01-01'
+ and paid_at < '#{params[:year].to_i + 1}-01-01'
+ }).first['amount']
+
+ category_percentages = ActiveRecord::Base.connection.execute(%{
+ select categories.name AS category, to_char(date_trunc('month', paid_at), 'Mon') AS month, sum(expenses.amount) / #{total.to_f} AS percentage
from expenses
join categories on expenses.category_id = categories.id
where paid_at >= '#{params[:year].to_i}-01-01'
and paid_at < '#{params[:year].to_i + 1}-01-01'
- group by month, categories.name;
+ group by month, categories.name
+ order by percentage;
})
- total = ActiveRecord::Base.connection.execute(%{
- select sum(expenses.amount) as amount
+ category_totals = ActiveRecord::Base.connection.execute(%{
+ select categories.name AS category, sum(expenses.amount) AS amount
from expenses
+ join categories on expenses.category_id = categories.id
where paid_at >= '#{params[:year].to_i}-01-01'
and paid_at < '#{params[:year].to_i + 1}-01-01'
+ group by categories.name
+ order by amount;
+ })
+
+ category_amounts_by_month = ActiveRecord::Base.connection.execute(%{
+ select categories.name AS category, to_char(date_trunc('month', paid_at), 'Mon') AS month, sum(expenses.amount) AS amount
+ from expenses
+ join categories on expenses.category_id = categories.id
+ where paid_at >= '#{params[:year].to_i}-01-01'
+ and paid_at < '#{params[:year].to_i + 1}-01-01'
+ group by month, categories.name;
})
- render json: { results: results, total: total.first['amount'], categories: Category.all.select(:id, :name, :color).order(:name) }
+ render json: {
+ category_percentages: category_percentages,
+ category_totals: category_totals,
+ category_amounts_by_month: category_amounts_by_month,
+ total: total,
+ categories: Category.all.select(:id, :name, :color).order(:name)
+ }
end
def month
diff --git a/app/javascript/components/insights/Month.jsx b/app/javascript/components/insights/Month.jsx
index 18f74ff..f72a50c 100644
--- a/app/javascript/components/insights/Month.jsx
+++ b/app/javascript/components/insights/Month.jsx
@@ -53,6 +53,14 @@ const Month = ({ availableMonths }) => {
+
+
+ Total spend
+
+ {Numerics.centsToDollars(spend)}
+
+
+
)}
-
-
- Total spend
-
- {Numerics.centsToDollars(spend)}
-
-
-
diff --git a/app/javascript/components/insights/PieChart.jsx b/app/javascript/components/insights/PieChart.jsx
index 575208e..f371dd9 100644
--- a/app/javascript/components/insights/PieChart.jsx
+++ b/app/javascript/components/insights/PieChart.jsx
@@ -24,7 +24,7 @@ const PieChart = ({ data, labels, colors }) => {
},
tooltips: {
callbacks: {
- label: t => `${labels[t.index]}: $${Numerics.commify(data[t.index].toFixed(2))}`,
+ label: t => `${labels[t.index]}: ${data[t.index]}%`,
},
},
},
diff --git a/app/javascript/components/insights/Year.jsx b/app/javascript/components/insights/Year.jsx
index 37d6eb4..4c9917e 100644
--- a/app/javascript/components/insights/Year.jsx
+++ b/app/javascript/components/insights/Year.jsx
@@ -9,6 +9,7 @@ import { Numerics } from '../../helpers/main';
const Year = ({ availableYears }) => {
const [year, setYear] = useState(availableYears[0]);
const [yearTotal, setYearTotal] = useState(0);
+ const [categoryTotals, setCategoryTotals] = useState([]);
const barChartLabels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const [barChartData, setBarChartData] = useState([]);
const [pieChartData, setPieChartData] = useState({
@@ -22,28 +23,27 @@ const Year = ({ availableYears }) => {
useEffect(() => {
Reports.year({ year }).then(
(resp) => {
- const barChartDatasets = [];
- resp.categories.forEach((category) => {
- const dataPoints = [];
- barChartLabels.forEach((mon) => {
- const spendForCategoryAndMonth = resp.results.find((monthData) => monthData.month == mon && monthData.category == category.name);
- dataPoints.push(spendForCategoryAndMonth ? spendForCategoryAndMonth.amount : 0);
+ const barChartDatasets = resp.categories.map((c) => {
+ const dataPoints = barChartLabels.map((mon) => {
+ const amount = resp.category_amounts_by_month.find((a) => a.month == mon && a.category == c.name)?.amount;
+ return Numerics.centsToFloat(amount || 0);
});
- barChartDatasets.push({ label: category.name, data: dataPoints, backgroundColor: category.color });
+ return { label: c.name, data: dataPoints, backgroundColor: c.color };
});
const pieChartDatasets = [];
const pieChartLabels = [];
const pieChartColors = [];
- resp.categories.forEach((category) => {
- pieChartLabels.push(category.name);
- pieChartColors.push(category.color);
- const totalForCategory = resp.results.filter((monthData) => monthData.category === category.name).reduce((a, b) => a + parseFloat(b.amount), 0);
- pieChartDatasets.push(totalForCategory);
+ resp.categories.forEach((c) => {
+ pieChartLabels.push(c.name);
+ pieChartColors.push(c.color);
+ const percentage = resp.category_percentages.find((p) => p.category === c.name)?.percentage;
+ pieChartDatasets.push(Numerics.floatToPercent(percentage || 0));
});
setBarChartData(barChartDatasets);
setPieChartData({ data: pieChartDatasets, colors: pieChartColors, labels: pieChartLabels });
+ setCategoryTotals(resp.category_totals);
setYearTotal(resp.total);
},
() => { Alerts.genericError(); },
@@ -53,9 +53,6 @@ const Year = ({ availableYears }) => {
return (
-
- Total spend: {Numerics.centsToDollars(yearTotal)}
-
-
+
-
-
+
+
+
+
+
+
+ {categoryTotals.map(t => (
+
+ {t.category} |
+ {Numerics.centsToDollars(t.amount)} |
+
+ ))}
+
+
+ Total |
+ {Numerics.centsToDollars(yearTotal)} |
+
+
+
+
);
diff --git a/app/javascript/helpers/numerics.js b/app/javascript/helpers/numerics.js
index 8276145..d1809b0 100644
--- a/app/javascript/helpers/numerics.js
+++ b/app/javascript/helpers/numerics.js
@@ -13,6 +13,30 @@ const Numerics = {
}
},
+ centsToFloat(value) {
+ if (value == null || value == undefined) { return 0; }
+
+ try {
+ return (value / 100).toFixed(2);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(value, error);
+ return value;
+ }
+ },
+
+ floatToPercent(value) {
+ if (value == null || value == undefined) { return 0; }
+
+ try {
+ return (value * 100).toFixed(2);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(value, error);
+ return value;
+ }
+ },
+
commify(value) {
if (value == null || value == undefined) { return ''; }
// This accounts for more than 3 digits after a decimal. We don't want commas there.