mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-05 20:22:07 +00:00
Expand layout
This commit is contained in:
68
app/Api/V2/Controllers/Chart/CategoryController.php
Normal file
68
app/Api/V2/Controllers/Chart/CategoryController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* BudgetController.php
|
||||
* Copyright (c) 2023 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/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V2\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
*/
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateRequest $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse {}
|
||||
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
BIN
public/build/assets/fa-brands-400-faae6fc0.woff2
Normal file
BIN
public/build/assets/fa-brands-400-faae6fc0.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/build/assets/fa-regular-400-9169d8be.woff2
Normal file
BIN
public/build/assets/fa-regular-400-9169d8be.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/build/assets/fa-solid-900-886c8611.woff2
Normal file
BIN
public/build/assets/fa-solid-900-886c8611.woff2
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf": {
|
||||
"file": "assets/fa-brands-400-20c4a58b.ttf",
|
||||
"file": "assets/fa-brands-400-003f1154.ttf",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2": {
|
||||
"file": "assets/fa-brands-400-74833209.woff2",
|
||||
"file": "assets/fa-brands-400-faae6fc0.woff2",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf": {
|
||||
"file": "assets/fa-regular-400-528d022d.ttf",
|
||||
"file": "assets/fa-regular-400-7d81a1a7.ttf",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2": {
|
||||
"file": "assets/fa-regular-400-8e7e5ea1.woff2",
|
||||
"file": "assets/fa-regular-400-9169d8be.woff2",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf": {
|
||||
"file": "assets/fa-solid-900-67a65763.ttf",
|
||||
"file": "assets/fa-solid-900-cea79b34.ttf",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2": {
|
||||
"file": "assets/fa-solid-900-7152a693.woff2",
|
||||
"file": "assets/fa-solid-900-886c8611.woff2",
|
||||
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"
|
||||
},
|
||||
"resources/assets/v2/dashboard.js": {
|
||||
"file": "assets/dashboard-157793a8.js",
|
||||
"file": "assets/dashboard-3896887c.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/dashboard.js"
|
||||
},
|
||||
"resources/assets/v2/sass/app.scss": {
|
||||
"file": "assets/app-40e01f65.css",
|
||||
"file": "assets/app-28a195fd.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/sass/app.scss"
|
||||
}
|
||||
|
30
resources/assets/v2/api/v2/chart/category/dashboard.js
Normal file
30
resources/assets/v2/api/v2/chart/category/dashboard.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* overview.js
|
||||
* Copyright (c) 2022 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/>.
|
||||
*/
|
||||
|
||||
import {api} from "../../../../boot/axios";
|
||||
import {format} from "date-fns";
|
||||
|
||||
export default class Dashboard {
|
||||
dashboard(start, end) {
|
||||
let startStr = format(start, 'y-MM-dd');
|
||||
let endStr = format(end, 'y-MM-dd');
|
||||
return api.get('/api/v2/chart/category/dashboard', {params: {start: startStr, end: endStr}});
|
||||
}
|
||||
}
|
@@ -23,11 +23,13 @@ import dates from './pages/shared/dates.js';
|
||||
import boxes from './pages/dashboard/boxes.js';
|
||||
import accounts from './pages/dashboard/accounts.js';
|
||||
import budgets from './pages/dashboard/budgets.js';
|
||||
import categories from './pages/dashboard/categories.js';
|
||||
|
||||
const comps = {dates, boxes, accounts, budgets};
|
||||
const comps = {dates, boxes, accounts, budgets, categories};
|
||||
|
||||
function loadPage(comps) {
|
||||
Object.keys(comps).forEach(comp => {
|
||||
console.log(`Loading ${comp}`);
|
||||
let data = comps[comp]();
|
||||
Alpine.data(comp, () => data);
|
||||
});
|
||||
@@ -36,11 +38,11 @@ function loadPage(comps) {
|
||||
|
||||
// wait for load until bootstrapped event is received.
|
||||
document.addEventListener('firefly-iii-bootstrapped', () => {
|
||||
console.log('Loaded through event listener.');
|
||||
//console.log('Loaded through event listener.');
|
||||
loadPage(comps);
|
||||
});
|
||||
// or is bootstrapped before event is triggered.
|
||||
if (window.bootstrapped) {
|
||||
console.log('Loaded through window variable.');
|
||||
//console.log('Loaded through window variable.');
|
||||
loadPage(comps);
|
||||
}
|
||||
|
@@ -37,106 +37,119 @@ export default () => ({
|
||||
accountList: [],
|
||||
autoConversion: false,
|
||||
chart: null,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
switchAutoConversion() {
|
||||
this.autoConversion = !this.autoConversion;
|
||||
setVariable('autoConversion', this.autoConversion);
|
||||
this.loadChart();
|
||||
},
|
||||
loadChart() {
|
||||
console.log('loadChart.');
|
||||
if (true === this.loading) {
|
||||
console.log('loadChart CANCELLED');
|
||||
return;
|
||||
}
|
||||
console.log('loadChart continues');
|
||||
// load chart data
|
||||
this.loading = true;
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
|
||||
// chart options (may need to be centralized later on)
|
||||
window.currencies = [];
|
||||
let options = {
|
||||
legend: {show: false},
|
||||
chart: {
|
||||
height: 400,
|
||||
toolbar: {tools: {zoom: false, download: false, pan: false}},
|
||||
type: 'line'
|
||||
}, series: [],
|
||||
settings: [],
|
||||
xaxis: {
|
||||
categories: [],
|
||||
labels: {
|
||||
formatter: function (value) {
|
||||
if (undefined === value) {
|
||||
return '';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
return formatLocal(date, 'PP');
|
||||
}
|
||||
console.error('Could not parse "' + value + '", return "".');
|
||||
return ':(';
|
||||
}
|
||||
}
|
||||
}, yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.seriesIndex;
|
||||
}
|
||||
//console.log(index);
|
||||
let currencyCode = window.currencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
// render data:
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
if (response.data.hasOwnProperty(i)) {
|
||||
let current = response.data[i];
|
||||
let entry = [];
|
||||
let collection = [];
|
||||
// use the "native" currency code and use the "native_entries" as array
|
||||
if (this.autoConversion) {
|
||||
window.currencies.push(current.native_code);
|
||||
collection = current.native_entries;
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.currencies.push(current.currency_code);
|
||||
collection = current.entries;
|
||||
}
|
||||
|
||||
for (const [ii, value] of Object.entries(collection)) {
|
||||
entry.push({x: format(new Date(ii), 'yyyy-MM-dd'), y: parseFloat(value)});
|
||||
}
|
||||
options.series.push({name: current.label, data: entry});
|
||||
}
|
||||
}
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(options);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#account-chart"), options);
|
||||
this.chart.render();
|
||||
}
|
||||
this.loading = false;
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
});
|
||||
}, loadAccounts() {
|
||||
console.log('loadAccounts');
|
||||
if (true === this.loadingAccounts) {
|
||||
console.log('loadAccounts CANCELLED');
|
||||
},
|
||||
generateOptions(data) {
|
||||
window.currencies = [];
|
||||
let options = {
|
||||
legend: {show: false},
|
||||
chart: {
|
||||
height: 400,
|
||||
type: 'line'
|
||||
},
|
||||
series: [],
|
||||
settings: [],
|
||||
xaxis: {
|
||||
categories: [],
|
||||
labels: {
|
||||
formatter: function (value) {
|
||||
if (undefined === value) {
|
||||
return '';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
return formatLocal(date, 'PP');
|
||||
}
|
||||
console.error('Could not parse "' + value + '", return "".');
|
||||
return ':(';
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.seriesIndex;
|
||||
}
|
||||
//console.log(index);
|
||||
let currencyCode = window.currencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
// render data:
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
let entry = [];
|
||||
let collection = [];
|
||||
// use the "native" currency code and use the "native_entries" as array
|
||||
if (this.autoConversion) {
|
||||
window.currencies.push(current.native_code);
|
||||
collection = current.native_entries;
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.currencies.push(current.currency_code);
|
||||
collection = current.entries;
|
||||
}
|
||||
|
||||
for (const [ii, value] of Object.entries(collection)) {
|
||||
entry.push({x: format(new Date(ii), 'yyyy-MM-dd'), y: parseFloat(value)});
|
||||
}
|
||||
options.series.push({name: current.label, data: entry});
|
||||
}
|
||||
}
|
||||
this.chartOptions = options;
|
||||
},
|
||||
loadChart() {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(this.chartOptions);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#account-chart"), this.chartOptions);
|
||||
this.chart.render();
|
||||
}
|
||||
},
|
||||
loadAccounts() {
|
||||
if (true === this.loadingAccounts) {
|
||||
return;
|
||||
}
|
||||
console.log('loadAccounts continues');
|
||||
this.loadingAccounts = true;
|
||||
const max = 10;
|
||||
let totalAccounts = 0;
|
||||
@@ -144,7 +157,6 @@ export default () => ({
|
||||
let accounts = [];
|
||||
Promise.all([getVariable('frontpageAccounts'),]).then((values) => {
|
||||
totalAccounts = values[0].length;
|
||||
console.log('total accounts is ' + totalAccounts);
|
||||
for (let i in values[0]) {
|
||||
let account = values[0];
|
||||
if (account.hasOwnProperty(i)) {
|
||||
@@ -186,7 +198,6 @@ export default () => ({
|
||||
if (count === totalAccounts) {
|
||||
this.accountList = accounts;
|
||||
}
|
||||
console.log('Count is now ' + count);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -196,16 +207,14 @@ export default () => ({
|
||||
},
|
||||
|
||||
init() {
|
||||
console.log('init accounts');
|
||||
Promise.all([getVariable('viewRange', '1M'), getVariable('autoConversion', false),]).then((values) => {
|
||||
console.log('from promise');
|
||||
this.autoConversion = values[1];
|
||||
// console.log(values[1]);
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
console.log('from observe end');
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
});
|
||||
|
@@ -31,149 +31,164 @@ export default () => ({
|
||||
netBox: {net: []},
|
||||
autoConversion: false,
|
||||
loading: false,
|
||||
loadBoxes() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
boxData: null,
|
||||
boxOptions: null,
|
||||
getFreshData() {
|
||||
// get stuff
|
||||
let getter = new Summary();
|
||||
let start = new Date(window.store.get('start'));
|
||||
let end = new Date(window.store.get('end'));
|
||||
|
||||
getter.get(format(start, 'yyyy-MM-dd'), format(end, 'yyyy-MM-dd'), null).then((response) => {
|
||||
// reset boxes:
|
||||
this.balanceBox = {amounts: [], subtitles: []};
|
||||
this.billBox = {paid: [], unpaid: []};
|
||||
this.leftBox = {left: [], perDay: []};
|
||||
this.netBox = {net: []};
|
||||
let subtitles = {};
|
||||
|
||||
// process new content:
|
||||
for (const i in response.data) {
|
||||
if (response.data.hasOwnProperty(i)) {
|
||||
const current = response.data[i];
|
||||
let key = current.key;
|
||||
// native (auto conversion):
|
||||
if (this.autoConversion) {
|
||||
if (key.startsWith('balance-in-native')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.value, current.currency_code));
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// spent info is used in subtitle:
|
||||
if (key.startsWith('spent-in-native')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// append the amount spent.
|
||||
subtitles[current.currency_code] =
|
||||
subtitles[current.currency_code] +
|
||||
formatMoney(current.value, current.currency_code);
|
||||
continue;
|
||||
}
|
||||
// earned info is used in subtitle:
|
||||
if (key.startsWith('earned-in-native')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// prepend the amount earned.
|
||||
subtitles[current.currency_code] =
|
||||
formatMoney(current.value, current.currency_code) + ' + ' +
|
||||
subtitles[current.currency_code];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith('bills-unpaid-in-native')) {
|
||||
this.billBox.unpaid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('bills-paid-in-native')) {
|
||||
this.billBox.paid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-to-spend-in-native')) {
|
||||
this.leftBox.left.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-per-day-to-spend-in-native')) { // per day
|
||||
this.leftBox.perDay.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('net-worth-in-native')) {
|
||||
this.netBox.net.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// not native
|
||||
if (!this.autoConversion && !key.endsWith('native')) {
|
||||
if (key.startsWith('balance-in-')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
// spent info is used in subtitle:
|
||||
if (key.startsWith('spent-in-')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// append the amount spent.
|
||||
subtitles[current.currency_code] =
|
||||
subtitles[current.currency_code] +
|
||||
formatMoney(current.value, current.currency_code);
|
||||
continue;
|
||||
}
|
||||
// earned info is used in subtitle:
|
||||
if (key.startsWith('earned-in-')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// prepend the amount earned.
|
||||
subtitles[current.currency_code] =
|
||||
formatMoney(current.value, current.currency_code) + ' + ' +
|
||||
subtitles[current.currency_code];
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (key.startsWith('bills-unpaid-in-')) {
|
||||
this.billBox.unpaid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('bills-paid-in-')) {
|
||||
this.billBox.paid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-to-spend-in-')) {
|
||||
this.leftBox.left.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-per-day-to-spend-in-')) {
|
||||
this.leftBox.perDay.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('net-worth-in-')) {
|
||||
this.netBox.net.push(formatMoney(current.value, current.currency_code));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i in subtitles) {
|
||||
if (subtitles.hasOwnProperty(i)) {
|
||||
this.balanceBox.subtitles.push(subtitles[i]);
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
this.boxData = response.data;
|
||||
this.generateOptions(this.boxData);
|
||||
//this.drawChart();
|
||||
});
|
||||
},
|
||||
generateOptions(data) {
|
||||
this.balanceBox = {amounts: [], subtitles: []};
|
||||
this.billBox = {paid: [], unpaid: []};
|
||||
this.leftBox = {left: [], perDay: []};
|
||||
this.netBox = {net: []};
|
||||
let subtitles = {};
|
||||
|
||||
// process new content:
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
const current = data[i];
|
||||
let key = current.key;
|
||||
// native (auto conversion):
|
||||
if (this.autoConversion) {
|
||||
if (key.startsWith('balance-in-native')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.value, current.currency_code));
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// spent info is used in subtitle:
|
||||
if (key.startsWith('spent-in-native')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// append the amount spent.
|
||||
subtitles[current.currency_code] =
|
||||
subtitles[current.currency_code] +
|
||||
formatMoney(current.value, current.currency_code);
|
||||
continue;
|
||||
}
|
||||
// earned info is used in subtitle:
|
||||
if (key.startsWith('earned-in-native')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// prepend the amount earned.
|
||||
subtitles[current.currency_code] =
|
||||
formatMoney(current.value, current.currency_code) + ' + ' +
|
||||
subtitles[current.currency_code];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith('bills-unpaid-in-native')) {
|
||||
this.billBox.unpaid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('bills-paid-in-native')) {
|
||||
this.billBox.paid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-to-spend-in-native')) {
|
||||
this.leftBox.left.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-per-day-to-spend-in-native')) { // per day
|
||||
this.leftBox.perDay.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('net-worth-in-native')) {
|
||||
this.netBox.net.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// not native
|
||||
if (!this.autoConversion && !key.endsWith('native')) {
|
||||
if (key.startsWith('balance-in-')) {
|
||||
this.balanceBox.amounts.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
// spent info is used in subtitle:
|
||||
if (key.startsWith('spent-in-')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// append the amount spent.
|
||||
subtitles[current.currency_code] =
|
||||
subtitles[current.currency_code] +
|
||||
formatMoney(current.value, current.currency_code);
|
||||
continue;
|
||||
}
|
||||
// earned info is used in subtitle:
|
||||
if (key.startsWith('earned-in-')) {
|
||||
// prep subtitles (for later)
|
||||
if (!subtitles.hasOwnProperty(current.currency_code)) {
|
||||
subtitles[current.currency_code] = '';
|
||||
}
|
||||
// prepend the amount earned.
|
||||
subtitles[current.currency_code] =
|
||||
formatMoney(current.value, current.currency_code) + ' + ' +
|
||||
subtitles[current.currency_code];
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (key.startsWith('bills-unpaid-in-')) {
|
||||
this.billBox.unpaid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('bills-paid-in-')) {
|
||||
this.billBox.paid.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-to-spend-in-')) {
|
||||
this.leftBox.left.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('left-per-day-to-spend-in-')) {
|
||||
this.leftBox.perDay.push(formatMoney(current.value, current.currency_code));
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith('net-worth-in-')) {
|
||||
this.netBox.net.push(formatMoney(current.value, current.currency_code));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i in subtitles) {
|
||||
if (subtitles.hasOwnProperty(i)) {
|
||||
this.balanceBox.subtitles.push(subtitles[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
loadBoxes() {
|
||||
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.boxData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.boxData) {
|
||||
this.generateOptions(this.boxData);
|
||||
//this.drawChart();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
// Getter
|
||||
@@ -183,6 +198,7 @@ export default () => ({
|
||||
this.loadBoxes();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
this.boxData = null;
|
||||
this.loadBoxes();
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
|
@@ -24,158 +24,176 @@ import formatMoney from "../../util/format-money.js";
|
||||
|
||||
window.budgetCurrencies = [];
|
||||
export default () => ({
|
||||
loadingChart: false,
|
||||
loading: false,
|
||||
chart: null,
|
||||
autoConversion: false,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
loadChart() {
|
||||
if (this.loadingChart) {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
// load chart data
|
||||
this.loadingChart = true;
|
||||
window.budgetCurrencies = [];
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(this.chartOptions);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#budget-chart"), this.chartOptions);
|
||||
this.chart.render();
|
||||
}
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
let options = {
|
||||
legend: {show: false},
|
||||
series: [{
|
||||
name: 'Spent',
|
||||
data: []
|
||||
}, {
|
||||
name: 'Left',
|
||||
data: []
|
||||
}, {
|
||||
name: 'Overspent',
|
||||
data: []
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 400,
|
||||
stacked: true,
|
||||
toolbar: {tools: {zoom: false, download: false, pan: false}},
|
||||
zoom: {
|
||||
enabled: true
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
});
|
||||
},
|
||||
generateOptions(data) {
|
||||
window.budgetCurrencies = [];
|
||||
let options = {
|
||||
legend: {show: false},
|
||||
series: [{
|
||||
name: 'Spent',
|
||||
data: []
|
||||
}, {
|
||||
name: 'Left',
|
||||
data: []
|
||||
}, {
|
||||
name: 'Overspent',
|
||||
data: []
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 400,
|
||||
stacked: true,
|
||||
toolbar: {tools: {zoom: false, download: false, pan: false}},
|
||||
zoom: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
offsetX: -10,
|
||||
offsetY: 0
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
offsetX: -10,
|
||||
offsetY: 0
|
||||
}
|
||||
}
|
||||
}],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
borderRadius: 10,
|
||||
dataLabels: {
|
||||
total: {
|
||||
enabled: true,
|
||||
style: {
|
||||
fontSize: '13px',
|
||||
fontWeight: 900
|
||||
},
|
||||
formatter: function (val, opt) {
|
||||
let index = 0;
|
||||
if (typeof opt === 'object') {
|
||||
index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(val, currencyCode);
|
||||
}
|
||||
}],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
borderRadius: 10,
|
||||
dataLabels: {
|
||||
total: {
|
||||
enabled: true,
|
||||
// style: {
|
||||
// fontSize: '13px',
|
||||
// fontWeight: 900
|
||||
// },
|
||||
formatter: function (val, opt) {
|
||||
let index = 0;
|
||||
if (typeof opt === 'object') {
|
||||
index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(val, currencyCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: []
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.8
|
||||
},
|
||||
dataLabels: {
|
||||
formatter: function (val, opt) {
|
||||
let index = 0;
|
||||
if (typeof opt === 'object') {
|
||||
index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(val, currencyCode);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
for (const i in response.data) {
|
||||
if (response.data.hasOwnProperty(i)) {
|
||||
let current = response.data[i];
|
||||
// convert to EUR yes no?
|
||||
let label = current.label + ' (' + current.currency_code + ')';
|
||||
options.xaxis.categories.push(label);
|
||||
if (this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.native_code);
|
||||
|
||||
// series 0: spent
|
||||
options.series[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.series[1].data.push(parseFloat(current.native_entries.left));
|
||||
// series 2: overspent
|
||||
options.series[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.currency_code);
|
||||
// series 0: spent
|
||||
options.series[0].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.series[1].data.push(parseFloat(current.entries.left));
|
||||
// series 2: overspent
|
||||
options.series[2].data.push(parseFloat(current.entries.overspent));
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: []
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.8
|
||||
},
|
||||
dataLabels: {
|
||||
formatter: function (val, opt) {
|
||||
let index = 0;
|
||||
if (typeof opt === 'object') {
|
||||
index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(val, currencyCode);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(options);
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
// convert to EUR yes no?
|
||||
let label = current.label + ' (' + current.currency_code + ')';
|
||||
options.xaxis.categories.push(label);
|
||||
if (this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.native_code);
|
||||
|
||||
// series 0: spent
|
||||
options.series[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.series[1].data.push(parseFloat(current.native_entries.left));
|
||||
// series 2: overspent
|
||||
options.series[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.currency_code);
|
||||
// series 0: spent
|
||||
options.series[0].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// series 1: left
|
||||
options.series[1].data.push(parseFloat(current.entries.left));
|
||||
// series 2: overspent
|
||||
options.series[2].data.push(parseFloat(current.entries.overspent));
|
||||
}
|
||||
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#budget-chart"), options);
|
||||
this.chart.render();
|
||||
}
|
||||
this.loadingChart = false;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
this.chartOptions = options;
|
||||
},
|
||||
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('autoConversion', false),]).then((values) => {
|
||||
this.autoConversion = values[0];
|
||||
this.loadChart();
|
||||
});
|
||||
// todo the charts don't need to reload from server if the autoConversion value changes.
|
||||
window.store.observe('end', () => {
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
|
205
resources/assets/v2/pages/dashboard/categories.js
Normal file
205
resources/assets/v2/pages/dashboard/categories.js
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* budgets.js
|
||||
* Copyright (c) 2023 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/>.
|
||||
*/
|
||||
import {getVariable} from "../../store/get-variable.js";
|
||||
import Dashboard from "../../api/v2/chart/category/dashboard.js";
|
||||
import ApexCharts from "apexcharts";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
|
||||
window.categoryCurrencies = [];
|
||||
export default () => ({
|
||||
loading: false,
|
||||
chart: null,
|
||||
autoConversion: false,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
generateOptions(data) {
|
||||
window.categoryCurrencies = [];
|
||||
let options = {
|
||||
series: [],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: '55%',
|
||||
endingShape: 'rounded'
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 2,
|
||||
colors: ['transparent']
|
||||
},
|
||||
xaxis: {
|
||||
categories: [],
|
||||
},
|
||||
yaxis: {},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.seriesIndex; // this is the currency index.
|
||||
}
|
||||
let currencyCode = window.categoryCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// first, collect all currencies and use them as series.
|
||||
let series = {};
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
let code = current.currency_code;
|
||||
// only use native code when doing auto conversion.
|
||||
if (this.autoConversion) {
|
||||
code = current.native_code;
|
||||
}
|
||||
|
||||
if (!series.hasOwnProperty(code)) {
|
||||
series[code] = {
|
||||
name: code,
|
||||
data: {},
|
||||
};
|
||||
window.categoryCurrencies.push(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop data again to add amounts.
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
let code = current.currency_code;
|
||||
if (this.autoConversion) {
|
||||
code = current.native_code;
|
||||
}
|
||||
|
||||
// loop series, add 0 if not present or add actual amount.
|
||||
for (const ii in series) {
|
||||
if (series.hasOwnProperty(ii)) {
|
||||
let amount = 0.0;
|
||||
if (code === ii) {
|
||||
// this series' currency matches this column's currency.
|
||||
amount = parseFloat(current.amount);
|
||||
if (this.autoConversion) {
|
||||
amount = parseFloat(current.native_amount);
|
||||
}
|
||||
}
|
||||
if (series[ii].data.hasOwnProperty(current.label)) {
|
||||
// there is a value for this particular currency. The amount from this column will be added.
|
||||
// (even if this column isn't recorded in this currency and a new filler value is written)
|
||||
// this is so currency conversion works.
|
||||
series[ii].data[current.label] = series[ii].data[current.label] + amount;
|
||||
}
|
||||
|
||||
if (!series[ii].data.hasOwnProperty(current.label)) {
|
||||
// this column's amount is not yet set in this series.
|
||||
series[ii].data[current.label] = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add label to x-axis, not unimportant.
|
||||
if (!options.xaxis.categories.includes(current.label)) {
|
||||
options.xaxis.categories.push(current.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop the series and create Apex-compatible data sets.
|
||||
for (const i in series) {
|
||||
let current = {
|
||||
name: i,
|
||||
data: [],
|
||||
}
|
||||
for (const ii in series[i].data) {
|
||||
current.data.push(series[i].data[ii]);
|
||||
}
|
||||
options.series.push(current);
|
||||
}
|
||||
this.chartOptions = options;
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(this.chartOptions);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#category-chart"), this.chartOptions);
|
||||
this.chart.render();
|
||||
}
|
||||
this.loading = false;
|
||||
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
});
|
||||
},
|
||||
|
||||
loadChart() {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
init() {
|
||||
Promise.all([getVariable('autoConversion', false),]).then((values) => {
|
||||
this.autoConversion = values[0];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
this.autoConversion = newValue;
|
||||
this.loadChart();
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
@@ -9,102 +9,100 @@
|
||||
<div class="container-fluid">
|
||||
@include('partials.dashboard.boxes')
|
||||
<!-- row with account data -->
|
||||
<div>
|
||||
<div class="row" x-data="accounts">
|
||||
<div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="row mb-2" x-data="accounts">
|
||||
<div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('accounts.index',['asset']) }}"
|
||||
title="{{ __('firefly.yourAccounts') }}">{{ __('firefly.yourAccounts') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="account-chart"></div>
|
||||
<p class="text-end">
|
||||
<template x-if="autoConversion">
|
||||
<button type="button" @click="switchAutoConversion"
|
||||
class="btn btn-outline-info btm-sm">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('accounts.index',['asset']) }}"
|
||||
title="{{ __('firefly.yourAccounts') }}">{{ __('firefly.yourAccounts') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="account-chart"></div>
|
||||
<p class="text-end">
|
||||
<template x-if="autoConversion">
|
||||
<button type="button" @click="switchAutoConversion"
|
||||
class="btn btn-outline-info btm-sm">
|
||||
<span
|
||||
class="fa-solid fa-comments-dollar"></span> {{ __('firefly.disable_auto_convert') }}
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="!autoConversion">
|
||||
<button type="button" @click="switchAutoConversion"
|
||||
class="btn btn-outline-info btm-sm">
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="!autoConversion">
|
||||
<button type="button" @click="switchAutoConversion"
|
||||
class="btn btn-outline-info btm-sm">
|
||||
<span
|
||||
class="fa-solid fa-comments-dollar"></span> {{ __('firefly.enable_auto_convert') }}
|
||||
</button>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2" x-data="budgets">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('budgets.index') }}"
|
||||
title="{{ __('firefly.go_to_budgets') }}">{{ __('firefly.go_to_budgets') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="budget-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2" x-data="budgets">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('budgets.index') }}"
|
||||
title="{{ __('firefly.go_to_budgets') }}">{{ __('firefly.budgetsAndSpending') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="budget-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('accounts.index',['asset']) }}"
|
||||
title="{{ __('firefly.yourAccounts') }}">cat</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="category-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" x-data="categories">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="{{ route('categories.index') }}"
|
||||
title="{{ __('firefly.yourAccounts') }}">{{ __('firefly.categories') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="category-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-12 col-sm-12 col-xs-12">
|
||||
<div class="row">
|
||||
<template x-for="account in accountList">
|
||||
<div class="col-12 mb-2" x-model="account">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<a :href="'{{ route('accounts.show','') }}/' + account.id"
|
||||
x-text="account.name"></a>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-12 col-sm-12 col-xs-12">
|
||||
<div class="row">
|
||||
<template x-for="account in accountList">
|
||||
<div class="col-12 mb-2" x-model="account">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<a :href="'{{ route('accounts.show','') }}/' + account.id"
|
||||
x-text="account.name"></a>
|
||||
|
||||
<span class="small text-muted">(<span
|
||||
x-text="account.balance"></span>)</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<p class="text-center small" x-show="account.groups.length < 1">
|
||||
TODO No transactions
|
||||
<span class="small text-muted">(<span
|
||||
x-text="account.balance"></span>)</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<p class="text-center small" x-show="account.groups.length < 1">
|
||||
TODO No transactions
|
||||
|
||||
</p>
|
||||
<table class="table table-sm" x-show="account.groups.length > 0">
|
||||
<tbody>
|
||||
<template x-for="group in account.groups">
|
||||
<tr>
|
||||
<td>
|
||||
<template x-if="group.title">
|
||||
</p>
|
||||
<table class="table table-sm" x-show="account.groups.length > 0">
|
||||
<tbody>
|
||||
<template x-for="group in account.groups">
|
||||
<tr>
|
||||
<td>
|
||||
<template x-if="group.title">
|
||||
<span><a
|
||||
:href="'{{route('transactions.show', '') }}/' + group.id"
|
||||
x-text="group.title"></a><br/></span>
|
||||
</template>
|
||||
<template x-for="transaction in group.transactions">
|
||||
</template>
|
||||
<template x-for="transaction in group.transactions">
|
||||
<span>
|
||||
<template x-if="group.title">
|
||||
<span>-
|
||||
@@ -119,32 +117,74 @@
|
||||
</span>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
<td style="width:30%;" class="text-end">
|
||||
<template x-if="group.title">
|
||||
<span><br/></span>
|
||||
</template>
|
||||
<template x-for="transaction in group.transactions">
|
||||
</template>
|
||||
</td>
|
||||
<td style="width:30%;" class="text-end">
|
||||
<template x-if="group.title">
|
||||
<span><br/></span>
|
||||
</template>
|
||||
<template x-for="transaction in group.transactions">
|
||||
<span>
|
||||
<span x-text="transaction.amount"></span><br>
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="#" title="Something">Expense accounts</a></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="#" title="Something">Bills</a></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="#" title="Something">Spaarpotjes</a></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><a href="#" title="Something">Revenue</a></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- row with budget chart -->
|
||||
|
||||
</div>
|
||||
|
||||
|
Reference in New Issue
Block a user