Merge branch 'adminlte4' into develop

This commit is contained in:
James Cole
2023-08-12 17:44:08 +02:00
119 changed files with 22217 additions and 5547 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

17
.gitattributes vendored
View File

@@ -1,8 +1,11 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
/tests export-ignore
/phpunit.xml export-ignore
/.ci export-ignore
.styleci.yml export-ignore

View File

@@ -322,16 +322,11 @@ class BasicController extends Controller
'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'local_icon' => 'money',
'sub_title' => (string)trans(
'firefly.box_spend_per_day',
[
'amount' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
]
'sub_title' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
];
}

View File

@@ -134,5 +134,4 @@ class CategoryController extends Controller
return response()->json($this->clean($return));
}
}

View File

@@ -11,7 +11,7 @@ define('LARAVEL_START', microtime(true));
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
| loading of any of our classes manually. It's great to relax.
|
*/

View File

@@ -31,7 +31,7 @@ return [
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
@@ -51,17 +51,30 @@ return [
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
//
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
'driver' => 'redis',
'driver' => 'redis',
'connection' => 'default',
],

View File

@@ -13,10 +13,42 @@ return [
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon"
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 10),
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [
'memory' => 65536,
'threads' => 1,
'time' => 4,
],
];

View File

@@ -21,6 +21,14 @@
declare(strict_types=1);
$paths = [realpath(base_path('resources/views'))];
if ('v2' === env('FIREFLY_III_LAYOUT')) {
$paths = [
realpath(base_path('resources/views/v2')),
realpath(base_path('resources/views'))];
}
return [
/*
|--------------------------------------------------------------------------
@@ -33,9 +41,7 @@ return [
|
*/
'paths' => [
realpath(base_path('resources/views')),
],
'paths' => $paths,
/*
|--------------------------------------------------------------------------

1036
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,26 @@
{
"private": true,
"type": "module",
"scripts": {
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"production": "mix --production",
"prod": "mix --production"
},
"dependencies": {
"date-fns": "^2.30.0",
"stream-browserify": "^3.0.0"
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@johmun/vue-tags-input": "^2",
"@vue/compiler-sfc": "^3.3.4",
"axios": "^1.3",
"bootstrap-sass": "^3",
"cross-env": "^7.0",
"font-awesome": "^4.7.0",
"jquery": "^3",
"laravel-mix": "^6.0",
"postcss": "^8.4",
"uiv": "^1.4",
"vue": "^2.7",
"vue-i18n": "^8",
"vue-loader": "^15",
"vue-template-compiler": "^2.7"
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.64.2",
"vite": "^4.0.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@popperjs/core": "^2.11.8",
"alpinejs": "^3.12.3",
"bootstrap": "^5.3.0",
"chart.js": "^4.3.3",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.12.0",
"date-fns": "^2.30.0",
"i18n-js": "^4.3.0",
"store": "^2.0.12"
}
}

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,36 @@
{
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.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-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-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-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-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-886c8611.woff2",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"
},
"resources/assets/v2/dashboard.js": {
"file": "assets/dashboard-40253f13.js",
"isEntry": true,
"src": "resources/assets/v2/dashboard.js"
},
"resources/assets/v2/sass/app.scss": {
"file": "assets/app-28a195fd.css",
"isEntry": true,
"src": "resources/assets/v2/sass/app.scss"
}
}

15383
public/v2/css/adminlte.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
public/v2/css/adminlte.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

73
public/v2/css/fonts.css Normal file
View File

@@ -0,0 +1,73 @@
/*
* fonts.css
* 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/>.
*/
/* source-sans-3-300 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: normal;
font-weight: 300;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* source-sans-3-300italic - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: italic;
font-weight: 300;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* source-sans-3-regular - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: normal;
font-weight: 400;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* source-sans-3-italic - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: italic;
font-weight: 400;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* source-sans-3-700 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: normal;
font-weight: 700;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* source-sans-3-700italic - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Source Sans 3';
font-style: italic;
font-weight: 700;
src: url('v4/fonts/source-sans-3-v9-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}

BIN
public/v2/i/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

27
public/v2/i18n/bg_BG.json Normal file
View File

@@ -0,0 +1,27 @@
{
"bg_BG": {
"config": {
"html_language": "bg"
},
"firefly": {
"spent": "\u041f\u043e\u0445\u0430\u0440\u0447\u0435\u043d\u0438",
"left": "\u041e\u0441\u0442\u0430\u043d\u0430\u043b\u0438",
"paid": "\u041f\u043b\u0430\u0442\u0435\u043d\u0438",
"unpaid": "\u041d\u0435\u043f\u043b\u0430\u0442\u0435\u043d\u0438",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u041f\u0440\u0435\u0440\u0430\u0437\u0445\u043e\u0434",
"money_flowing_in": "\u0412\u0445\u043e\u0434\u044f\u0449\u0438",
"money_flowing_out": "\u0418\u0437\u0445\u043e\u0434\u044f\u0449\u0438",
"category": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u0411\u044e\u0434\u0436\u0435\u0442"
}
}
}

27
public/v2/i18n/ca_ES.json Normal file
View File

@@ -0,0 +1,27 @@
{
"ca_ES": {
"config": {
"html_language": "ca"
},
"firefly": {
"spent": "Gastat",
"left": "Queda",
"paid": "Pagat",
"unpaid": "Pendent de pagament",
"default_group_title_name_plain": "ungrouped",
"overspent": "Gastat de m\u00e9s",
"money_flowing_in": "Entrant",
"money_flowing_out": "Eixint",
"category": "Categoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Pressupost"
}
}
}

27
public/v2/i18n/cs_CZ.json Normal file
View File

@@ -0,0 +1,27 @@
{
"cs_CZ": {
"config": {
"html_language": "cs"
},
"firefly": {
"spent": "Utraceno",
"left": "Zb\u00fdv\u00e1",
"paid": "Zaplaceno",
"unpaid": "Nezaplaceno",
"default_group_title_name_plain": "ungrouped",
"overspent": "P\u0159ekro\u010deny v\u00fddaje",
"money_flowing_in": "Vstup",
"money_flowing_out": "V\u00fdstup",
"category": "Kategorie",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Rozpo\u010det"
}
}
}

27
public/v2/i18n/da_DK.json Normal file
View File

@@ -0,0 +1,27 @@
{
"da_DK": {
"config": {
"html_language": "da"
},
"firefly": {
"spent": "Spent",
"left": "Left",
"paid": "Paid",
"unpaid": "Ubetalt",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overspent",
"money_flowing_in": "In",
"money_flowing_out": "Ud",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/de_DE.json Normal file
View File

@@ -0,0 +1,27 @@
{
"de_DE": {
"config": {
"html_language": "de"
},
"firefly": {
"spent": "Ausgegeben",
"left": "\u00dcbrig",
"paid": "Bezahlt",
"unpaid": "Unbezahlt",
"default_group_title_name_plain": "ungrouped",
"overspent": "Zuviel ausgegeben",
"money_flowing_in": "Eingehend",
"money_flowing_out": "Ausgehend",
"category": "Kategorie",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/el_GR.json Normal file
View File

@@ -0,0 +1,27 @@
{
"el_GR": {
"config": {
"html_language": "el"
},
"firefly": {
"spent": "\u0394\u03b1\u03c0\u03b1\u03bd\u03ae\u03b8\u03b7\u03ba\u03b1\u03bd",
"left": "\u0391\u03c0\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5\u03bd",
"paid": "\u03a0\u03bb\u03b7\u03c1\u03c9\u03bc\u03ad\u03bd\u03bf",
"unpaid": "\u0391\u03c0\u03bb\u03ae\u03c1\u03c9\u03c4\u03bf",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03ce\u03bd",
"money_flowing_in": "\u0395\u03b9\u03c3\u03c1\u03bf\u03ad\u03c2",
"money_flowing_out": "\u0395\u03ba\u03c1\u03bf\u03ad\u03c2",
"category": "\u039a\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b1",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u03a0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03cc\u03c2"
}
}
}

27
public/v2/i18n/en_GB.json Normal file
View File

@@ -0,0 +1,27 @@
{
"en_GB": {
"config": {
"html_language": "en-gb"
},
"firefly": {
"spent": "Spent",
"left": "Left",
"paid": "Paid",
"unpaid": "Unpaid",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overspent",
"money_flowing_in": "In",
"money_flowing_out": "Out",
"category": "Category",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/en_US.json Normal file
View File

@@ -0,0 +1,27 @@
{
"en_US": {
"config": {
"html_language": "en"
},
"firefly": {
"spent": "Spent",
"left": "Left",
"paid": "Paid",
"unpaid": "Unpaid",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overspent",
"money_flowing_in": "In",
"money_flowing_out": "Out",
"category": "Category",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/es_ES.json Normal file
View File

@@ -0,0 +1,27 @@
{
"es_ES": {
"config": {
"html_language": "es"
},
"firefly": {
"spent": "Gastado",
"left": "Disponible",
"paid": "Pagado",
"unpaid": "No pagado",
"default_group_title_name_plain": "ungrouped",
"overspent": "Sobrepasadas",
"money_flowing_in": "Entrada",
"money_flowing_out": "Salida",
"category": "Categoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Presupuesto"
}
}
}

27
public/v2/i18n/fi_FI.json Normal file
View File

@@ -0,0 +1,27 @@
{
"fi_FI": {
"config": {
"html_language": "fi"
},
"firefly": {
"spent": "K\u00e4ytetty",
"left": "J\u00e4ljell\u00e4",
"paid": "Maksettu",
"unpaid": "Maksamatta",
"default_group_title_name_plain": "ungrouped",
"overspent": "Varojen ylitys",
"money_flowing_in": "Sis\u00e4\u00e4n",
"money_flowing_out": "Ulos",
"category": "Kategoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budjetti"
}
}
}

27
public/v2/i18n/fr_FR.json Normal file
View File

@@ -0,0 +1,27 @@
{
"fr_FR": {
"config": {
"html_language": "fr"
},
"firefly": {
"spent": "D\u00e9pens\u00e9",
"left": "Reste",
"paid": "Pay\u00e9",
"unpaid": "Impay\u00e9",
"default_group_title_name_plain": "ungrouped",
"overspent": "Trop d\u00e9pens\u00e9",
"money_flowing_in": "Entr\u00e9e",
"money_flowing_out": "Sortie",
"category": "Cat\u00e9gorie",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/hu_HU.json Normal file
View File

@@ -0,0 +1,27 @@
{
"hu_HU": {
"config": {
"html_language": "hu"
},
"firefly": {
"spent": "Elk\u00f6lt\u00f6tt",
"left": "Maradv\u00e1ny",
"paid": "Kifizetve",
"unpaid": "Nincs fizetve",
"default_group_title_name_plain": "ungrouped",
"overspent": "T\u00falk\u00f6lt\u00f6tt",
"money_flowing_in": "Be",
"money_flowing_out": "Ki",
"category": "Kateg\u00f3ria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "K\u00f6lts\u00e9gkeret"
}
}
}

27
public/v2/i18n/id_ID.json Normal file
View File

@@ -0,0 +1,27 @@
{
"id_ID": {
"config": {
"html_language": "id"
},
"firefly": {
"spent": "Menghabiskan",
"left": "Kiri",
"paid": "Dibayar",
"unpaid": "Tidak dibayar",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overspent",
"money_flowing_in": "Dalam",
"money_flowing_out": "Keluar",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Anggaran"
}
}
}

27
public/v2/i18n/it_IT.json Normal file
View File

@@ -0,0 +1,27 @@
{
"it_IT": {
"config": {
"html_language": "it"
},
"firefly": {
"spent": "Speso",
"left": "Resto",
"paid": "Pagati",
"unpaid": "Da pagare",
"default_group_title_name_plain": "ungrouped",
"overspent": "Speso troppo",
"money_flowing_in": "Entrate",
"money_flowing_out": "Uscite",
"category": "Categoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/ja_JP.json Normal file
View File

@@ -0,0 +1,27 @@
{
"ja_JP": {
"config": {
"html_language": "ja"
},
"firefly": {
"spent": "\u652f\u51fa",
"left": "\u6b8b\u308a",
"paid": "\u652f\u6255\u3044\u6e08\u307f",
"unpaid": "\u672a\u6255\u3044",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u4f7f\u3044\u3059\u304e",
"money_flowing_in": "\u5165",
"money_flowing_out": "\u51fa",
"category": "\u30ab\u30c6\u30b4\u30ea",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u4e88\u7b97"
}
}
}

27
public/v2/i18n/ko_KR.json Normal file
View File

@@ -0,0 +1,27 @@
{
"ko_KR": {
"config": {
"html_language": "ko"
},
"firefly": {
"spent": "\uc9c0\ucd9c",
"left": "\ub0a8\uc74c",
"paid": "\uc9c0\ubd88\ub428",
"unpaid": "\ubbf8\uc9c0\ubd88",
"default_group_title_name_plain": "ungrouped",
"overspent": "\ucd08\uacfc \uc9c0\ucd9c",
"money_flowing_in": "\ub4e4\uc5b4\uc634",
"money_flowing_out": "\ub098\uac10",
"category": "\uce74\ud14c\uace0\ub9ac",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\uc608\uc0b0"
}
}
}

27
public/v2/i18n/nb_NO.json Normal file
View File

@@ -0,0 +1,27 @@
{
"nb_NO": {
"config": {
"html_language": "nb"
},
"firefly": {
"spent": "Brukt",
"left": "Gjenv\u00e6rende",
"paid": "Betalt",
"unpaid": "Ikke betalt",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overforbruk",
"money_flowing_in": "Inn",
"money_flowing_out": "Ut",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Busjett"
}
}
}

27
public/v2/i18n/nl_NL.json Normal file
View File

@@ -0,0 +1,27 @@
{
"nl_NL": {
"config": {
"html_language": "nl"
},
"firefly": {
"spent": "Uitgegeven",
"left": "Over",
"paid": "Betaald",
"unpaid": "Niet betaald",
"default_group_title_name_plain": "ungrouped",
"overspent": "Teveel uitgegeven",
"money_flowing_in": "In",
"money_flowing_out": "Uit",
"category": "Categorie",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/nn_NO.json Normal file
View File

@@ -0,0 +1,27 @@
{
"nn_NO": {
"config": {
"html_language": "nn"
},
"firefly": {
"spent": "Brukt",
"left": "Gjenverande",
"paid": "Betalt",
"unpaid": "Ikke betalt",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overforbruk",
"money_flowing_in": "Inn",
"money_flowing_out": "Ut",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Busjett"
}
}
}

27
public/v2/i18n/pl_PL.json Normal file
View File

@@ -0,0 +1,27 @@
{
"pl_PL": {
"config": {
"html_language": "pl"
},
"firefly": {
"spent": "Wydano",
"left": "Pozosta\u0142o",
"paid": "Zap\u0142acone",
"unpaid": "Niezap\u0142acone",
"default_group_title_name_plain": "ungrouped",
"overspent": "Przep\u0142acono",
"money_flowing_in": "Przychodz\u0105ce",
"money_flowing_out": "Wychodz\u0105ce",
"category": "Kategoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Bud\u017cet"
}
}
}

27
public/v2/i18n/pt_BR.json Normal file
View File

@@ -0,0 +1,27 @@
{
"pt_BR": {
"config": {
"html_language": "pt-br"
},
"firefly": {
"spent": "Gasto",
"left": "Restante",
"paid": "Pago",
"unpaid": "N\u00e3o pago",
"default_group_title_name_plain": "ungrouped",
"overspent": "Gasto excedido",
"money_flowing_in": "Entrada",
"money_flowing_out": "Sa\u00edda",
"category": "Categoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Or\u00e7amento"
}
}
}

27
public/v2/i18n/pt_PT.json Normal file
View File

@@ -0,0 +1,27 @@
{
"pt_PT": {
"config": {
"html_language": "pt"
},
"firefly": {
"spent": "Gasto",
"left": "Em falta",
"paid": "Pago",
"unpaid": "Por pagar",
"default_group_title_name_plain": "ungrouped",
"overspent": "Gasto excedido",
"money_flowing_in": "Dentro",
"money_flowing_out": "Fora",
"category": "Categoria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Or\u00e7amento"
}
}
}

27
public/v2/i18n/ro_RO.json Normal file
View File

@@ -0,0 +1,27 @@
{
"ro_RO": {
"config": {
"html_language": "ro"
},
"firefly": {
"spent": "Cheltuit",
"left": "R\u0103mas",
"paid": "Pl\u0103tit",
"unpaid": "Nepl\u0103tit",
"default_group_title_name_plain": "ungrouped",
"overspent": "Dep\u0103\u0219ire de buget",
"money_flowing_in": "\u00cen",
"money_flowing_out": "Afar\u0103",
"category": "Categorie",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Buget"
}
}
}

27
public/v2/i18n/ru_RU.json Normal file
View File

@@ -0,0 +1,27 @@
{
"ru_RU": {
"config": {
"html_language": "ru"
},
"firefly": {
"spent": "\u0420\u0430\u0441\u0445\u043e\u0434",
"left": "\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c",
"paid": "\u041e\u043f\u043b\u0430\u0447\u0435\u043d\u043e",
"unpaid": "\u041d\u0435 \u043e\u043f\u043b\u0430\u0447\u0435\u043d\u043e",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u041f\u0435\u0440\u0435\u0440\u0430\u0441\u0445\u043e\u0434",
"money_flowing_in": "\u0412",
"money_flowing_out": "\u0418\u0437",
"category": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u0411\u044e\u0434\u0436\u0435\u0442"
}
}
}

27
public/v2/i18n/sk_SK.json Normal file
View File

@@ -0,0 +1,27 @@
{
"sk_SK": {
"config": {
"html_language": "sk"
},
"firefly": {
"spent": "Utraten\u00e9",
"left": "Zost\u00e1va",
"paid": "Uhraden\u00e9",
"unpaid": "Neuhraden\u00e9",
"default_group_title_name_plain": "ungrouped",
"overspent": "Prekro\u010den\u00e9 v\u00fddaje",
"money_flowing_in": "Prich\u00e1dzaj\u00face",
"money_flowing_out": "Odch\u00e1dzaj\u00face",
"category": "Kateg\u00f3ria",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Rozpo\u010det"
}
}
}

27
public/v2/i18n/sl_SI.json Normal file
View File

@@ -0,0 +1,27 @@
{
"sl_SI": {
"config": {
"html_language": "sl"
},
"firefly": {
"spent": "Porabljeno",
"left": "Preostalo",
"paid": "Pla\u010dano",
"unpaid": "Nepla\u010dano",
"default_group_title_name_plain": "ungrouped",
"overspent": "Preve\u010d porabljeno",
"money_flowing_in": "Na",
"money_flowing_out": "Iz",
"category": "Kategorija",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Prora\u010dun"
}
}
}

27
public/v2/i18n/sv_SE.json Normal file
View File

@@ -0,0 +1,27 @@
{
"sv_SE": {
"config": {
"html_language": "sv"
},
"firefly": {
"spent": "Spenderat",
"left": "\u00c5terst\u00e5r",
"paid": "Betald",
"unpaid": "Obetald",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u00d6veranstr\u00e4ngd",
"money_flowing_in": "In",
"money_flowing_out": "Ut",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/tr_TR.json Normal file
View File

@@ -0,0 +1,27 @@
{
"tr_TR": {
"config": {
"html_language": "tr"
},
"firefly": {
"spent": "Harcanan",
"left": "Ayr\u0131ld\u0131",
"paid": "\u00d6dendi",
"unpaid": "\u00d6denmedi",
"default_group_title_name_plain": "ungrouped",
"overspent": "Fazladan",
"money_flowing_in": "\u0130\u00e7eri",
"money_flowing_out": "D\u0131\u015far\u0131",
"category": "Kategori",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "B\u00fct\u00e7e"
}
}
}

27
public/v2/i18n/uk_UA.json Normal file
View File

@@ -0,0 +1,27 @@
{
"uk_UA": {
"config": {
"html_language": "uk"
},
"firefly": {
"spent": "Spent",
"left": "Left",
"paid": "Paid",
"unpaid": "Unpaid",
"default_group_title_name_plain": "ungrouped",
"overspent": "Overspent",
"money_flowing_in": "In",
"money_flowing_out": "Out",
"category": "Category",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Budget"
}
}
}

27
public/v2/i18n/vi_VN.json Normal file
View File

@@ -0,0 +1,27 @@
{
"vi_VN": {
"config": {
"html_language": "vi"
},
"firefly": {
"spent": "\u0110\u00e3 chi",
"left": "C\u00f2n l\u1ea1i",
"paid": "\u0110\u00e3 thanh to\u00e1n",
"unpaid": "Ch\u01b0a thanh to\u00e1n",
"default_group_title_name_plain": "ungrouped",
"overspent": "Qu\u00e1 h\u1ea1n",
"money_flowing_in": "V\u00e0o",
"money_flowing_out": "Ra",
"category": "Danh m\u1ee5c",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "Ng\u00e2n s\u00e1ch"
}
}
}

27
public/v2/i18n/zh_CN.json Normal file
View File

@@ -0,0 +1,27 @@
{
"zh_CN": {
"config": {
"html_language": "zh-cn"
},
"firefly": {
"spent": "\u652f\u51fa",
"left": "\u5269\u4f59",
"paid": "\u5df2\u4ed8\u6b3e",
"unpaid": "\u672a\u4ed8\u6b3e",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u8d85\u652f",
"money_flowing_in": "\u6d41\u5165",
"money_flowing_out": "\u6d41\u51fa",
"category": "\u5206\u7c7b",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u9884\u7b97"
}
}
}

27
public/v2/i18n/zh_TW.json Normal file
View File

@@ -0,0 +1,27 @@
{
"zh_TW": {
"config": {
"html_language": "zh-tw"
},
"firefly": {
"spent": "\u652f\u51fa",
"left": "\u5269\u9918",
"paid": "\u5df2\u4ed8\u6b3e",
"unpaid": "\u672a\u4ed8\u6b3e",
"default_group_title_name_plain": "ungrouped",
"overspent": "\u8d85\u652f",
"money_flowing_in": "\u5728",
"money_flowing_out": "\u5916",
"category": "\u5206\u985e",
"unknown_category_plain": "No category",
"all_money": "All your money",
"unknown_source_plain": "Unknown source account",
"unknown_dest_plain": "Unknown destination account",
"unknown_any_plain": "Unknown account",
"unknown_budget_plain": "No budget",
"expense_account": "Expense account",
"revenue_account": "Revenue account",
"budget": "\u9810\u7b97"
}
}
}

364
public/v2/js/adminlte.js Normal file
View File

@@ -0,0 +1,364 @@
/*!
* AdminLTE v4.0.0-alpha2 (https://adminlte.io)
* Copyright 2014-2023 Colorlib <https://colorlib.com>
* Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {}));
})(this, (function (exports) { 'use strict';
const domContentLoadedCallbacks = [];
const onDOMContentLoaded = (callback) => {
if (document.readyState === 'loading') {
// add listener on the first call when the document is in loading state
if (!domContentLoadedCallbacks.length) {
document.addEventListener('DOMContentLoaded', () => {
for (const callback of domContentLoadedCallbacks) {
callback();
}
});
}
domContentLoadedCallbacks.push(callback);
}
else {
callback();
}
};
/* SLIDE UP */
const slideUp = (target, duration = 500) => {
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = `${duration}ms`;
target.style.boxSizing = 'border-box';
target.style.height = `${target.offsetHeight}px`;
target.style.overflow = 'hidden';
window.setTimeout(() => {
target.style.height = '0';
target.style.paddingTop = '0';
target.style.paddingBottom = '0';
target.style.marginTop = '0';
target.style.marginBottom = '0';
}, 1);
window.setTimeout(() => {
target.style.display = 'none';
target.style.removeProperty('height');
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
/* SLIDE DOWN */
const slideDown = (target, duration = 500) => {
target.style.removeProperty('display');
let { display } = window.getComputedStyle(target);
if (display === 'none') {
display = 'block';
}
target.style.display = display;
const height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = '0';
target.style.paddingTop = '0';
target.style.paddingBottom = '0';
target.style.marginTop = '0';
target.style.marginBottom = '0';
window.setTimeout(() => {
target.style.boxSizing = 'border-box';
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = `${duration}ms`;
target.style.height = `${height}px`;
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
}, 1);
window.setTimeout(() => {
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
/**
* --------------------------------------------
* AdminLTE layout.ts
* License MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
const CLASS_NAME_APP_LOADED = 'app-loaded';
/**
* Class Definition
* ====================================================
*/
class Layout {
constructor(element) {
this._element = element;
}
holdTransition() {
let resizeTimer;
window.addEventListener('resize', () => {
document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
}, 400);
});
}
}
onDOMContentLoaded(() => {
const data = new Layout(document.body);
data.holdTransition();
setTimeout(() => {
document.body.classList.add(CLASS_NAME_APP_LOADED);
}, 400);
});
/**
* --------------------------------------------
* AdminLTE push-menu.ts
* License MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const DATA_KEY$1 = 'lte.push-menu';
const EVENT_KEY$1 = `.${DATA_KEY$1}`;
const EVENT_OPEN = `open${EVENT_KEY$1}`;
const EVENT_COLLAPSE = `collapse${EVENT_KEY$1}`;
const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
const CLASS_NAME_MENU_OPEN$1 = 'menu-open';
const SELECTOR_APP_SIDEBAR = '.app-sidebar';
const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
const SELECTOR_NAV_ITEM$1 = '.nav-item';
const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
const SELECTOR_APP_WRAPPER = '.app-wrapper';
const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
const Defaults = {
sidebarBreakpoint: 992
};
/**
* Class Definition
* ====================================================
*/
class PushMenu {
constructor(element, config) {
this._element = element;
this._config = Object.assign(Object.assign({}, Defaults), config);
}
// TODO
menusClose() {
const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
navTreeview.forEach(navTree => {
navTree.style.removeProperty('display');
navTree.style.removeProperty('height');
});
const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
const navItem = navSidebar === null || navSidebar === void 0 ? void 0 : navSidebar.querySelectorAll(SELECTOR_NAV_ITEM$1);
if (navItem) {
navItem.forEach(navI => {
navI.classList.remove(CLASS_NAME_MENU_OPEN$1);
});
}
}
expand() {
const event = new Event(EVENT_OPEN);
document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
this._element.dispatchEvent(event);
}
collapse() {
const event = new Event(EVENT_COLLAPSE);
document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
this._element.dispatchEvent(event);
}
addSidebarBreakPoint() {
var _a, _b, _c;
const sidebarExpandList = (_b = (_a = document.querySelector(SELECTOR_SIDEBAR_EXPAND)) === null || _a === void 0 ? void 0 : _a.classList) !== null && _b !== void 0 ? _b : [];
const sidebarExpand = (_c = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND))) !== null && _c !== void 0 ? _c : '';
const sidebar = document.getElementsByClassName(sidebarExpand)[0];
const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content');
this._config = Object.assign(Object.assign({}, this._config), { sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) });
if (window.innerWidth <= this._config.sidebarBreakpoint) {
this.collapse();
}
else {
if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
this.expand();
}
if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
this.collapse();
}
}
}
toggle() {
if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
this.expand();
}
else {
this.collapse();
}
}
init() {
this.addSidebarBreakPoint();
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
onDOMContentLoaded(() => {
var _a;
const sidebar = document === null || document === void 0 ? void 0 : document.querySelector(SELECTOR_APP_SIDEBAR);
if (sidebar) {
const data = new PushMenu(sidebar, Defaults);
data.init();
window.addEventListener('resize', () => {
data.init();
});
}
const sidebarOverlay = document.createElement('div');
sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
(_a = document.querySelector(SELECTOR_APP_WRAPPER)) === null || _a === void 0 ? void 0 : _a.append(sidebarOverlay);
sidebarOverlay.addEventListener('touchstart', event => {
event.preventDefault();
const target = event.currentTarget;
const data = new PushMenu(target, Defaults);
data.collapse();
});
sidebarOverlay.addEventListener('click', event => {
event.preventDefault();
const target = event.currentTarget;
const data = new PushMenu(target, Defaults);
data.collapse();
});
const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
fullBtn.forEach(btn => {
btn.addEventListener('click', event => {
event.preventDefault();
let button = event.currentTarget;
if ((button === null || button === void 0 ? void 0 : button.dataset.lteToggle) !== 'sidebar') {
button = button === null || button === void 0 ? void 0 : button.closest(SELECTOR_SIDEBAR_TOGGLE);
}
if (button) {
event === null || event === void 0 ? void 0 : event.preventDefault();
const data = new PushMenu(button, Defaults);
data.toggle();
}
});
});
});
/**
* --------------------------------------------
* AdminLTE treeview.ts
* License MIT
* --------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
// const NAME = 'Treeview'
const DATA_KEY = 'lte.treeview';
const EVENT_KEY = `.${DATA_KEY}`;
const EVENT_EXPANDED = `expanded${EVENT_KEY}`;
const EVENT_COLLAPSED = `collapsed${EVENT_KEY}`;
// const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`
const CLASS_NAME_MENU_OPEN = 'menu-open';
const SELECTOR_NAV_ITEM = '.nav-item';
const SELECTOR_NAV_LINK = '.nav-link';
const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="treeview"]';
const Default = {
animationSpeed: 300
};
/**
* Class Definition
* ====================================================
*/
class Treeview {
constructor(element, config) {
this._element = element;
this._config = Object.assign(Object.assign({}, Default), config);
}
open() {
var _a;
const event = new Event(EVENT_EXPANDED);
this._element.classList.add(CLASS_NAME_MENU_OPEN);
const childElement = (_a = this._element) === null || _a === void 0 ? void 0 : _a.querySelector(SELECTOR_TREEVIEW_MENU);
if (childElement) {
slideDown(childElement, this._config.animationSpeed);
}
this._element.dispatchEvent(event);
}
close() {
var _a;
const event = new Event(EVENT_COLLAPSED);
this._element.classList.remove(CLASS_NAME_MENU_OPEN);
const childElement = (_a = this._element) === null || _a === void 0 ? void 0 : _a.querySelector(SELECTOR_TREEVIEW_MENU);
if (childElement) {
slideUp(childElement, this._config.animationSpeed);
}
this._element.dispatchEvent(event);
}
toggle() {
if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {
this.close();
}
else {
this.open();
}
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
onDOMContentLoaded(() => {
const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
button.forEach(btn => {
btn.addEventListener('click', event => {
const target = event.target;
const targetItem = target.closest(SELECTOR_NAV_ITEM);
const targetLink = target.closest(SELECTOR_NAV_LINK);
if ((target === null || target === void 0 ? void 0 : target.getAttribute('href')) === '#' || (targetLink === null || targetLink === void 0 ? void 0 : targetLink.getAttribute('href')) === '#') {
event.preventDefault();
}
if (targetItem) {
const data = new Treeview(targetItem, Default);
data.toggle();
}
});
});
});
exports.Layout = Layout;
exports.PushMenu = PushMenu;
exports.Treeview = Treeview;
}));
//# sourceMappingURL=adminlte.js.map

File diff suppressed because one or more lines are too long

7
public/v2/js/adminlte.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -26,4 +26,5 @@
//@import "~bootstrap-sass/assets/stylesheets/bootstrap";
// Font awesome
//@import "~font-awesome/css/font-awesome";
//@import "~font-awesome/css/font-awesome";

View File

@@ -0,0 +1,49 @@
/*
* list.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/format";
export default class Get {
/**
*
* @param identifier
* @param date
* @returns {Promise<AxiosResponse<any>>}
*/
get(identifier, date) {
let params = {date: format(date, 'y-MM-d').slice(0, 10)};
if (!date) {
return api.get('/api/v1/accounts/' + identifier);
}
return api.get('/api/v1/accounts/' + identifier, {params: params});
}
/**
*
* @param identifier
* @param page
* @returns {Promise<AxiosResponse<any>>}
*/
transactions(identifier, page) {
return api.get('/api/v1/accounts/' + identifier + '/transactions', {params: {page: page}});
}
}

View 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 Overview {
overview(start, end) {
let startStr = format(start, 'y-MM-dd');
let endStr = format(end, 'y-MM-dd');
return api.get('/api/v1/chart/account/overview', {params: {start: startStr, end: endStr}});
}
}

View File

@@ -0,0 +1,35 @@
/*
* basic.js
* Copyright (c) 2021 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";
export default class Preferences {
getByName(name) {
return api.get('/api/v1/preferences/' + name);
}
getByNameNow(name) {
return api.get('/api/v1/preferences/' + name);
}
postByName(name, value) {
return api.post('/api/v1/preferences', {name: name, data: value});
}
}

View File

@@ -0,0 +1,28 @@
/*
* post.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";
export default class Post {
post(name, value) {
let url = '/api/v1/preferences';
return api.post(url, {name: name, data: value});
}
}

View File

@@ -0,0 +1,28 @@
/*
* post.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";
export default class Put {
put(name, value) {
let url = '/api/v1/preferences/' + name;
return api.put(url, {data: value});
}
}

View File

@@ -0,0 +1,28 @@
/*
* index.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 {api} from "../../../boot/axios.js";
export default class Summary {
get(start, end, code) {
return api.get('/api/v1/summary/basic', {params: {start: start, end: end, code: code}});
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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/account/dashboard', {params: {start: startStr, end: endStr}});
}
expense(start, end) {
let startStr = format(start, 'y-MM-dd');
let endStr = format(end, 'y-MM-dd');
return api.get('/api/v2/chart/account/expense-dashboard', {params: {start: startStr, end: endStr}});
}
}

View 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/budget/dashboard', {params: {start: startStr, end: endStr}});
}
}

View 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}});
}
}

View File

@@ -0,0 +1,49 @@
/*
* list.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/format";
export default class Get {
/**
*
* @param identifier
* @param date
* @returns {Promise<AxiosResponse<any>>}
*/
get(identifier, date) {
let params = {date: format(date, 'y-MM-d').slice(0, 10)};
if (!date) {
return api.get('/api/v2/accounts/' + identifier);
}
return api.get('/api/v2/accounts/' + identifier, {params: params});
}
/**
*
* @param identifier
* @param page
* @returns {Promise<AxiosResponse<any>>}
*/
transactions(identifier, page) {
return api.get('/api/v2/accounts/' + identifier + '/transactions', {params: {page: page}});
}
}

View File

@@ -0,0 +1,35 @@
/*
* get.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 {api} from "../../../../boot/axios";
export default class Get {
/**
*
* @param params
* @returns {Promise<AxiosResponse<any>>}
*/
get(params) {
return api.get('/api/v2/piggy-banks', {params: params});
}
}

View File

@@ -0,0 +1,42 @@
/*
* get.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 {api} from "../../../../boot/axios";
export default class Get {
/**
*
* @param params
* @returns {Promise<AxiosResponse<any>>}
*/
get(params) {
return api.get('/api/v2/subscriptions', {params: params});
}
paid(params) {
return api.get('/api/v2/subscriptions/sum/paid', {params: params});
}
unpaid(params) {
return api.get('/api/v2/subscriptions/sum/unpaid', {params: params});
}
}

View File

@@ -0,0 +1,34 @@
/*
* get.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 {api} from "../../../../boot/axios";
export default class Get {
/**
*
* @param params
* @returns {Promise<AxiosResponse<any>>}
*/
get(params) {
return api.get('/api/v2/transactions', {params: params});
}
}

View File

@@ -0,0 +1,28 @@
/*
* index.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 {api} from "../../../boot/axios.js";
export default class Summary {
get(start, end, code) {
return api.get('/api/v2/summary/basic', {params: {start: start, end: end, code: code}});
}
}

187
resources/assets/v2/app.js Normal file
View File

@@ -0,0 +1,187 @@
// import './bootstrap';
// import {
// addMonths,
// endOfDay,
// endOfMonth,
// endOfQuarter,
// endOfWeek,
// startOfDay,
// startOfMonth,
// startOfQuarter,
// startOfWeek,
// startOfYear,
// subDays,
// subMonths
// } from "date-fns";
// import format from './util/format'
//
// export default () => ({
// range: {
// start: null, end: null
// },
// defaultRange: {
// start: null, end: null
// },
//
// init() {
// console.log('MainApp init');
// // get values from store and use them accordingly.
// // this.viewRange = window.BasicStore.get('viewRange');
// // this.locale = window.BasicStore.get('locale');
// // this.language = window.BasicStore.get('language');
// // this.locale = 'equal' === this.locale ? this.language : this.locale;
// // window.__localeId__ = this.language;
// //
// // // the range is always null but later on we will store it in BasicStore.
// // if (null === this.range.start && null === this.range.end) {
// // console.log('start + end = null, calling setDatesFromViewRange()');
// // this.range = this.setDatesFromViewRange(new Date);
// // }
// // console.log('MainApp: set defaultRange');
// // this.defaultRange = this.setDatesFromViewRange(new Date);
// // // default range is always the current period (initialized ahead)
// },
//
//
// buildDateRange() {
// console.log('MainApp: buildDateRange');
// // generate ranges
// let nextRange = this.getNextRange();
// let prevRange = this.getPrevRange();
// let last7 = this.lastDays(7);
// let last30 = this.lastDays(30);
// let mtd = this.mtd();
// let ytd = this.ytd();
//
// // set the title:
// let element = document.getElementsByClassName('daterange-holder')[0];
// element.textContent = format(this.range.start) + ' - ' + format(this.range.end);
// element.setAttribute('data-start', format(this.range.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(this.range.end, 'yyyy-MM-dd'));
//
// // set the current one
// element = document.getElementsByClassName('daterange-current')[0];
// element.textContent = format(this.defaultRange.start) + ' - ' + format(this.defaultRange.end);
// element.setAttribute('data-start', format(this.defaultRange.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(this.defaultRange.end, 'yyyy-MM-dd'));
//
// // generate next range
// element = document.getElementsByClassName('daterange-next')[0];
// element.textContent = format(nextRange.start) + ' - ' + format(nextRange.end);
// element.setAttribute('data-start', format(nextRange.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(nextRange.end, 'yyyy-MM-dd'));
//
// // previous range.
// element = document.getElementsByClassName('daterange-prev')[0];
// element.textContent = format(prevRange.start) + ' - ' + format(prevRange.end);
// element.setAttribute('data-start', format(prevRange.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(prevRange.end, 'yyyy-MM-dd'));
//
// // last 7
// element = document.getElementsByClassName('daterange-7d')[0];
// element.setAttribute('data-start', format(last7.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(last7.end, 'yyyy-MM-dd'));
//
// // last 30
// element = document.getElementsByClassName('daterange-90d')[0];
// element.setAttribute('data-start', format(last30.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(last30.end, 'yyyy-MM-dd'));
//
// // MTD
// element = document.getElementsByClassName('daterange-mtd')[0];
// element.setAttribute('data-start', format(mtd.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(mtd.end, 'yyyy-MM-dd'));
//
// // YTD
// element = document.getElementsByClassName('daterange-ytd')[0];
// element.setAttribute('data-start', format(ytd.start, 'yyyy-MM-dd'));
// element.setAttribute('data-end', format(ytd.end, 'yyyy-MM-dd'));
//
// // custom range.
// console.log('MainApp: buildDateRange end');
// },
//
// getNextRange() {
// let start = startOfMonth(this.range.start);
// let nextMonth = addMonths(start, 1);
// let end = endOfMonth(nextMonth);
// return {start: nextMonth, end: end};
// },
//
// getPrevRange() {
// let start = startOfMonth(this.range.start);
// let prevMonth = subMonths(start, 1);
// let end = endOfMonth(prevMonth);
// return {start: prevMonth, end: end};
// },
//
// ytd() {
// let end = new Date;
// let start = startOfYear(this.range.start);
// return {start: start, end: end};
// },
//
// mtd() {
//
// let end = new Date;
// let start = startOfMonth(this.range.start);
// return {start: start, end: end};
// },
//
// lastDays(days) {
// let end = new Date;
// let start = subDays(end, days);
// return {start: start, end: end};
// },
//
// changeDateRange(e) {
// console.log('MainApp: changeDateRange');
// let target = e.currentTarget;
// //alert('OK 3');
// let start = new Date(target.getAttribute('data-start'));
// let end = new Date(target.getAttribute('data-end'));
// console.log('MainApp: Change date range', start, end);
// e.preventDefault();
// // TODO send start + end to the store and trigger this again?
// window.app.setStart(start);
// window.app.setEnd(end);
// window.app.buildDateRange();
// console.log('MainApp: end changeDateRange');
// return false;
// },
//
// setStart(date) {
// console.log('MainApp: setStart');
// this.range.start = date;
// window.BasicStore.store('start', date);
// },
//
// setEnd(date) {
// console.log('MainApp: setEnd');
// this.range.end = date;
// window.BasicStore.store('end', date);
// },
// });
//
// // let app = new MainApp();
// //
// // // Listen for the basic store, we need it to continue with the
// // document.addEventListener("BasicStoreReady", (e) => {
// // console.log('MainApp: app.js from event handler');
// // app.init();
// // app.buildDateRange();
// // const event = new Event("AppReady");
// // document.dispatchEvent(event);
// // }, false,);
// //
// // if (window.BasicStore.isReady()) {
// // console.log('MainApp: app.js from store ready');
// // app.init();
// // app.buildDateRange();
// // const event = new Event("AppReady");
// // document.dispatchEvent(event);
// // }
// //
// // window.app = app;
// //
// // export default app;

View File

@@ -0,0 +1,40 @@
/*
* axios.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 axios from 'axios'
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
// for use inside Vue files (Options API) through this.$axios and this.$api
const url = '/';
const api = axios.create({baseURL: url, withCredentials: true});
axios.defaults.withCredentials = true;
axios.defaults.baseURL = url;
export {api}

51
resources/assets/v2/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
// import things
import axios from 'axios';
import store from "store";
import observePlugin from 'store/plugins/observe';
import Alpine from "alpinejs";
import * as bootstrap from 'bootstrap'
// add plugin to store and put in window
store.addPlugin(observePlugin);
window.store = store;
// import even more
import {getVariable} from "./store/get-variable.js";
import {getViewRange} from "./support/get-viewrange.js";
// wait for 3 promises, because we need those later on.
window.bootstrapped = false;
Promise.all([
getVariable('viewRange'),
getVariable('darkMode'),
getVariable('locale'),
getVariable('language'),
]).then((values) => {
if (!store.get('start') || !store.get('end')) {
// calculate new start and end, and store them.
const range = getViewRange(values[0], new Date);
store.set('start', range.start);
store.set('end', range.end);
}
// save local in window.__ something
window.__localeId__ = values[2];
store.set('language', values[3]);
store.set('locale', values[3]);
const event = new Event('firefly-iii-bootstrapped');
document.dispatchEvent(event);
window.bootstrapped = true;
});
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.Alpine = Alpine

View File

@@ -0,0 +1,98 @@
/*
* dashboard.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 './bootstrap.js';
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';
import sankey from './pages/dashboard/sankey.js';
import subscriptions from './pages/dashboard/subscriptions.js';
import piggies from './pages/dashboard/piggies.js';
import {
Chart,
LineController,
LineElement,
PieController,
BarController,
BarElement,
TimeScale,
ArcElement,
LinearScale,
Legend,
Filler,
Colors,
CategoryScale,
PointElement,
Tooltip
} from "chart.js";
import 'chartjs-adapter-date-fns';
// register things
Chart.register({
LineController,
LineElement,
ArcElement,
BarController,
TimeScale,
PieController,
BarElement,
Filler,
Colors,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Legend
});
const comps = {
dates,
boxes,
accounts,
budgets,
categories,
sankey,
subscriptions,
piggies
};
function loadPage(comps) {
Object.keys(comps).forEach(comp => {
console.log(`Loading page component "${comp}"`);
let data = comps[comp]();
Alpine.data(comp, () => data);
});
Alpine.start();
}
// wait for load until bootstrapped event is received.
document.addEventListener('firefly-iii-bootstrapped', () => {
//console.log('Loaded through event listener.');
loadPage(comps);
});
// or is bootstrapped before event is triggered.
if (window.bootstrapped) {
//console.log('Loaded through window variable.');
loadPage(comps);
}

View File

@@ -0,0 +1 @@
// NOT IN USE

View File

@@ -0,0 +1,241 @@
/*
* accounts.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 {setVariable} from "../../store/set-variable.js";
import Dashboard from "../../api/v2/chart/account/dashboard.js";
import formatMoney from "../../util/format-money.js";
import Get from "../../api/v2/model/account/get.js";
import {Chart} from 'chart.js';
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
// this is very ugly, but I have no better ideas at the moment to save the currency info
// for each series.
let currencies = [];
let chart = null;
let chartData = null;
let afterPromises = false;
export default () => ({
loading: false,
loadingAccounts: false,
accountList: [],
autoConversion: false,
chartOptions: null,
switchAutoConversion() {
this.autoConversion = !this.autoConversion;
setVariable('autoConversion', this.autoConversion);
},
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.drawChart(this.generateOptions(this.chartData));
this.loading = false;
});
},
generateOptions(data) {
currencies = [];
let options = getDefaultChartSettings('line');
for (let i = 0; i < data.length; i++) {
if (data.hasOwnProperty(i)) {
let yAxis = 'y';
let current = data[i];
let dataset = {};
let collection = [];
// if index = 0, push all keys as labels:
if (0 === i) {
options.data.labels = Object.keys(current.entries);
}
dataset.label = current.label;
// use the "native" currency code and use the "native_entries" as array
if (this.autoConversion) {
currencies.push(current.native_code);
dataset.currency_code = current.native_code;
collection = Object.values(current.native_entries);
yAxis = 'y' + current.native_code;
}
if (!this.autoConversion) {
yAxis = 'y' + current.currency_code;
dataset.currency_code = current.currency_code;
currencies.push(current.currency_code);
collection = Object.values(current.entries);
}
dataset.yAxisID = yAxis;
dataset.data = collection;
// add data set to the correct Y Axis:
options.data.datasets.push(dataset);
}
}
// for each entry in currencies, add a new y-axis:
for (let currency in currencies) {
if (currencies.hasOwnProperty(currency)) {
let code = 'y' + currencies[currency];
if (!options.options.scales.hasOwnProperty(code)) {
options.options.scales[code] = {
id: currency,
type: 'linear',
position: 1 === parseInt(currency) ? 'right' : 'left',
ticks: {
callback: function (value, index, values) {
return formatMoney(value, currencies[currency]);
}
}
};
}
}
}
return options;
},
loadChart() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null === chartData) {
this.getFreshData();
return;
}
this.drawChart(this.generateOptions(chartData));
this.loading = false;
},
drawChart(options) {
if (null !== chart) {
// chart already in place, refresh:
chart.options = options.options;
chart.data = options.data;
chart.update();
return;
}
chart = new Chart(document.querySelector("#account-chart"), options);
},
loadAccounts() {
// console.log('loadAccounts');
if (true === this.loadingAccounts) {
// console.log('loadAccounts CANCELLED');
return;
}
this.loadingAccounts = true;
if (this.accountList.length > 0) {
// console.log('NO need to load account data');
this.loadingAccounts = false;
return;
}
// console.log('loadAccounts continue!');
const max = 10;
let totalAccounts = 0;
let count = 0;
let accounts = [];
Promise.all([getVariable('frontpageAccounts'),]).then((values) => {
totalAccounts = values[0].length;
//console.log(values[0]);
for (let i in values[0]) {
let account = values[0];
if (account.hasOwnProperty(i)) {
let accountId = account[i];
// grab account info for box:
(new Get).get(accountId, new Date(window.store.get('end'))).then((response) => {
let parent = response.data.data;
// get groups for account:
(new Get).transactions(parent.id, 1).then((response) => {
let groups = [];
for (let ii = 0; ii < response.data.data.length; ii++) {
if (ii >= max) {
break;
}
let current = response.data.data[ii];
let group = {
title: null === current.attributes.group_title ? '' : current.attributes.group_title,
id: current.id,
transactions: [],
};
for (let iii = 0; iii < current.attributes.transactions.length; iii++) {
let currentTransaction = current.attributes.transactions[iii];
//console.log(currentTransaction);
group.transactions.push({
description: currentTransaction.description,
id: current.id,
amount: formatMoney(currentTransaction.amount, currentTransaction.currency_code),
native_amount: formatMoney(currentTransaction.native_amount, currentTransaction.native_code),
});
}
groups.push(group);
}
// console.log(parent);
accounts.push({
name: parent.attributes.name,
id: parent.id,
balance: formatMoney(parent.attributes.current_balance, parent.attributes.currency_code),
native_balance: formatMoney(parent.attributes.native_current_balance, parent.attributes.native_code),
groups: groups,
});
count++;
if (count === totalAccounts) {
this.accountList = accounts;
this.loadingAccounts = false;
}
});
});
}
}
//this.loadingAccounts = false;
});
},
init() {
// console.log('accounts init');
Promise.all([getVariable('viewRange', '1M'), getVariable('autoConversion', false), getVariable('language', 'en-US')]).then((values) => {
//console.log('accounts after promises');
this.autoConversion = values[1];
afterPromises = true;
// main dashboard chart:
this.loadChart();
this.loadAccounts();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('accounts observe end');
chartData = null;
this.accountList = [];
// main dashboard chart:
this.loadChart();
this.loadAccounts();
});
window.store.observe('autoConversion', () => {
if (!afterPromises) {
return;
}
// console.log('accounts observe autoconversion');
this.loadChart();
this.loadAccounts();
});
},
});

View File

@@ -0,0 +1,218 @@
/*
* dashboard.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 Summary from "../../api/v2/summary/index.js";
import {format} from "date-fns";
import {getVariable} from "../../store/get-variable.js";
import formatMoney from "../../util/format-money.js";
let afterPromises = false;
export default () => ({
balanceBox: {amounts: [], subtitles: []},
billBox: {paid: [], unpaid: []},
leftBox: {left: [], perDay: []},
netBox: {net: []},
autoConversion: false,
loading: false,
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) => {
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]);
}
}
this.loading = false;
},
loadBoxes() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null === this.boxData) {
this.getFreshData();
return;
}
this.generateOptions(this.boxData);
this.loading = false;
},
// Getter
init() {
// console.log('boxes init');
Promise.all([getVariable('viewRange'), getVariable('autoConversion', false)]).then((values) => {
// console.log('boxes after promises');
afterPromises = true;
this.autoConversion = values[1];
this.loadBoxes();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('boxes observe end');
this.boxData = null;
this.loadBoxes();
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('boxes observe autoConversion');
this.autoConversion = newValue;
this.loadBoxes();
});
},
});

View File

@@ -0,0 +1,193 @@
/*
* 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/budget/dashboard.js";
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
import formatMoney from "../../util/format-money.js";
import {Chart} from 'chart.js';
import {I18n} from "i18n-js";
import {loadTranslations} from "../../support/load-translations.js";
let currencies = [];
let chart = null;
let chartData = null;
let afterPromises = false;
let i18n; // for translating items in the chart.
export default () => ({
loading: false,
autoConversion: false,
loadChart() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null !== chartData) {
this.drawChart(this.generateOptions(chartData));
this.loading = false;
return;
}
this.getFreshData();
},
drawChart(options) {
if (null !== chart) {
chart.data.datasets = options.data.datasets;
chart.update();
return;
}
chart = new Chart(document.querySelector("#budget-chart"), options);
},
getFreshData() {
const dashboard = new Dashboard();
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
chartData = response.data; // save chart data for later.
this.drawChart(this.generateOptions(response.data));
this.loading = false;
});
},
generateOptions(data) {
currencies = [];
let options = getDefaultChartSettings('column');
options.options.locale = window.store.get('locale').replace('_', '-');
options.options.plugins = {
tooltip: {
callbacks: {
title: function (context) {
return context.label;
},
label: function (context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
return label + ' ' + formatMoney(context.parsed.y, currencies[context.parsed.x] ?? 'EUR');
}
}
}
};
options.data = {
labels: [],
datasets: [
{
label: i18n.t('firefly.spent'),
data: [],
borderWidth: 1,
stack: 1
},
{
label: i18n.t('firefly.left'),
data: [],
borderWidth: 1,
stack: 1
},
{
label: i18n.t('firefly.overspent'),
data: [],
borderWidth: 1,
stack: 1
}
]
};
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.data.labels.push(label);
if (this.autoConversion) {
currencies.push(current.native_code);
// series 0: spent
options.data.datasets[0].data.push(parseFloat(current.native_entries.spent) * -1);
// series 1: left
options.data.datasets[1].data.push(parseFloat(current.native_entries.left));
// series 2: overspent
options.data.datasets[2].data.push(parseFloat(current.native_entries.overspent));
}
if (!this.autoConversion) {
currencies.push(current.currency_code);
// series 0: spent
options.data.datasets[0].data.push(parseFloat(current.entries.spent) * -1);
// series 1: left
options.data.datasets[1].data.push(parseFloat(current.entries.left));
// series 2: overspent
options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
}
}
}
// the currency format callback for the Y axis is AlWAYS based on whatever the first currency is.
// start
options.options.scales = {
y: {
ticks: {
callback: function (context) {
return formatMoney(context, currencies[0]);
}
}
}
};
// end
return options;
},
init() {
// console.log('budgets init');
Promise.all([getVariable('autoConversion', false), getVariable('language', 'en-US')]).then((values) => {
i18n = new I18n();
i18n.locale = values[1];
loadTranslations(i18n, values[1]);
this.autoConversion = values[0];
afterPromises = true;
if (false === this.loading) {
this.loadChart();
}
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('boxes observe end');
if (false === this.loading) {
this.chartData = null;
this.loadChart();
}
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('boxes observe autoConversion');
this.autoConversion = newValue;
if (false === this.loading) {
this.loadChart();
}
});
},
});

View File

@@ -0,0 +1,189 @@
/*
* 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 {getDefaultChartSettings} from "../../support/default-chart-settings.js";
import {Chart} from "chart.js";
import formatMoney from "../../util/format-money.js";
let currencies = [];
let chart = null;
let chartData = null;
let afterPromises = false;
export default () => ({
loading: false,
autoConversion: false,
generateOptions(data) {
currencies = [];
let options = getDefaultChartSettings('column');
// first, create "series" per currency.
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,
yAxisID: '',
data: {},
};
currencies.push(code);
}
}
}
// loop data again to add amounts to each series.
for (const i in data) {
if (data.hasOwnProperty(i)) {
let yAxis = 'y';
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);
yAxis = 'y' + current.currency_code;
if (this.autoConversion) {
amount = parseFloat(current.native_amount);
yAxis = 'y' + current.native_code;
}
}
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.data.labels.includes(current.label)) {
options.data.labels.push(current.label);
}
}
}
// loop the series and create ChartJS-compatible data sets.
let count = 0;
for (const i in series) {
let yAxisID = 'y' + i;
let dataset = {
label: i,
currency_code: i,
yAxisID: yAxisID,
data: [],
}
for (const ii in series[i].data) {
dataset.data.push(series[i].data[ii]);
}
options.data.datasets.push(dataset);
if (!options.options.scales.hasOwnProperty(yAxisID)) {
options.options.scales[yAxisID] = {
beginAtZero: true,
type: 'linear',
position: 1 === count ? 'right' : 'left',
ticks: {
callback: function (value, index, values) {
return formatMoney(value, i);
}
}
};
count++;
}
}
return options;
},
drawChart(options) {
if (null !== chart) {
chart.options = options.options;
chart.data = options.data;
chart.update();
return;
}
chart = new Chart(document.querySelector("#category-chart"), options);
},
getFreshData() {
const dashboard = new Dashboard();
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
chartData = response.data; // save chart data for later.
this.drawChart(this.generateOptions(response.data));
this.loading = false;
});
},
loadChart() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null !== chartData) {
this.drawChart(this.generateOptions(chartData));
this.loading = false;
return;
}
this.getFreshData();
},
init() {
// console.log('categories init');
Promise.all([getVariable('autoConversion', false),]).then((values) => {
this.autoConversion = values[0];
afterPromises = true;
this.loadChart();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
this.chartData = null;
this.loadChart();
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
this.autoConversion = newValue;
this.loadChart();
});
},
});

View File

@@ -0,0 +1,144 @@
/*
* 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 Get from "../../api/v2/model/piggy-bank/get.js";
import {I18n} from "i18n-js";
import {loadTranslations} from "../../support/load-translations.js";
let apiData = {};
let afterPromises = false;
let i18n;
export default () => ({
loading: false,
autoConversion: false,
sankeyGrouping: 'account',
piggies: [],
getFreshData() {
let params = {
start: window.store.get('start').slice(0, 10),
end: window.store.get('end').slice(0, 10),
page: 1
};
this.downloadPiggyBanks(params);
},
downloadPiggyBanks(params) {
// console.log('Downloading page ' + params.page + '...');
const getter = new Get();
getter.get(params).then((response) => {
apiData = [...apiData, ...response.data.data];
if (parseInt(response.data.meta.pagination.total_pages) > params.page) {
params.page++;
this.downloadPiggyBanks(params);
return;
}
this.parsePiggies();
this.loading = false;
});
},
parsePiggies() {
let dataSet = [];
for (let i in apiData) {
if (apiData.hasOwnProperty(i)) {
let current = apiData[i];
if (current.attributes.percentage >= 100) {
continue;
}
if (0 === current.attributes.percentage) {
continue;
}
let groupName = current.object_group_title ?? i18n.t('firefly.default_group_title_name_plain');
if (!dataSet.hasOwnProperty(groupName)) {
dataSet[groupName] = {
id: current.object_group_id ?? 0,
title: groupName,
order: current.object_group_order ?? 0,
piggies: [],
};
}
let piggy = {
id: current.id,
name: current.attributes.name,
percentage: parseInt(current.attributes.percentage),
amount: this.autoConversion ? current.attributes.native_current_amount : current.attributes.current_amount,
// left to save
left_to_save: this.autoConversion ? current.attributes.native_left_to_save : current.attributes.left_to_save,
// target amount
target_amount: this.autoConversion ? current.attributes.native_target_amount : current.attributes.target_amount,
// save per month
save_per_month: this.autoConversion ? current.attributes.native_save_per_month : current.attributes.save_per_month,
currency_code: this.autoConversion ? current.attributes.native_code : current.attributes.currency_code,
};
dataSet[groupName].piggies.push(piggy);
}
}
this.piggies = Object.values(dataSet);
// console.log(this.piggies);
},
loadPiggyBanks() {
if (true === this.loading) {
return;
}
this.loading = true;
if (0 !== this.piggies.length) {
this.parsePiggies();
this.loading = false;
return;
}
this.getFreshData();
},
init() {
// console.log('piggies init');
apiData = [];
Promise.all([getVariable('autoConversion', false), getVariable('language', 'en-US')]).then((values) => {
i18n = new I18n();
i18n.locale = values[1];
loadTranslations(i18n, values[1]);
// console.log('piggies after promises');
afterPromises = true;
this.autoConversion = values[0];
this.loadPiggyBanks();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('piggies observe end');
apiData = [];
this.loadPiggyBanks();
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('piggies observe autoConversion');
this.autoConversion = newValue;
this.loadPiggyBanks();
});
},
});

View File

@@ -0,0 +1,378 @@
/*
* 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 Get from "../../api/v2/model/transaction/get.js";
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
import {Chart} from 'chart.js';
import {Flow, SankeyController} from 'chartjs-chart-sankey';
import {loadTranslations} from "../../support/load-translations.js";
import {I18n} from "i18n-js";
Chart.register({SankeyController, Flow});
let i18n;
let currencies = [];
let afterPromises = false;
let chart = null;
let transactions = [];
let translations = {
category: null,
unknown_category: null,
in: null,
out: null,
// TODO
unknown_source: null,
unknown_dest: null,
unknown_account: null,
expense_account: null,
revenue_account: null,
budget: null,
unknown_budget: null,
all_money: null,
};
const colors = {
a: 'red',
b: 'green',
c: 'blue',
d: 'gray'
};
const getColor = function (key) {
if (key.includes(translations.revenue_account)) {
return 'forestgreen';
}
if (key.includes('(' + translations.in + ',')) {
return 'green';
}
if (key.includes(translations.budget) || key.includes(translations.unknown_budget)) {
return 'Orchid';
}
if (key.includes('(' + translations.out + ',')) {
return 'MediumOrchid';
}
if (key.includes(translations.all_money)) {
return 'blue';
}
return 'red';
}
// little helper
function getObjectName(type, name, direction, code) {
// category 4x
if ('category' === type && null !== name && 'in' === direction) {
return translations.category + ' "' + name + '" (' + translations.in + ', ' + code + ')';
}
if ('category' === type && null === name && 'in' === direction) {
return translations.unknown_category + ' (' + translations.in + ', ' + code + ')';
}
if ('category' === type && null !== name && 'out' === direction) {
return translations.category + ' "' + name + '" (' + translations.out + ', ' + code + ')';
}
if ('category' === type && null === name && 'out' === direction) {
return translations.unknown_category + ' (' + translations.out + ', ' + code + ')';
}
// account 4x
if ('account' === type && null === name && 'in' === direction) {
return translations.unknown_source + ' (' + code + ')';
}
if ('account' === type && null !== name && 'in' === direction) {
return translations.revenue_account + '"' + name + '" (' + code + ')';
}
if ('account' === type && null === name && 'out' === direction) {
return translations.unknown_dest + ' (' + code + ')';
}
if ('account' === type && null !== name && 'out' === direction) {
return translations.expense_account + ' "' + name + '" (' + code + ')';
}
// budget 2x
if ('budget' === type && null !== name) {
return translations.budget + ' "' + name + '" (' + code + ')';
}
if ('budget' === type && null === name) {
return translations.unknown_budget + ' (' + code + ')';
}
console.error('Cannot handle: type:"' + type + '", dir: "' + direction + '"');
}
function getLabelName(type, name, code) {
// category
if ('category' === type && null !== name) {
return translations.category + ' "' + name + '" (' + code + ')';
}
if ('category' === type && null === name) {
return translations.unknown_category + ' (' + code + ')';
}
// account
if ('account' === type && null === name) {
return translations.unknown_account + ' (' + code + ')';
}
if ('account' === type && null !== name) {
return name + ' (' + code + ')';
}
// budget 2x
if ('budget' === type && null !== name) {
return translations.budget + ' "' + name + '" (' + code + ')';
}
if ('budget' === type && null === name) {
return translations.unknown_budget + ' (' + code + ')';
}
console.error('Cannot handle: type:"' + type + '"');
}
export default () => ({
loading: false,
autoConversion: false,
generateOptions() {
let options = getDefaultChartSettings('sankey');
// reset currencies
currencies = [];
// variables collected for the sankey chart:
let amounts = {};
let labels = {};
for (let i in transactions) {
if (transactions.hasOwnProperty(i)) {
let group = transactions[i];
for (let ii in group.attributes.transactions) {
if (group.attributes.transactions.hasOwnProperty(ii)) {
// properties of the transaction, used in the generation of the chart:
let transaction = group.attributes.transactions[ii];
let currencyCode = this.autoConversion ? transaction.native_code : transaction.currency_code;
let amount = this.autoConversion ? parseFloat(transaction.native_amount) : parseFloat(transaction.amount);
let flowKey;
/*
Two entries in the sankey diagram for deposits:
1. From the revenue account (source) to a category (in).
2. From the category (in) to the big inbox.
*/
if ('deposit' === transaction.type) {
// nr 1
let category = getObjectName('category', transaction.category_name, 'in', currencyCode);
let revenueAccount = getObjectName('account', transaction.source_name, 'in', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
labels[revenueAccount] = getLabelName('account', transaction.source_name, currencyCode);
flowKey = revenueAccount + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: revenueAccount,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// nr 2
flowKey = category + '-' + translations.all_money + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: translations.all_money + ' (' + currencyCode + ')',
amount: 0
};
}
amounts[flowKey].amount += amount;
}
/*
Three entries in the sankey diagram for withdrawals:
1. From the big box to a budget.
2. From a budget to a category.
3. From a category to an expense account.
*/
if ('withdrawal' === transaction.type) {
// 1.
let budget = getObjectName('budget', transaction.budget_name, 'out', currencyCode);
labels[budget] = getLabelName('budget', transaction.budget_name, currencyCode);
flowKey = translations.all_money + '-' + budget + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: translations.all_money + ' (' + currencyCode + ')',
to: budget,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 2.
let category = getObjectName('category', transaction.category_name, 'out', currencyCode);
labels[category] = getLabelName('category', transaction.category_name, currencyCode);
flowKey = budget + '-' + category + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: budget,
to: category,
amount: 0
};
}
amounts[flowKey].amount += amount;
// 3.
let expenseAccount = getObjectName('account', transaction.destination_name, 'out', currencyCode);
labels[expenseAccount] = getLabelName('account', transaction.destination_name, currencyCode);
flowKey = category + '-' + expenseAccount + '-' + currencyCode;
if (!amounts.hasOwnProperty(flowKey)) {
amounts[flowKey] = {
from: category,
to: expenseAccount,
amount: 0
};
}
amounts[flowKey].amount += amount;
}
}
}
}
}
let dataSet =
// sankey chart has one data set.
{
label: 'My sankey',
data: [],
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
colorMode: 'gradient', // or 'from' or 'to'
labels: labels,
size: 'min', // or 'min' if flow overlap is preferred
};
for (let i in amounts) {
if (amounts.hasOwnProperty(i)) {
let amount = amounts[i];
dataSet.data.push({from: amount.from, to: amount.to, flow: amount.amount});
}
}
options.data.datasets.push(dataSet);
return options;
},
drawChart(options) {
if (null !== chart) {
chart.data.datasets = options.data.datasets;
chart.update();
return;
}
chart = new Chart(document.querySelector("#sankey-chart"), options);
},
getFreshData() {
let params = {
start: window.store.get('start').slice(0, 10),
end: window.store.get('end').slice(0, 10),
type: 'withdrawal,deposit',
page: 1
};
this.downloadTransactions(params);
},
downloadTransactions(params) {
//console.log('Downloading page ' + params.page + '...');
const getter = new Get();
getter.get(params).then((response) => {
transactions = [...transactions, ...response.data.data];
//this.drawChart(this.generateOptions(response.data));
//this.loading = false;
if (parseInt(response.data.meta.pagination.total_pages) > params.page) {
// continue to next page.
params.page++;
this.downloadTransactions(params);
return;
}
// continue to next step.
//console.log('Final page!');
//console.log(transactions);
this.drawChart(this.generateOptions());
this.loading = false;
});
},
loadChart() {
if (true === this.loading) {
return;
}
this.loading = true;
if (0 !== transactions.length) {
this.drawChart(this.generateOptions());
this.loading = false;
return;
}
this.getFreshData();
},
init() {
// console.log('sankey init');
transactions = [];
Promise.all([getVariable('autoConversion', false), getVariable('language', 'en-US')]).then((values) => {
i18n = new I18n();
i18n.locale = values[1];
loadTranslations(i18n, values[1]).then(() => {
// some translations:
translations.all_money = i18n.t('firefly.all_money');
translations.category = i18n.t('firefly.category');
translations.in = i18n.t('firefly.money_flowing_in');
translations.out = i18n.t('firefly.money_flowing_out');
translations.unknown_category = i18n.t('firefly.unknown_category_plain');
translations.unknown_source = i18n.t('firefly.unknown_source_plain');
translations.unknown_dest = i18n.t('firefly.unknown_dest_plain');
translations.unknown_account = i18n.t('firefly.unknown_any_plain');
translations.unknown_budget = i18n.t('firefly.unknown_budget_plain');
translations.expense_account = i18n.t('firefly.expense_account');
translations.revenue_account = i18n.t('firefly.revenue_account');
translations.budget = i18n.t('firefly.budget');
});
// console.log('sankey after promises');
afterPromises = true;
this.autoConversion = values[0];
this.loadChart();
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('sankey observe end');
this.transactions = [];
this.loadChart();
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('sankey observe autoConversion');
this.autoConversion = newValue;
this.loadChart();
});
},
});

View File

@@ -0,0 +1,172 @@
/*
* 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 Get from "../../api/v2/model/subscription/get.js";
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
import {format} from "date-fns";
import {Chart} from 'chart.js';
import {I18n} from "i18n-js";
import {loadTranslations} from "../../support/load-translations.js";
let chart = null;
let chartData = null;
let afterPromises = false;
let i18n; // for translating items in the chart.
export default () => ({
loading: false,
autoConversion: false,
loadChart() {
if (true === this.loading) {
return;
}
this.loading = true;
if (null !== chartData) {
this.drawChart(this.generateOptions(chartData));
this.loading = false;
return;
}
this.getFreshData();
},
drawChart(options) {
if (null !== chart) {
chart.data.datasets = options.data.datasets;
chart.update();
return;
}
chart = new Chart(document.querySelector("#subscriptions-chart"), options);
},
getFreshData() {
const getter = new Get();
let params = {
start: format(new Date(window.store.get('start')), 'y-MM-dd'),
end: format(new Date(window.store.get('end')), 'y-MM-dd')
};
getter.paid(params).then((response) => {
let paidData = response.data;
getter.unpaid(params).then((response) => {
let unpaidData = response.data;
let chartData = {paid: paidData, unpaid: unpaidData};
this.drawChart(this.generateOptions(chartData));
this.loading = false;
});
});
},
generateOptions(data) {
let options = getDefaultChartSettings('pie');
// console.log(data);
options.data.labels = [i18n.t('firefly.paid'), i18n.t('firefly.unpaid')];
options.data.datasets = [];
let collection = {};
for (let i in data.paid) {
if (data.paid.hasOwnProperty(i)) {
let current = data.paid[i];
let currencyCode = this.autoConversion ? current.native_code : current.currency_code;
let amount = this.autoConversion ? current.native_sum : current.sum;
if (!collection.hasOwnProperty(currencyCode)) {
collection[currencyCode] = {
paid: 0,
unpaid: 0,
};
}
// in case of paid, add to "paid":
collection[currencyCode].paid += (parseFloat(amount) * -1);
}
}
// unpaid
for (let i in data.unpaid) {
if (data.unpaid.hasOwnProperty(i)) {
let current = data.unpaid[i];
let currencyCode = this.autoConversion ? current.native_code : current.currency_code;
let amount = this.autoConversion ? current.native_sum : current.sum;
if (!collection.hasOwnProperty(currencyCode)) {
collection[currencyCode] = {
paid: 0,
unpaid: 0,
};
}
// console.log(current);
// in case of paid, add to "paid":
collection[currencyCode].unpaid += parseFloat(amount);
}
}
for (let currencyCode in collection) {
if (collection.hasOwnProperty(currencyCode)) {
let current = collection[currencyCode];
options.data.datasets.push(
{
label: currencyCode,
data: [current.paid, current.unpaid],
backgroundColor: [
'rgb(54, 162, 235)', // green (paid)
'rgb(255, 99, 132)', // red (unpaid_
],
//hoverOffset: 4
}
)
}
}
return options;
},
init() {
// console.log('subscriptions init');
Promise.all([getVariable('autoConversion', false), getVariable('language', 'en-US')]).then((values) => {
// console.log('subscriptions after promises');
this.autoConversion = values[0];
afterPromises = true;
i18n = new I18n();
i18n.locale = values[1];
loadTranslations(i18n, values[1]);
if (false === this.loading) {
this.loadChart();
}
});
window.store.observe('end', () => {
if (!afterPromises) {
return;
}
// console.log('subscriptions observe end');
if (false === this.loading) {
this.chartData = null;
this.loadChart();
}
});
window.store.observe('autoConversion', (newValue) => {
if (!afterPromises) {
return;
}
// console.log('subscriptions observe autoConversion');
this.autoConversion = newValue;
if (false === this.loading) {
this.loadChart();
}
});
},
});

View File

@@ -0,0 +1,177 @@
import {
addMonths,
endOfDay,
endOfMonth,
endOfQuarter,
endOfWeek,
startOfDay,
startOfMonth,
startOfQuarter,
startOfWeek,
startOfYear,
subDays,
subMonths
} from "date-fns";
import format from '../../util/format'
export default () => ({
range: {
start: null, end: null
},
defaultRange: {
start: null, end: null
},
language: 'en-US',
init() {
// console.log('Dates init');
this.range = {
start: new Date(window.store.get('start')),
end: new Date(window.store.get('end'))
};
this.defaultRange = {
start: new Date(window.store.get('start')),
end: new Date(window.store.get('end'))
};
this.language = window.store.get('language');
this.locale = window.store.get('locale');
this.locale = 'equal' === this.locale ? this.language : this.locale;
window.__localeId__ = this.language;
this.buildDateRange();
window.store.observe('start', (newValue) => {
this.range.start = new Date(newValue);
});
window.store.observe('end', (newValue) => {
this.range.end = new Date(newValue);
this.buildDateRange();
});
//this.range = this.setDatesFromViewRange(this.range.start);
// get values from store and use them accordingly.
// this.viewRange = window.BasicStore.get('viewRange');
// this.locale = window.BasicStore.get('locale');
// this.language = window.BasicStore.get('language');
// this.locale = 'equal' === this.locale ? this.language : this.locale;
// window.__localeId__ = this.language;
//
// // the range is always null but later on we will store it in BasicStore.
// if (null === this.range.start && null === this.range.end) {
// console.log('start + end = null, calling setDatesFromViewRange()');
// this.range = this.setDatesFromViewRange(new Date);
// }
// console.log('MainApp: set defaultRange');
// this.defaultRange = this.setDatesFromViewRange(new Date);
// // default range is always the current period (initialized ahead)
},
buildDateRange() {
// console.log('Dates buildDateRange');
// generate ranges
let nextRange = this.getNextRange();
let prevRange = this.getPrevRange();
let last7 = this.lastDays(7);
let last30 = this.lastDays(30);
let mtd = this.mtd();
let ytd = this.ytd();
// set the title:
let element = document.getElementsByClassName('daterange-holder')[0];
element.textContent = format(this.range.start) + ' - ' + format(this.range.end);
element.setAttribute('data-start', format(this.range.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(this.range.end, 'yyyy-MM-dd'));
// set the current one
element = document.getElementsByClassName('daterange-current')[0];
element.textContent = format(this.defaultRange.start) + ' - ' + format(this.defaultRange.end);
element.setAttribute('data-start', format(this.defaultRange.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(this.defaultRange.end, 'yyyy-MM-dd'));
// generate next range
element = document.getElementsByClassName('daterange-next')[0];
element.textContent = format(nextRange.start) + ' - ' + format(nextRange.end);
element.setAttribute('data-start', format(nextRange.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(nextRange.end, 'yyyy-MM-dd'));
// previous range.
element = document.getElementsByClassName('daterange-prev')[0];
element.textContent = format(prevRange.start) + ' - ' + format(prevRange.end);
element.setAttribute('data-start', format(prevRange.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(prevRange.end, 'yyyy-MM-dd'));
// last 7
element = document.getElementsByClassName('daterange-7d')[0];
element.setAttribute('data-start', format(last7.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(last7.end, 'yyyy-MM-dd'));
// last 30
element = document.getElementsByClassName('daterange-90d')[0];
element.setAttribute('data-start', format(last30.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(last30.end, 'yyyy-MM-dd'));
// MTD
element = document.getElementsByClassName('daterange-mtd')[0];
element.setAttribute('data-start', format(mtd.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(mtd.end, 'yyyy-MM-dd'));
// YTD
element = document.getElementsByClassName('daterange-ytd')[0];
element.setAttribute('data-start', format(ytd.start, 'yyyy-MM-dd'));
element.setAttribute('data-end', format(ytd.end, 'yyyy-MM-dd'));
// custom range.
// console.log('MainApp: buildDateRange end');
},
getNextRange() {
let start = startOfMonth(this.range.start);
let nextMonth = addMonths(start, 1);
let end = endOfMonth(nextMonth);
return {start: nextMonth, end: end};
},
getPrevRange() {
let start = startOfMonth(this.range.start);
let prevMonth = subMonths(start, 1);
let end = endOfMonth(prevMonth);
return {start: prevMonth, end: end};
},
ytd() {
let end = new Date;
let start = startOfYear(this.range.start);
return {start: start, end: end};
},
mtd() {
let end = new Date;
let start = startOfMonth(this.range.start);
return {start: start, end: end};
},
lastDays(days) {
let end = new Date;
let start = subDays(end, days);
return {start: start, end: end};
},
changeDateRange(e) {
e.preventDefault();
// console.log('MainApp: changeDateRange');
let target = e.currentTarget;
let start = new Date(target.getAttribute('data-start'));
let end = new Date(target.getAttribute('data-end'));
// console.log('MainApp: Change date range', start, end);
window.store.set('start', start);
window.store.set('end', end);
//this.buildDateRange();
return false;
},
});

View File

@@ -0,0 +1,32 @@
/*!
* app.scss
* Copyright (c) 2019 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/>.
*/
$color-mode-type: media-query;
// Bootstrap
//@import "~bootstrap-sass/assets/stylesheets/bootstrap";
// Font awesome
//@import "~font-awesome/css/font-awesome";
$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "@fortawesome/fontawesome-free/scss/solid.scss";
@import "@fortawesome/fontawesome-free/scss/brands.scss";
@import "@fortawesome/fontawesome-free/scss/regular.scss";

View File

@@ -0,0 +1,100 @@
// basic store for preferred date range and some other vars.
// used in layout.
import Get from '../api/preferences/index.js';
import store from 'store';
/**
* A basic store for Firefly III persistent UI data and preferences.
*/
const Basic = () => {
// currently availabel variables:
const viewRange = '1M';
const darkMode = 'browser';
const language = 'en-US';
const locale = 'en-US';
// start and end are used by most pages to allow the user to browse back and forth.
const start = null;
const end = null;
// others, to be used in the future.
const listPageSize = 10;
const currencyCode = 'AAA';
const currencyId = '0';
const ready = false;
//
// a very basic way to signal the store now contains all variables.
const count = 0;
const readyCount = 4;
/**
*
*/
const init = () => {
this.loadVariable('viewRange')
this.loadVariable('darkMode')
this.loadVariable('language')
this.loadVariable('locale')
}
/**
* Load a variable, fresh or from storage.
* @param name
*/
const loadVariable = (name) => {
// currently unused, window.X can be used by the blade template
// to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow.
if (window.hasOwnProperty(name)) {
this[name] = window[name];
this.triggerReady();
return;
}
// load from store
if (store.get(name)) {
this[name] = store.get(name);
this.triggerReady();
return;
}
// grab
let getter = (new Get);
getter.getByName(name).then((response) => this.parseResponse(name, response));
}
//
const parseResponse = (name, response) => {
let value = response.data.data.attributes.data;
this[name] = value;
// TODO store.
store.set(name, value);
this.triggerReady();
}
//
// set(name, value) {
// this[name] = value;
// store.set(name, value);
// }
//
// get(name) {
// return store.get(name, this[name]);
// }
//
const isReady = () => {
return this.count === this.readyCount;
}
const triggerReady = () => {
this.count++;
if (this.count === this.readyCount) {
// trigger event:
const event = new Event("BasicStoreReady");
document.dispatchEvent(event);
}
}
return {
init
};
}
export const basic = Basic();

View File

@@ -0,0 +1,58 @@
/*
* get-variable.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 Get from "../api/v1/preferences/index.js";
import Post from "../api/v1/preferences/post.js";
export function getVariable(name, defaultValue = null) {
// currently unused, window.X can be used by the blade template
// to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow.
if (window.hasOwnProperty(name)) {
// console.log('Get from window');
return Promise.resolve(window[name]);
}
// load from store2, if it's present.
if (window.store.get(name)) {
// console.log('Get from store');
return Promise.resolve(window.store.get(name));
}
let getter = (new Get);
return getter.getByName(name).then((response) => {
// console.log('Get from API');
return Promise.resolve(parseResponse(name, response));
}).catch(() => {
// preference does not exist (yet).
// POST it and then return it anyway.
let poster = (new Post);
poster.post(name, defaultValue).then((response) => {
return Promise.resolve(parseResponse(name, response));
});
});
}
function parseResponse(name, response) {
let value = response.data.data.attributes.data;
window.store.set(name, value);
// console.log('Store from API');
return value;
}

View File

@@ -0,0 +1,46 @@
/*
* get-variable.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 Put from "../api/v1/preferences/put.js";
import Post from "../api/v1/preferences/post.js";
export function setVariable(name, value = null) {
// currently unused, window.X can be used by the blade template
// to make things available quicker than if the store has to grab it through the API.
// then again, it's not that slow.
// set in window.x
// window[name] = value;
// set in store:
window.store.set(name, value);
// post to user preferences (because why not):
let putter = new Put();
putter.put(name, value).then((response) => {
}).catch(() => {
// preference does not exist (yet).
// POST it
let poster = (new Post);
poster.post(name, value).then((response) => {
});
});
}

View File

@@ -0,0 +1,100 @@
/*
* default-chart-settings.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 formatMoney from "../util/format-money.js";
function getDefaultChartSettings(type) {
if ('sankey' === type) {
return {
type: 'sankey',
data: {
datasets: [],
},
}
}
if ('pie' === type) {
return {
type: 'pie',
data: {
datasets: [],
}
}
}
if ('column' === type) {
return {
type: 'bar',
data: {
labels: [],
datasets: [],
},
options: {
plugins: {
tooltip: {
callbacks: {
label: function (tooltipItem) {
// console.log(tooltipItem);
let currency = tooltipItem.dataset.currency_code;
return formatMoney(tooltipItem.raw, currency);
},
},
},
},
maintainAspectRatio: false,
scales: {}
},
};
}
if ('line' === type) {
return {
options: {
plugins: {
tooltip: {
callbacks: {
label: function (tooltipItem) {
// console.log(tooltipItem);
let currency = tooltipItem.dataset.currency_code;
return formatMoney(tooltipItem.raw, currency);
},
},
},
},
maintainAspectRatio: false,
scales: {
x: {
// The axis for this scale is determined from the first letter of the id as `'x'`
// It is recommended to specify `position` and / or `axis` explicitly.
type: 'time',
time: {
tooltipFormat: 'PP',
}
},
},
},
type: 'line',
data: {
labels: [],
datasets: []
},
};
}
return {};
}
export {getDefaultChartSettings};

Some files were not shown because too many files have changed in this diff Show More