diff --git a/app/Api/V2/Controllers/Model/Transaction/StoreController.php b/app/Api/V2/Controllers/Model/Transaction/StoreController.php
new file mode 100644
index 0000000000..24f5c815fb
--- /dev/null
+++ b/app/Api/V2/Controllers/Model/Transaction/StoreController.php
@@ -0,0 +1,43 @@
+.
+ */
+
+namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
+
+use FireflyIII\Api\V2\Controllers\Controller;
+use Illuminate\Http\JsonResponse;
+
+/**
+ * Class StoreController
+ */
+class StoreController extends Controller
+{
+ /**
+ * @return JsonResponse
+ */
+ public function post(): JsonResponse
+ {
+
+ return response()->json([]);
+
+ }
+
+
+}
diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php
index 8ad641805b..eee489d612 100644
--- a/app/Helpers/Collector/GroupCollector.php
+++ b/app/Helpers/Collector/GroupCollector.php
@@ -871,7 +871,7 @@ class GroupCollector implements GroupCollectorInterface
public function setLimit(int $limit): GroupCollectorInterface
{
$this->limit = $limit;
- app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
+ //app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
@@ -976,7 +976,7 @@ class GroupCollector implements GroupCollectorInterface
{
$page = 0 === $page ? 1 : $page;
$this->page = $page;
- app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
+ //app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
return $this;
}
diff --git a/resources/assets/v2/api/v2/model/transaction/post.js b/resources/assets/v2/api/v2/model/transaction/post.js
new file mode 100644
index 0000000000..0b02577566
--- /dev/null
+++ b/resources/assets/v2/api/v2/model/transaction/post.js
@@ -0,0 +1,28 @@
+/*
+ * post.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 .
+ */
+
+import {api} from "../../../../boot/axios";
+
+export default class Post {
+ post(submission) {
+ let url = '/api/v2/transactions';
+ return api.post(url, submission);
+ }
+}
diff --git a/resources/assets/v2/boot/bootstrap.js b/resources/assets/v2/boot/bootstrap.js
index 984bb10e9e..a9da211fd2 100644
--- a/resources/assets/v2/boot/bootstrap.js
+++ b/resources/assets/v2/boot/bootstrap.js
@@ -29,39 +29,53 @@ import axios from 'axios';
import store from "store";
import observePlugin from 'store/plugins/observe';
import Alpine from "alpinejs";
-import * as bootstrap from 'bootstrap'
+import * as bootstrap from 'bootstrap';
+import {getFreshVariable} from "../store/get-fresh-variable.js";
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);
- }
+window.store = store;
- // 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;
+// always grab the preference "marker" from Firefly III.
+getFreshVariable('lastActivity').then((serverValue) => {
+ const localValue = store.get('lastActivity');
+ store.set('cacheValid', localValue === serverValue);
+ store.set('lastActivity', serverValue);
+ console.log('Server value: ' + serverValue);
+ console.log('Local value: ' + localValue);
+ console.log('Cache valid: ' + (localValue === serverValue));
+}).then(() => {
+ 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;
+ });
});
+// wait for 3 promises, because we need those later on.
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
diff --git a/resources/assets/v2/pages/dashboard/accounts.js b/resources/assets/v2/pages/dashboard/accounts.js
index 520da9d979..9f2f94db0a 100644
--- a/resources/assets/v2/pages/dashboard/accounts.js
+++ b/resources/assets/v2/pages/dashboard/accounts.js
@@ -33,6 +33,8 @@ let chart = null;
let chartData = null;
let afterPromises = false;
+const CHART_CACHE_KEY = 'dashboard-accounts-chart';
+const ACCOUNTS_CACHE_KEY = 'dashboard-accounts-data';
export default () => ({
loading: false,
loadingAccounts: false,
@@ -44,12 +46,28 @@ export default () => ({
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));
+ const cacheValid = window.store.get('cacheValid');
+ let cachedData = window.store.get(CHART_CACHE_KEY);
+
+ if (cacheValid && typeof cachedData !== 'undefined') {
+ let options = window.store.get(CHART_CACHE_KEY);
+ this.drawChart(options);
this.loading = false;
- });
+ console.log('Chart from cache');
+ }
+ if (!cacheValid || typeof cachedData === 'undefined') {
+ 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;
+ // cache generated options:
+ let options = this.generateOptions(this.chartData);
+ window.store.set(CHART_CACHE_KEY, options);
+ this.drawChart(options);
+ this.loading = false;
+ console.log('Chart FRESH');
+ });
+ }
+
},
generateOptions(data) {
currencies = [];
@@ -145,6 +163,15 @@ export default () => ({
this.loadingAccounts = false;
return;
}
+ const cacheValid = window.store.get('cacheValid');
+ let cachedData = window.store.get(ACCOUNTS_CACHE_KEY);
+
+ if (cacheValid && typeof cachedData !== 'undefined') {
+ this.accountList = cachedData;
+ this.loadingAccounts = false;
+ return;
+ }
+
// console.log('loadAccounts continue!');
const max = 10;
let totalAccounts = 0;
@@ -180,7 +207,10 @@ export default () => ({
group.transactions.push({
description: currentTransaction.description,
id: current.id,
+ type: currentTransaction.type,
+ amount_raw: parseFloat(currentTransaction.amount),
amount: formatMoney(currentTransaction.amount, currentTransaction.currency_code),
+ native_amount_raw: parseFloat(currentTransaction.native_amount),
native_amount: formatMoney(currentTransaction.native_amount, currentTransaction.native_code),
});
}
@@ -190,7 +220,9 @@ export default () => ({
accounts.push({
name: parent.attributes.name,
id: parent.id,
+ balance_raw: parseFloat(parent.attributes.current_balance),
balance: formatMoney(parent.attributes.current_balance, parent.attributes.currency_code),
+ native_balance_raw: parseFloat(parent.attributes.native_current_balance),
native_balance: formatMoney(parent.attributes.native_current_balance, parent.attributes.native_code),
groups: groups,
});
@@ -198,6 +230,7 @@ export default () => ({
if (count === totalAccounts) {
this.accountList = accounts;
this.loadingAccounts = false;
+ window.store.set(ACCOUNTS_CACHE_KEY, accounts);
}
});
});
diff --git a/resources/assets/v2/pages/transactions/create.js b/resources/assets/v2/pages/transactions/create.js
index 622b041334..5dd61c9560 100644
--- a/resources/assets/v2/pages/transactions/create.js
+++ b/resources/assets/v2/pages/transactions/create.js
@@ -21,16 +21,66 @@
import '../../boot/bootstrap.js';
import dates from '../../pages/shared/dates.js';
import {createEmptySplit} from "./shared/create-empty-split.js";
+import {parseFromEntries} from "./shared/parse-from-entries.js";
import formatMoney from "../../util/format-money.js";
+//import Autocomplete from "bootstrap5-autocomplete";
+import Post from "../../api/v2/model/transaction/post.js";
let transactions = function () {
return {
count: 0,
totalAmount: 0,
entries: [],
+
+ // error and success messages:
+ showError: false,
+ showSuccess: false,
+
init() {
- this.entries.push(createEmptySplit());
+ const opts = {
+ onSelectItem: console.log,
+ };
+ let src = [];
+ for (let i = 0; i < 50; i++) {
+ src.push({
+ title: "Option " + i,
+ id: "opt" + i,
+ data: {
+ key: i,
+ },
+ });
+ }
+
+ // for each thing, make autocomplete?
+
+
+ this.addSplit();
console.log('Ik ben init hoera');
+
+ // let element = document.getElementById('source_0');
+ // new Autocomplete(element, {
+ // items: src,
+ // valueField: "id",
+ // labelField: "title",
+ // highlightTyped: true,
+ // onSelectItem: console.log,
+ // });
+ },
+ submitTransaction() {
+ let transactions = parseFromEntries(this.entries);
+ let submission = {
+ group_title: null,
+ fire_webhooks: false,
+ apply_rules: false,
+ transactions: transactions
+ };
+ let poster = new Post();
+ console.log(submission);
+ poster.post(submission).then((response) => {
+ console.log(response);
+ }).catch((error) => {
+ console.error(error);
+ });
},
addSplit() {
this.entries.push(createEmptySplit());
diff --git a/resources/assets/v2/pages/transactions/shared/create-empty-split.js b/resources/assets/v2/pages/transactions/shared/create-empty-split.js
index 3947f396e5..809e35ef7c 100644
--- a/resources/assets/v2/pages/transactions/shared/create-empty-split.js
+++ b/resources/assets/v2/pages/transactions/shared/create-empty-split.js
@@ -19,6 +19,8 @@
*/
+import format from "date-fns/format";
+
function getAccount() {
return {
id: '',
@@ -27,10 +29,13 @@ function getAccount() {
}
export function createEmptySplit() {
+ let now = new Date();
+ let formatted = format(now, 'yyyy-MM-dd HH:mm');
return {
- description: 'I am descr',
+ description: 'OK then',
amount: '',
source_account: getAccount(),
destination_account: getAccount(),
+ date: formatted
};
}
diff --git a/resources/assets/v2/pages/transactions/shared/parse-from-entries.js b/resources/assets/v2/pages/transactions/shared/parse-from-entries.js
new file mode 100644
index 0000000000..f561023bc4
--- /dev/null
+++ b/resources/assets/v2/pages/transactions/shared/parse-from-entries.js
@@ -0,0 +1,47 @@
+/*
+ * parse-from-entries.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 .
+ */
+
+/**
+ *
+ * @param entries
+ */
+export function parseFromEntries(entries) {
+ let returnArray = [];
+ for (let i in entries) {
+ if (entries.hasOwnProperty(i)) {
+ const entry = entries[i];
+ let current = {};
+
+ // fields for transaction
+ current.description = entry.description;
+ current.source_name = entry.source_account.name;
+ current.destination_name = entry.destination_account.name;
+ current.amount = entry.amount;
+ current.date = entry.date;
+
+ // TODO transaction type is hard coded:
+ current.type = 'withdrawal';
+
+
+ returnArray.push(current);
+ }
+ }
+ return returnArray;
+}
diff --git a/resources/assets/v2/store/get-fresh-variable.js b/resources/assets/v2/store/get-fresh-variable.js
new file mode 100644
index 0000000000..9f425d5711
--- /dev/null
+++ b/resources/assets/v2/store/get-fresh-variable.js
@@ -0,0 +1,42 @@
+/*
+ * 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 .
+ */
+
+import Get from "../api/v1/preferences/index.js";
+import Post from "../api/v1/preferences/post.js";
+
+export function getFreshVariable(name, defaultValue = null) {
+ 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) {
+ return response.data.data.attributes.data;
+}
+
diff --git a/resources/assets/v2/store/get-variable.js b/resources/assets/v2/store/get-variable.js
index 2a59337e0b..e9ba25a721 100644
--- a/resources/assets/v2/store/get-variable.js
+++ b/resources/assets/v2/store/get-variable.js
@@ -23,27 +23,30 @@ import Post from "../api/v1/preferences/post.js";
export function getVariable(name, defaultValue = null) {
+ const validCache = window.store.get('cacheValid');
// 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)) {
+ if (validCache && 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));
+ const fromStore = window.store.get(name);
+ if (validCache && typeof fromStore !== 'undefined') {
+ // console.log('Get "' + name + '" from store');
+ return Promise.resolve(fromStore);
}
let getter = (new Get);
return getter.getByName(name).then((response) => {
- // console.log('Get from API');
+ // console.log('Get "' + name + '" 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));
});
});
@@ -52,7 +55,7 @@ export function getVariable(name, defaultValue = null) {
function parseResponse(name, response) {
let value = response.data.data.attributes.data;
window.store.set(name, value);
- // console.log('Store from API');
+ // console.log('Store "' + name + '" in localStorage');
return value;
}
diff --git a/resources/views/v2/index.blade.php b/resources/views/v2/index.blade.php
index 1b24580a9d..b958a9394e 100644
--- a/resources/views/v2/index.blade.php
+++ b/resources/views/v2/index.blade.php
@@ -88,12 +88,9 @@
- (
-
-
-
-
- )
+
+ @include('partials.elements.amount', ['autoConversion' => true,'amount' => 'account.balance','native' => 'account.native_balance'])
+
@@ -106,7 +103,9 @@
-
+ TODO ICON
+
@@ -119,7 +118,22 @@
-
+
+
+
+
+
+
+
+
+
+
+
@@ -133,12 +147,7 @@
-
-
-
-
-
-
+ @include('partials.elements.amount', ['autoConversion' => true,'amount' => 'transaction.amount','native' => 'transaction.native_amount'])
|
diff --git a/resources/views/v2/partials/elements/amount.blade.php b/resources/views/v2/partials/elements/amount.blade.php
new file mode 100644
index 0000000000..995f530a60
--- /dev/null
+++ b/resources/views/v2/partials/elements/amount.blade.php
@@ -0,0 +1,41 @@
+@if($autoConversion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@else
+
+
+
+
+
+
+
+
+
+
+@endif
diff --git a/routes/api.php b/routes/api.php
index c77a37d9af..ac0bdbe35a 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -153,6 +153,20 @@ Route::group(
}
);
+/**
+ * V2 API route for transactions
+ */
+Route::group(
+ [
+ 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Transaction',
+ 'prefix' => 'v2/transactions',
+ 'as' => 'api.v2.transactions.',
+ ],
+ static function () {
+ Route::post('', ['uses' => 'StoreController@post', 'as' => 'store']);
+ }
+);
+
/**
* V2 API route for budgets and budget limits:
*/