Budget and category charts in new layout.

This commit is contained in:
James Cole
2020-07-04 12:08:38 +02:00
parent d33c87c565
commit f48bc8d299
32 changed files with 704 additions and 222 deletions

View File

@@ -194,6 +194,8 @@ class AccountController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'start_date' => $start->format('Y-m-d'),
'end_date' => $end->format('Y-m-d'),
'type' => 'line', // line, area or bar
'yAxisID' => 0, // 0, 1, 2
'entries' => [],

View File

@@ -0,0 +1,308 @@
<?php
/**
* BudgetController.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class BudgetController
*/
class BudgetController extends Controller
{
private BudgetLimitRepositoryInterface $blRepository;
private OperationsRepositoryInterface $opsRepository;
private BudgetRepositoryInterface $repository;
/**
* BudgetController constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
//$this->generator = app(GeneratorInterface::class);
$this->repository = app(BudgetRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
//$this->nbRepository = app(NoBudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* [
* 'label' => 'label for entire set'
* 'currency_id' => 0,
* 'currency_code' => 'EUR',
* 'currency_symbol' => '$',
* 'currency_decimal_places' => 2,
* 'type' => 'bar', // line, area or bar
* 'yAxisID' => 0, // 0, 1, 2
* 'entries' => ['a' => 1, 'b' => 4],
* ],
*
* @param DateRequest $request
*
* @return JsonResponse
*/
public function overview(DateRequest $request): JsonResponse
{
$dates = $request->getAll();
$budgets = $this->repository->getActiveBudgets();
$budgetNames = [];
$currencyNames = [];
$sets = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
$expenses = $this->getExpenses($budget, $dates['start'], $dates['end']);
$expenses = $this->filterNulls($expenses);
foreach ($expenses as $set) {
$budgetNames[] = $set['budget_name'];
$currencyNames[] = $set['currency_name'];
$sets[] = $set;
}
}
$budgetNames = array_unique($budgetNames);
$currencyNames = array_unique($currencyNames);
$basic = $this->createSets($budgetNames, $currencyNames);
$filled = $this->fillSets($basic, $sets);
$keys = array_values($filled);
return response()->json($keys);
}
/**
* @param Collection $limits
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function getExpenses(Budget $budget, Carbon $start, Carbon $end): array
{
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
if (0 === $limits->count()) {
return $this->getExpenseInRange($budget, $start, $end);
}
$arr = [];
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$arr[] = $this->getExpensesForLimit($limit);
}
return $arr;
}
/**
* @param array $budgetNames
* @param array $currencyNames
*
* @return array
*/
private function createSets(array $budgetNames, array $currencyNames): array
{
$return = [];
foreach ($currencyNames as $currencyName) {
$entries = [];
foreach ($budgetNames as $budgetName) {
$label = sprintf('%s (%s)', $budgetName, $currencyName);
$entries[$label] = '0';
}
// left
$return['left'] = [
'label' => sprintf('%s (%s)', trans('firefly.left'), $currencyName),
'data_type' => 'left',
'currency_name' => $currencyName,
'type' => 'bar',
'yAxisID' => 0, // 0, 1, 2
'entries' => $entries,
];
// // spent
// $return['spent'] = [
// 'label' => sprintf('%s (%s)', trans('firefly.spent'), $currencyName),
// 'data_type' => 'spent',
// 'currency_name' => $currencyName,
// 'type' => 'bar',
// 'yAxisID' => 0, // 0, 1, 2
// 'entries' => $entries,
// ];
// spent_capped
$return['spent_capped'] = [
'label' => sprintf('%s (%s)', trans('firefly.spent'), $currencyName),
'data_type' => 'spent_capped',
'currency_name' => $currencyName,
'type' => 'bar',
'yAxisID' => 0, // 0, 1, 2
'entries' => $entries,
];
// overspent
$return['overspent'] = [
'label' => sprintf('%s (%s)', trans('firefly.overspent'), $currencyName),
'data_type' => 'overspent',
'currency_name' => $currencyName,
'type' => 'bar',
'yAxisID' => 0, // 0, 1, 2
'entries' => $entries,
];
}
return $return;
}
/**
* @param array $basic
* @param array $sets
*
* @return array
*/
private function fillSets(array $basic, array $sets): array
{
foreach ($sets as $set) {
$label = $set['label'];
//$basic['spent']['entries'][$label] = $set['entries']['spent'];
$basic['spent_capped']['entries'][$label] = $set['entries']['spent_capped'];
$basic['left']['entries'][$label] = $set['entries']['left'];
$basic['overspent']['entries'][$label] = $set['entries']['overspent'];
}
return $basic;
}
/**
* @param array $expenses
*
* @return array
*/
private function filterNulls(array $expenses): array
{
$return = [];
/** @var array|null $arr */
foreach ($expenses as $arr) {
if (null !== $arr) {
$return[] = $arr;
}
}
return $return;
}
/**
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function getExpenseInRange(Budget $budget, Carbon $start, Carbon $end): array
{
$spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget]), null);
$return = [];
/** @var array $set */
foreach ($spent as $set) {
$current = [
'label' => sprintf('%s (%s)', $budget->name, $set['currency_name']),
'budget_name' => $budget->name,
'start_date' => $start->format('Y-m-d'),
'end_date' => $end->format('Y-m-d'),
'currency_id' => (int) $set['currency_id'],
'currency_code' => $set['currency_code'],
'currency_name' => $set['currency_name'],
'currency_symbol' => $set['currency_symbol'],
'currency_decimal_places' => (int) $set['currency_decimal_places'],
'type' => 'bar', // line, area or bar,
'entries' => [],
];
$sumSpent = bcmul($set['sum'], '-1'); // spent
$current['entries']['spent'] = $sumSpent;
$current['entries']['amount'] = '0';
$current['entries']['spent_capped'] = $sumSpent;
$current['entries']['left'] = '0';
$current['entries']['overspent'] = '0';
$return[] = $current;
}
return $return;
}
/**
* @param BudgetLimit $limit
*
* @return array|null
*/
private function getExpensesForLimit(BudgetLimit $limit): ?array
{
$budget = $limit->budget;
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $limit->transactionCurrency);
$currency = $limit->transactionCurrency;
// when limited to a currency, the count is always one. Or it's empty.
$set = array_shift($spent);
if (null === $set) {
return null;
}
$return = [
'label' => sprintf('%s (%s)', $budget->name, $set['currency_name']),
'budget_name' => $budget->name,
'start_date' => $limit->start_date->format('Y-m-d'),
'end_date' => $limit->end_date->format('Y-m-d'),
'currency_id' => (int) $currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int) $currency->decimal_places,
'type' => 'bar', // line, area or bar,
'entries' => [],
];
$sumSpent = bcmul($set['sum'], '-1'); // spent
$return['entries']['spent'] = $sumSpent;
$return['entries']['amount'] = $limit->amount;
$return['entries']['spent_capped'] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent;
$return['entries']['left'] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($set['sum'], $limit->amount) : '0'; // left
$return['entries']['overspent'] = 1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($set['sum'], $limit->amount), '-1'); // overspent
return $return;
}
}

View File

@@ -74,8 +74,6 @@ class CategoryController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
*
* TODO after 4.8,0, simplify
*/
public function overview(DateRequest $request): JsonResponse
{
@@ -89,32 +87,15 @@ class CategoryController extends Controller
$tempData = [];
$spentWith = $this->opsRepository->listExpenses($start, $end);
$earnedWith = $this->opsRepository->listIncome($start, $end);
$spentWithout = $this->noCatRepository->listExpenses($start, $end);
$earnedWithout = $this->noCatRepository->listIncome($start, $end);
$categories = [];
foreach ([$spentWith, $earnedWith, $spentWithout, $earnedWithout] as $set) {
foreach ([$spentWith, $spentWithout,] as $set) {
foreach ($set as $currency) {
foreach ($currency['categories'] as $category) {
$categories[] = $category['name'];
$inKey = sprintf('%d-i', $currency['currency_id']);
$outKey = sprintf('%d-e', $currency['currency_id']);
// make data arrays if not yet present.
$tempData[$inKey] = $tempData[$inKey] ?? [
'currency_id' => $currency['currency_id'],
'label' => (string) trans('firefly.box_earned_in_currency', ['currency' => $currency['currency_name']]),
'currency_code' => $currency['currency_code'],
'currency_symbol' => $currency['currency_symbol'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'type' => 'bar', // line, area or bar
'yAxisID' => 0, // 0, 1, 2
'entries' => [
// per category:
// "category" => 5,
],
];
$tempData[$outKey] = $tempData[$outKey] ?? [
'currency_id' => $currency['currency_id'],
'label' => (string) trans('firefly.box_spent_in_currency', ['currency' => $currency['currency_name']]),
@@ -123,16 +104,12 @@ class CategoryController extends Controller
'currency_decimal_places' => $currency['currency_decimal_places'],
'type' => 'bar', // line, area or bar
'yAxisID' => 0, // 0, 1, 2
'entries' => [
// per category:
// "category" => 5,
],
'entries' => [],
];
foreach ($category['transaction_journals'] as $journal) {
// is it expense or income?
$letter = -1 === bccomp($journal['amount'], '0') ? 'e' : 'i';
$currentKey = sprintf('%d-%s', $currency['currency_id'], $letter);
$currentKey = sprintf('%d-%s', $currency['currency_id'], 'e');
$name = $category['name'];
$tempData[$currentKey]['entries'][$name] = $tempData[$currentKey]['entries'][$name] ?? '0';
$tempData[$currentKey]['entries'][$name] = bcadd($tempData[$currentKey]['entries'][$name], $journal['amount']);

View File

@@ -43,8 +43,7 @@ class AccountController extends Controller
{
use AccountFilter;
/** @var array */
private $validFields;
private array $validFields;
public function __construct()
{

View File

@@ -1,115 +0,0 @@
<?php
/**
* TransferController.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Search;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Search\TransferRequest;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Support\Search\TransferSearch;
use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
/**
* Class TransferController
*/
class TransferController extends Controller
{
/**
* @param TransferRequest $request
*
* @return JsonResponse|Response
*/
public function search(TransferRequest $request)
{
// configure transfer search to search for a > b
$search = app(TransferSearch::class);
$search->setSource($request->get('source'));
$search->setDestination($request->get('destination'));
$search->setAmount($request->get('amount'));
$search->setDescription($request->get('description'));
$search->setDate($request->get('date'));
$left = $search->search();
// configure transfer search to search for b > a
$search->setSource($request->get('destination'));
$search->setDestination($request->get('source'));
$search->setAmount($request->get('amount'));
$search->setDescription($request->get('description'));
$search->setDate($request->get('date'));
$right = $search->search();
// add parameters to URL:
$this->parameters->set('source', $request->get('source'));
$this->parameters->set('destination', $request->get('destination'));
$this->parameters->set('amount', $request->get('amount'));
$this->parameters->set('description', $request->get('description'));
$this->parameters->set('date', $request->get('date'));
// get all journal ID's.
$total = $left->merge($right)->unique('id')->pluck('id')->toArray();
if (0 === count($total)) {
// forces search to be empty.
$total = [-1];
}
// collector to return results.
$pageSize = (int) app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
$manager = $this->getManager();
/** @var User $admin */
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
// all info needed for the API:
->withAPIInformation()
// set page size:
->setLimit($pageSize)
// set page to retrieve
->setPage(1)
->setJournalIds($total);
$paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.search.transfers') . $this->buildParams());
$transactions = $paginator->getCollection();
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}

View File

@@ -412,7 +412,7 @@ class BudgetController extends Controller
$cache->addProperty($end);
$cache->addProperty('chart.budget.frontpage');
if ($cache->has()) {
// return response()->json($cache->get()); // @codeCoverageIgnore
return response()->json($cache->get()); // @codeCoverageIgnore
}
$budgets = $this->repository->getActiveBudgets();
$chartData = [

View File

@@ -1030,9 +1030,9 @@
"dev": true
},
"@popperjs/core": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.3.tgz",
"integrity": "sha512-r0jArf9l4hXBJVgWlFiDj3rC+51G9d+AfycAp2YacHeKZnslGQblxGdRpbprZ2snXzYVjrSu0Tt0TZFQAILipg=="
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz",
"integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg=="
},
"@types/glob": {
"version": "7.1.2",

View File

@@ -24,7 +24,7 @@
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0",
"@popperjs/core": "^2.4.3",
"@popperjs/core": "^2.4.4",
"bootstrap": "^4.5.0",
"chart.js": "^2.9.3",
"icheck-bootstrap": "^3.0.1",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -100,9 +100,11 @@
},
getLabels() {
let firstSet = this.dataSet[0];
for (const entryLabel in firstSet.entries) {
if (firstSet.entries.hasOwnProperty(entryLabel)) {
this.newDataSet.labels.push(entryLabel);
if (typeof firstSet !== 'undefined') {
for (const entryLabel in firstSet.entries) {
if (firstSet.entries.hasOwnProperty(entryLabel)) {
this.newDataSet.labels.push(entryLabel);
}
}
}
},
@@ -111,18 +113,20 @@
if (this.dataSet.hasOwnProperty(setKey)) {
let newSet = {};
let oldSet = this.dataSet[setKey];
newSet.label = oldSet.label;
newSet.type = oldSet.type;
newSet.currency_symbol = oldSet.currency_symbol;
newSet.currency_code = oldSet.currency_code;
newSet.yAxisID = oldSet.yAxisID;
newSet.data = [];
for (const entryLabel in oldSet.entries) {
if (oldSet.entries.hasOwnProperty(entryLabel)) {
newSet.data.push(oldSet.entries[entryLabel]);
if (typeof oldSet !== 'undefined') {
newSet.label = oldSet.label;
newSet.type = oldSet.type;
newSet.currency_symbol = oldSet.currency_symbol;
newSet.currency_code = oldSet.currency_code;
newSet.yAxisID = oldSet.yAxisID;
newSet.data = [];
for (const entryLabel in oldSet.entries) {
if (oldSet.entries.hasOwnProperty(entryLabel)) {
newSet.data.push(oldSet.entries[entryLabel]);
}
}
this.newDataSet.datasets.push(newSet);
}
this.newDataSet.datasets.push(newSet);
}
}
}

View File

@@ -0,0 +1,148 @@
<!--
- DefaultLineOptions.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
</template>
<script>
export default {
name: "DefaultBarOptions",
data() {
return {}
},
methods: {
/**
* Takes a string phrase and breaks it into separate phrases no bigger than 'maxwidth', breaks are made at complete words.
* https://stackoverflow.com/questions/21409717/chart-js-and-long-labels
*
* @param str
* @param maxwidth
* @returns {Array}
*/
formatLabel(str, maxwidth) {
var sections = [];
str = String(str);
var words = str.split(" ");
var temp = "";
words.forEach(function (item, index) {
if (temp.length > 0) {
var concat = temp + ' ' + item;
if (concat.length > maxwidth) {
sections.push(temp);
temp = "";
} else {
if (index === (words.length - 1)) {
sections.push(concat);
return;
} else {
temp = concat;
return;
}
}
}
if (index === (words.length - 1)) {
sections.push(item);
return;
}
if (item.length < maxwidth) {
temp = item;
} else {
sections.push(item);
}
});
return sections;
},
getDefaultOptions() {
console.log('getDefaultOptions()');
return {
legend: {
display: false,
},
animation: {
duration: 0,
},
responsive: true,
maintainAspectRatio: false,
elements: {
line: {
cubicInterpolationMode: 'monotone'
}
},
scales: {
xAxes: [
{
stacked: true,
gridLines: {
display: false
},
ticks: {
// break ticks when too long.
callback: function (value, index, values) {
//return this.formatLabel(value, 20);
return value;
}
}
}
],
yAxes: [{
display: true,
ticks: {
callback: function (tickValue) {
"use strict";
let currencyCode = this.chart.data.datasets[0].currency_code ? this.chart.data.datasets[0].currency_code : 'EUR';
return new Intl.NumberFormat(window.localeValue, {style: 'currency', currency: currencyCode}).format(tickValue);
},
beginAtZero: true
}
}]
},
tooltips: {
mode: 'label',
callbacks: {
label: function (tooltipItem, data) {
"use strict";
let currencyCode = data.datasets[tooltipItem.datasetIndex].currency_code ? data.datasets[tooltipItem.datasetIndex].currency_code : 'EUR';
let nrString =
new Intl.NumberFormat(window.localeValue, {style: 'currency', currency: currencyCode}).format(tooltipItem.yLabel)
return data.datasets[tooltipItem.datasetIndex].label + ': ' + nrString;
}
}
}
};
}
}
}
</script>
<style scoped>
</style>

View File

@@ -30,10 +30,10 @@
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<main-budget-chart/>
<main-budget/>
</div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<main-category-chart/>
<main-category />
</div>
</div>

View File

@@ -24,8 +24,8 @@
<h3 class="card-title">{{ $t('firefly.yourAccounts') }}</h3>
</div>
<div class="card-body">
<div class="main-account-chart">
<main-account-chart v-if="loaded" :styles="myStyles" :options="chartOptions" :chart-data="chartData"></main-account-chart>
<div>
<main-account-chart v-if="loaded" :styles="chartStyles" :options="chartOptions" :chart-data="chartData"></main-account-chart>
</div>
</div>
<div class="card-footer">
@@ -67,7 +67,7 @@
},
computed: {
myStyles() {
chartStyles() {
return {
height: '400px',
'max-height': '400px',
@@ -79,8 +79,3 @@
name: "MainAccount"
}
</script>
<style scoped>
.main-account-chart {
}
</style>

View File

@@ -0,0 +1,82 @@
<!--
- MainBudgetChart.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.budgets') }}</h3>
</div>
<div class="card-body">
<div>
<main-budget-chart v-if="loaded" :styles="chartStyles" :options="chartOptions" :chart-data="chartData"></main-budget-chart>
</div>
</div>
<div class="card-footer">
<a href="./budgets" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
</div>
</div>
</template>
<script>
import MainBudgetChart from "./MainBudgetChart";
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainBudget",
components: {
MainBudgetChart
},
data() {
return {
chartData: null,
loaded: false,
chartOptions: null,
}
},
mounted() {
this.chartOptions = DefaultBarOptions.methods.getDefaultOptions();
this.loaded = false;
axios.get('./api/v1/chart/budget/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
this.chartData = response.data;
//this.chartData = DataConverter.methods.colorizeData(this.chartData);
this.chartData = DataConverter.methods.convertChart(this.chartData);
this.loaded = true
});
},
methods: {
},
computed: {
chartStyles() {
return {
height: '400px',
'max-height': '400px',
position: 'relative',
display: 'block',
}
}
},
}
</script>
<style scoped>
</style>

View File

@@ -18,25 +18,17 @@
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">I am a card</h3>
</div>
<div class="card-body">
<p>
I am card body
</p>
</div>
</div>
</template>
<script>
import {Line} from 'vue-chartjs'
export default {
name: "MainBudgetChart"
name: "MainBudgetChart",
extends: Line,
props: ['options', 'chartData'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,81 @@
<!--
- MainCategoryChart.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.categories') }}</h3>
</div>
<div class="card-body">
<div>
<main-category-chart v-if="loaded" :styles="chartStyles" :options="chartOptions" :chart-data="chartData"></main-category-chart>
</div>
</div>
<div class="card-footer">
<a href="./categories" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_categories') }}</a>
</div>
</div>
</template>
<script>
import MainCategoryChart from "./MainCategoryChart";
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainCategory",
components: {
MainCategoryChart
},
data() {
return {
chartData: null,
loaded: false,
chartOptions: null,
}
},
mounted() {
this.chartOptions = DefaultBarOptions.methods.getDefaultOptions();
this.loaded = false;
axios.get('./api/v1/chart/category/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
this.chartData = response.data;
this.chartData = DataConverter.methods.convertChart(this.chartData);
this.loaded = true
});
},
methods: {
},
computed: {
chartStyles() {
return {
height: '400px',
'max-height': '400px',
position: 'relative',
display: 'block',
}
}
},
}
</script>
<style scoped>
</style>

View File

@@ -17,23 +17,17 @@
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">I am a card</h3>
</div>
<div class="card-body">
<p>
I am card body
</p>
</div>
</div>
</template>
<script>
import {Line} from "vue-chartjs";
export default {
name: "MainCategoryChart"
name: "MainCategoryChart",
extends: Line,
props: ['options', 'chartData'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
</script>

View File

@@ -64,10 +64,14 @@
</span>
</td>
<td>
cat
<span v-for="tr in transaction.attributes.transactions">
<a :href="'categories/show/' + transaction.category_id" v-if="0!==tr.category_id">{{ tr.category_name }}</a><br />
</span>
</td>
<td>
bud
<span v-for="tr in transaction.attributes.transactions">
<a :href="'budgets/show/' + transaction.budget_id" v-if="0!==tr.budget_id">{{ tr.budget_name }}</a><br />
</span>
</td>
</tr>
</tbody>

View File

@@ -23,8 +23,8 @@ import TopBoxes from "../components/dashboard/TopBoxes";
import MainAccount from "../components/dashboard/MainAccount";
import MainAccountList from "../components/dashboard/MainAccountList";
import MainBillsChart from "../components/dashboard/MainBillsChart";
import MainBudgetChart from "../components/dashboard/MainBudgetChart";
import MainCategoryChart from "../components/dashboard/MainCategoryChart";
import MainBudget from "../components/dashboard/MainBudget";
import MainCategory from "../components/dashboard/MainCategory";
import MainCrebitChart from "../components/dashboard/MainCrebitChart";
import MainDebitChart from "../components/dashboard/MainDebitChart";
import MainPiggyList from "../components/dashboard/MainPiggyList";
@@ -49,8 +49,8 @@ Vue.component('top-boxes', TopBoxes);
Vue.component('main-account', MainAccount);
Vue.component('main-account-list', MainAccountList);
Vue.component('main-bills-chart', MainBillsChart);
Vue.component('main-budget-chart', MainBudgetChart);
Vue.component('main-category-chart', MainCategoryChart);
Vue.component('main-budget', MainBudget);
Vue.component('main-category', MainCategory);
Vue.component('main-credit-chart', MainCrebitChart);
Vue.component('main-debit-chart', MainDebitChart);
Vue.component('main-piggy-list', MainPiggyList);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1319,6 +1319,7 @@ return [
'month' => 'Month',
'budget' => 'Budget',
'spent' => 'Spent',
'spent_capped' => 'Spent (capped)',
'spent_in_budget' => 'Spent in budget',
'left_to_spend' => 'Left to spend',
'earned' => 'Earned',

View File

@@ -197,6 +197,17 @@ Route::group(
}
);
// Budgets
Route::group(
['namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/budget',
'as' => 'api.v1.chart.budget.',],
static function () {
// (frontpage) budget overview
Route::get('overview', ['uses' => 'BudgetController@overview', 'as' => 'overview']);
}
);
// Categories
Route::group(
['namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/category',
@@ -349,7 +360,6 @@ Route::group(
// Attachment API routes:
Route::get('transactions', ['uses' => 'TransactionController@search', 'as' => 'transactions']);
Route::get('accounts', ['uses' => 'AccountController@search', 'as' => 'accounts']);
Route::get('transfers', ['uses' => 'TransferController@search', 'as' => 'transfers']);
}
);